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; }
和:
-
为什么要为每个实例创建一个新的记录器? 典型的模式是每个类有一个记录器(作为私有静态成员)。
-
如果你真的想这样做:也许你可以编写一个记录器工厂类,并注入它? 就像是:
@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;
很抱歉没有提供相关的答案。