Spring构造函数注入SLF4J logger – 如何获取注入目标类?

我正在尝试使用Spring将SLF4J记录器注入类中,如下所示:

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

我找到了FactoryBean类,我已经实现了它。 但问题是我无法获得有关注射目标的任何信息:

 public class LoggingFactoryBean implements FactoryBean { @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? */); } } 

FactoryBean甚至是正确的方法吗? 当使用picocontainers 工厂注入时 ,你会得到传入目标的Type 。在guice中它有点棘手 。 但是你如何在Spring中实现这一目标?

这是您的解决方案的替代方案。 您可以使用BeanFactoryPostProcessor实现实现目标。

假设你想要一个带日志的类。 这里是:

  package log; import org.apache.log4j.Logger; @Loggable public class MyBean { private Logger logger; } 

正如您所看到的,这个类什么都不做,只是为了简单而创建了一个记录器容器。 这里唯一值得注意的是@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 { } 

此注释仅是进一步处理的标记。 这是一个最有趣的部分:

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

它搜索所有bean,如果bean被标记为@Loggable ,它会使用名称logger初始化其私有字段。 您可以更进一步并在@Loggable注释中传递一些参数。 例如,它可以是与记录器对应的字段的名称。

我在这个例子中使用了Log4j,但我想它应该与slf4j完全相同。

我用自定义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 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); } } } 

XML配置的示例用法:

  CustomBeanFactory customBeanFactory = new CustomBeanFactory(); GenericApplicationContext ctx = new GenericApplicationContext(customBeanFactory); XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx); xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml")); ctx.refresh(); 

编辑:

您可以在下面找到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 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."); } } 

为了使您的代码更具弹性,请使用InjectionPoint定义记录器,即:

 @Bean @Scope("prototype") public Logger logger(InjectionPoint ip) { return Logger.getLogger(ip.getMember().getDeclaringClass()); } 

这里需要@Scope("prototype")来在每次调用方法时创建’logger’bean实例。

尝试以下方法:

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

和:

    
  1. 为什么要为每个实例创建一个新的记录器? 典型的模式是每个类有一个记录器(作为私有静态成员)。

  2. 如果你真的想这样做:也许你可以编写一个记录器工厂类,并注入它? 就像是:

     @Singleton public class LogFactory { public Logger getLogger(Object o) { return LoggerFactory.getLogger(o.getClass()); } } 

是的,你的方向是错误的。 如果我是你,我会注入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); } } 

但是,在你去那里之前,这大概是org.apache.commons.logging做得对吗? http://commons.apache.org/logging/

您使用Log而不是Loggers:

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

Apache然后浏览类路径以查看是否有log4j或其他人,并委托给它找到的“最佳”。 Slf4j替换了类路径中的log4j,所以如果你已经加载了它(并且排除了apache log4j),那么commons将委托给它。

从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); } } 

我正在尝试将此function纳入官方SLF4J API。 请支持/投票/贡献: https : //issues.jboss.org/browse/JBLOGGING-62

(此function已由JBoss Logging + Seam Solder实现,请参阅http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html )

11.4。 本机记录器API

您还可以注入一个“普通旧”Logger(来自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; 

很抱歉没有提供相关的答案。