Implementing Seam style @Logger injection with Spring

Seam provides a number of features to help programmers with the tedious but necessary logging. One of them is the @Logger annotation that is used to inject a Seam Log instance into a Seam component. For example instead of writing:

private Log log = LogFactory.getLog(MyClass.class);

you can write:

@Logger private Log log;

and Seam will inject an appropriate logger. Below we will try to to implement this feature using the Spring Framework for objects managed by the Spring DI container (actually a BeanFactory).

The Spring DI container provides a number of extension interfaces that beans can implement to get callbacks from the container in various stages of the container operation. One callback interface is the BeanPostProcessor. BeanPostProcessors are called before and after the initialization of each bean and allow the custom modification of bean instances (for example wrapping an instance with a dynamic proxy).

The only thing we have to do to implement the @Logger injection (besides defining a @Logger annotation) is to write a BeanPostProcessor that, before each bean gets initialized (right after it gets constructed), will iterate over the fields of the bean to detect any @Logger annotations and construct and inject a new logger instance.

Let’s define the annotation:

package com.tzavellas.spring.logger;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(RUNTIME)
@Target(FIELD)
@Documented
public @interface Logger { }

and now the BeanPostProcessor:

package com.tzavellas.spring.logger;

import java.lang.reflect.Field;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.util.ReflectionUtils;

import static org.springframework.util.ReflectionUtils.FieldCallback;

public class LoggerPostProcessor implements BeanPostProcessor {

  public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    return bean;
  }

  public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
    ReflectionUtils.doWithFields(bean.getClass(), new FieldCallback() {
      public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
        if (field.getAnnotation(Logger.class) != null) {
          Log log = LogFactory.getLog(bean.getClass());
          field.set(bean, log);
        }
      }
    });
    return bean;
  }
}

Below I have a small JUnit test to verify that it works:

package com.tzavellas.spring.logger;

import static org.junit.Assert.*;

import org.apache.commons.logging.Log;
import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.beans.factory.support.RootBeanDefinition;

public class LoggerPostProcessorTest {

  DefaultListableBeanFactory factory = new DefaultListableBeanFactory();

  @Before
  public void setupFactory() {
    RootBeanDefinition bean = new RootBeanDefinition(Bean.class, true);
    factory.addBeanPostProcessor(new LoggerPostProcessor());
    factory.registerBeanDefinition("bean", bean);
  }

  @Test
  public void injectLogger() {
    Bean b = (Bean) factory.getBean("bean");
    assertNotNull(b.log);
    b.doSomething();
  }
}

class Bean {

  @Logger Log log;

  public void doSomething() {
    log.debug("message");
  }
}

This is a very simple implementation since the goal of this article is to demonstrate the extensibility of the Spring DI container and not to implement a complete solution. One limitation is that this implementation only injects loggers (actually commons-logging Logs) to public fields (Seam can inject Seam Logger to private fields).

To use the above in a Spring XML file you simply define a bean with class com.tzavellas.spring.logging.LoggerPostProcessor (the id/name is not needed). The BeanFactory/ApplicationContext will automatically detect all beans that implement the BeanPostProcessor interface at startup time and will initialize them and then call them every time a bean gets initialized.