SLF4J源码解析

作者: CodeNeverStops | 来源:发表于2018-12-14 14:01 被阅读1次

    提出问题

    阅读源码之前,首先提几个问题

    • SLF4J是如何整合不同的日志框架的
    • Class Path中为什么只能有且仅有一种日志框架的binding

    这段文字摘录自官网:In your code, in addition to slf4j-api-1.8.0-beta2.jar, you simply drop one and only one binding of your choice onto the appropriate class path location. Do not place more than one binding on your class path.

    源码版本

    • org.slf4j:slf4j-api:1.7.25
    • org.apache.logging.log4j:log4j-slf4j-impl:2.11.1
    • ch.qos.logback:logback-classic:1.2.3
    • org.slf4j:slf4j-jcl:1.7.25

    源码解析

    带着上面的两个问题看下源码

    1. bind()方法中通过调用findPossibleStaticLoggerBinderPathSet()方法来查找日志框架的绑定
    // org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/org/slf4j/LoggerFactory.class
    private static final void bind() {
        // ...
        if (!isAndroid()) {
            // 查找日志框架的绑定
            staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
        }
        // ...
    }
    
    1. findPossibleStaticLoggerBinderPathSet()方法中通过ClassLoader或者loggerFactoryClassLoader来获取名为"STATIC_LOGGER_BINDER_PATH"的Resources
      "STATIC_LOGGER_BINDER_PATH"的值在文件一开始已经定义过了
    private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
    
    // org/slf4j/slf4j-api/1.7.25/slf4j-api-1.7.25.jar!/org/slf4j/LoggerFactory.class
        static Set<URL> findPossibleStaticLoggerBinderPathSet() {
            LinkedHashSet staticLoggerBinderPathSet = new LinkedHashSet();
    
            try {
                ClassLoader loggerFactoryClassLoader = LoggerFactory.class.getClassLoader();
                Enumeration paths;
                // 通过ClassLoader或者loggerFactoryClassLoader来获取Resources
                if (loggerFactoryClassLoader == null) {
                    paths = ClassLoader.getSystemResources(STATIC_LOGGER_BINDER_PATH);
                } else {
                    paths = loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH);
                }
    
                while(paths.hasMoreElements()) {
                    URL path = (URL)paths.nextElement();
                    staticLoggerBinderPathSet.add(path);
                }
            } catch (IOException var4) {
                Util.report("Error getting resources from path", var4);
            }
    
            return staticLoggerBinderPathSet;
        }
    
    1. 这里拿整合Log4j举例,"org/slf4j/impl/StaticLoggerBinder.class"就在log4j的log4j-slf4j-impl库中,这个类负责初始化Log4j相关的类。StaticLoggerBinder实现了LoggerFactoryBinder接口。
    // org/apache/logging/log4j/log4j-slf4j-impl/2.11.1/log4j-slf4j-impl-2.11.1.jar!/org/slf4j/impl/StaticLoggerBinder.class
    
    // 这里的包名定义为org.slf4j.impl,使得上述第2步的Class Loader可以加载到这个类
    package org.slf4j.impl;
    
    public final class StaticLoggerBinder implements LoggerFactoryBinder {
    // log4j init...
    }
    
    1. 其他日志框架也定义了StaticLoggerBinder类,且实现了LoggerFactoryBinder接口。
      例如: logback,jcl
    // ch/qos/logback/logback-classic/1.2.3/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class
    
    // 这里的包名定义为org.slf4j.impl,使得上述第2步的Class Loader可以加载到这个类
    package org.slf4j.impl;
    
    public class StaticLoggerBinder implements LoggerFactoryBinder {
    // logback init...
    }
    
    // org/slf4j/slf4j-jcl/1.7.25/slf4j-jcl-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class
    
    // 这里的包名定义为org.slf4j.impl,使得上述第2步的Class Loader可以加载到这个类
    package org.slf4j.impl;
    
    public class StaticLoggerBinder implements LoggerFactoryBinder {
    // jcl init...
    }
    

    问题答案

    • SLF4J是如何整合不同的日志框架的
      SLF4J通过加载各个底层日志框架桥接库的org/slf4j/impl/StaticLoggerBinder.class来加载初始化对应的日志框架。
    • Class Path中为什么只能有且仅有一种日志框架的binding
      slf4j-api中的LoggerFactory中,在上述第2步返回staticLoggerBinderPathSet后,会立马调用
      reportMultipleBindingAmbiguity(staticLoggerBinderPathSet)方法来检测是否有多个binding。如果有多个binding,就输出错误信息"Class path contains multiple SLF4J bindings."
        private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
            if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
                Util.report("Class path contains multiple SLF4J bindings.");
                Iterator i$ = binderPathSet.iterator();
    
                while(i$.hasNext()) {
                    URL path = (URL)i$.next();
                    Util.report("Found binding in [" + path + "]");
                }
    
                Util.report("See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.");
            }
    
        }
    

    最后

    代码中使用SLF4J来记录日志,可以任意切换底层日志框架而不需要修改代码,只需要更新依赖以及日志配置文件即可。

    相关文章

      网友评论

        本文标题:SLF4J源码解析

        本文链接:https://www.haomeiwen.com/subject/qyvwhqtx.html