Разрабатывая приложения используя IoC-контейнер Spring думаю каждый задумывался, а как же «правильнее и красивее» создать логгер. В данной публикации хочу привести несколько примеров решения данной задачи.
Решение 1
Получаем логгер напрямую через LoggerFactory:
@Component
public class MyBean {
private static final Logger log = LoggerFactory.getLogger("application");
...
}
Данное решение является классическим, безусловно работающим, но нарушает саму идеологию IoC, ведь нам хочется, что бы работу по созданию логгера выполнил сам контейнер.
Решение 2
Получаем логгер из контейнера при помощи Autowired:
@Component
public class MyBean {
@Autowired
private Logger log;
...
}
Для этого в конфигурации Spring объявляем Bean:
@EnableAutoConfiguration
@ComponentScan
public class ApplicationConfiguration {
@Bean
public Logger logger(){
return LoggerFactory.getLogger("application");
}
...
}
В данном решении задача по созданию логгера возложена на сам контейнер и укладывается в идеологию IoC, но что же делать, если логгеров в приложении должно быть больше одного?
Решение 3
Объявляем каждый логгер в виде отдельного Bean:
@EnableAutoConfiguration
@ComponentScan
public class ApplicationConfiguration {
@Bean
@Primary
public Logger logger(){
return LoggerFactory.getLogger("application");
}
@Bean(name = "loggerBean")
public Logger loggerBean(){
return LoggerFactory.getLogger("loggerBean");
}
...
}
Получаем нужный логгер используя соответствующий Qualifier:
@Component
public class MyBean {
@Autowired
private Logger log;
@Autowired
@Qualifier("loggerBean")
private Logger log2;
...
}
Данное решение является достаточным в большинстве случаев, и использует только готовые средства контейнера. Одним из минусов данного решения является то, что при добавлении нового логгера всегда придется объявлять новый Bean. Есть ли более универсальный способ?
Решение 4
Получаем логгер из контейнера при помощи специальной аннотации, назовем ее Logging:
@Component
public class MyBean {
@Logging
private Logger log;
@Logging("loggerBean")
private Logger log2;
...
}
Для это собственно необходимо объявить аннотацию:
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Logging {
String value();
}
Данная аннотация будет указывать контейнеру на то, что необходим логгер с именем переданным в параметр value. Если данный параметр не указан, то логгер будет получен по классу компонента, в нашем случае MyBean.
Отлично, но контейнер не умеет обрабатывать нашу аннотацию. Давайте его научим, для этого создадим процессор:
public class LoggingAnnotationProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
Class clazz = bean.getClass();
do {
for (Field field : clazz.getDeclaredFields()) {
Logging annotation = field.getAnnotation(Logging.class);
if (annotation!= null) {
boolean accessible = field.isAccessible();
field.setAccessible(true);
try {
if(!annotation.value().isEmpty()){
field.set(bean, LoggerFactory.getLogger(annotation.value()));
} else {
field.set(bean, LoggerFactory.getLogger(clazz));
}
} catch (IllegalAccessException e) {
LoggerFactory.getLogger(this.getClass()).error(e.getMessage(), e);
}
field.setAccessible(accessible);
}
}
clazz = clazz.getSuperclass();
} while (clazz != null);
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
return bean;
}
}
И объявим процессор в конфигурации Spring:
@EnableAutoConfiguration
@ComponentScan
public class ApplicationConfiguration {
@Bean
public LoggingAnnotationProcessor loggingAnnotationProcessor(){
return new LoggingAnnotationProcessor();
}
...
}
Данное решение является более универсальным, но необходимо дополнительно объявить аннотацию и написать для нее процессор.
Заключение
Друзья, предлагай в комментариях ваши варианты решения данной задачи, буду очень рад!
Исходный код примеров доступен на GitHub
Автор: uchonyy