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);
}
}