18

我正在尝试使用 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工厂注入时,你会得到Type传入的目标。在 guice 中它有点棘手。但是你如何在 Spring 中实现这一点?

4

8 回答 8

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注释中传递一些参数。例如,它可以是 logger 对应的字段名称。

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

于 2010-06-15T19:38:44.537 回答
11

我用自定义 BeanFactory 解决了它。如果有人提出更好的解决方案,我会很高兴听到。无论如何,这是 bean 工厂:

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.");
    }
}
于 2010-06-15T12:05:13.410 回答
11

为了让您的代码更加了解 Spring,请使用InjectionPoint来定义记录器,即:

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

@Scope("prototype")每次调用方法时都需要在这里创建“记录器”bean 实例。

于 2017-01-16T15:07:58.653 回答
2

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>
于 2012-09-24T14:34:11.487 回答
0

是的,你走错了方向。如果我是你,我会注入 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/

您使用日志而不是记录器:

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),则公共日志记录将委托给它。

于 2010-06-14T23:17:53.707 回答
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);
    }
}
于 2016-06-11T15:06:32.627 回答
-1
  1. 为什么要为每个实例创建一个新的记录器?典型的模式是每个类有一个记录器(作为私有静态成员)。

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

    @Singleton 
    public class LogFactory { 
        public Logger getLogger(Object o) {  
            return LoggerFactory.getLogger(o.getClass());  
        }  
    }
    
于 2010-06-14T14:58:41.190 回答
-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

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

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

于 2011-04-18T11:17:34.013 回答