3.12 Context Events

Micronaut supports a general event system through the context. The ApplicationEventPublisher API is used to publish events and the ApplicationEventListener API is used to listen to events. The event system is not limited to the events that Micronaut publishes and can be used for custom events created by the users.

Publishing Events

The ApplicationEventPublisher API supports events of any type, however all events that Micronaut publishes extend ApplicationEvent.

To publish an event, obtain an instance of ApplicationEventPublisher either directly from the context or through dependency injection, and execute the publishEvent method with your event object.

Publishing an Event

  1. public class SampleEvent {
  2. private String message = "Something happened";
  3. public String getMessage() {
  4. return message;
  5. }
  6. public void setMessage(String message) {
  7. this.message = message;
  8. }
  9. }
  10. import io.micronaut.context.event.ApplicationEventPublisher;
  11. import javax.inject.Inject;
  12. import javax.inject.Singleton;
  13. @Singleton
  14. public class SampleEventEmitterBean {
  15. @Inject
  16. ApplicationEventPublisher eventPublisher;
  17. public void publishSampleEvent() {
  18. eventPublisher.publishEvent(new SampleEvent());
  19. }
  20. }

Publishing an Event

  1. class SampleEvent {
  2. String message = "Something happened"
  3. }
  4. import io.micronaut.context.event.ApplicationEventPublisher
  5. import javax.inject.Inject
  6. import javax.inject.Singleton
  7. @Singleton
  8. class SampleEventEmitterBean {
  9. @Inject
  10. ApplicationEventPublisher eventPublisher
  11. void publishSampleEvent() {
  12. eventPublisher.publishEvent(new SampleEvent())
  13. }
  14. }

Publishing an Event

  1. data class SampleEvent(val message: String = "Something happened")
  2. import io.micronaut.context.event.ApplicationEventPublisher
  3. import javax.inject.Inject
  4. import javax.inject.Singleton
  5. @Singleton
  6. class SampleEventEmitterBean {
  7. @Inject
  8. internal var eventPublisher: ApplicationEventPublisher? = null
  9. fun publishSampleEvent() {
  10. eventPublisher!!.publishEvent(SampleEvent())
  11. }
  12. }
Publishing an event is synchronous by default! The publishEvent method will not return until all listeners have been executed. Move this work off to a thread pool if it is time intensive.

Listening for Events

To listen to an event, register a bean that implements ApplicationEventListener where the generic type is the type of event the listener should be executed for.

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener;
  2. import io.micronaut.docs.context.events.SampleEvent;
  3. @Singleton
  4. public class SampleEventListener implements ApplicationEventListener<SampleEvent> {
  5. private int invocationCounter = 0;
  6. @Override
  7. public void onApplicationEvent(SampleEvent event) {
  8. invocationCounter++;
  9. }
  10. public int getInvocationCounter() {
  11. return invocationCounter;
  12. }
  13. }
  14. import io.micronaut.context.ApplicationContext;
  15. import io.micronaut.docs.context.events.SampleEventEmitterBean;
  16. import org.junit.Test;
  17. import static org.junit.Assert.assertEquals;
  18. public class SampleEventListenerSpec {
  19. @Test
  20. public void testEventListenerIsNotified() {
  21. try (ApplicationContext context = ApplicationContext.run()) {
  22. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
  23. SampleEventListener listener = context.getBean(SampleEventListener.class);
  24. assertEquals(0, listener.getInvocationCounter());
  25. emitter.publishSampleEvent();
  26. assertEquals(1, listener.getInvocationCounter());
  27. }
  28. }
  29. }

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener
  2. import io.micronaut.docs.context.events.SampleEvent
  3. @Singleton
  4. class SampleEventListener implements ApplicationEventListener<SampleEvent> {
  5. int invocationCounter = 0
  6. @Override
  7. void onApplicationEvent(SampleEvent event) {
  8. invocationCounter++
  9. }
  10. }
  11. import io.micronaut.context.ApplicationContext
  12. import io.micronaut.docs.context.events.SampleEventEmitterBean
  13. import spock.lang.Specification
  14. class SampleEventListenerSpec extends Specification {
  15. void "test event listener is notified"() {
  16. given:
  17. ApplicationContext context = ApplicationContext.run()
  18. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
  19. SampleEventListener listener = context.getBean(SampleEventListener)
  20. assert listener.invocationCounter == 0
  21. when:
  22. emitter.publishSampleEvent()
  23. then:
  24. listener.invocationCounter == 1
  25. cleanup:
  26. context.close()
  27. }
  28. }

