10.Spring Framework 5 新特性之 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 依赖分析

image

由此可见,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 依赖分析

image

由此可见,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);
	}
}
发布了37 篇原创文章 · 获赞 3 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/masteryourself/article/details/105547843