【问题标题】:Using java annotation to inject logger dependency使用java注解注入logger依赖
【发布时间】:2011-09-15 02:48:46
【问题描述】:

我正在使用带有 aspect-j 注释支持的 spring,以允许 @Loggable 注释。这允许根据配置自动记录类。

我想知道我是否可以以某种方式使用此注释将 slf4j Logger 变量公开到类中以供直接使用,这样我就不必做任何事情:

Logger logger = LoggerFactory.getLogger(MyClass.class);

如果上面由于注释而隐式可用,那就太好了,我可以在没有声明的情况下继续做logger.debug("...");。我不确定这是否可能。

【问题讨论】:

  • 您是在说实际上将记录器字段添加到没有记录器的 bean/类?

标签: java spring annotations slf4j


【解决方案1】:

您可以使用BeanPostProcessor 接口,该接口由ApplicationContext 为所有创建的bean 调用,因此您有机会填充适当的属性。

我创建了一个简单的实现,它做到了:

import java.lang.reflect.Field;
import java.util.List;

import net.vidageek.mirror.dsl.Mirror;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        List<Field> fields = new Mirror().on(bean.getClass()).reflectAll().fields(); 
        for (Field field : fields) {
            if (Logger.class.isAssignableFrom(field.getType()) && new Mirror().on(field).reflect().annotation(InjectLogger.class) != null) {
                new Mirror().on(bean).set().field(field).withValue(LoggerFactory.getLogger(bean.getClass()));
            }
        }
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

您不必执行任何复杂的注册步骤,因为ApplicationContext 能够识别BeanPostProcessor 实例并自动注册它们。

@InjectLogger 注释是:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface InjectLogger {
}

然后你就可以轻松使用注解了:

public static @InjectLogger Logger LOGGER;

...

LOGGER.info("Testing message");

我使用Mirror 库来查找带注释的字段,但显然您可以执行手动查找以避免这种额外的依赖。

避免重复代码实际上是一个好主意,甚至是从其他类复制和粘贴 Logger 定义产生的小问题,例如当我们忘记更改 class 参数时,这会导致错误的日志。

【讨论】:

  • 非常酷的解决方案。如果您想按包或一组类对日志进行分组,您可以使用此解决方案在 XML 中移动该配置。此外,一个非常好的完整答案,带有清晰的示例。
  • 这是一个糟糕的解决方案。
  • 想说点什么。除了引入自定义的'Log'注解,你可以使用'standard''Autowired'注解和'required='false',并且不仅通过注解而且通过'BeanPostProcessor'中的'Logger'类型搜索目标字段。
【解决方案2】:

你不能用一个方面来做,但 可以帮助你,在我看来,优雅的方式。请参阅@Log 注释。

【讨论】:

  • 哇——龙目岛很酷。基本上与 Groovy AST 转换相同。目前它看起来有点前沿,但也许随着 OpenJDK 的发展,这将成为标准。
  • 我同意,lombok 应该是标准的 java (+1)
  • Lombok 真的很棒,即使是用于创建 getter、setter、构造函数或构建器......它也很规则!
  • 这是 Lombok 完美运行的少数实例之一:减少样板代码,同时仅更改类的内部结构。
【解决方案3】:

我认为@Redder 的解决方案是一种很好的方法。但是,我不想包含 Mirror 库,所以我编写了 LoggerPostProcessor 的实现,它使用 Java 反射库来代替。这里是:

package com.example.spring.postProcessor;

