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:
import javax.annotation.PostConstruct; (1)
import javax.inject.Singleton;
@Singleton
public class V8Engine implements Engine {
private int cylinders = 8;
private boolean initialized = false; (2)
public String start() {
if(!initialized) {
throw new IllegalStateException("Engine not initialized!");
}
return "Starting V8";
}
@Override
public int getCylinders() {
return cylinders;
}
public boolean isIntialized() {
return this.initialized;
}
@PostConstruct (3)
public void initialize() {
this.initialized = true;
}
}
import javax.annotation.PostConstruct (1)
import javax.inject.Singleton
@Singleton
class V8Engine implements Engine {
int cylinders = 8
boolean initialized = false (2)
String start() {
if(!initialized) throw new IllegalStateException("Engine not initialized!")
return "Starting V8"
}
@PostConstruct (3)
void initialize() {
this.initialized = true
}
}
import javax.annotation.PostConstruct
import javax.inject.Singleton
@Singleton
class V8Engine : Engine {
override val cylinders = 8
var isIntialized = false
private set (2)
override fun start(): String {
check(isIntialized) { "Engine not initialized!" }
return "Starting V8"
}
@PostConstruct (3)
fun initialize() {
this.isIntialized = true
}
}
1 | The PostConstruct annotation is imported |
2 | A field is defined that requires initialization |
3 | A 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:
import javax.annotation.PreDestroy; (1)
import javax.inject.Singleton;
import java.util.concurrent.atomic.AtomicBoolean;
@Singleton
public class PreDestroyBean implements AutoCloseable {
AtomicBoolean stopped = new AtomicBoolean(false);
@PreDestroy (2)
@Override
public void close() throws Exception {
stopped.compareAndSet(false, true);
}
}
import javax.annotation.PreDestroy (1)
import java.util.concurrent.atomic.AtomicBoolean
import javax.inject.Singleton
@Singleton
class PreDestroyBean implements AutoCloseable {
AtomicBoolean stopped = new AtomicBoolean(false)
@PreDestroy (2)
@Override
void close() throws Exception {
stopped.compareAndSet(false, true)
}
}
import javax.annotation.PreDestroy
import javax.inject.Singleton
import java.util.concurrent.atomic.AtomicBoolean
@Singleton
class PreDestroyBean : AutoCloseable {
internal var stopped = AtomicBoolean(false)
@PreDestroy (2)
@Throws(Exception::class)
override fun close() {
stopped.compareAndSet(false, true)
}
}
1 | The PreDestroy annotation is imported |
2 | A 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.
import io.micronaut.context.annotation.Bean;
import io.micronaut.context.annotation.Factory;
import javax.inject.Singleton;
@Factory
public class ConnectionFactory {
@Bean(preDestroy = "stop") (1)
@Singleton
public Connection connection() {
return new Connection();
}
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class ConnectionFactory {
@Bean(preDestroy = "stop") (1)
@Singleton
Connection connection() {
new Connection()
}
}
import io.micronaut.context.annotation.Bean
import io.micronaut.context.annotation.Factory
import javax.inject.Singleton
@Factory
class ConnectionFactory {
@Bean(preDestroy = "stop") (1)
@Singleton
fun connection(): Connection {
return Connection()
}
}
import java.util.concurrent.atomic.AtomicBoolean;
public class Connection {
AtomicBoolean stopped = new AtomicBoolean(false);
public void stop() { (2)
stopped.compareAndSet(false, true);
}
}
import java.util.concurrent.atomic.AtomicBoolean
class Connection {
AtomicBoolean stopped = new AtomicBoolean(false)
void stop() { (2)
stopped.compareAndSet(false, true)
}
}
import java.util.concurrent.atomic.AtomicBoolean
class Connection {
internal var stopped = AtomicBoolean(false)
fun stop() { (2)
stopped.compareAndSet(false, true)
}
}
1 | The preDestroy value is set on the annotation |
2 | The 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
public class SampleEvent {
private String message = "Something happened";
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
import io.micronaut.context.event.ApplicationEventPublisher;
import javax.inject.Inject;
import javax.inject.Singleton;
@Singleton
public class SampleEventEmitterBean {
@Inject
ApplicationEventPublisher eventPublisher;
public void publishSampleEvent() {
eventPublisher.publishEvent(new SampleEvent());
}
}
Publishing an Event
class SampleEvent {
String message = "Something happened"
}
import io.micronaut.context.event.ApplicationEventPublisher
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SampleEventEmitterBean {
@Inject
ApplicationEventPublisher eventPublisher
void publishSampleEvent() {
eventPublisher.publishEvent(new SampleEvent())
}
}
Publishing an Event
data class SampleEvent(val message: String = "Something happened")
import io.micronaut.context.event.ApplicationEventPublisher
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class SampleEventEmitterBean {
@Inject
internal var eventPublisher: ApplicationEventPublisher? = null
fun publishSampleEvent() {
eventPublisher!!.publishEvent(SampleEvent())
}
}
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
import io.micronaut.context.event.ApplicationEventListener;
import io.micronaut.docs.context.events.SampleEvent;
@Singleton
public class SampleEventListener implements ApplicationEventListener<SampleEvent> {
private int invocationCounter = 0;
@Override
public void onApplicationEvent(SampleEvent event) {
invocationCounter++;
}
public int getInvocationCounter() {
return invocationCounter;
}
}
import io.micronaut.context.ApplicationContext;
import io.micronaut.docs.context.events.SampleEventEmitterBean;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
public class SampleEventListenerSpec {
@Test
public void testEventListenerIsNotified() {
try (ApplicationContext context = ApplicationContext.run()) {
SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
SampleEventListener listener = context.getBean(SampleEventListener.class);
assertEquals(0, listener.getInvocationCounter());
emitter.publishSampleEvent();
assertEquals(1, listener.getInvocationCounter());
}
}
}
Listening for Events with ApplicationEventListener
import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.docs.context.events.SampleEvent
@Singleton
class SampleEventListener implements ApplicationEventListener<SampleEvent> {
int invocationCounter = 0
@Override
void onApplicationEvent(SampleEvent event) {
invocationCounter++
}
}
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import spock.lang.Specification
class SampleEventListenerSpec extends Specification {
void "test event listener is notified"() {
given:
ApplicationContext context = ApplicationContext.run()
SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
SampleEventListener listener = context.getBean(SampleEventListener)
assert listener.invocationCounter == 0
when:
emitter.publishSampleEvent()
then:
listener.invocationCounter == 1
cleanup:
context.close()
}
}
Listening for Events with ApplicationEventListener
import io.micronaut.context.event.ApplicationEventListener
import io.micronaut.docs.context.events.SampleEvent
@Singleton
class SampleEventListener : ApplicationEventListener<SampleEvent> {
var invocationCounter = 0
override fun onApplicationEvent(event: SampleEvent) {
invocationCounter++
}
}
import io.kotlintest.shouldBe
import io.kotlintest.specs.AnnotationSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
class SampleEventListenerSpec : AnnotationSpec() {
@Test
fun testEventListenerWasNotified() {
val context = ApplicationContext.run()
val emitter = context.getBean(SampleEventEmitterBean::class.java)
val listener = context.getBean(SampleEventListener::class.java)
listener.invocationCounter.shouldBe(0)
emitter.publishSampleEvent()
listener.invocationCounter.shouldBe(1)
context.close()
}
}
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
import io.micronaut.docs.context.events.SampleEvent;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.context.event.ShutdownEvent;
import io.micronaut.runtime.event.annotation.EventListener;
@Singleton
public class SampleEventListener {
private int invocationCounter = 0;
@EventListener
public void onSampleEvent(SampleEvent event) {
invocationCounter++;
}
@EventListener
public void onStartupEvent(StartupEvent event) {
// startup logic here
}
@EventListener
public void onShutdownEvent(ShutdownEvent event) {
// shutdown logic here
}
public int getInvocationCounter() {
return invocationCounter;
}
}
Listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener
@Singleton
class SampleEventListener {
int invocationCounter = 0
@EventListener
void onSampleEvent(SampleEvent event) {
invocationCounter++
}
@EventListener
void onStartupEvent(StartupEvent event) {
// startup logic here
}
@EventListener
void onShutdownEvent(ShutdownEvent event) {
// shutdown logic here
}
}
Listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.context.event.StartupEvent
import io.micronaut.context.event.ShutdownEvent
import io.micronaut.runtime.event.annotation.EventListener
@Singleton
class SampleEventListener {
var invocationCounter = 0
@EventListener
internal fun onSampleEvent(event: SampleEvent) {
invocationCounter++
}
@EventListener
internal fun onStartupEvent(event: StartupEvent) {
// startup logic here
}
@EventListener
internal fun onShutdownEvent(event: ShutdownEvent) {
// shutdown logic here
}
}
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
import io.micronaut.docs.context.events.SampleEvent;
import io.micronaut.runtime.event.annotation.EventListener;
import io.micronaut.scheduling.annotation.Async;
@Singleton
public class SampleEventListener {
private AtomicInteger invocationCounter = new AtomicInteger(0);
@EventListener
@Async
public void onSampleEvent(SampleEvent event) {
invocationCounter.getAndIncrement();
}
public int getInvocationCounter() {
return invocationCounter.get();
}
}
import io.micronaut.context.ApplicationContext;
import io.micronaut.docs.context.events.SampleEventEmitterBean;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.equalTo;
public class SampleEventListenerSpec {
@Test
public void testEventListenerIsNotified() {
try (ApplicationContext context = ApplicationContext.run()) {
SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean.class);
SampleEventListener listener = context.getBean(SampleEventListener.class);
assertEquals(0, listener.getInvocationCounter());
emitter.publishSampleEvent();
await().atMost(10, SECONDS).until(listener::getInvocationCounter, equalTo(1));
}
}
}
Asynchronously listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.annotation.Async
@Singleton
class SampleEventListener {
AtomicInteger invocationCounter = new AtomicInteger(0)
@EventListener
@Async
void onSampleEvent(SampleEvent event) {
invocationCounter.getAndIncrement()
}
}
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import spock.lang.Specification
import spock.util.concurrent.PollingConditions
class SampleEventListenerSpec extends Specification {
void "test event listener is notified"() {
given:
ApplicationContext context = ApplicationContext.run()
SampleEventEmitterBean emitter = context.getBean(SampleEventEmitterBean)
SampleEventListener listener = context.getBean(SampleEventListener)
assert listener.invocationCounter.get() == 0
when:
emitter.publishSampleEvent()
then:
new PollingConditions().eventually {
listener.invocationCounter.get() == 1
}
cleanup:
context.close()
}
}
Asynchronously listening for Events with @EventListener
import io.micronaut.docs.context.events.SampleEvent
import io.micronaut.runtime.event.annotation.EventListener
import io.micronaut.scheduling.annotation.Async
import java.util.concurrent.atomic.AtomicInteger
@Singleton
open class SampleEventListener {
var invocationCounter = AtomicInteger(0)
@EventListener
@Async
open fun onSampleEvent(event: SampleEvent) {
println("Incrementing invocation counter...")
invocationCounter.getAndIncrement()
}
}
import io.kotlintest.eventually
import io.kotlintest.seconds
import io.kotlintest.shouldBe
import io.kotlintest.specs.AnnotationSpec
import io.micronaut.context.ApplicationContext
import io.micronaut.docs.context.events.SampleEventEmitterBean
import org.opentest4j.AssertionFailedError
class SampleEventListenerSpec : AnnotationSpec() {
@Test
// @Ignore // TODO can't get this to pass on CI, any help is welcome
fun testEventListenerWasNotified() {
val context = ApplicationContext.run()
val emitter = context.getBean(SampleEventEmitterBean::class.java)
val listener = context.getBean(SampleEventListener::class.java)
listener.invocationCounter.get().shouldBe(0)
emitter.publishSampleEvent()
eventually(5.seconds, AssertionFailedError::class.java) {
println("Current value of counter: " + listener.invocationCounter.get())
listener.invocationCounter.get().shouldBe(1)
}
context.close()
}
}
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
micronaut:
executors:
scheduled:
type: scheduled
core-pool-size: 30