I\'m trying to use Spring to inject a SLF4J logger into a class like so:
@Component
public class Example {
private final Logger logger;
@Autowired
pu
Here is an alternative to your solution. You could achieve your goal with BeanFactoryPostProcessor implementation.
Let's assume you want to have a class with logging. Here it is:
package log;
import org.apache.log4j.Logger;
@Loggable
public class MyBean {
private Logger logger;
}
As you could see this class does nothing and created just to be a logger container for simplicity. The only remarkable thing here is @Loggable annotation. Here its source code:
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 {
}
This annotation is only a marker for further processing. And here is a most interesting part:
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();
}
}
}
}
}
It searches through all beans, and if a bean is marked as @Loggable, it initialize its private field with name logger. You could go even further and pass some parameters in @Loggable annotation. For example, it could be a name of field corresponding to logger.
I used Log4j in this example, but I guess it should work exactly the same way with slf4j.
Since Spring 4.3.0 you can use InjectionPoint or DependencyDescriptor as parameters for bean producing methods:
@Component
public class LoggingFactoryBean {
@Bean
public Logger logger(InjectionPoint injectionPoint) {
Class<?> targetClass = injectionPoint.getMember().getDeclaringClass();
return LoggerFactory.getLogger(targetClass);
}
}
Why are you creating a new logger for each instance? The typical pattern is to have one logger per class (as a private static member).
If you really do want to do it that way: Maybe you can write a logger factory class, and inject that? Something like:
@Singleton
public class LogFactory {
public Logger getLogger(Object o) {
return LoggerFactory.getLogger(o.getClass());
}
}
I am trying to get this feature into official SLF4J API. Please support/vote/contribute: https://issues.jboss.org/browse/JBLOGGING-62
(this feature is already implemented by JBoss Logging + Seam Solder, see http://docs.jboss.org/seam/3/latest/reference/en-US/html/solder-logging.html)
11.4. Native logger API
You can also inject a "plain old" Logger (from the 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!"); } }
Log messages created from this Logger will have a category (logger name) equal to the fully-qualified class name of the bean implementation class. You can specify a category explicitly using an annotation.
@Inject @Category("billing") private Logger log;
You can also specify a category using a reference to a type:
@Inject @TypedCategory(BillingService.class) private Logger log;
Sorry for not providing a relevant answer.
I resolved it with a custom BeanFactory. If anyone comes up with a better solution, I would be happy to hear it. Anyway, here's the 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);
}
}
}
Example usage with an XML config:
CustomBeanFactory customBeanFactory = new CustomBeanFactory();
GenericApplicationContext ctx = new GenericApplicationContext(customBeanFactory);
XmlBeanDefinitionReader xmlReader = new XmlBeanDefinitionReader(ctx);
xmlReader.loadBeanDefinitions(new ClassPathResource("beans.xml"));
ctx.refresh();
EDIT:
Below you can find Arend v. Reinersdorffs improved version (see the comments for an explanation).
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.");
}
}
Try something like:
@Component
public class Example {
@Autowired
@Qualifier("exampleLogger")
private final Logger logger;
}
And:
<bean id="exampleLogger" class="org.slf4j.LoggerFactory" factory-method="getLogger">
<constructor-arg type="java.lang.Class" value="package.Example"/>
</bean>