Search code examples
javaspringslf4jconstructor-injection

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


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?


Solution

  • 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();
    

    EDIT:

    Below you can find Arend v. Reinersdorffs improved version (see the comments for an explanation).

    import java.lang.reflect.Field;
    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;
    import org.springframework.core.MethodParameter;
    
    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 (Logger.class == descriptor.getDependencyType()) {            
                return LoggerFactory.getLogger(getDeclaringClass(descriptor));
            } else {
                return super.resolveDependency(descriptor, beanName,
                        autowiredBeanNames, typeConverter);
            }
        }
    
        private Class<?> getDeclaringClass(DependencyDescriptor descriptor) {
            MethodParameter methodParameter = descriptor.getMethodParameter();
            if (methodParameter != null) {
                return methodParameter.getDeclaringClass();
            }
            Field field = descriptor.getField();
            if (field != null) {
                return field.getDeclaringClass();
            }
            throw new AssertionError("Injection must be into a method parameter or field.");
        }
    }