Разрабатывая приложения используя IoC-контейнер Spring думаю каждый задумывался, а как же «правильнее и красивее» создать логгер. В данной публикации хочу привести несколько примеров решения данной задачи.
Получаем логгер напрямую через LoggerFactory:
Данное решение является классическим, безусловно работающим, но нарушает саму идеологию IoC, ведь нам хочется, что бы работу по созданию логгера выполнил сам контейнер.
Получаем логгер из контейнера при помощи Autowired:
Для этого в конфигурации Spring объявляем Bean:
В данном решении задача по созданию логгера возложена на сам контейнер и укладывается в идеологию IoC, но что же делать, если логгеров в приложении должно быть больше одного?
Объявляем каждый логгер в виде отдельного Bean:
Получаем нужный логгер используя соответствующий Qualifier:
Данное решение является достаточным в большинстве случаев, и использует только готовые средства контейнера. Одним из минусов данного решения является то, что при добавлении нового логгера всегда придется объявлять новый Bean. Есть ли более универсальный способ?
Получаем логгер из контейнера при помощи специальной аннотации, назовем ее Logging:
Для это собственно необходимо объявить аннотацию:
Данная аннотация будет указывать контейнеру на то, что необходим логгер с именем переданным в параметр value. Если данный параметр не указан, то логгер будет получен по классу компонента, в нашем случае MyBean.
Отлично, но контейнер не умеет обрабатывать нашу аннотацию. Давайте его научим, для этого создадим процессор:
И объявим процессор в конфигурации Spring:
Данное решение является более универсальным, но необходимо дополнительно объявить аннотацию и написать для нее процессор.
Друзья, предлагай в комментариях ваши варианты решения данной задачи, буду очень рад!
Исходный код примеров доступен на GitHub
Решение 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