Defining spring @EventListener in an abstract super class
Asked Answered
F

1

7

I've stared to use spring's @EventListener annotation to create event handlers that handle my non-spring specific events. Initially everything went pretty well. I used a test to verify that I could put the @EventListener annotation on a method of a abstract class and everything worked as expected.

However, once I started adding generics to the mix I started getting NullPointerExceptions from ApplicationListenerMethodAdapter.java:337.

I've created a test case to illustrate the problem. Currently all the test methods fail with the exception:

java.lang.NullPointerException
at java.lang.Class.isAssignableFrom(Native Method)
at org.springframework.context.event.ApplicationListenerMethodAdapter.getResolvableType(ApplicationListenerMethodAdapter.java:337)
at org.springframework.context.event.ApplicationListenerMethodAdapter.resolveArguments(ApplicationListenerMethodAdapter.java:161)
at org.springframework.context.event.ApplicationListenerMethodAdapter.processEvent(ApplicationListenerMethodAdapter.java:142)
at org.springframework.context.event.ApplicationListenerMethodAdapter.onApplicationEvent(ApplicationListenerMethodAdapter.java:106)
at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:163)
at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:136)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:381)
at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:348)

When I move the @EventListener annotation down to each concrete listener the exception disappears and things behave as expected with the exception of testSendingEventWithGenericsWithExtendedUniquePayload.

Questions

Q1) Is it a valid usage pattern to put @EventListener on a method of a abstract super class? I was hoping to implement common behavior there.

Q2) I read about implementing ResolvableTypeProvider on my event in the spring docs. My understanding was that this would allow me to avoid having to create many concrete subclasses for each payload type. This is what I'm attempting to test in testSendingEventWithGenericsWithExtendedUniquePayload. I'm expecting the event fired in this test to be handled by TestEventWithGenericsExtendedUniquePayloadListener but it's not. Have I misunderstood something here?

Spring: 4.2.4.RELEASE Java: 1.8.0_65

Thanks for your help Oliver

Test Code

import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.core.ResolvableType;
import org.springframework.core.ResolvableTypeProvider;
import org.springframework.stereotype.Component;
import org.springframework.test.annotation.DirtiesContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Collections;
import java.util.List;
import java.util.UUID;

import static org.springframework.core.ResolvableType.*;

/**
 * @author Oliver Henlich
 */
