一、导读

我们使用log4j框架时,经常会用slf4j-api。在运行时,经常会遇到如下的错误提示:
1
2
3
4
5
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/slf4j/slf4j-simple/1.7.26/slf4j-simple-1.7.26.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/Users/abc/maven-repository/org/apache/logging/log4j/log4j-slf4j-impl/2.7/log4j-slf4j-impl-2.7.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 [org.slf4j.impl.SimpleLoggerFactory]
也即classpath下有多个日志实现,slf4j-api在绑定时出现了冲突。那么这时slf4j-api到底会绑定哪一个具体的实现呢?
 
官方文档有如下说明:

也就是说,当有多个日志实现时,SLF4J会在编译期随机选择其中的一个实现。那么,它到底是如何随机选择的呢?下面我们通过源码来分析。

请尊重作者劳动成果,转载请标明原文链接:https://www.cnblogs.com/waterystone/p/11329645.html
 

二、源码分析

2.1 LoggerFactory.getLogger(this.getClass())

我们通过LoggerFactory.getLogger(this.getClass())拿到Logger对象,其代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
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.2 LoggerFactory.bind()

getLogger() -> getILoggerFactory()-> performInitialization() -> bind() ,bind()即绑定日志实现的函数,其源码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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(); //在classpath下查找有多少个日志实现
            reportMultipleBindingAmbiguity(staticLoggerBinderPathSet); //如果有多个日志实现,打印出来
        }
        // the next line does the binding。classpath下每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类,这里会随机加载其中的一个。
        StaticLoggerBinder.getSingleton();
        INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
        reportActualBinding(staticLoggerBinderPathSet);
        fixSubstituteLoggers();
        replayEvents();
        // release all resources in SUBST_FACTORY
        SUBST_FACTORY.clear();
    } catch (NoClassDefFoundError ncde) { //如果在classpath下没有找到一个org.slf4j.impl.StaticLoggerBinder
        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);
    }
}

  

2.3 LoggerFactory.findPossibleStaticLoggerBinderPathSet()

接下来,我们再看看findPossibleStaticLoggerBinderPathSet()如何在classpath下查找有多少个日志实现的源码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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) { //用ClassLoader去查找classpath下有多少个org.slf4j.impl.StaticLoggerBinder类
            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;
}

  

通过源码我们可以看到,findPossibleStaticLoggerBinderPathSet()会用ClassLoader查找当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,每个path的URL能定位到其具体jar包位置。每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类,反过来,那么classpath下有多少个StaticLoggerBinder类,就会有多少个相应的jar包,也即有多少个日志实现。所以findPossibleStaticLoggerBinderPathSet()通过扫描classpath下的org.slf4j.impl.StaticLoggerBinder类就能找到有多少个日志实现。
 
接下来,我们再看看是不是每个日志实现jar包都会有个org.slf4j.impl.StaticLoggerBinder类呢?答案是YES。
 

三、具体日志实现

3.1 slf4j-simple

3.2 slf4j-nop

3.3 logback-classic

3.4 log4j-slf4j-impl

四、总结

  • 每个日志实现jar都会有org.slf4j.impl.StaticLoggerBinder类的实现;
  • SLF4J会通过ClassLoad扫描当前classpath下有多少个org.slf4j.impl.StaticLoggerBinder类,也就找到了有多少个日志实现(通过这样,我们只需在项目中加入日志实现的jar包,编译时即可自动加载,业务代码无须显式依赖,实现解耦);
  • 如果有多个org.slf4j.impl.StaticLoggerBinder类,SLF4J会在LoggerFactory.bind()里调用StaticLoggerBinder.getSingleton()随机加载一个日志实现jar的StaticLoggerBinder。
  • 发现一个有趣的现象,在本地idea上运行时,是加载pom.xml里声明的第一个日志实现;但是如果打包好后通过java -jar启动时,其加载的日志实现确实是随机的(是在编译打包时随机加载一个日志实现,所以一旦编译打包好后其加载的那个日志实现就会固定不变)。所以,我们在具体使用时一定要通过排除依赖的方式来确定日志实现,不要由于日志实现的不确定性引入难以排查、不必要的坑。

  • 作者:水岩
  • 出处:http://www.cnblogs.com/waterystone
  • 本博客中未标明转载的文章归作者水岩和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

如果您觉得本文对您的学习有所帮助,可通过支付宝(左) 或者 微信(右) 来打赏博主,增加博主的写作动力