Listening for Events with ApplicationEventListener

  1. import io.micronaut.context.event.ApplicationEventListener
  2. import io.micronaut.docs.context.events.SampleEvent
  3. @Singleton
  4. class SampleEventListener : ApplicationEventListener<SampleEvent> {
  5. var invocationCounter = 0
  6. override fun onApplicationEvent(event: SampleEvent) {
  7. invocationCounter++
  8. }
  9. }
  10. import io.kotlintest.shouldBe
  11. import io.kotlintest.specs.AnnotationSpec
  12. import io.micronaut.context.ApplicationContext
  13. import io.micronaut.docs.context.events.SampleEventEmitterBean
  14. class SampleEventListenerSpec : AnnotationSpec() {
  15. @Test
  16. fun testEventListenerWasNotified() {
  17. val context = ApplicationContext.run()
  18. val emitter = context.getBean(SampleEventEmitterBean::class.java)
  19. val listener = context.getBean(SampleEventListener::class.java)
  20. listener.invocationCounter.shouldBe(0)
  21. emitter.publishSampleEvent()
  22. listener.invocationCounter.shouldBe(1)
  23. context.close()
  24. }
  25. }
The supports method can be overridden to further clarify events that should be processed.

Alternatively you can use the @EventListener annotation if you do not wish to specifically implement an interface or utilize one of the built in events like StartupEvent and ShutdownEvent:

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent;
  2. import io.micronaut.context.event.StartupEvent;
  3. import io.micronaut.context.event.ShutdownEvent;
  4. import io.micronaut.runtime.event.annotation.EventListener;
  5. @Singleton
  6. public class SampleEventListener {
  7. private int invocationCounter = 0;
  8. @EventListener
  9. public void onSampleEvent(SampleEvent event) {
  10. invocationCounter++;
  11. }
  12. @EventListener
  13. public void onStartupEvent(StartupEvent event) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. public void onShutdownEvent(ShutdownEvent event) {
  18. // shutdown logic here
  19. }
  20. public int getInvocationCounter() {
  21. return invocationCounter;
  22. }
  23. }

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.context.event.StartupEvent
  3. import io.micronaut.context.event.ShutdownEvent
  4. import io.micronaut.runtime.event.annotation.EventListener
  5. @Singleton
  6. class SampleEventListener {
  7. int invocationCounter = 0
  8. @EventListener
  9. void onSampleEvent(SampleEvent event) {
  10. invocationCounter++
  11. }
  12. @EventListener
  13. void onStartupEvent(StartupEvent event) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. void onShutdownEvent(ShutdownEvent event) {
  18. // shutdown logic here
  19. }
  20. }

Listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.context.event.StartupEvent
  3. import io.micronaut.context.event.ShutdownEvent
  4. import io.micronaut.runtime.event.annotation.EventListener
  5. @Singleton
  6. class SampleEventListener {
  7. var invocationCounter = 0
  8. @EventListener
  9. internal fun onSampleEvent(event: SampleEvent) {
  10. invocationCounter++
  11. }
  12. @EventListener
  13. internal fun onStartupEvent(event: StartupEvent) {
  14. // startup logic here
  15. }
  16. @EventListener
  17. internal fun onShutdownEvent(event: ShutdownEvent) {
  18. // shutdown logic here
  19. }
  20. }

