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