3.11 Life-Cycle Methods

When The Context Starts

If you wish for a particular method to be invoked when a bean is constructed then you can use the javax.annotation.PostConstruct annotation:

  1. import javax.annotation.PostConstruct; (1)
  2. import javax.inject.Singleton;
  3. @Singleton
  4. public class V8Engine implements Engine {
  5. private int cylinders = 8;
  6. private boolean initialized = false; (2)
  7. public String start() {
  8. if(!initialized) {
  9. throw new IllegalStateException("Engine not initialized!");
  10. }
  11. return "Starting V8";
  12. }
  13. @Override
  14. public int getCylinders() {
  15. return cylinders;
  16. }
  17. public boolean isIntialized() {
  18. return this.initialized;
  19. }
  20. @PostConstruct (3)
  21. public void initialize() {
  22. this.initialized = true;
  23. }
  24. }
  1. import javax.annotation.PostConstruct (1)
  2. import javax.inject.Singleton
  3. @Singleton
  4. class V8Engine implements Engine {
  5. int cylinders = 8
  6. boolean initialized = false (2)
  7. String start() {
  8. if(!initialized) throw new IllegalStateException("Engine not initialized!")
  9. return "Starting V8"
  10. }
  11. @PostConstruct (3)
  12. void initialize() {
  13. this.initialized = true
  14. }
  15. }
  1. import javax.annotation.PostConstruct
  2. import javax.inject.Singleton
  3. @Singleton
  4. class V8Engine : Engine {
  5. override val cylinders = 8
  6. var isIntialized = false
  7. private set (2)
  8. override fun start(): String {
  9. check(isIntialized) { "Engine not initialized!" }
  10. return "Starting V8"
  11. }
  12. @PostConstruct (3)
  13. fun initialize() {
  14. this.isIntialized = true
  15. }
  16. }
1The PostConstruct annotation is imported
2A field is defined that requires initialization
3A method is annotated with @PostConstruct and will be invoked once the object is constructed and fully injected.

When The Context Closes

If you wish for a particular method to be invoked when the context is closed then you can use the javax.annotation.PreDestroy annotation:

  1. import javax.annotation.PreDestroy; (1)
  2. import javax.inject.Singleton;
  3. import java.util.concurrent.atomic.AtomicBoolean;
  4. @Singleton
  5. public class PreDestroyBean implements AutoCloseable {
  6. AtomicBoolean stopped = new AtomicBoolean(false);
  7. @PreDestroy (2)
  8. @Override
  9. public void close() throws Exception {
  10. stopped.compareAndSet(false, true);
  11. }
  12. }
  1. import javax.annotation.PreDestroy (1)
  2. import java.util.concurrent.atomic.AtomicBoolean
  3. import javax.inject.Singleton
  4. @Singleton
  5. class PreDestroyBean implements AutoCloseable {
  6. AtomicBoolean stopped = new AtomicBoolean(false)
  7. @PreDestroy (2)
  8. @Override
  9. void close() throws Exception {
  10. stopped.compareAndSet(false, true)
  11. }
  12. }
  1. import javax.annotation.PreDestroy
  2. import javax.inject.Singleton
  3. import java.util.concurrent.atomic.AtomicBoolean
  4. @Singleton
  5. class PreDestroyBean : AutoCloseable {
  6. internal var stopped = AtomicBoolean(false)
  7. @PreDestroy (2)
  8. @Throws(Exception::class)
  9. override fun close() {
  10. stopped.compareAndSet(false, true)
  11. }
  12. }
1The PreDestroy annotation is imported
2A method is annotated with @PreDestroy and will be invoked when the context is closed.

For factory beans, the preDestroy value in the Bean annotation can be used to tell Micronaut which method to invoke.

  1. import io.micronaut.context.annotation.Bean;
  2. import io.micronaut.context.annotation.Factory;
  3. import javax.inject.Singleton;
  4. @Factory
  5. public class ConnectionFactory {
  6. @Bean(preDestroy = "stop") (1)
  7. @Singleton
  8. public Connection connection() {
  9. return new Connection();
  10. }
  11. }
  1. import io.micronaut.context.annotation.Bean
  2. import io.micronaut.context.annotation.Factory
  3. import javax.inject.Singleton
  4. @Factory
  5. class ConnectionFactory {
  6. @Bean(preDestroy = "stop") (1)
  7. @Singleton
  8. Connection connection() {
  9. new Connection()
  10. }
  11. }
  1. import io.micronaut.context.annotation.Bean
  2. import io.micronaut.context.annotation.Factory
  3. import javax.inject.Singleton
  4. @Factory
  5. class ConnectionFactory {
  6. @Bean(preDestroy = "stop") (1)
  7. @Singleton
  8. fun connection(): Connection {
  9. return Connection()
  10. }
  11. }

  1. import java.util.concurrent.atomic.AtomicBoolean;
  2. public class Connection {
  3. AtomicBoolean stopped = new AtomicBoolean(false);
  4. public void stop() { (2)
  5. stopped.compareAndSet(false, true);
  6. }
  7. }
  1. import java.util.concurrent.atomic.AtomicBoolean
  2. class Connection {
  3. AtomicBoolean stopped = new AtomicBoolean(false)
  4. void stop() { (2)
  5. stopped.compareAndSet(false, true)
  6. }
  7. }
  1. import java.util.concurrent.atomic.AtomicBoolean
  2. class Connection {
  3. internal var stopped = AtomicBoolean(false)
  4. fun stop() { (2)
  5. stopped.compareAndSet(false, true)
  6. }
  7. }
1The preDestroy value is set on the annotation
2The annotation value matches the method name
Just simply implementing the Closeable or AutoCloseable interfaces is not enough to get a bean to close with the context. One of the above methods must be used.

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