If your listener performs work that could take a while then you can use the @Async annotation to run the operation on a separate thread:

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent;
  2. import io.micronaut.runtime.event.annotation.EventListener;
  3. import io.micronaut.scheduling.annotation.Async;
  4. @Singleton
  5. public class SampleEventListener {
  6. private AtomicInteger invocationCounter = new AtomicInteger(0);
  7. @EventListener
  8. @Async
  9. public void onSampleEvent(SampleEvent event) {
  10. invocationCounter.getAndIncrement();
  11. }
  12. public int getInvocationCounter() {
  13. return invocationCounter.get();
  14. }
  15. }
  16. import io.micronaut.context.ApplicationContext;
  17. import io.micronaut.docs.context.events.SampleEventEmitterBean;
  18. import org.junit.Test;
  19. import static org.junit.Assert.assertEquals;
  20. import static java.util.concurrent.TimeUnit.SECONDS;
  21. import static org.awaitility.Awaitility.await;
  22. import static org.hamcrest.Matchers.equalTo;
  23. public class SampleEventListenerSpec {
  24. @Test
  25. public void testEventListenerIsNotified() {
  26. try (ApplicationContext context = ApplicationContext.run()) {
  27. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
  28. SampleEventListener listener = context.getBean(SampleEventListener.class);
  29. assertEquals(0, listener.getInvocationCounter());
  30. emitter.publishSampleEvent();
  31. await().atMost(10, SECONDS).until(listener::getInvocationCounter, equalTo(1));
  32. }
  33. }
  34. }

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.runtime.event.annotation.EventListener
  3. import io.micronaut.scheduling.annotation.Async
  4. @Singleton
  5. class SampleEventListener {
  6. AtomicInteger invocationCounter = new AtomicInteger(0)
  7. @EventListener
  8. @Async
  9. void onSampleEvent(SampleEvent event) {
  10. invocationCounter.getAndIncrement()
  11. }
  12. }
  13. import io.micronaut.context.ApplicationContext
  14. import io.micronaut.docs.context.events.SampleEventEmitterBean
  15. import spock.lang.Specification
  16. import spock.util.concurrent.PollingConditions
  17. class SampleEventListenerSpec extends Specification {
  18. void "test event listener is notified"() {
  19. given:
  20. ApplicationContext context = ApplicationContext.run()
  21. SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
  22. SampleEventListener listener = context.getBean(SampleEventListener)
  23. assert listener.invocationCounter.get() == 0
  24. when:
  25. emitter.publishSampleEvent()
  26. then:
  27. new PollingConditions().eventually {
  28. listener.invocationCounter.get() == 1
  29. }
  30. cleanup:
  31. context.close()
  32. }
  33. }

Asynchronously listening for Events with @EventListener

  1. import io.micronaut.docs.context.events.SampleEvent
  2. import io.micronaut.runtime.event.annotation.EventListener
  3. import io.micronaut.scheduling.annotation.Async
  4. import java.util.concurrent.atomic.AtomicInteger
  5. @Singleton
  6. open class SampleEventListener {
  7. var invocationCounter = AtomicInteger(0)
  8. @EventListener
  9. @Async
  10. open fun onSampleEvent(event: SampleEvent) {
  11. println("Incrementing invocation counter...")
  12. invocationCounter.getAndIncrement()
  13. }
  14. }
  15. import io.kotlintest.eventually
  16. import io.kotlintest.seconds
  17. import io.kotlintest.shouldBe
  18. import io.kotlintest.specs.AnnotationSpec
  19. import io.micronaut.context.ApplicationContext
  20. import io.micronaut.docs.context.events.SampleEventEmitterBean
  21. import org.opentest4j.AssertionFailedError
  22. class SampleEventListenerSpec : AnnotationSpec() {
  23. @Test
  24. // @Ignore // TODO can't get this to pass on CI, any help is welcome
  25. fun testEventListenerWasNotified() {
  26. val context = ApplicationContext.run()
  27. val emitter = context.getBean(SampleEventEmitterBean::class.java)
  28. val listener = context.getBean(SampleEventListener::class.java)
  29. listener.invocationCounter.get().shouldBe(0)
  30. emitter.publishSampleEvent()
  31. eventually(5.seconds, AssertionFailedError::class.java) {
  32. println("Current value of counter: " + listener.invocationCounter.get())
  33. listener.invocationCounter.get().shouldBe(1)
  34. }
  35. context.close()
  36. }
  37. }

The event listener will by default run on the scheduled executor. You can configure this thread pool as required in application.yml:

Configuring Scheduled Task Thread Pool

  1. micronaut:
  2. executors:
  3. scheduled:
  4. type: scheduled
  5. core-pool-size: 30