import com.example.annotation.InjectLogger;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.Arrays;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.stereotype.Component;

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    private static Logger logger = LoggerFactory.getLogger(LoggerPostProcessor.class);

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessBeforeInitialization(java.lang.Object, java.lang.String)
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        List<Field> fields = Arrays.asList(bean.getClass().getDeclaredFields());

        for (Field field : fields) {
            if (Logger.class.isAssignableFrom(field.getType()) && field.getAnnotation(InjectLogger.class) != null) {

                logger.debug("Attempting to inject a SLF4J logger on bean: " + bean.getClass());

                if (field != null && (field.getModifiers() & Modifier.STATIC) == 0) {
                    field.setAccessible(true);
                    try {
                        field.set(bean, LoggerFactory.getLogger(bean.getClass()));
                        logger.debug("Successfully injected a SLF4J logger on bean: " + bean.getClass());
                    } catch (IllegalArgumentException e) {
                        logger.warn("Could not inject logger for class: " + bean.getClass(), e);
                    } catch (IllegalAccessException e) {
                        logger.warn("Could not inject logger for class: " + bean.getClass(), e);
                    }
                }
            }
        }

        return bean;
    }

    /* (non-Javadoc)
     * @see org.springframework.beans.factory.config.BeanPostProcessor#postProcessAfterInitialization(java.lang.Object, java.lang.String)
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

}

【讨论】:

  • 这有效,但仅适用于顶层具体类。如果您在下面有一个包含 log 变量的扩展类,它似乎无法访问它。我必须在具体类中声明我的日志变量,并在具体类中声明我的 getLog() 方法。
【解决方案4】:

我想对@Redder 的解决方案进行一些改进。

  • 首先 - 我们可以省略引入新注解@Log,而是我们 可以使用 Spring 的 @Autowired 注释并将 'required' 标志设置为 'false' 使 Spring 不检查 bean 是否被注入 (因为,我们稍后会注入它)。
  • 第二 - 使用 Spring 的 ReflectionUtils API 提供所有 字段发现和操作所需的方法,因此我们不需要额外的外部依赖项。

这里是一个示例(在 Java 8 中,但可以在 Java 7/6/等中重写,也使用 slf4j 外观,但可以用任何其他记录器替换):

@Component
public class LoggerPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {

        Logger logger = getLogger(bean.getClass());
        doWithFields(bean.getClass(), field -> {

            makeAccessible(field);
            setField(field, bean, logger);

        }, field -> field.isAnnotationPresent(Autowired.class) && Logger.class.equals(field.getType()));

        return bean;
    }
    ...
}
...
//logger injection candidate
@Autowired(required = false)
private Logger log;

【讨论】:

    【解决方案5】:

    因为我在CDI (JSR 299:上下文和依赖注入)this link shows the straightforward way to do this using CDI(以及使用@987654323 的替代方法)中尝试做同样的事情时得到了第一个结果@):

    基本上只需要注入:

    class MyClass {
       @Inject private Log log;
    

    并且有一个像这样的记录器工厂:

    @Singleton
    public class LoggerFactory implements Serializable {
        private static final long serialVersionUID = 1L;
    
        static final Log log = LogFactory.getLog(LoggerFactory.class);
    
       @Produces Log createLogger(InjectionPoint injectionPoint) {
        String name = injectionPoint.getMember().getDeclaringClass().getName(); 
        log.debug("creating Log instance for injecting into " + name); 
        return LogFactory.getLog(name);
        }   
    }
    

    我发现我需要将transient 添加到注入的log 中,这样我的会话作用域bean 中就不会出现钝化作用域异常:

    @Named()
    @SessionScoped()
    public class MyBean implements Serializable {
        private static final long serialVersionUID = 1L;
    
        @Inject
        private transient Log log;
    

    【讨论】:

      【解决方案6】:

      这是一篇包含所有代码的完整示例的博客文章:Injecting loggers using Spring

      【讨论】:

        【解决方案7】:

        Herald 提供了一个非常简单的 BeanPostProcessor,它可以为您完成所有的魔法。您可以在 Spring bean 的任何字段上使用 @Log 注解,让​​ Herald 在该字段中注入合适的记录器。

        支持的日志框架:

        • JavaTM 2 平台的核心日志框架
        • Apache Commons 日志记录
        • Java 的简单日志外观 (SLF4J)
        • SLF4J 扩展记录器
        • 回退
        • Apache Log4j
        • Apache Log4j 2
        • JBoss 日志记录
        • Syslog4j
        • 来自 Graylog 的 Syslog4j 分支
        • Fluent Logger for Java

        也可以添加其他日志框架。

        Github 仓库:https://github.com/vbauer/herald

        【讨论】:

        • 我在我的 Spring Boot 应用程序中尝试了这个库。我在 pom.xml 中添加了依赖 我在一个字段上使用了注解。但是当我尝试使用时该字段为空。
        • @HarishReddy 您能否与我联系或在 Github 上创建问题?看起来很奇怪,我在不同的项目中使用过这个库,一切都很好。此外,Hareld 项目中的测试检查与 Spring Boot 的集成
        • 当然。我将创建一个仅包含要复制的代码的 github 存储库,并且我将在 github 上与该存储库 URL 一起提出问题。
        • @HarishReddy 我刚刚回答了问题:github.com/vbauer/herald/issues/2 Hareld 没有问题
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2023-03-09
        • 2011-11-06
        • 2010-11-29
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多