1. Spring4 日誌
在 Spring4 中使用 log4j 日誌框架,只需要引入 log4j
jar 包即可
1.1 環境搭建
代碼已經上傳至 https://github.com/masteryourself-tutorial/tutorial-spring ,詳見
tutorial-spring-framework/tutorial-spring-framework-log
工程
1.1.1 配置文件
1. pom.xml
<!-- spring4 日誌 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.26.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
1.2 源碼分析
1.2.1 依賴分析
由此可見,Spring4 依賴的是 commons-logging
,而 commons-logging
是一款日誌門面框架,並不提供任何具體實現,所以獲取 Log 的方法是靠 commons-logging
完成的
1.2.2 源碼分析
1. org.apache.commons.logging.impl.LogFactoryImpl#discoverLogImplementation
private Log discoverLogImplementation(String logCategory)
throws LogConfigurationException {
...
// 其中 classesToDiscover 屬性是 commons-logging 框架寫死的一個數組
// 這個方法就是用 classLoader 去循環加載日誌實現,如果能夠加載到,就使用這個日誌框架
for(int i=0; i<classesToDiscover.length && result == null; ++i) {
result = createLogFromClass(classesToDiscover[i], logCategory, true);
}
...
return result;
}
2. org.apache.commons.logging.impl.LogFactoryImpl
關於 classesToDiscover 屬性,這個屬性是在 JUL 中寫死的一個數組,而且已經不再更新維護了,所以它無法使用 log4j2 等新的日誌框架
// Log4JLogger,底層使用 Log4j 打印日誌
private static final String LOGGING_IMPL_LOG4J_LOGGER = "org.apache.commons.logging.impl.Log4JLogger";
private static final String[] classesToDiscover = {
LOGGING_IMPL_LOG4J_LOGGER,
"org.apache.commons.logging.impl.Jdk14Logger",
"org.apache.commons.logging.impl.Jdk13LumberjackLogger",
"org.apache.commons.logging.impl.SimpleLog"
};
2. Spring5 日誌
在 Spring5 中使用 log4j 日誌框架,必須要引入 slf4j-api
+ slf4j-log4j12
(它提供了 log4j 的實現) 纔行
2.1 環境搭建
代碼已經上傳至 https://github.com/masteryourself-tutorial/tutorial-spring ,詳見
tutorial-spring-framework/tutorial-spring-framework-log
工程
2.1.1 配置文件
1. pom.xml
<!-- spring5 日誌,需要藉助 SLF4J -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.1.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.25</version>
</dependency>
2.2 源碼分析
2.2.1 依賴分析
由此可見,Spring5 依賴的是 spring-jcl
,spring-jcl
是 Spring 仿照 commons-logging
自己實現的一套日誌框架,主要就是爲了解決 commons-logging
無法使用 Log4j2 等最新日誌框架的問題
點開 spring-jcl
的 jar 包,可以看到它的包名竟然是 org.apache.commons.logging
開頭的,爲什麼要做成這樣呢?爲什麼不直接使用當下最流行的 SLF4J 門面技術呢?個人猜測是因爲 Spring 之前的版本都是使用 commons-logging
打印日誌的,如果直接改用 SLF4J,這樣將會替換很多類中的代碼,所以 Spring 仿照 commons-logging
實現了自己的一套日誌框架,從而避免改動
2.2.2 源碼分析
1. org.apache.commons.logging.LogAdapter
初始化要使用的日誌技術,這裏會逐級查找要使用的日誌技術,給 logApi
屬性賦值
如果不加入 SLF4J 的包,那麼這裏會默認使用 LogApi.JUL
去打印日誌,所以在 Spring5 中切換日誌框架,是需要藉助 SLF4J 的
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
// 這裏是判斷 Log4J2
if (isPresent(LOG4J_SPI)) {
// 判斷 是否有SLF4J
if (isPresent(LOG4J_SLF4J_PROVIDER) && isPresent(SLF4J_SPI)) {
// log4j-to-slf4j bridge -> we'll rather go with the SLF4J SPI;
// however, we still prefer Log4j over the plain SLF4J API since
// the latter does not have location awareness support.
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
// 直接使用 Log4J2
logApi = LogApi.LOG4J;
}
}
// 判斷 是否有SLF4J
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
// 判斷 是否有SLF4J
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
// 如果沒有 SLF4J 支持,只有 Log4J,那麼日誌會選用 JUL
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
2. org.apache.commons.logging.LogAdapter#createLog
這裏是創建 Log 對象,根據之前初始化的 logApi
的值來選擇不同的日誌框架
public static Log createLog(String name) {
switch (logApi) {
case LOG4J:
return Log4jAdapter.createLog(name);
case SLF4J_LAL:
return Slf4jAdapter.createLocationAwareLog(name);
case SLF4J:
return Slf4jAdapter.createLog(name);
default:
// Defensively use lazy-initializing adapter class here as well since the
// java.logging module is not present by default on JDK 9. We are requiring
// its presence if neither Log4j nor SLF4J is available; however, in the
// case of Log4j or SLF4J, we are trying to prevent early initialization
// of the JavaUtilLog adapter - e.g. by a JVM in debug mode - when eagerly
// trying to parse the bytecode for all the cases of this switch clause.
return JavaUtilAdapter.createLog(name);
}
}