slf4j源码解析-门面模式

正文开始

读源码最好的方式是带着疑问去解析,这样的话你会更理解源码每一步的意义。

疑问:

为什么 slf4j 能整合不同的日志框架?

源码解析开始

1. 首先搭建slf4j_demo

创建一个maven项目,在pom文件引入

<!--
 这个包中已经整合了 logback-classic-1.2.3.jar ,logback-core-1.2.3.jar,slf4j-api-1.7.25.jar
 -->
<dependency>
    <groupId>ch.qos.logback</groupId>
    <artifactId>logback-classic</artifactId>
    <version>1.2.3</version>
</dependency>

在 src\main\resources 目录下添加文件logback.xml :

<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <!-- ch.qos.logback.core.ConsoleAppender 控制台输出 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <Pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
            </Pattern>
        </encoder>
    </appender>

    <!-- 日志级别 -->
    <root level="debug">
        <appender-ref ref="console" />
    </root>
</configuration>

编写一个测试demo

public class DemoController {
    static Logger logger = LoggerFactory.getLogger(DemoController.class);

    public static void main(String[] args) {
        logger.trace("-----trace-----");
        logger.debug("-----debug-----");
        logger.info("-----info-----");
        logger.warn("-----warn-----");
        logger.error("-----error-----");
    }
}

debug运行起来,这时候控制台打印

08:45:50.856 [main] DEBUG com.cn.controller.DemoController - -----debug-----
08:45:50.857 [main] INFO  com.cn.controller.DemoController - -----info-----
08:45:50.858 [main] WARN  com.cn.controller.DemoController - -----warn-----
08:45:50.858 [main] ERROR com.cn.controller.DemoController - -----error-----

打印这些日志就代表,slf4j已经成功使用啦。

ps:需要注意的是logger 和 loggerFactory 都是slf4j里的包

2. 源码解析开始

不管读什么源码都要先找到一个入口,然后从入口开始循序渐进的分析,在上面这段代码中,LoggerFactory.getLogger 是用来初始化Logger对象的所以这个代码就是入口,所以我们就从这开始分析

public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        if (DETECT_LOGGER_NAME_MISMATCH) {
            Class<?> autoComputedCallingClass = Util.getCallingClass();
            if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) {
                Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(),
                                autoComputedCallingClass.getName()));
                Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation");
            }
        }
        return logger;
    }

第2行:用来初始化logger对象的

第3行~第10行:用来检测记录器名称是否匹配

在此我们的重点是怎么获取logger对象,所以我们点击第二行代码

public static Logger getLogger(String name) {
    ILoggerFactory iLoggerFactory = getILoggerFactory();
    return iLoggerFactory.getLogger(name);
}

这里面是通过 getILoggerFactory() 初始化一个 loggerFactory ,然后根据loggerFactory获取 logger对象,这里使用了工厂设计模式,当开始阅读源码时会发现设计模式无处不在,我们继续往下走getILoggerFactory

public static ILoggerFactory getILoggerFactory() {
    if (INITIALIZATION_STATE == UNINITIALIZED) {
        synchronized (LoggerFactory.class) {
            if (INITIALIZATION_STATE == UNINITIALIZED) {
                INITIALIZATION_STATE = ONGOING_INITIALIZATION;
                performInitialization();
            }
        }
    }
    switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
    case NOP_FALLBACK_INITIALIZATION:
        return NOP_FALLBACK_FACTORY;
    case FAILED_INITIALIZATION:
        throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG);
    case ONGOING_INITIALIZATION:
        // support re-entrant behavior.
        // See also http://jira.qos.ch/browse/SLF4J-97
        return SUBST_FACTORY;
    }
    throw new IllegalStateException("Unreachable code");
}

第2行~第9行: 判断loggerFactory 是否已经初始化,如果没有初始化就开始执行初始化performInitialization(),这里使用单例模式,保证 loggerFactory 只被初始化一次。

第10行 ~ 第20:判断 loggerFactory 初始化状态,成功、失败等,从字面意思上就可以理解,从这里看出一般都会静态变量来做为状态码,比如 INITIALIZATION_STATE ,这还使用volatile修饰符保证变量线程间的一致性

static volatile int INITIALIZATION_STATE = UNINITIALIZED;

然后我们继续往下走

private final static void performInitialization() {
    bind();
    if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
        versionSanityCheck();
    }
}

重点来了!bind()方法是绑定loggerFactory的实现,往下走

