java SLF4J记录器的Spring构造函数注入-如何获得注入目标类?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 
原文地址: http://stackoverflow.com/questions/3038179/
Warning: these are provided under cc-by-sa 4.0 license.  You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Spring constructor injection of SLF4J logger - how to get injection target class?
提问by Alexander Torstling
I'm trying to use Spring to inject a SLF4J logger into a class like so:
我正在尝试使用 Spring 将 SLF4J 记录器注入到这样的类中:
@Component
public class Example {
  private final Logger logger;
  @Autowired
  public Example(final Logger logger) {
    this.logger = logger;
  }
}
I've found the FactoryBeanclass, which I've implemented. But the problem is that I cannot get any information about the injection target:
我找到了FactoryBean我已经实施的课程。但问题是我无法获得有关注入目标的任何信息:
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 Typeof the target passed in. In guice it is a bit trickier. But how do you accomplish this in Spring?
FactoryBean 甚至是正确的方法吗?使用 picocontainers工厂注入时,您会Type传入目标的 。实际上,这有点棘手。但是你如何在 Spring 中做到这一点呢?
采纳答案by Alexander Torstling
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:
我用自定义 BeanFactory 解决了它。如果有人提出更好的解决方案,我会很高兴听到。无论如何,这是豆工厂:
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:
使用 XML 配置的示例用法:
        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).
您可以在下面找到 Arend v. Reinersdorffs 的改进版本(请参阅注释以获取解释)。
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.");
    }
}
回答by wax
Here is an alternative to your solution. You could achieve your goal with BeanFactoryPostProcessorimplementation.
这是您的解决方案的替代方案。您可以使用BeanFactoryPostProcessor实现来实现您的目标。
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 @Loggableannotation. Here its source code:
正如你所看到的,这个类什么都不做,为了简单起见,它只是作为一个记录器容器而创建的。这里唯一值得注意的是@Loggable注释。这是它的源代码:
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 @Loggableannotation. For example, it could be a name of field corresponding to logger.
它搜索所有 bean,如果 bean 被标记为@Loggable,它会使用名称logger初始化其私有字段。您可以更进一步并在@Loggable注释中传递一些参数。例如,它可以是对应于 logger 的字段名称。
I used Log4j in this example, but I guess it should work exactly the same way with slf4j.
我在这个例子中使用了 Log4j,但我想它应该与 slf4j 以完全相同的方式工作。
回答by magiccrafter
To make your code more Spring aware use the InjectionPointto define the loggers, i.e.:
为了使您的代码更能识别 Spring,请使用InjectionPoint来定义记录器,即:
@Bean
@Scope("prototype")
public Logger logger(InjectionPoint ip) {
    return Logger.getLogger(ip.getMember().getDeclaringClass());
}
@Scope("prototype")is needed here to create 'logger' bean instance every time method is called.  
@Scope("prototype")每次调用方法时都需要在此处创建“记录器”bean 实例。  
回答by Adolfo
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>
回答by Arend v. Reinersdorff
Since Spring 4.3.0 you can use InjectionPointor DependencyDescriptor as parameters for bean producing methods:
从 Spring 4.3.0 开始,您可以使用InjectionPoint或 DependencyDescriptor 作为 bean 生成方法的参数:
@Component
public class LoggingFactoryBean {
    @Bean
    public Logger logger(InjectionPoint injectionPoint) {
        Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
        return LoggerFactory.getLogger(targetClass);
    }
}
回答by Mike Baranczak
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).
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()); } }
为什么要为每个实例创建一个新的记录器?典型的模式是每个类有一个记录器(作为私有静态成员)。
如果您真的想这样做:也许您可以编写一个记录器工厂类,然后注入它?就像是:
@Singleton public class LogFactory { public Logger getLogger(Object o) { return LoggerFactory.getLogger(o.getClass()); } }
回答by Gray
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.
是的,你走错了方向。如果我是你,我会注入 LoggerFactory。如果您想隐藏它是 slf4j,那么我将定义一个 LoggerFactory 接口并注入一个委托给 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/
但是,在你去那里之前,这大约是 org.apache.commons.logging 正在做的事情吗? http://commons.apache.org/logging/
You use Log's instead of Loggers:
您使用 Log's 而不是 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.
Apache 然后查看类路径以查看您是否有 log4j 或其他,并将其委托给它找到的“最佳”。Slf4j 替换了类路径中的 log4j,因此如果您加载了它(并且排除了 apache log4j),公共日志记录将委托给它。
回答by Hendy Irawan
I am trying to get this feature into official SLF4J API. Please support/vote/contribute: https://issues.jboss.org/browse/JBLOGGING-62
我正在尝试将此功能纳入官方 SLF4J API。请支持/投票/贡献: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)
(此功能已由 JBoss Logging + Seam Solder 实现,参见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;
11.4. 原生记录器 API
您还可以注入一个“普通的”记录器(来自 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!"); } }从此 Logger 创建的日志消息的类别(记录器名称)等于 bean 实现类的完全限定类名。您可以使用注释显式指定类别。
@Inject @Category("billing") private Logger log;您还可以使用对类型的引用来指定类别:
@Inject @TypedCategory(BillingService.class) private Logger log;
Sorry for not providing a relevant answer.
很抱歉没有提供相关的答案。

