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;
}

No comments:

Post a Comment