10.Spring Framework 之 Log

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 日誌依賴

由此可見,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 日誌依賴

由此可見,Spring5 依賴的是 spring-jclspring-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);
	}
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章