@ContextConfiguration
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class EventListenerTest {
    private static final Logger log = LoggerFactory.getLogger(EventListenerTest.class);

    @Autowired
    protected transient ApplicationEventPublisher applicationEventPublisher;

    @Test
    public void testSendingEvent1() {
        log.info("testSendingEvent1");

        // this should go to TestEvent1Listener
        applicationEventPublisher.publishEvent(new TestEvent1(new UniquePayload()));

    }

    @Test
    public void testSendingEventWithGenerics() {
        log.info("testSendingEventWithGenerics");

        // this should go to TestEventWithGenericsListener
        applicationEventPublisher.publishEvent(new TestEventWithGenerics<>(new UniquePayload()));
    }

    @Test
    public void testSendingEventWithGenericsWithExtendedUniquePayload() {
        log.info("testSendingEventWithGenerics");

        // I was expecting this to go to TestEventWithGenericsExtendedUniquePayloadListener
        applicationEventPublisher.publishEvent(new TestEventWithGenerics<>(new ExtendedUniquePayload()));
    }

    @Test
    public void testSendingEvent2() {
        log.info("testSendingEvent2");

        // there is no listener for this one
        applicationEventPublisher.publishEvent(new TestEvent2(new UniquePayload()));
    }


    // LISTENERS --------------------------------------------------------------
    interface TestDataEventListener<E extends TestDataEvent> {
        @SuppressWarnings("unused")
        List<String> handleEvent(E event);
    }

    abstract static class AbstractTestDataEventListener<E extends TestDataEvent> implements TestDataEventListener<E> {
        @Override
        @EventListener
        public final List<String> handleEvent(E event) {
            return onEvent(event);
        }

        public abstract List<String> onEvent(E event);
    }

    @Component
    static final class TestEvent1Listener extends AbstractTestDataEventListener<TestEvent1> {

        @Override
        public List<String> onEvent(TestEvent1 event) {
            log.info("Listener {} handled {}", this, event);
            return Collections.emptyList();
        }
    }

    @Component
    static final class TestEventWithGenericsListener extends AbstractTestDataEventListener<TestEventWithGenerics> {

        @Override
        public List<String> onEvent(TestEventWithGenerics event) {
            log.info("Listener {} handled {}", this, event);
            return Collections.emptyList();
        }
    }

    @Component
    static final class TestEventWithGenericsExtendedUniquePayloadListener extends AbstractTestDataEventListener<TestEventWithGenerics<ExtendedUniquePayload>> {

        @Override
        public List<String> onEvent(TestEventWithGenerics<ExtendedUniquePayload> event) {
            log.info("Listener {} handled {}", this, event);
            return Collections.emptyList();
        }
    }


    // EVENTS -----------------------------------------------------------------
    interface TestDataEvent<T extends Unique> extends ResolvableTypeProvider {
        T load();
    }

    abstract static class AbstractTestDataEvent<T extends Unique> implements TestDataEvent<T> {
        protected final UUID uuid;

        private final ResolvableType resolvableType;

        public AbstractTestDataEvent(T uniqueObject) {
            uuid = uniqueObject.getUuid();

            ResolvableType temp = ResolvableType.forClass(getClass());
            if (temp.hasGenerics()) {
                temp = forClassWithGenerics(getClass(), forInstance(uniqueObject));
            }
            resolvableType = temp;
            log.info("class = {} resolvableType = {}", getClass(), resolvableType);
        }


        @Override
        public ResolvableType getResolvableType() {
            return resolvableType;
        }
    }


    static final class TestEvent1 extends AbstractTestDataEvent<UniquePayload> {
        public TestEvent1(UniquePayload uniqueObject) {
            super(uniqueObject);
        }

        @Override
        public UniquePayload load() {
            return new UniquePayload(uuid);
        }
    }

    static final class TestEvent2 extends AbstractTestDataEvent<UniquePayload> {
        public TestEvent2(UniquePayload uniqueObject) {
            super(uniqueObject);
        }

        @Override
        public UniquePayload load() {
            return new UniquePayload(uuid);
        }
    }

    static final class TestEventWithGenerics<T extends UniquePayload> extends AbstractTestDataEvent<T> {
        public TestEventWithGenerics(T uniqueObject) {
            super(uniqueObject);
        }

        @Override
        public T load() {
            return (T) new UniquePayload(uuid);
        }
    }


    static class UniquePayload implements Unique {
        private final UUID uuid;

        public UniquePayload() {
            this(UUID.randomUUID());
        }

        public UniquePayload(UUID uuid) {
            this.uuid = uuid;
        }


        @Override
        public UUID getUuid() {
            return uuid;
        }
    }

    static class ExtendedUniquePayload extends UniquePayload {

    }

    interface Unique {
        UUID getUuid();
    }

    @Configuration
    @ComponentScan(basePackageClasses = EventListenerTest.class)
    public static class ContextConfiguration {

    }


}
Fastigium answered 8/1, 2016 at 1:41 Comment(1)
Hi, You should refer thisAlvey
L
0

It seems that spring cannot resolve a type that you define.

You may need to use @Qualifier annotation to specify the concrete type you wanna get where you call your abstract classes.

You can search for BeanCreationException in startup logs to find which bean is not resolved by spring.

Remember! If you try to reach multiple different concrete types upon an abstract class or interface you have to specify which concrete type you wanna get by using @Qualifier annotation

Lucio answered 14/7, 2024 at 19:36 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.