我正在尝试使用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<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? */);
}
}
FactoryBean甚至是正确的方法吗?使用picocontainers factory injection时,您会获得传入目标的Type
。在guice中它有点trickier。但是你如何在Spring中实现这一目标呢?
答案 0 :(得分:20)
以下是您的解决方案的替代方案。您可以使用 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完全相同。
答案 1 :(得分:10)
我用自定义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);
}
}
}
使用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<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.");
}
}
答案 2 :(得分:6)
为了使您的代码更具弹性,请使用InjectionPoint
来定义记录器,即:
@Bean
@Scope("prototype")
public Logger logger(InjectionPoint ip) {
return Logger.getLogger(ip.getMember().getDeclaringClass());
}
这里需要 @Scope("prototype")
来在每次调用方法时创建'logger'bean实例。
答案 3 :(得分:2)
尝试类似:
@Component
public class Example {
@Autowired
@Qualifier("exampleLogger")
private final Logger logger;
}
和
<bean id="exampleLogger" class="org.slf4j.LoggerFactory" factory-method="getLogger">
<constructor-arg type="java.lang.Class" value="package.Example"/>
</bean>
答案 4 :(得分:0)
为什么要为每个实例创建一个新的记录器?典型的模式是每个类有一个记录器(作为私有静态成员)。
如果你真的想这样做:也许你可以编写一个记录器工厂类,并注入它?类似的东西:
@Singleton
public class LogFactory {
public Logger getLogger(Object o) {
return LoggerFactory.getLogger(o.getClass());
}
}
答案 5 :(得分:0)
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会将委托给它。
答案 6 :(得分:0)
从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);
}
}
答案 7 :(得分:-2)
我正在尝试将此功能纳入官方SLF4J API。请支持/投票/投稿:https://issues.jboss.org/browse/JBLOGGING-62
(此功能已由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;
很抱歉没有提供相关的答案。