Внедрение конструктора Spring регистратора SLF4J – как получить целевой класс инъекции?

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


person Alexander Torstling    schedule 14.06.2010    source источник
comment
Действительно ли оно того стоит, просто чтобы не говорить `= LoggerFactory.getLogger()`?   -  person skaffman    schedule 14.06.2010
comment
Мне не нравится статическая привязка к LoggerFactory по тем же причинам, которые Мартин Фаулер излагает в martinfowler.com/articles. /injection.html. Внедренный LoggerFactory является приемлемым решением (в соответствии с шаблоном локатора службы), но немного подробным. Я полагаю, можно возразить, что для внедрения журнала необходимо использовать локатор службы, поскольку чистая зависимость должна быть независимой от цели. Но решение локатора многословно, другие фреймворки поддерживают его, и я ожидаю, что Spring сможет предоставить какую-то информацию о цели. Мне просто интересно, действительно ли это невозможно.   -  person Alexander Torstling    schedule 15.06.2010
comment
Я имею в виду, что эта информация передается в BeanPostProcessors: tzavellas.com/techblog/2007/03/31/. Разве нельзя сделать то же самое для внедрения конструктора?   -  person Alexander Torstling    schedule 15.06.2010


Ответы (8)


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

person wax    schedule 15.06.2010
comment
+1 Самый понятный пример BeanFactoryPostProcessor, который я когда-либо видел - person Anonymoose; 15.06.2010
comment
Спасибо за ваше сообщение. Ваш пример очень похож на ссылку, на которую я ссылался в одном из комментариев выше, tzavellas.com/techblog/2007/03/31/. Он работает хорошо, но не может работать с конструкторами. Я хотел бы использовать внедрение конструктора, так как это означает, что регистратор может использоваться в конструкторе, и что я могу объявить поле final, что является хорошей привычкой в ​​многопоточных приложениях. В идеале внедрение конструктора должно работать так же, как внедрение поля, но для этого потребуются какие-то построители аргументов конструктора. - person Alexander Torstling; 16.06.2010
comment
@disown Я думаю, ты пытаешься решить несуществующую проблему. Очень маловероятно, что вам придется предотвращать проблемы с многопоточностью при запуске. Потомки ApplicationContext гарантированно будут потокобезопасными (forum.springsource.org/showthread.php? т=11791). - person wax; 16.06.2010
comment
Итак... есть ли какой-нибудь чистый способ сделать регистратор доступным и в конструкторе? Внедрение конструктора выглядит беспорядочно, требуя места в подписи конструктора и двух дополнительных строк для сохранения введенного регистратора в поле экземпляра... если я правильно понял внедрение конструктора. Это единственный способ? - person Tuukka Mustonen; 18.06.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.");
    }
}
person Alexander Torstling    schedule 15.06.2010
comment
Хорошо работает, спасибо. Два момента: 1. Должно быть Logger.class == ... вместо isAssignableFrom(...). isAssignableFrom() верно для любого подкласса Logger, но Logger нельзя внедрить в поле его подкласса. Например, @Autowired MyLogger myLogger; для class MyLogger implements Logger всегда будет вызывать исключение. 2. Есть ли причина, по которой Logger не внедряется в необязательные зависимости? - person Arend v. Reinersdorff; 25.09.2013
comment
1. Хорошая мысль. Я думаю, что намеревался сделать обратное, то есть descriptor.getMethodParameter().getParameterType().isAssignableFrom(Logger.class), но это введет регистраторы в поля объекта, так что, возможно, следует использовать equals. 2. Нет, без причины. Приведенный выше код работал для моего случая, но вы можете изменить его. Если у вас есть обновление, пришлите его мне, и я отредактирую пост. Ваше здоровье - person Alexander Torstling; 26.09.2013
comment
@AlexanderTorstling Вот моя тестовая версия с небольшими улучшениями: pastebin.com/b3hnV73U - person Arend v. Reinersdorff; 01.10.2013
comment
@Arend v. Reinersdorff Спасибо! Я добавил это к ответу. - person Alexander Torstling; 02.10.2013
comment
Я реализовал очень похожее решение, но столкнулся с проблемой в модульных тестах, которые создают свои собственные экземпляры Loggable bean-компонентов. Если модульный тест не является тестом с поддержкой Spring (т. е. выполняется с SpringJUnit4ClassRunner и/или включает @ContextConfiguration), то очевидно, что постпроцессор bean-компонента не запускается, и, следовательно, в Loggables не установлено поле журнала. Поскольку у меня есть много тестов, которые действительно являются модульными тестами (т.е. не хотят или не нуждаются в использовании Spring), я ищу чистый способ справиться с этой проблемой, не прибегая к раскрытию общедоступного установщика для поля журнала. . Есть предположения? - person E-Riz; 19.02.2014

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

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

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

person magiccrafter    schedule 16.01.2017

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

@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>
person Adolfo    schedule 24.09.2012

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

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

    @Singleton 
    public class LogFactory { 
        public Logger getLogger(Object o) {  
            return LoggerFactory.getLogger(o.getClass());  
        }  
    }
    
person Mike Baranczak    schedule 14.06.2010
comment
1: см. wiki.apache.org/commons/Logging/StaticLog 2: это что я уже сделал. Это работает нормально, но очень многословно IMO. - person Alexander Torstling; 14.06.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), ведение журнала общих ресурсов будет делегировано ему вместо этого.

person Gray    schedule 14.06.2010
comment
Это почти тот же ответ, что дал Майк. Я хотел бы войти в систему, так как мне нравится чистый стиль oo. Подход «локатор услуг» возможен, но немного навязчив. Можно возразить, что класс среды выполнения не должен быть частью интерфейса в getLog, но я ожидаю, что Spring передаст какую-то структуру, описывающую цель. Это вообще невозможно? - person Alexander Torstling; 15.06.2010
comment
И, вдобавок ко всему, хак с автоматическим обнаружением журналов общих ресурсов был той самой причиной, по которой родился slf4j. - person Alexander Torstling; 15.06.2010
comment
У Spring нет такой возможности с FactoryBean, нет. Это создало бы своего рода циклическую зависимость, если X зависит от Y, но Y знает об X, когда он создается. - person Gray; 15.06.2010

Начиная с 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);
    }
}
person Arend v. Reinersdorff    schedule 11.06.2016

Я пытаюсь включить эту функцию в официальный API SLF4J. Пожалуйста, поддержите/голосуйте/внесите свой вклад: 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!");
    }

}

Сообщения журнала, созданные из этого регистратора, будут иметь категорию (имя регистратора), равную полному имени класса класса реализации компонента. Вы можете указать категорию явно, используя аннотацию.

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

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

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

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

person Hendy Irawan    schedule 18.04.2011