Этот вопрос связан с вопросом Скипоппи , на который я ответил сегодня ранее. Щедрый вопрос, который он добавил к этому, требует более хитрого решения, чем вопрос Яна Зики:
Поскольку инициализация по умолчанию представляет собой жестко закодированный статический блок, выполняемый только один раз во время загрузки класса LogManager
, вам необходим AOP (аспектно-ориентированное программирование), более конкретно AspectJ, чтобы перехватить статический инициализатор. Я объяснил, как это сделать в моем ответе на другой вопрос Скифоппи.
Хорошо, теперь мы можем перехватить статическую инициализацию и обмануть LogManager, сообщив нам URL, но для повторного выполнения всего блока кода нам понадобится еще один прием, называемый шаблон рабочего объекта . Вот пример кода, пояснение приведено ниже:
Пример приложения:
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
public class Log4jDemo {
public static Runnable log4jDefaultInitCmd;
private static Logger logger = Logger.getLogger("scrum-master.de");
public static void main(String[] args) throws InterruptedException {
BasicConfigurator.configure();
logger.info("Hello world!");
logger.info("Now sleeping for 2 sec...");
Thread.sleep(2000);
logger.info("I am awake again!");
if (log4jDefaultInitCmd != null) {
logger.info("Re-running log4j default initialisation");
log4jDefaultInitCmd.run();
}
logger.info("Done");
}
}
Аспект перехвата статической инициализации LogManager
:
import org.apache.log4j.LogManager;
public aspect Log4jAspect {
Object around() : staticinitialization(LogManager) {
System.out.println("log4j static initialisation");
Log4jDemo.log4jDefaultInitCmd = new Runnable() {
@Override public void run() {
proceed();
}
};
Log4jDemo.log4jDefaultInitCmd.run();
return null;
}
}
Как это работает:
Объяснение общей концепции АОП выходит за рамки этого ответа, поэтому я предполагаю, что вы это знаете или собираетесь прочитать что-то, чтобы понять это.
Log4jAspect
перехватывает * статическую инициализацию LogManager
в рекомендации around()
.
- В рамках рекомендации вызов
proceed()
(т.е. выполнение статической инициализации) упакован внутри рабочего объекта, реализованного анонимным экземпляром Runnable
. Это эффективно оборачивает вызов в объект с помощью метода run()
, который может быть выполнен по желанию. (Ага, вот наш трюк! В более динамичных языках, таких как Scala, вы бы назвали это лексической областью действия.)
- После переноса статической инициализации мы назначаем экземпляр
Runnable
общедоступному статическому члену другого класса, чтобы он стал доступен за пределами аспекта.
- Тем не менее внутри рекомендации, мы продолжаем статическую инициализацию, вызывая метод
run()
рабочего объекта.
Пока все хорошо, теперь класс LogManager
загружен и правильно инициализирован, как если бы не существовало никакого аспекта. Но теперь посмотрим на Log4jDemo.main
:
- Мы инициализируем регистратор и регистрируем некоторые события.
- Мы ждем 2 секунды (достаточно времени, чтобы проверить вывод консоли на предмет того, что произошло до сих пор).
- Мы продолжаем и повторно выдаем инициализацию по умолчанию , снова вызывая метод
run()
рабочего объекта.
Если вы используете аргумент командной строки -Dlog4j.debug=true
, вы увидите что-то вроде этого:
log4j static initialisation
log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@17182c1.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@17182c1 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@17182c1.
log4j: Using URL [file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties
log4j: Parsing for [root] with value=[debug, stdout].
log4j: Level token is [debug].
log4j: Category root set to DEBUG
log4j: Parsing appender named "stdout".
log4j: Parsing layout options for "stdout".
log4j: Setting property [conversionPattern] to [%d{ABSOLUTE} %5p %c{1}:%L - %m%n].
log4j: End of parsing for "stdout".
log4j: Setting property [target] to [System.out].
log4j: Parsed "stdout" options.
log4j: Finished configuring.
12:41:22,647 INFO de:13 - Hello world!
0 [main] INFO scrum-master.de - Hello world!
12:41:22,663 INFO de:14 - Now sleeping for 2 sec...
16 [main] INFO scrum-master.de - Now sleeping for 2 sec...
12:41:24,663 INFO de:16 - I am awake again!
2016 [main] INFO scrum-master.de - I am awake again!
12:41:24,663 INFO de:18 - Re-running log4j default initialisation
2016 [main] INFO scrum-master.de - Re-running log4j default initialisation
log4j: Trying to find [log4j.xml] using context classloader sun.misc.Launcher$AppClassLoader@17182c1.
log4j: Trying to find [log4j.xml] using sun.misc.Launcher$AppClassLoader@17182c1 class loader.
log4j: Trying to find [log4j.xml] using ClassLoader.getSystemResource().
log4j: Trying to find [log4j.properties] using context classloader sun.misc.Launcher$AppClassLoader@17182c1.
log4j: Using URL [file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties] for automatic log4j configuration.
log4j: Reading configuration from URL file:/C:/Dokumente%20und%20Einstellungen/Robin/Eigene%20Dateien/java-src/dummy2/bin/log4j.properties
log4j: Parsing for [root] with value=[debug, stdout].
log4j: Level token is [debug].
log4j: Category root set to DEBUG
log4j: Parsing appender named "stdout".
log4j: Parsing layout options for "stdout".
log4j: Setting property [conversionPattern] to [%d{ABSOLUTE} %5p %c{1}:%L - %m%n].
log4j: End of parsing for "stdout".
log4j: Setting property [target] to [System.out].
log4j: Parsed "stdout" options.
log4j: Finished configuring.
12:41:24,663 INFO de:21 - Done
2016 [main] INFO scrum-master.de - Done
Tadaa! Как видите, инициализация по умолчанию действительно была выполнена дважды. Вывод журнала подтверждает это. Например, вы видите Using URL [file:/(...)]
дважды в журнале.
Вывод:
Хотя это не очень хороший способ перезапустить инициализацию по умолчанию в log4j по сравнению с более желательной ситуацией, когда она не жестко запрограммирована, а предоставляется пользователю через вызов API, факты таковы, как они есть, и нам нужно этот трюк Я сомневаюсь, что в любой конкретной ситуации необходимо повторно запустить полный блок инициализации по умолчанию, но поскольку вопрос был задан, я хотел ответить на него точно, а не предлагая обходной путь. Наслаждайтесь!