views:

428

answers:

4

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?

A: 
  1. Why are you creating a new logger for each instance? The typical pattern is to have one logger per class (as a private static member).

  2. If you really do want to do it that way: Maybe you can write a logger factory class, and inject that? Something like:

    @Singleton 
    public class LogFactory { 
        public Logger getLogger(Object o) {  
            return LoggerFactory.getLogger(o.getClass());  
        }  
    }
    
Mike Baranczak
Sorry, the code formatting got fucked, and I can't seem to fix it. You get the idea, though.
Mike Baranczak
1: See http://wiki.apache.org/commons/Logging/StaticLog2: This is what I have done already. It works ok, but is very verbose IMO.
disown
A: 

Yeah, you are going in the wrong direction. If I were you I would inject the LoggerFactory. If you want to hide that it is slf4j then I'd define a LoggerFactory interface and inject a class which delegates through to slf4j Logger.

public interface LoggerFactory {
    public Logger getLogger(Class<?> clazz);
}
...
import org.slf4j.LoggerFactory;
public class Slf4jLoggerFactory implements LoggerFactory {
    public Logger getLogger(Class<?> clazz) {
        return org.slf4j.LoggerFactory.getLogger(clazz);
    }
}

However, before you go there, this is approximately what org.apache.commons.logging is doing right? http://commons.apache.org/logging/

You use Log's instead of Loggers:

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class CLASS {
    private Log log = LogFactory.getLog(CLASS.class);
    ...

Apache then looks through the classpath to see if you have log4j or others and delegates to the "best" one that it finds. Slf4j replaces log4j in the classpath so if you have it loaded (and apache log4j excluded) commons logging will delegate to it instead.

Gray
This is almost the same answer as Mike gave. I would like pass the log in, since I like a pure oo style. The 'service locator' approach is feasible, but a bit intrusive. One could argue that the runtime class should not be part of the interface in getLog, but I would expect Spring to pass on some sort of structure describing the target. Is this not at all possible?
disown
And on top of all, the commons logging hack of auto-discovery was the very reason that slf4j was born.
disown
Spring does not have this capacity with FactoryBean, no. It would create some sort of circular dependency if X depends on Y but Y knows about X when it is constructed.
Gray
A: 

I resolved it with a custom BeanFactory. If anyone comes up with a better solution, I would be happy to hear it. Anyway, here's the bean factory:

import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.TypeConverter;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;

public class CustomBeanFactory extends DefaultListableBeanFactory {

    public CustomBeanFactory() {
    }

    public CustomBeanFactory(DefaultListableBeanFactory delegate) {
        super(delegate);
    }

    @Override
    public Object resolveDependency(DependencyDescriptor descriptor,
            String beanName, Set<String> autowiredBeanNames,
            TypeConverter typeConverter) throws BeansException {
        //Assign Logger parameters if required      
        if (descriptor.isRequired()
                && Logger.class.isAssignableFrom(descriptor
                        .getMethodParameter().getParameterType())) {            
            return LoggerFactory.getLogger(descriptor.getMethodParameter()
                    .getDeclaringClass());
        } else {
            return super.resolveDependency(descriptor, beanName,
                    autowiredBeanNames, typeConverter);
        }
    }
}

Example usage with an XML config:

        CustomBeanFactory customBeanFactory = new CustomBeanFactory();      
        GenericApplicationContext ctx = new GenericApplicationContext(customBeanFactory);
        XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
        xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
        ctx.refresh();
disown
+1  A: 

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.

wax
+1 Clearest BeanFactoryPostProcessor example I've seen anywhere
Anonymouse
Thank you for your post. Your example is quite similar to the link I referenced in one of the comments above, http://www.tzavellas.com/techblog/2007/03/31/implementing-seam-style-logger-injection-with-spring/. It works well, but cannot work on constructors. I would like to use constructor injection since this means that the logger can be used in the constructor, and that I can declare the field final, a good habit in multi-threaded applications. Ideally constructor injection would work in the same way as field injection, but this would require some sort of constructor argument builders.
disown
@disown I think you're trying to solve non-existent problem. It's very unlikely you'll have to prevent multithreading issues on start. ApplicationContext descendants are guaranteed to be thread-safe (http://forum.springsource.org/showthread.php?t=11791).
wax
So... is there any clean way to have the logger available in constructor as well? Constructor injection looks messy, requiring the space in the constructor signature and two additional lines of saving the injected logger into the instance's field... if I have understood constructor injection correctly. Is that the only way?
Tuukka Mustonen