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 даже правильный путь? При использовании picocontainers factory injection вы получаете переданный объект Type
. В виде это немного сложнее. Но как вы это делаете в Spring?
Ответы
Ответ 1
Я разрешил его с помощью настраиваемого BeanFactory. Если кто-нибудь придумает лучшее решение, я был бы рад услышать его. Во всяком случае, здесь bean factory:
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.");
}
}
Ответ 2
Вот альтернатива вашему решению. Вы можете достичь своей цели с помощью реализации 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();
}
}
}
}
}
Он просматривает все beans, а если bean помечен как @Loggable, он инициализирует свое личное поле с именем logger. Вы можете пойти еще дальше и передать некоторые параметры в аннотации @Loggable. Например, это может быть имя поля, соответствующее журналу.
В этом примере я использовал Log4j, но я думаю, что он должен работать точно так же с slf4j.
Ответ 3
Чтобы сделать ваш код более Spring осведомленным, используйте InjectionPoint
для определения регистраторов, то есть:
@Bean
@Scope("prototype")
public Logger logger(InjectionPoint ip) {
return Logger.getLogger(ip.getMember().getDeclaringClass());
}
@Scope("prototype")
необходим здесь для создания экземпляра 'logger' bean каждый раз, когда вызывается метод.
Ответ 4
Попробуйте что-то вроде:
@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>
Ответ 5
-
Почему вы создаете новый журнал для каждого экземпляра? Типичный шаблон - иметь один регистратор для каждого класса (как частный статический член).
-
Если вы действительно хотите это сделать: может быть, вы можете написать класс журнала factory и ввести это? Что-то вроде:
@Singleton
public class LogFactory {
public Logger getLogger(Object o) {
return LoggerFactory.getLogger(o.getClass());
}
}
Ответ 6
Да, вы идете в неправильном направлении. Если бы я был вами, я бы ввел 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 вместо Loggers:
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), то ведение коллекций будет делегироваться ему.
Ответ 7
Поскольку Spring 4.3.0 вы можете использовать InjectionPoint или DependencyDescriptor в качестве параметров для методов bean:
@Component
public class LoggingFactoryBean {
@Bean
public Logger logger(InjectionPoint injectionPoint) {
Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
return LoggerFactory.getLogger(targetClass);
}
}
Ответ 8
Я пытаюсь включить эту функцию в официальный 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
Вы также можете ввести "простой старый" регистратор (из API ведения журнала JBoss):
import javax.inject.Inject;
import org.jboss.logging.Logger;
public class LogService {
@Inject
private Logger log;
public void logMessage() {
log.info("Hey sysadmins!");
}
}
Журнальные сообщения, созданные из этого регистратора, будут иметь категорию (имя регистратора), равную полному классу имени класса класса bean. Вы можете указать категорию явно с помощью аннотации.
@Inject @Category("billing")
private Logger log;
Вы также можете указать категорию, используя ссылку на тип:
@Inject @TypedCategory(BillingService.class)
private Logger log;
Извините за отсутствие соответствующего ответа.