【读源码】看看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中我找到了这行代码,注意这里的LogFactoryspring-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()方法,这里做了两件事

  1. 是使用了slf4jLoggerFactory来构造Logger
  2. 是将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));
		}
	}
posted @   AlenYang  阅读(470)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
点击右上角即可分享
微信分享提示