java下的slf4j的更多相关文章

  1. Java下好用的开源库推荐

    作者:Jack47 转载请保留作者和原文出处 欢迎关注我的微信公众账号程序员杰克,两边的文章会同步,也可以添加我的RSS订阅源. 本文想介绍下自己在Java下做开发使用到的一些开源的优秀编程库,会不定 ...

  2. Java下Elasticsearh应用指南

    简介 本文针对在Java下操作elasticsearch给出应用示例,主要涉及创建连接,构建索引以及检索数据3个部分. 环境 1)elasticsearch2.4.4, 2)jdk1.8. 客户端连接 ...

  3. Java下利用Jackson进行JSON解析和序列化

    Java下利用Jackson进行JSON解析和序列化   Java下常见的Json类库有Gson.JSON-lib和Jackson等,Jackson相对来说比较高效,在项目中主要使用Jackson进行 ...

  4. maven项目编译漏掉src/main/java下的xml配置文件

    在整合Spring + Mybatis框架的时候,自动扫描配置都已经配置好了. 配置如下: <?xml version="1.0" encoding="UTF-8& ...

  5. maven 编译部署src/main/java下的资源文件

    maven 编译部署src/main/java下的资源文件 maven默认会把src/main/resources下的所有配置文件以及src/main/java下的所有java文件打包或发布到targ ...

  6. Java 下 SSL 通信原理及实例

    有关SSL的原理和介绍在网上已经有不少,对于Java下使用keytool生成证书,配置SSL通信的教程也非常多.但如果我们不能够亲自动手做一个SSL Sever和SSL Client,可能就永远也不能 ...

  7. Java 下实现锁无关数据结构--转载

    介绍 通常在一个多线程环境下,我们需要共享某些数据,但为了避免竞争条件引致数据出现不一致的情况,某些代码段需要变成原子操作去执行.这时,我们便需要利用各种同步机制如互斥(Mutex)去为这些代码段加锁 ...

  8. 关于Objective-c和Java下DES加密保持一致的方式

    转载自:http://www.cnblogs.com/janken/archive/2012/04/05/2432930.html 最近做了一个移动项目,是有服务器和客户端类型的项目,客户端是要登录才 ...

  9. objective-c和java下解析对象类型和数组类型JSON字符串

    首先讲objective-c如何实现: 这里需要用到2个插件,一个是JSONKit,另一个是Jastor,一共包含6个文件,3个.h头文件和3个.m实现文件.在ARC的工程中如何导入不支持ARC的第三 ...

随机推荐

  1. light oj1170 - Counting Perfect BST卡特兰数

    1170 - Counting Perfect BST BST is the acronym for Binary Search Tree. A BST is a tree data structur ...

  2. 在列表中动态设置元素的id

    <div class="col-6" v-for="(item,i) in showpics" :key="i"> <im ...

  3. css position absolute相对于父元素的设置方式

    手机赚钱怎么赚,给大家推荐一个手机赚钱APP汇总平台:手指乐(http://www.szhile.com/),辛苦搬砖之余用闲余时间动动手指,就可以日赚数百元 大家知道css的position abs ...

  4. js文本复制插件&vue

    /* HTML: * <a href="javascript:;" class="copy" data-clipboard-text="copy ...

  5. Caliburn.Micro框架之Action Convertions

    首先新建一个项目,名称叫Caliburn.Micro.ActionConvertions 然后删掉MainWindow.xaml 然后去app.xaml删掉StartupUri这行代码 其次,安装Ca ...

  6. 简单了解css3样式表写法和优先级

    css3和css有什么区别?首先css3是css(层叠样式表)技术的升级版本,而css是一种用来表现HTML(标准通用标记语言的一个应用)或XML(标准通用标记语言的一个子集)等文件样式的计算机语言. ...

  7. html网页基本结构

    <!DOCTYPE> 不是 HTML 标签.它为浏览器提供一项信息(声明),即 HTML 是用什么版本编写的. HTML5 DOCTYPE 的 HTML 文档类型如下: <!DOCT ...

  8. HDU-1754 I Hate It (树状数组模板题——单点更新,区间查询最大值)

    题目链接 ac代码(注意字符读入前需要注意回车的影响) #include<iostream> #include<cstdio> #include<cstring> ...

  9. 拍摄UFP 单一职责原则

    3.1 新手机 3.2 拍摄 3.3 没用的东西 3.4 单一职责原则 就一个类而言,应该仅有一个引起它变化的原因, 3.5 方块游戏的设计 如果一个类承担的职责过多,就等于把这些职责耦合在一起,一个 ...

  10. AGC011-C Squared Graph

    题意 给定一个\(n\)个点\(m\)条边的图,构建一个\(n^2\)个点的图,新图的每个点都可以看成一个二元组,新图上的点\((a,b)和(a′,b′)\)之间有边,当且仅当原图中\((a,a′), ...