private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        // skip check under android, see also
        // http://jira.qos.ch/browse/SLF4J-328
        if (!isAndroid()) {
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // the next line does the binding
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        // release all resources in SUBST_FACTORY
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError ncde) {
        String msg = ncde.getMessage();
        if (messageContainsOrgSlf4jImplStaticLoggerBinder(msg)) {
            INITIALIZATION_STATE = NOP_FALLBACK_INITIALIZATION;
            Util.report("Failed to load class \"org.slf4j.impl.StaticLoggerBinder\".");
            Util.report("Defaulting to no-operation (NOP) logger implementation");
            Util.report("See " + NO_STATICLOGGERBINDER_URL + " for further details.");
        } else {
            failedBinding(ncde);
            throw ncde;
        }
    } catch (java.lang.NoSuchMethodError nsme) {
        String msg = nsme.getMessage();
        if (msg != null && msg.contains("org.slf4j.impl.StaticLoggerBinder.getSingleton()")) {
            INITIALIZATION_STATE = FAILED_INITIALIZATION;
            Util.report("slf4j-api 1.6.x (or later) is incompatible with this binding.");
            Util.report("Your binding is version 1.5.5 or earlier.");
            Util.report("Upgrade your binding to version 1.6.x.");
        }
        throw nsme;
    } catch (Exception e) {
        failedBinding(e);
        throw new IllegalStateException("Unexpected initialization failure", e);
    }
}

第7行:findPossibleStaticLoggerBinderPathSet()是绑定的重点

第8行:是检查是否两个logger日志绑定器,这就是为什么 slf4j只能有且仅有一种日志框架的binding,在这里做校验了

第11行:是初始化StaticLoggerBinder绑定器对象,这个对象是用来创建loggerFatory,在前面getILoggerFactory() 方法中有使用 StaticLoggerBinder.getSingleton().getLoggerFactory()

然后到这里我们已经知道 loggerFactory工厂是怎么创建的,但是StaticLoggerBinder 是怎么加载的,我们不知道,所以我们到findPossibleStaticLoggerBinderPathSet()里看

static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    // use Set instead of list in order to deal with bug #138
    // LinkedHashSet appropriate here because it preserves insertion order
    // during iteration
    Set<URL> staticLoggerBinderPathSet = new LinkedHashSet<URL>();
    try {
        ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
        Enumeration<URL> paths;
        if (loggerFactoryClassLoader == null) {
            paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
        } else {
            paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
        }
        while (paths.hasMoreElements()) {
            URL path = paths.nextElement();
            staticLoggerBinderPathSet.add(path);
        }
    } catch (IOException ioe) {
        Util.report("Error getting resources from path", ioe);
    }
    return staticLoggerBinderPathSet;
}

这里就是精髓了, 通过ClassLoader.getResources 去项目的资源目录下寻找org/slf4j/impl/StaticLoggerBinder.class 文件,如果找到多个的StaticLoggerBinderclass文件,就会在reportMultipleBindingAmbiguity中判断,并将每个StaticLoggerBinder文件路径打印出来。这里就解释了为什么slf4j 不能同时整合多个日志框架。

SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Document/Service/Maven/apache-maven-3.6.3/repository/ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Document/Service/Maven/apache-maven-3.6.3/repository/org/slf4j/slf4j-log4j12/1.7.21/slf4j-log4j12-1.7.21.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
SLF4J: Actual binding is of type [ch.qos.logback.classic.util.ContextSelectorStaticBinder]

 

3. 最后总结

slf4j 提供了统一的 logger api ,每个日志框架都会实现logger api。在调用logger的时候 ,slf4j 会根据ClassLoader 加载各框架的StaticLoggerBinder ,然后根据StaticLoggerBinder获取对应的loggerFactory对象,在根据LoggerFactory 创建Logger对象,所以调用logger的方法时,实际是调用各个框架logger方法,这实际上就是门面模式的一种实现。

这就解释了为什么slf4j能整合不同日志框架,实际slf4j只是提供了了一个接口规范,具体实现是交给对应的日志框架实现的。

 

 

 

  • 7
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
log4j-to-slf4j 和 jul-to-slf4j 都是用于将不同日志框架(log4j和JUL)的日志转发到slf4j的桥接器。它们的作用是在项目中统一使用slf4j接口进行日志记录,而不需要直接使用特定的日志框架。简单来说,它们是用来解决日志框架的兼容性问题的。 引用提到了一个错误信息,即log4j-slf4j-impl 不能与log4j-to-slf4j 同时存在。这是因为log4j-slf4j-impl是log4j框架的一个实现,而log4j-to-slf4j是将log4j框架转发到slf4j的桥接器。因此,当同时存在这两个包时会造成冲突。 综上所述,log4j-to-slf4j 和 jul-to-slf4j都是用于桥接不同日志框架到slf4j的工具,用于统一日志记录接口。在使用过程中需要注意避免与其他框架的冲突,比如log4j-slf4j-impl。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* [log4j-slf4j-impl cannot be present with log4j-to-slf4j](https://blog.csdn.net/Master_Shifu_/article/details/125925944)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *3* [slf4j log4j log4j-over-slf4j self-log4j12](https://blog.csdn.net/song854601134/article/details/130624626)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值