简体   繁体   English

使用java注释注入logger依赖项

[英]Using java annotation to inject logger dependency

I am using spring with aspect-j annotation support to allow for an @Loggable annotation. 我使用带有aspect-j注释支持的@Loggable来允许@Loggable注释。 This allows automatic logging on a class based on the configuration. 这允许基于配置自动登录类。

I am wondering if I can somehow use this annotation to expose an slf4j Logger variable into the class for direct use, so that I don't have to do something to the effect of: 我想知道我是否可以以某种方式使用此注释将slf4j Logger变量暴露给类以供直接使用,这样我就不必对以下内容执行某些操作:

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

It would be nice if the above was implicitly available due to the annotation and I could just go about doing logger.debug("..."); 如果上面因为注释而隐式可用,那将是很好的,我可以去做logger.debug("..."); without the declaration. 没有声明。 I'm not sure if this is even possible. 我不确定这是否可行。

You can use the BeanPostProcessor interface, which is called by the ApplicationContext for all created beans, so you have the chance to fill the appropriate properties. 您可以使用BeanPostProcessor接口,该接口由ApplicationContext为所有创建的bean调用,因此您有机会填充相应的属性。

I created a simple implementation, which does that: 我创建了一个简单的实现,它可以做到:

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

You don't have to do any complex registration step, since the ApplicationContext is capable of recognizing BeanPostProcessor instances and automatically register them. 您不必执行任何复杂的注册步骤,因为ApplicationContext能够识别BeanPostProcessor实例并自动注册它们。

The @InjectLogger annotation is: @InjectLogger注释是:

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

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

And then you can easily use the annotation: 然后您可以轻松使用注释:

public static @InjectLogger Logger LOGGER;

...

LOGGER.info("Testing message");

I used the Mirror library to find the annotated fields, but obviously you may perform a manual lookup in order to avoid this additional dependency. 我使用镜像库来查找带注释的字段,但显然您可以执行手动查找以避免这种额外的依赖性。

It's actually a nice idea to avoid repeated code, and even small issues that come from copying and paste the Logger definitions from other classes, like when we forget to change the class parameter, which leads to wrong logs. 实际上,避免重复代码,甚至是从其他类复制和粘贴Logger定义所产生的小问题也是一个好主意,比如当我们忘记更改class参数时,这会导致错误的日志。

You can't do it with an aspect, but can help you in a, in my opinion, elegant way. 你无法用一个方面做到这一点,但是可以帮助你,在我看来,优雅的方式。 See @Log annotation. 请参阅@Log注释。

I think the solution from @Redder is a great way of doing this. 我认为@Redder的解决方案是一种很好的方法。 However, I didn't want to include the Mirror library so I wrote an implementation of LoggerPostProcessor that uses the Java reflect library instead. 但是,我不想包含镜像库,因此我编写了一个使用Java反射库的LoggerPostProcessor实现。 Here it is: 这里是:

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

}

I want to make some improvements to @Redder's solution. 我想对@Redder的解决方案做一些改进。

  • First - we can omit introduction of new annotation @Log , instead we can use Spring's @Autowired annotation with 'required' flag set to 'false' to make Spring not to check that bean was injected or not (because, we will inject it later). 首先 - 我们可以省略新注释@Log引入,而是我们可以使用Spring的@Autowired注释,并将'required'标志设置为'false',以使Spring不检查是否注入了bean(因为,我们稍后会注入它) )。
  • Second - use Spring's ReflectionUtils API that provides all needed methods for field discovering and manipulation, so we don't need additional external dependencies. 第二 - 使用Spring的ReflectionUtils API,它提供了字段发现和操作所需的所有方法,因此我们不需要额外的外部依赖。

Here an example (in Java 8, but can be rewritten in Java 7/6/etc., also slf4j facade is used but it can be replaced with any other logger): 这里有一个例子(在Java 8中,但可以在Java slf4j /等中重写,也使用了slf4j facade,但它可以替换为任何其他记录器):

@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;

Since I got this as the first result when trying to do the same thing in CDI (JSR 299: Context and Dependency Injection) , this link shows the straightforward way to do this using CDI (and also an alternative using Spring ): 因为我在尝试在CDI (JSR 299:上下文和依赖注入)中做同样的事情时得到了这个结果, 这个链接显示了使用CDI (以及使用Spring的替代方法) 这样做的简单方法

Basically, you only need to inject: 基本上,你只需要注入:

class MyClass {
   @Inject private Log log;

And have a logger factory like so: 并有一个像这样的记录器工厂:

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

I found that I needed to add transient to the injected log so that I did not get a passivating scope exception in my session scoped beans: 我发现我需要在注入的log添加transient ,以便在我的会话范围bean中没有得到钝化范围异常:

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

    @Inject
    private transient Log log;

Herald provides a very simple BeanPostProcessor which does all the magic for you. Herald提供了一个非常简单的BeanPostProcessor,它为您提供了所有的魔力。 You can annotate any field of Spring bean with a @Log annotation to let Herald inject suitable logger in this field. 您可以使用@Log注释来注释Spring bean的任何字段,以使Herald在此字段中注入合适的记录器。

Supported logging frameworks: 支持的日志框架:

  • JavaTM 2 platform's core logging framework JavaTM 2平台的核心日志框架
  • Apache Commons Logging Apache Commons Logging
  • Simple Logging Facade for Java (SLF4J) 简单的Java日志记录(SLF4J)
  • SLF4J Extended logger SLF4J扩展记录器
  • Logback 的logback
  • Apache Log4j Apache Log4j
  • Apache Log4j 2 Apache Log4j 2
  • JBoss Logging JBoss日志记录
  • Syslog4j Syslog4j
  • Syslog4j fork from Graylog 来自Graylog的Syslog4j分叉
  • Fluent Logger for Java 适用于Java的Fluent Logger

It is also possible to add other logging frameworks. 还可以添加其他日志框架。

Github repo: https://github.com/vbauer/herald Github回购: https//github.com/vbauer/herald

这是一篇博客文章,其中包含一个包含所有代码的完整示例: 使用Spring注入记录器

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM