【读源码】看看spring-boot是如何引入logback的
首先在一个空项目下只依赖的spring-boot-starter-web
时,我们查看依赖树会发现spring-boot-starter-web->spring-boot-starter->spring-core->spring-jcl
[INFO] -----------------------< cn.witsky:logback-demo >-----------------------
[INFO] Building logback-demo 1.0-SNAPSHOT
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-dependency-plugin:3.1.1:tree (default-cli) @ logback-demo ---
[INFO] cn.witsky:logback-demo:jar:1.0-SNAPSHOT
[INFO] +- org.springframework.boot:spring-boot-starter-web:jar:2.1.6.RELEASE:compile
[INFO] | +- org.springframework.boot:spring-boot-starter:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-autoconfigure:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.springframework.boot:spring-boot-starter-logging:jar:2.1.6.RELEASE:compile
[INFO] | | | +- ch.qos.logback:logback-classic:jar:1.2.3:compile
[INFO] | | | | +- ch.qos.logback:logback-core:jar:1.2.3:compile
[INFO] | | | | \- org.slf4j:slf4j-api:jar:1.7.26:compile
[INFO] | | | +- org.apache.logging.log4j:log4j-to-slf4j:jar:2.11.2:compile
[INFO] | | | | \- org.apache.logging.log4j:log4j-api:jar:2.11.2:compile
[INFO] | | | \- org.slf4j:jul-to-slf4j:jar:1.7.26:compile
[INFO] | | +- javax.annotation:javax.annotation-api:jar:1.3.2:compile
[INFO] | | +- org.springframework:spring-core:jar:5.1.8.RELEASE:compile
[INFO] | | | \- org.springframework:spring-jcl:jar:5.1.8.RELEASE:compile
[INFO] | | \- org.yaml:snakeyaml:jar:1.23:runtime
[INFO] | +- org.springframework.boot:spring-boot-starter-json:jar:2.1.6.RELEASE:compile
[INFO] | | +- com.fasterxml.jackson.core:jackson-databind:jar:2.9.9:compile
[INFO] | | | +- com.fasterxml.jackson.core:jackson-annotations:jar:2.9.0:compile
[INFO] | | | \- com.fasterxml.jackson.core:jackson-core:jar:2.9.9:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jdk8:jar:2.9.9:compile
[INFO] | | +- com.fasterxml.jackson.datatype:jackson-datatype-jsr310:jar:2.9.9:compile
[INFO] | | \- com.fasterxml.jackson.module:jackson-module-parameter-names:jar:2.9.9:compile
[INFO] | +- org.springframework.boot:spring-boot-starter-tomcat:jar:2.1.6.RELEASE:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-core:jar:9.0.21:compile
[INFO] | | +- org.apache.tomcat.embed:tomcat-embed-el:jar:9.0.21:compile
[INFO] | | \- org.apache.tomcat.embed:tomcat-embed-websocket:jar:9.0.21:compile
[INFO] | +- org.hibernate.validator:hibernate-validator:jar:6.0.17.Final:compile
[INFO] | | +- javax.validation:validation-api:jar:2.0.1.Final:compile
[INFO] | | +- org.jboss.logging:jboss-logging:jar:3.3.2.Final:compile
[INFO] | | \- com.fasterxml:classmate:jar:1.4.0:compile
[INFO] | +- org.springframework:spring-web:jar:5.1.8.RELEASE:compile
[INFO] | | \- org.springframework:spring-beans:jar:5.1.8.RELEASE:compile
[INFO] | \- org.springframework:spring-webmvc:jar:5.1.8.RELEASE:compile
[INFO] | +- org.springframework:spring-aop:jar:5.1.8.RELEASE:compile
[INFO] | +- org.springframework:spring-context:jar:5.1.8.RELEASE:compile
[INFO] | \- org.springframework:spring-expression:jar:5.1.8.RELEASE:compile
[INFO] +- org.projectlombok:lombok:jar:1.18.8:compile (optional)
[INFO] +- com.alibaba:fastjson:jar:1.2.70:compile
[INFO] \- cn.hutool:hutool-core:jar:5.8.0.M4:compile
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 1.149 s
[INFO] Finished at: 2023-02-23T17:46:34+08:00
[INFO] ------------------------------------------------------------------------
可以发现spring-boot-starter-logging依赖了三个包
- logback-classic
- log4j-to-slf4j
- jul-to-slf4j
可以看出来,spring在这里引入这三个包的目的其实是为了使用logback作为底层实现,而上层如果使用了log4j2或者jul作为日志门面的话也也会通过log4j-to-slf4j和jul-to-slf4j这两个包来适配成slf4j的实现。
现在我们看看spring是如何帮我们加载日志框架的,在spring入口启动类SpringApplication
中我找到了这行代码,注意这里的LogFactory
是spring-jcl
中的,并不是jcl-over-slf4j
中的。
public class SpringApplication {
//...
private static final Log logger = LogFactory.getLog(SpringApplication.class);
//...
}
发现最终都是使用LogAdapter创建的Log,注意这里的Log对象是spring-jcl自己封的接口。
/**
* Convenience method to return a named logger.
* @param clazz containing Class from which a log name will be derived
*/
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
/**
* Convenience method to return a named logger.
* @param name logical name of the <code>Log</code> instance to be returned
*/
public static Log getLog(String name) {
return LogAdapter.createLog(name);
}
最终都会走这个方法LogAdapter.createLog(name);
这里使用的是适配器模式,适配器模式的目的是为了让jcl提供的接口与日志门面接口相适配。适配器在这里用的很妙,适配器本身就是解决接口间不兼容问题的。
/**
* Create an actual {@link Log} instance for the selected API.
* @param name the logger name
*/
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);
}
}
LogAdapter静态代码块中使用Class.forName()方法查找并加载指定的类,根据这些类是否存在来判断使用的是什么日志框架。
// log4j-api-2.11.2.jar#org.apache.logging.log4j.spi.ExtendedLogger
private static final String LOG4J_SPI = "org.apache.logging.log4j.spi.ExtendedLogger";
// log4j-to-slf4j-2.11.2.jar#org.apache.logging.slf4j.SLF4JProvider
private static final String LOG4J_SLF4J_PROVIDER = "org.apache.logging.slf4j.SLF4JProvider";
// slf4j-api-1.7.26.jar#org.slf4j.spi.LocationAwareLogger
private static final String SLF4J_SPI = "org.slf4j.spi.LocationAwareLogger";
// slf4j-api-1.7.26.jar#org.slf4j.Logger
private static final String SLF4J_API = "org.slf4j.Logger";
private static final LogApi logApi;
static {
if (isPresent(LOG4J_SPI)) {
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.
// log4j-to-slf4j桥接->我们宁愿使用slf4j SPI;
// 然而,我们仍然更喜欢Log4j而不是普通的SLF4J API,因为
// 后者没有占位符支持。
logApi = LogApi.SLF4J_LAL;
}
else {
// Use Log4j 2.x directly, including location awareness support
logApi = LogApi.LOG4J;
}
}
else if (isPresent(SLF4J_SPI)) {
// Full SLF4J SPI including location awareness support
logApi = LogApi.SLF4J_LAL;
}
else if (isPresent(SLF4J_API)) {
// Minimal SLF4J API without location awareness support
logApi = LogApi.SLF4J;
}
else {
// java.util.logging as default
logApi = LogApi.JUL;
}
}
private static boolean isPresent(String className) {
try {
Class.forName(className, false, LogAdapter.class.getClassLoader());
return true;
}
catch (ClassNotFoundException ex) {
return false;
}
}
继续接case SLF4J_LAL:
分支调用createLocationAwareLog()
方法,这里做了两件事
- 是使用了
slf4j
的LoggerFactory
来构造Logger
- 是将
Logger
封装成Log
这里也是适配器模式。至此spring做的工作已经完成,下回我们继续看看slf4j是如何找他的实现的。
private static class Slf4jAdapter {
public static Log createLocationAwareLog(String name) {
Logger logger = LoggerFactory.getLogger(name);
return (logger instanceof LocationAwareLogger ?
new Slf4jLocationAwareLog((LocationAwareLogger) logger) : new Slf4jLog<>(logger));
}
public static Log createLog(String name) {
return new Slf4jLog<>(LoggerFactory.getLogger(name));
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异