Пружинный конструктор инъекций SLF4J logger - как получить целевой класс инъекций? - PullRequest
15 голосов
/ 14 июня 2010

Я пытаюсь использовать 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 правильным выбором?При использовании пикоконтейнеров фабричной инъекции вы получаете Type цели, переданной внутрь. В общем, это немного хитрее .Но как вы делаете это весной?

Ответы [ 8 ]

20 голосов
/ 15 июня 2010

Вот альтернатива вашему решению. Вы можете достичь своей цели с помощью 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.

10 голосов
/ 15 июня 2010

Я решил это с помощью пользовательского 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.");
    }
}
6 голосов
/ 16 января 2017

Чтобы сделать ваш код более осведомленным о Spring, используйте InjectionPoint для определения регистраторов, т. Е .:

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

@Scope("prototype") здесь необходимо для создания экземпляра бина 'logger' каждый раз, когда вызывается метод.

2 голосов
/ 24 сентября 2012

Попробуйте что-то вроде:

@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>
0 голосов
/ 11 июня 2016

Начиная с Spring 4.3.0, вы можете использовать InjectionPoint или DependencyDescriptor в качестве параметров для методов создания бинов:

@Component
public class LoggingFactoryBean {
    @Bean
    public Logger logger(InjectionPoint injectionPoint) {
        Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
        return LoggerFactory.getLogger(targetClass);
    }
}
0 голосов
/ 18 апреля 2011

Я пытаюсь включить эту функцию в официальный 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, будут иметь категорию (имя регистратора), равную полной квалификацииимя класса класса реализации компонента. Вы можете указать категорию явно, используя аннотацию.

@Inject @Category("billing")
private Logger log;

Вы также можете указать категорию, используя ссылку на тип:

@Inject @TypedCategory(BillingService.class)
private Logger log;

Извините, что не предоставил соответствующего ответа.

0 голосов
/ 15 июня 2010

Да, вы идете в неправильном направлении. На вашем месте я бы ввел 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 исключен), регистрация общих ресурсов будет делегироваться ему.

0 голосов
/ 14 июня 2010
  1. Почему вы создаете новый регистратор для каждого экземпляра? Типичный шаблон - один логгер на класс (в качестве частного статического члена).

  2. Если вы действительно хотите сделать это таким образом: Может быть, вы можете написать класс фабрики логгеров и внедрить это? Что-то вроде:

    @Singleton 
    public class LogFactory { 
        public Logger getLogger(Object o) {  
            return LoggerFactory.getLogger(o.getClass());  
        }  
    }
    
...