Я пытаюсь использовать 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 {
@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
из цели, переданной в. В облике это немного более хитро. Но как Вы выполняете это в Spring?
Я решил эту проблему с помощью пользовательского 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();
EDIT:
Ниже вы можете найти улучшенную версию 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.");
}
}
Почему вы создаете новый регистратор для каждого экземпляра? Типичный шаблон - иметь по одному регистратору на класс (как частный статический член).
Если вы действительно хотите сделать это таким образом: может быть, вы можете написать класс фабрики логгеров и внедрить его? Примерно так:
@Singleton
public class LogFactory {
public Logger getLogger (Object o) {
return LoggerFactory.getLogger (o.getClass ());
}
}
Да, вы идете в неправильном направлении. На вашем месте я бы ввел 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/
Вы используете Log'ы вместо Logger'ов:
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
public class CLASS {
private Log log = LogFactory.getLog(CLASS.class);
...
Затем Apache просматривает classpath, чтобы увидеть, есть ли у вас log4j или другие, и делегирует "лучшему" из найденных. Slf4j заменяет log4j в classpath, поэтому если он у вас загружен (и apache log4j исключен), commons logging будет делегирован ему вместо этого.
Вот альтернатива вашему решению. Вы можете достичь своей цели с помощью реализации 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.