Thursday, February 17, 2011

Dependency Injection and Logging

Dependency Injection under Java EE 6 (CDI)

I was recently working on a project that runs under Java EE 6. One of the new features of Java EE 6 is CDI, also known as "Contexts and Dependency Injection for the Java EE platform", or JSR 299. CDI allows fine grained dependency injection for initializing container managed objects ("beans"). CDI allows any Java object to be injected into a bean. This is a big improvement over the dependency injection introduced in Java EE 5, which allows only certain objects such as EJB's or JNDI registered resources to be injected.

The reference implemntation of CDI is called Weld, and comes from the JBoss division of Red Hat.

What's in a name? My logging woes

I use logging in almost every Java class I write. I prefer the Log4J logging framework, but write my code that uses logging to use the Apache Commons Logging library, which provides a thin wrapper around Log4J and other logging frameworks. Using Commons Logging allows for flexibility when deploying code in different environments, and fixes an issue with using Log4J directly in serializable classes. (This issue will be discussed in a later post.) In the past I always used code like this to initialize the Log object for each class:
class MyClass {
    private static final Log log = LogFactory.getLog(MyClass.class);

That's a bit of typing, so I often copy the line that initializes the log field from an existing class. If I had a dollar for every time I copied such a line but forgot to change the class name to the name of the class I was pasting into, I would right now be enjoying a nice sushi dinner at my favorite restaurant instead of typing this blog post!

A solution to my logging woes

You may be asking yourself what this discussion about logging has to do with the JSR-299 (CDI) dependency injection framework I mentioned earlier. While reading the documentation for the Weld implementation of JSR-2991, I came across a nifty solution to my Log mis-naming problem. I replace the above code with:
class MyClass {
   @Inject private Log log;

I configure CDI to automatically inject a Log instance properly configured for the name of the class being injected into by defining a CDI bean in my application that has an appropriate factory method:

public class LoggerFactory {
   
    @Produces Log createLogger(InjectionPoint injectionPoint) {
        String name = injectionPoint.getMember().getDeclaringClass().getName(); 
        return LogFactory.getLog(name);
     }

Now whenever the CDI framework creates an object that contains the @Inject line shown above, a Log object is created for the field that is automatically configured with the name of the class that contains the log field.

Annotations vs. XML configuration files

The @Inject annotation shown above simplifies the use of dependency injection compared to earlier frameworks which required all dependencies to be described in an XML configuration file. Usin XML files to describe every bean gave early dependency injection frameworks one of the same problems as early versions of EJB. You had to write your code, and then describe your code in some detail in an XML configuration file.

Modern dependency injection frameworks usually provide the option of describing dependencies with Java annotations such as the @Inject annotation shown above. This reduces the need to write XML configuration files. However, using framework specific annotations in your business classes causes one of the problems dependency injection was supposed to avoid: a tight coupling between your business classes and a specific framework.

Fortunatel the Java community is converging on a solution. The @Inject annotation in the examples above is defined in JSR 330, which defines annotations for describing dependencies of a bean. JSR 330 is now supported by multiple dependency injection frameworks, including CDI, Spring (3.0 and later), and Google Guice (3.0 and later).

It's time for Spring!

Or so says a certain groundhog that lives near here. As mentioned above, Spring 3.0 supports the JSR 330 annotations such as the @Inject annotation used in the examples above.

I recently worked on a Spring based project that needed to use the classes I had written for a Java EE 6 project using @Inject annotations to configure logging. Getting this to work under Spring was a bit trickier than under CDI.

Spring supports defining factory methods to create object instances to satisify dependencies. The dependencies for Log fields can by satisfied by adding the following to the Spring config file:

<bean id="defaultLog"
            class="org.apache.commons.logging.LogFactory" 
            factory-method="getLog"scope="singleton">
        <constructor-arg type="java.lang.String" value="defaultLog"/>
    </bean>

Although this succeeds in injecting a valid Log instance into any bean that uses @Inject to defines a Log field, all of the Log instances inserted have the same name, which is less than ideal.

I found a solution to this on the web.2 A Spring bean is defined that implements the BeanPostProcessor interface. This bean contains a method that replaces the injected Log instance with one that has a name that matches the class name of the bean that contains the log field:

public class LoggerPostProcessor implements BeanPostProcessor {
    public Object postProcessBeforeInitialization(final Object bean, String beanName) throws BeansException {
     ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
         public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
             if (field.getAnnotation(Inject.class) != null && field.getType().equals(Log.class)) {
                 ReflectionUtils.makeAccessible(field);
                 field.set(bean, LogFactory.getLog(bean.getClass()));
             }
         }
       });

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

This is certainly messier than the CDI implementation, but once the LoggerPostProcessor is added to your Spring application you can use @Inject to set up your Log fields and it will work the same way in both CDI and Spring.

Note the the factory method shown in the Spring configuration above must still be present, or Spring will get errors at runtime because it cannot satisfy the declared dependency.

My implementations of LoggerFactory and LoggerPostProcessor are availabe at:

    http://www.bberger.net/di/

Feel free to use them as is or adapt them to your own needs.

In my next post I will show how to configure Google Guice to work with classes that use the same @Inject annotation used in these examples.

References

  1. http://docs.jboss.org/weld/reference/1.0.0/en-US/html/injection.html#d0e1540
    JBoss Weld documentation example using InjectionPoint to configure logging
  2. http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring
    Example using a Spring BeanPostProcessor to configure logging

5 comments:

  1. If you use Eclipse, create a Java Template called "logger" with the following content:

    private static final Logger log = Logger.getLogger(${enclosing_type}.class);

    Then you just have to type "logger" and hit Ctrl+Space. Much easier and cleaner :-)

    ReplyDelete
  2. Hi Daniel, I don't understand how your 'Eclipse' solution is much cleaner...

    E.g. With CDI solution, for each class you end up with:

    @Inject private Log log;

    and with your solution, you end up with:

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

    which is very long and not as easy to maintain as the CDI solution.

    ReplyDelete
  3. Hi Robert,
    great approach i like it specially since it delegates the initialization of the loggger to one single place and reduce the amount of code one need to type => more code more errors :)

    ReplyDelete
  4. Nit-picky point, but do you mind at all that you lose the fact that your logger which was initially declared as a static variable is no longer static in your CDI example?

    ReplyDelete
  5. I've implemented a very similar solution, but ran into a problem in unit tests that create their own instances of @Loggable beans. If the unit test is not a Spring-aware test (ie, run with the SpringJUnit4ClassRunner and/or including @ContextConfiguration) then obviously the bean post-processor doesn't run and thus the @Loggables don't have their Log field set.
    As I have a lot of tests that are truly unit tests (ie don't want or need to have Spring involved), I'm looking for a clean way to handle this problem, without resorting to exposing a public setter for the Log field. Any thoughts?

    ReplyDelete