这个问题与我今天早些时候回答的skiphoppy的问题有关。他在这里添加的赏金问题需要一个比Jan Zyka更棘手的解决方案:
因为缺省初始化是一个硬编码的静态块,在 类加载期间只执行一次,所以需要 AOP(面向方面的编程),更具体地说是 AspectJ,以便拦截静态初始化器。我在回答skiphoppy的另一个问题时解释了如何做到这一点。LogManager
好了,现在我们可以拦截静态初始化并欺骗LogManager告诉我们URL,但是为了重新执行整个代码块,我们需要另一个称为worker对象模式的技巧。下面是示例代码,说明如下:
示例应用程序类:
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;
}
}
它是如何工作的:
解释AOP的一般概念超出了这个答案的范围,所以我假设你知道它,或者为了理解它而要读一些东西。
-
Log4jAspect
在建议中截获 的静态初始化。LogManager
around()
- 在建议中,调用(即静态初始化的执行)被打包在由匿名实例实现的工作器对象中。这有效地将调用包装到一个对象中,该对象具有可以随意发出的方法。(啊哈,这是我们的诀窍!在像Scala这样更动态的语言中,你可以称之为词汇范围。
proceed()
Runnable
run()
- 包装静态初始化后,我们将实例分配给另一个类的公共静态成员,以便它可以在方面之外访问。
Runnable
- 仍然在建议中,我们通过调用 worker 对象的方法继续进行静态初始化。
run()
到目前为止,一切都很好,现在类已经正确加载和初始化,就好像不存在任何方面一样。但现在看看:LogManager
Log4jDemo.main
- 我们初始化记录器并记录一些事件。
- 我们等待 2 秒钟(刚好有足够的时间来检查控制台输出到目前为止发生了什么)。
- 我们通过再次调用 worker 对象的方法继续并重新发出默认初始化。
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
哒哒哒!如您所见,默认初始化确实执行了两次。日志输出证明了这一点。例如,您在日志中看到两次。Using URL [file:/(...)]
结论:
虽然这不是重新发布log4j默认初始化的特别好的方法,而不是更理想的情况,即它不是硬编码的,而是通过API调用向用户公开的,但事实就是这样,我们需要这个技巧。我确实怀疑在任何给定情况下是否有必要重新运行完整的默认初始化块,但是因为提出了这个问题,所以我想准确地回答它,而不是通过建议解决方法。享受!