The Grails Spring Events plugin provides a lightweight mechanism for asynchronously publishing Spring application events.
The plugin adds a new ApplicationEventMulticaster to the Spring application context that processes events asynchronously and is capable of retrying certain types of notification failure.
Spring Events adds a bean called asyncEventPublisher
to the Spring context. You can inject it into Grails artefacts and other Spring beans in the same way as any other bean dependency. To publish an event you just call the publishEvent
method.
To make things even easier the plugin adds a publishEvent
dynamic method to every domain class, controller and service in the application.
class Pirate {
String name
void afterUpdate() {
def event = new PirateUpdateEvent(this)
publishEvent event
}
}
class PirateUpdateEvent extends ApplicationEvent {
PirateUpdateEvent(Pirate source) {
super(source)
}
}
Events are dispatched to any beans in the Spring context that implement the ApplicationListener interface. You can register listener beans in resources.groovy
. Also, remember that Grails services are Spring beans, so simply implementing the interface in a service will automatically register it as a listener.
The ApplicationListener
interface has a generic type parameter that you can use to filter the types of event that a listener implementation will be notified about. Spring will simply not invoke your listener for other types of event.
class PirateUpdateResponderService implements ApplicationListener<PirateUpdateEvent> {
void onApplicationEvent(PirateUpdateEvent event) {
log.info "Yarrr! The dread pirate $event.source.name has been updated!"
}
}
Listener implementations may declare a retryPolicy
property of type grails.plugin.springevents.RetryPolicy
(or declare a getRetryPolicy()
method). If such a property is present and the listener throws grails.plugin.springevents.RetryableFailureException
from the onApplicationEvent
method it will be re-notified at some time in the future according to the retryPolicy
value. Throwing any other exception type will not result in notification being retried.
Note: A
RetryableFailureException
thrown by a listener implementation is treated just like any other exception if the listener does not declare aretryPolicy
.
The RetryPolicy
class simply defines the rules governing how and when to re-notify the listener of any events it fails to handle. It defines the following properties:
maxRetries
: The maximum number of times that the listener will be re-notified of an event. AftermaxRetries
is reached an exception is thrown and will be handled as any other exception thrown by the listener would be. A value of-1
indicates that the listener should be re-notified indefinitely until it successfully processes the event. Defaults to 3.initialRetryDelayMillis
: The initial period in milliseconds that the service will wait before re-notifying the listener. Defaults to 1 minute.backoffMultiplier
: The multiplier applied to the retry timeout before the second and subsequent retry. For example with abackoffMultiplier
of 2 andinitialRetryDelayMillis
of 1000 the listener will be re-notified after 1000 milliseconds, 2000 milliseconds, 4000 milliseconds, 8000 milliseconds and so on. AbackoffMultiplier
of 1 would mean the listener will be re-notified at a fixed interval until it successfully handles the event ormaxRetries
is exceeded. Defaults to 2.
class UnreliableListener implements ApplicationListener {
def unreliableService
final RetryPolicy retryPolicy = new RetryPolicy()
void onApplicationEvent(ApplicationEvent event) {
if (unreliableService.isAvailable()) {
unreliableService.doSomething()
} else {
throw new RetryableFailureException("the unreliable service is currently unavailable")
}
}
}
In this example the listener throws RetryableFailureException
to indicate that the external service it attempts to call is not currently available and notification should be attempted later.
The multicaster has several default dependencies that can be overridden using Grails' property override configuration mechanism.
If a listener throws an exception from its onApplicationEvent
method (or its retry policy's maxRetries
is exceeded) then the multicaster will notify its error handler. The default error handler simply logs errors but you can override it by assigning a different ErrorHandler implementation to the service in Config.groovy
:
The multicaster uses a ExecutorService to poll the queue and notify the target listener. By default the service uses a single thread but you can use an alternate ExecutorService
implementation by overriding the service's taskExecutor
property in Config.groovy
.
Similarly the service uses a ScheduledExecutorService to re-queue failed notifications after the delay specified by the listener's retry policy. The default implementation uses a single thread which can be overridden by setting the property retryScheduler
in Config.groovy
.
beans {
applicationEventMulticaster {
errorHandler = new SomeErrorHandlerImpl()
taskExecutor = java.util.concurrent.Executors.newCachedThreadPool()
retryScheduler = java.util.concurrent.Executors.newScheduledThreadPool(5)
}
}
Sometimes you might need to send notifications synchronously rather than via the ExecutorService. For example, it can make testing more straightforward. If you need to do this just set the multicaster's dispatchMode
property to grails.plugin.springevents.DispatchMode.SYNCHRONOUS
.