Showing posts with label Spring. Show all posts
Showing posts with label Spring. Show all posts

Tuesday, February 12, 2013

Using Guava EventBus with Spring - Part 2


In my previous post on the topic of Using Guava EventBus with Spring, several smart people pointed out shortcomings in my code sample.

Yvan DeBoeck pointed out that Guava was already doing the work of scanning an object for @Subscribe annotations. So, I reduced that aspect in my code. My scanning is now limited to reporting a warning in the logs, and is only performed on beans that I am not registering with EventBus.

Benjamin Diedrichsen pointed out that EventBus uses strong references. This is a really important tip! Spring will happily generate plenty of non-singleton beans, and giving them to EventBus is a recipe for a memory leak. So, I added an @Autowired reference to the ApplicationContext and use that to verify if a bean is a singleton before calling eventBus.register().

Here is my updated version of EventBusPostProcessor:

/*
 * EventBusPostProcessor.java
 * Author: Patrick Meade
 *
 * EventBusPostProcessor.java is hereby placed into the public domain.
 * Use it as you see fit, and entirely at your own risk.
 */

package com.example.spring.guava;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;

/**
 * EventBusPostProcessor registers Spring beans with EventBus.
 * @author pmeade
 */
public class EventBusPostProcessor implements BeanPostProcessor
{
    private static final Logger log = LoggerFactory.getLogger(EventBusPostProcessor.class);

    public static boolean containsSubscribe(Object bean)
    {
        Method[] methods = bean.getClass().getMethods();
        for(Method method : methods)
        {
            Subscribe subscribe = method.getAnnotation(Subscribe.class);
            if(subscribe != null)
            {
                return true;
            }
        }
        return false;
    }
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
                  throws BeansException
    {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
                  throws BeansException
    {
        if(applicationContext.isSingleton(beanName)) {
            eventBus.register(bean);
        } else {
            if(containsSubscribe(bean)) {
                log.warn("Bean {} containing @Subscribe annotation(s) was not "
                    + "registered with EventBus. EventBus registration of "
                    + "prototype beans (isSingleton() == false) can cause "
                    + "memory leaks.", beanName);
            }
        }
        return bean;
    }

    @Autowired private ApplicationContext applicationContext;
    @Autowired private EventBus eventBus;
}

Tuesday, June 5, 2012

Using Guava EventBus with Spring

Recently, I started working on VeloxMUD again. The time away has really given me a new perspective on the code. Usually, that perspective goes like this:
  • What the heck was I thinking?
The first thing I looked at was the code that handled connections for the MUD. There is a thread that waits on socket.accept() and then passes the Socket objects off to a handler service (injected by Spring). Although this isn't terrible, I thought I could improve it by using a Publish-Subscribe pattern to decouple the two.

I started my search with Observable and found that it wasn't going to fit. Although the Observer interface isn't bad, the need to derive from Observable wasn't going to work for me. My publisher (the "observable" object) already derived from Thread. And it seemed there had to be a better way.

I stumbled upon Guava's EventBus. Ah ha! This is what I need! But wait, where are all the publish-subscribe guts that I need to implement? Gone... Publishers post events of any arbitrary type, subscribers add a method (with any name) and a @Subscribe annotation. Done. Wow! This is observable done right!

Oops... one small problem. Most of the stuff that needs to register is created and managed by Spring. How do I register my subscribers without tackling all sorts of nasty lifecycle problems?

BeanPostProcessor comes to the rescue! We can provide custom bean-processing code to Spring. With the right piece of code, Spring can do all the necessary registration for us.

/*
 * EventBusPostProcessor.java
 * Author: Patrick Meade
 *
 * EventBusPostProcessor.java is hereby placed into the public domain.
 * Use it as you see fit, and entirely at your own risk.
 */

package com.example.spring.guava;

import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanPostProcessor;

/**
 * EventBusPostProcessor registers Spring beans with EventBus. All beans
 * containing Guava's @Subscribe annotation are registered.
 * @author pmeade
 */
public class EventBusPostProcessor implements BeanPostProcessor
{
    private final Logger log = LoggerFactory.getLogger(this.getClass());
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName)
            throws BeansException
    {
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName)
            throws BeansException
    {
        // for each method in the bean
        Method[] methods = bean.getClass().getMethods();
        for(Method method : methods)
        {
            // check the annotations on that method
            Annotation[] annotations = method.getAnnotations();
            for(Annotation annotation : annotations)
            {
                // if it contains the Subscribe annotation
                if(annotation.annotationType().equals(Subscribe.class))
                {
                    // register it with the event bus
                    eventBus.register(bean);
                    log.trace("Bean {} containing method {} was subscribed to {}",
                        new Object[] {
                            beanName, method.getName(),
                            EventBus.class.getCanonicalName()
                        });
                    // we only need to register once
                    return bean;
                }
            }
        }
        
        return bean;
    }
    
    @Autowired
    private EventBus eventBus;
}

We add the following XML stanzas to Spring's application context:

<bean id="eventBus"
      class="com.google.common.eventbus.EventBus"/>
<bean id="eventBusPostProcessor"
      class="com.example.spring.guava.EventBusPostProcessor"/>

And it works! Spring registers every bean with a @Subscribe annotation.

2012-06-05 00:50:09,084 TRACE [main] com.example.spring.guava.EventBusPostProcessor (EventBusPostProcessor.java:64) - Bean connectionService containing method handleConnection was subscribed to com.google.common.eventbus.EventBus