Dan
Dan

Reputation: 195

Unable to catch and handle spring custom events in java

I am trying to set up a custom spring event publisher and listener. The flow of the process seems to be working fine, I can see the prints before the event publishes, however, my event listeners do not catch the event. my main class is:

public class TestClass {

        public static void main(String[] args) throws Exception {
            testEvents();
        }

        private static void testEvents() throws InterruptedException {
            AnnotationConfigApplicationContext eventPublisherContext = new AnnotationConfigApplicationContext(EventListenerConfig.class);
            CustomSpringEventPublisher eventPublisher = eventPublisherContext.getBean(CustomSpringEventPublisher.class);
            List<CustomSpringEventListener> listenersList = new ArrayList<CustomSpringEventListener>();

            for (int i = 0; i < 5; i++) {
                listenersList.add(new CustomSpringEventListener("LR" + (i + 1)));
            }

            for (int i = 0; i < 10; i++) {
                Thread.sleep(5000);

                LocalDateTime now  = LocalDateTime.now();
                eventPublisher.doStuffAndPublishAnEvent("new event " + now.format(Formats.isolong));
            }
        }
    }

here is my custom event class:

public class CustomSpringEvent extends ApplicationEvent {
    private String message;

    public CustomSpringEvent(Object source, String message) {
        super(source);
        this.message = message;
    }

    public String getMessage() {
        return message;
    }

}

here is my custom event publisher class:

@Component
public class CustomSpringEventPublisher {

    @Autowired
    private ApplicationEventPublisher applicationEventPublisher;

    public void doStuffAndPublishAnEvent(final String message) {
        System.out.println("Publishing custom event --> " + message);
        this.applicationEventPublisher.publishEvent(new CustomSpringEvent(this, message));
    }

}

here is my custom event listener class:

public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    private String name;

    public CustomSpringEventListener(String name) {
        this.name = name;
    }

    @Override
    @EventListener
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println(name + " received spring custom event - " + event.getMessage());
    }

}

here is my event listener config class:

@Configuration
@ComponentScan({"some.local.directory.testCases"})
public class EventListenerConfig {
}

What am I missing here? How can I get my listener class to recognize the published event and handle it? I saw some examples in which the listener class has the @Component, but when I tried to add it I got an error from spring: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'java.lang.String' available: expected at least 1 bean which qualifies as autowire candidate., also I don't see any reason why would the listener class have this annotation in this case. Any help would be appreciated. Thank you.

Upvotes: 1

Views: 3611

Answers (1)

RUARO Thibault
RUARO Thibault

Reputation: 2850

In order to call event listeners, Spring needs to know the listener class and have it into its applicationContext, under what is called a Bean.

You do need to annotate your class with @Component. But when you do so, you get an error. Let's clarify this.

Quick overview of Spring

Having a class annotated with @Component will indicate to Spring it needs to "manage" the class himself. By managing, it includes creating all the "dependencies" first, and then calling the "constructor". Let's call this the dependency tree. Once Spring gets to the bottom of it, it can start climbing back up and creating the object and its way back to the top.

Dependencies management

Spring knows your classe's dependencies by analyzing the constructor, setter or attributes annotated by @Autowired. Note that if your class has only one constructor, Spring automatically scans it to know about dependencies, even if there is not @Autowired at the top of it.

In your case, for CustomSpringEventListener, you have one constructor taking in parameter a String. If we build the dependency tree of your CustomSpringEventListener, we get something like that :

CustomSpringEventListener ---> String

So when Spring tries to create your CustomSpringEventListener, it will look for a bean of type String, because, your code is saying : If you ever want to create an object CustomSpringEventListener, you need to give a name which is a String. But you do not have a bean of type String in your code I guess.

The solution

In a first time, annotate your class CustomSpringEventListener with @Component and remove your attribute and the constructor.

@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {

    @Override
    @EventListener
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println("Received spring custom event - " + event.getMessage());
    }

}

In a second time, find a different way to have a name in your class if you really need it.

Listener with attributes

Let's say you need a more sophisticated Listener, like one using a ServiceA. You can do the following :

@Service
public class ServiceA { ... }
@Component
public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    private ServiceA serviceA;

    public CustomSpringEventListener(ServiceA serviceA) {
        this.serviceA = serviceA;
    }

}

Same listener with a name

You can combine @Configuration class to create many times the same Listener. Let's use your first Listener, with the name attribute.

public class CustomSpringEventListener implements ApplicationListener<CustomSpringEvent> {
    private String name;

    public CustomSpringEventListener(String name) {
        this.name = name;
    }

    @Override
    @EventListener
    public void onApplicationEvent(CustomSpringEvent event) {
        System.out.println(name + " received spring custom event - " + event.getMessage());
    }

}

Without the @Component annotation, what you can do is using a @Configuration class to instantiate your Listener:

@Configuration
public ListenerConfig {

    public CustomSpringEventListener listenerA() {
        return new CustomSpringEventListener("listenerA");
    }

    public CustomSpringEventListener listenerB() {
        return new CustomSpringEventListener("listenerB");
    }

    public CustomSpringEventListener listenerC() {
        return new CustomSpringEventListener("listenerC");
    }

    ...
}

You can mix Spring configuration to do whatever configuration you need.

Hope it helps.

Upvotes: 3

Related Questions