Spring constructor injection of SLF4J logger - how to get injection target class?

Alexander Torstling picture Alexander Torstling · Jun 14, 2010 · Viewed 18.4k times · Source

I'm trying to use Spring to inject a SLF4J logger into a class like so:

@Component
public class Example {

  private final Logger logger;

  @Autowired
  public Example(final Logger logger) {
    this.logger = logger;
  }
}

I've found the FactoryBean class, which I've implemented. But the problem is that I cannot get any information about the injection target:

public class LoggingFactoryBean implements FactoryBean<Logger> {

    @Override
    public Class<?> getObjectType() {
        return Logger.class;
    }  

    @Override
    public boolean isSingleton() {  
        return false;
    }

    @Override
    public Logger getObject() throws Exception {
        return LoggerFactory.getLogger(/* how do I get a hold of the target class (Example.class) here? */);
    }
}   

Is FactoryBean even the right way to go? When using picocontainers factory injection, you get the Type of the target passed in. In guice it is a bit trickier. But how do you accomplish this in Spring?

Answer

wax picture wax · Jun 15, 2010

Here is an alternative to your solution. You could achieve your goal with BeanFactoryPostProcessor implementation.

Let's assume you want to have a class with logging. Here it is:

  package log;
  import org.apache.log4j.Logger;

  @Loggable
  public class MyBean {

     private Logger logger;
  }

As you could see this class does nothing and created just to be a logger container for simplicity. The only remarkable thing here is @Loggable annotation. Here its source code:

package log;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Loggable {
}

This annotation is only a marker for further processing. And here is a most interesting part:

package log;

import org.apache.log4j.Logger;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;

import java.lang.reflect.Field;

public class LoggerBeanFactoryPostProcessor implements BeanFactoryPostProcessor{

    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        String[] names = beanFactory.getBeanDefinitionNames();
        for(String name : names){
            Object bean = beanFactory.getBean(name);
            if(bean.getClass().isAnnotationPresent(Loggable.class)){
                try {
                    Field field = bean.getClass().getDeclaredField("logger");
                    field.setAccessible(true);
                    field.set(bean, Logger.getLogger(bean.getClass()));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

It searches through all beans, and if a bean is marked as @Loggable, it initialize its private field with name logger. You could go even further and pass some parameters in @Loggable annotation. For example, it could be a name of field corresponding to logger.

I used Log4j in this example, but I guess it should work exactly the same way with slf4j.