Quantcast
Channel: technoChord » tech
Viewing all articles
Browse latest Browse all 15

The Guava EventBus and Spring Proxies

$
0
0

The Guava EventBus model is a neat way to use events (both synchronous and async) in your application without having to use the explicit Listener interfaces. More can be read about the simplicity of that approach  here.

However, I recently discovered that Spring Proxies and the EventBus registration, as it’s implemented currently,  don’t play well together.

First a bit about the key differences between the implementations of the Observer/Observable pattern using the Listener Interface and the EventBus model:

  • Instead of having each of your Observer classes implement a Listener interface, you now use an annotation on a certain method(s) of a class(es) that are registered with the event bus.
  • The EventBus.register(…) method now looks for those annotations and invokes those methods when events are posted to the bus.

The registration of the class(es) that have the @Subscribe annotation in them is usually done at initialization time. In the case of a Spring application, we can register these classes when  loading up the application context.

In the course of developing  an application, typically we are not sure which Spring managed bean will end up having the @Subscribe annotation in it.  So a good idea is to register all beans with the event bus at start up. And to do so, a BeanPostProcessor implementation would implement this as follows:

First define the EventBus in an explicit xml configuration, because we cannot @Autowire a BeanPostProcessor:

...
<task:executor "threadedEventBusExecutor" pool-size="10" />
<bean: id="eventBus" class="com.google.common.eventbus.AsyncEventBus"
c:executor-ref="threadedEventBusExecutor" />
<bean id="eventBusRegistration" class="com.acme.eventbus.EventBusRegistrationBeanPostProcessor"
c:eventBus-ref = "eventBus" />
...

And next define the BeanPostProcessor like so:

public class EventBusRegistrationBeanPostProcessor implements BeanPostProcessor {

private final EventBus eventBus;

public EventBusRegistrationBeanPostProcessor(EventBus eventBus) {
    this.eventBus = eventBus;
}

@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    return bean;
}

@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    eventBus.register(bean);
    return bean;
}

However, here is where we discover that this approach will not work for Spring managed beans for which Spring has created a JDK dynamic proxy. This is because the annotations on the original class are not available on the proxy. So while the proxy will be registered on the eventbus, it will be ineffective because the bus does not know of the @Subscribe annotation on it’s methods.
Looking at how the eventBus has been implemented we see that there is only one implementation of the HandlerFindingStrategy interface: the AnnotatedHandlerFinder.

So, how do we get around this?

One approach would be to simply use the postProcessBeforeInitialization callback instead of the postProcessAfterInitialization and register the bean on the event bus before Spring has had a chance to create a proxy for it.

This may well work except in the cases where another  BeanPostProcesser (like the PropertyPlaceHolderConfigurer) has been used to specify the Event class on the method signature of the @Subscribed method. In that case, the wrong event class will be subscribed. (There may even be an error issued at time of registration, if there’s a property value specified).

So, a more robust way to make this work could be to give the EventBusRegistrationBeanPostProcessor a low order of precedence and do something like this:

public class EventBusRegistrationBeanPostProcessor implements BeanPostProcessor {

private final EventBus eventBus;
private Object preInitializedBean;

public EventBusRegistrationBeanPostProcessor(EventBus eventBus) {
this.eventBus = eventBus;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    this.preInitializedBean = bean;
    return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    if (bean instanceof Proxy) {
        eventBus.register(preInitializedBean);
    } else {
        eventBus.register(bean);
    }
    return bean;
}

By doing so, we see that :

  1. All BeanPostProcessors would have had a chance to post process the bean instance.
  2. Proxied beans still work as they still proxy the original bean which is now registered properly with the eventBus.

Hope this helps someone till such time other implementations of the HandlerFindingStrategy become available. A useful implementation would be one that looks for a tag interface (like Subscribed, maybe), which will automatically be available on the proxy. Then there will be no need to place that instanceof check in the post processor.


Viewing all articles
Browse latest Browse all 15

Trending Articles