Решение 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
Комментарии (38)
Borz
07.02.2016 18:50+2Не очень понял, зачем бин для логгера? в тексте не увидел пояснений, которыми руководствуется автор. И сохраняется ли при этом разделение уровней логирования по пакетам/классам?
В IDEA у себя держу следующий шаблон, который генерит код для Slf4J:
@SuppressWarnings("unused") private static final transient Logger LOG = LoggerFactory.getLogger(CURRENT_CLASS.class.getName());
но в последнее перебрался на аннотацию @Slf4j из Lombokgurinderu
07.02.2016 20:04+3Зачем static field у вас transient? Они же вроде несереализуемые.
Borz
07.02.2016 20:13omickron
08.02.2016 09:21+1Если я правильно понял, это на случай, когда автор напишет кастомный сериализатор, чтобы случайно не сериализовать LOG через рефлексию.
Правда, я всё равно не понимаю, что помешает обойти transient в сериализаторе, ведь в рефлексии всё доступно.Borz
08.02.2016 11:00это не для защиты против обхода, а скорее для тех, кто смотрит только на transient и не смотрит на static. А в целом не понятно — что плохого в такой избыточности?
gurinderu
08.02.2016 11:20+1Не знаю насколько это плохо, но если ваш сериализатор сериализует static, то скорее всего нужно дать по шее разработчику этого сериализатора. ИМХО static field никакого отношения к вашим объектам иметь не должны. В этом я даже одобряю scala подход к разделение на class и object.
omickron
08.02.2016 11:27+1Ответ в самом вопросе — избыточность. Избыточность ведёт к неоднозначности.
Если в спецификации сказано, что static поля не должны быть сериализованы, значит нужно бить по рукам тех, кто это делает, во время code review.
В то же время я понимаю, что это исключительно моё субъективное мнение, и другие могут быть с ним не согласиться.
ruslanys
08.02.2016 12:13Всем рекомендую Lombok и его аннотацию @Sl4j.
uchonyy
08.02.2016 16:33@Slf4j на сколько я помню создает логгер по названию класса, а если необходимо создавать по имени — то не подойдет. Другой вопрос кому как удобнее получать логгер — по классу или по имени, лично у меня есть примеры когда в одних случаях удобнее первый подход, есть когда второй — тут вопрос как личных предпочтений так и зависимости от контекста конкретной задачи =)
omickron
08.02.2016 16:37Если не ошибаюсь, логгер всегда получается по имени. В частном случае имя логгера берётся из полного названия класса.
Мне было бы интересно узнать, в каком именно случае вы выбрали подход получения логгера по произвольному имени?Borz
08.02.2016 16:43имеется в виду, что в логгере будет в качестве имени — полный путь до класса или просто некое слово.
Lure_of_Chaos
08.02.2016 15:21+2Стоит отметить, что подход универсален и годится не только для логгеров. Наоборот, имхо, логгеры — не самый удачный пример.
omickron
08.02.2016 15:25Профит этого подхода — вместо `@Autowired` + `@Qualifier` использовать одну аннотацию.
Но для каждого класса придётся объявлять свою аннотацию + процессор. Разве это удобно?Lure_of_Chaos
08.02.2016 15:42Но для каждого класса придётся объявлять свою аннотацию + процессор
Разве?
И, кстати, хорошо бы вместо @Autowired и Qualifier использовать Inject и Named, а еще бы объединить эти аннотации в одну.omickron
08.02.2016 15:59Разве?
Конечно.
LoggingAnnotationProcessor работает только с `@Logging` аннотацией — создаёт логгер и присваивает его полю с этой аннотацией.
На мой взгляд, этот подход — велосипед. Я бы не стал его использовать.
А конкретно для логгера, как уже некоторые писали в комментариях, лучше использовать Lombok.Lure_of_Chaos
08.02.2016 16:17LoggingAnnotationProcessor работает только с `@Logging` аннотацией
Не вижу труда написать аннотацию+процессор, использующую некоторую фабрику, в зависимости от конкретного типа поля с аннтацией.
Хотя, видимо, для общего случая и правда нет необходимости дублировать уже существующую в Спринге фунциональность.
А вот вообще написать несколько процессоров разнообразных аннотаций, декорирующих, скажем, методы, во избежание дублирования кода — идея сама по себе красивая.omickron
08.02.2016 16:20написать аннотацию+процессор, использующую некоторую фабрику
Добро пожаловать в клуб любителей шаблона ServiceLocator :)
написать несколько процессоров разнообразных аннотаций, декорирующих, скажем, методы, во избежание дублирования кода
Это же делает AOP. Нужно ли писать велосипед?Lure_of_Chaos
08.02.2016 16:29Добро пожаловать в клуб любителей шаблона ServiceLocator :)
А и верно :)
Это же делает AOP.
Точно.
Но и AOP не предел.
А в целом, мне все больше кажется, что уже для каждой заманчивой идеи уже есть своя библиотека, и все самописное окажется велосипедом :)
Тот же Lombok уже упоминали.
Впрочем, все когда-то было велосипедом, пока не стало достаточно популярным :)omickron
08.02.2016 16:34все когда-то было велосипедом, пока не стало достаточно популярным :)
Это и заставляет писать новые велосипеды :)
Borz
08.02.2016 16:19а разве @Autowired и Inject не эквивалентны?
omickron
08.02.2016 16:23@Autowired — личная аннотация Spring.
Inject — спецификация JavaEE.
Spring поддерживает оба варианта. Но если переезжать со Spring на другой контейнер, который не поддерживает @Autowired, код придётся редактировать.
P. S. Парень, зарегистрировавшийся с логином Inject, наверно икает :)Borz
08.02.2016 16:27+1Не сталкивался ещё со случаями, когда понадобилось избавиться от Spring и целиком переехать на JavaEE. Можете привести?
P. S. Парень, зарегистрировавшийся с логином Inject, наверно икает :)
а нечего служебные слова использовать ;)omickron
08.02.2016 16:28У меня тоже не было такого опыта. Думаю, это индивидуальный случай, специфичный для проекта.
Lure_of_Chaos
08.02.2016 16:35Видимо, у Спринга нет (а, похоже, и не будет) достойных конкурентов.
Например, мне очень когда-то понравился фреймворк Tapestry в его 5ой версии. Но пока парни пилили версию 5.4, он безнадежно устарел, увы…gurinderu
08.02.2016 18:20А как же play?
Lure_of_Chaos
08.02.2016 18:34Play я ковырял достаточно давно, тогда он меня не впечатлил, особенно тем, что он stateless, а также для раскрытия потенциала нужна scala.
zencd
08.02.2016 20:49Автор сам своё творение не использовал что ли…
Class.getDeclaredFields()
может запросто вернуть (и таки возвращает) кучу автогенерируемых полей и только их, т.к. спринг наследует наши классы, аgetDeclaredFields()
работает только на один уровень; так что до наших собственных полей дело просто не дойдёт (по крайней мере, не сделает это гарантированно).
gurinderu
Мне кажется логгер нужно получать по месту ИМХО.
Ибо уровень логирования в настройках указать на package проще будет.
xonix
Плюсану, всегда использовал что-то типа
Также подход автора не позволит логировать из static методов.
MzMz
Зато позволит весело словить NPE — если вызов логера произойдет до связывания.
gurinderu
Не уверен, что это возможно. Спринг бины иницилизирует при старте контексте(кроме lazy).
MzMz
статические инициализаторы, конструкторы класса, сеттеры — все останется без логирования, в юнит-тестах придется инжектить еще и мок-логгер
gurinderu
Ну если вы используете статические инициализаторы в Spring, то пожалуй сами виноваты.
В конструктор логгер можно заинжектить, не проблема. А вот моки да, проблема.