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

后端 未结 8 1970
太阳男子
太阳男子 2020-12-25 15:20

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
  pu         


        
相关标签:
8条回答
  • 2020-12-25 15:34

    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.

    0 讨论(0)
  • 2020-12-25 15:35

    Since Spring 4.3.0 you can use InjectionPoint or DependencyDescriptor as parameters for bean producing methods:

    @Component
    public class LoggingFactoryBean {
        @Bean
        public Logger logger(InjectionPoint injectionPoint) {
            Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
            return LoggerFactory.getLogger(targetClass);
        }
    }
    
    0 讨论(0)
  • 2020-12-25 15:38
    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());  
          }  
      }
      
    0 讨论(0)
  • 2020-12-25 15:41

    I am trying to get this feature into official SLF4J API. Please support/vote/contribute: https://issues.jboss.org/browse/JBLOGGING-62

    (this feature is already implemented by JBoss Logging + Seam Solder, see http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html)

    11.4. Native logger API

    You can also inject a "plain old" Logger (from the JBoss Logging API):

    import javax.inject.Inject;
    import org.jboss.logging.Logger;
    
    public class LogService {
    
        @Inject
        private Logger log;
    
        public void logMessage() {
            log.info("Hey sysadmins!");
        }
    
    }
    

    Log messages created from this Logger will have a category (logger name) equal to the fully-qualified class name of the bean implementation class. You can specify a category explicitly using an annotation.

    @Inject @Category("billing")
    private Logger log;
    

    You can also specify a category using a reference to a type:

    @Inject @TypedCategory(BillingService.class)
    private Logger log;
    

    Sorry for not providing a relevant answer.

    0 讨论(0)
  • 2020-12-25 15:42

    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.");
        }
    }
    
    0 讨论(0)
  • 2020-12-25 15:51

    Try something like:

    @Component
    public class Example {
    
      @Autowired
      @Qualifier("exampleLogger")
      private final Logger logger;
    
    }
    

    And:

    <bean id="exampleLogger" class="org.slf4j.LoggerFactory" factory-method="getLogger">
      <constructor-arg type="java.lang.Class" value="package.Example"/>        
    </bean>
    
    0 讨论(0)
提交回复
热议问题