log4j与logback包冲突原因及解决,不可忽视的Warning
场景
一个简单的spring-boot程序,需要用kafka做消息队列,于是在maven中引入kafka依赖,一切看似没问题,在启动时,打印出Warning信息:
SLF4J: Class path contains multiple SLF4J bindings.
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/logback-classic-1.2.3.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: Found binding in [jar:file:/xxx/learning-slf4j-multiple-bindings/WEB-INF/lib/slf4j-log4j12-1.7.25.jar!/org/slf4j/impl/StaticLoggerBinder.class]
SLF4J: See http://www.slf4j.org/codes.html#multiple_bindings for an explanation.
原因分析
通过警告消息,可以简单的看出是slf4j绑定发生问题,有多个StaticLoggerBinder.class存在,即slf4j-log4j12和logback-classic冲突。
- 疑惑点1是我并没有手动引入
slf4j-log4j12依赖,依赖jar包是被自动引入的,通过maven自带工具分析依赖路径,可以看出是kafka依赖于slf4j-log4j12,自动导入的依赖包。


 - 日志绑定的机制分析
从日志对象开始探究slf4j的绑定方式。 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
···
private final Logger logs = LoggerFactory.getLogger(***.class);
LoggerFactory.getLogger()方法:(下述均只保留关键逻辑代码 )
public static Logger getLogger(Class<?> clazz) {
        Logger logger = getLogger(clazz.getName());
        ···
        return logger;
}
public static Logger getLogger(String name) {
        ILoggerFactory iLoggerFactory = getILoggerFactory();//看这里
        return iLoggerFactory.getLogger(name);//根据名字返回一个Logger实例对象
}
ILoggerFactory是一个接口,归属package org.slf4j;仅存在一个方法为:
public Logger getLogger(String name);
接下来就是看看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");
}
可以看到performInitialization()是进行初始化的方法:
private final static void performInitialization() {
    bind();
    ···
}
performInitialization()内部调用bind()方法:
private final static void bind() {
    try {
        Set<URL> staticLoggerBinderPathSet = null;
        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) {...
	} catch (java.lang.NoSuchMethodError nsme) {...
	} catch (Exception e) {...}
}
其中关键在于findPossibleStaticLoggerBinderPathSet()方法,终于到了查找绑定相关的部分内容,可以看到是查找所有的"org/slf4j/impl/StaticLoggerBinder.class"类并加载,同时while循环里,将可能存在的多个StaticLoggerBinder.class路径均加入Set<URL>返回。
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
static Set<URL> findPossibleStaticLoggerBinderPathSet() {
    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;
}
返回到bind()方法中:
private final static void bind() {
	···
	staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
	reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);//看这里
	···
}
···
private static void reportMultipleBindingAmbiguity(Set<URL> binderPathSet) {
    if (isAmbiguousStaticLoggerBinderPathSet(binderPathSet)) {
        Util.report("Class path contains multiple SLF4J bindings.");
        for (URL path : binderPathSet) {
            Util.report("Found binding in [" + path + "]");
        }
        Util.report("See " + MULTIPLE_BINDINGS_URL + " for an explanation.");
    }
}
这里可以看到reportMultipleBindingAmbiguity()里判断是否发生多重绑定,就是打印文章开头Warning信息的地方。
成功加载StaticLoggerBinder后,在bind()方法中调用其getSingleton()方法得到单例,并修改INITIALIZATION_STATE 状态,至此完成日志框架的绑定。
private final static void bind() {
	if (!isAndroid()) {
	    staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
	    reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
	}
	StaticLoggerBinder.getSingleton();//看这里
	INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
	reportActualBinding(staticLoggerBinderPathSet);
	···
}
最后附上slf4j-api-1.7.25.jar和logback-classic-1.2.3.jar的目录结构供参考:


解决方案
分析了原因,那么解决方案自然很简单,就是剔除不需要的依赖包,此处就是在kafka的依赖中剔除slf4j-log4j12。maven项目中可以通过exclusions标签来完成。
<dependency>
	<groupId>org.apache.kafka</groupId>
	<artifactId>kafka_2.11</artifactId>
	<version>0.10.0.1</version>
	<exclusions>
		<exclusion>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
		</exclusion>
	</exclusions>
</dependency>
本文就简单分析了日志加载绑定的过程,如有遗漏请不吝指出。
log4j与logback包冲突原因及解决,不可忽视的Warning的更多相关文章
- Maven类包冲突终极三大解决技巧 mvn dependency:tree
		
Maven对于新手来说是<步步惊心>,因为它包罗万象,博大精深,因为当你初来乍到时,你就像一个进入森林的陌生访客一样迷茫. Maven对于老手来说是<真爱配方>,因为它无所不能 ...
 - Maven中 jar包冲突原理与解决办法
		
Maven中jar包冲突是开发过程中比较常见而又令人头疼的问题,我们需要知道 jar包冲突的原理,才能更好的去解决jar包冲突的问题.本文将从jar包冲突的原理和解决两个方面阐述Maven中jar包冲 ...
 - [转]Maven类包冲突终极三大解决技巧
		
举例 A依赖于B及C,而B又依赖于X.Y,而C依赖于X.M,则A除引B及C的依赖包下,还会引入X,Y,M的依赖包(一般情况下了,Maven可通过<scope>等若干种方式控制传递依赖).这 ...
 - jar包冲突常用的解决方法
		
jar包冲突常见的异常为找不到类(java.lang.ClassNotFoundException).找不到具体方法(java.lang.NoSuchMethodError).字段错误( java.l ...
 - was(websphere application server)中用apache的httpclient时jar包冲突问题的解决
		
这个问题可以用was的共享库解决. 具体解决方案如下图所示: 对于有多个jar包冲突时,为每个冲突的jar包都新建一个共享库即可. 我之前的错误操作是以为一个共享库可以添加多个冲突的jar包用分号和逗 ...
 - 部分APP无法代理抓包的原因及解决方法
		
引言 HTTP应用层的抓包已经成为日常工作测试与调试中的重要一环,最近接触新项目突然之间发现之前的抓包手段都不好使了,顿时模块与模块之间的前端与服务之间的交互都变成了不可见,整个人都好像被蒙住了眼睛. ...
 - tcp粘包问题原因及解决办法
		
1.粘包概念及产生原因 1.1粘包概念: TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾. 粘包可能由发送方造成,也可能由接收方造成. ...
 - [log4j]Slf4j的包冲突
		
Caused by: java.lang.NoClassDefFoundError: Could not initialize class org.apache.log4j.Log4jLoggerFa ...
 - Android手机出现"已安装了存在签名冲突的同名数据包"的原因及解决办法
		
http://blog.csdn.net/dyllove98/article/details/8830264 如果你不是开发者:如果你在android上更新一个已经安装过较早版本软件时,安装到最后一步 ...
 
随机推荐
- Vijos / 题库 / 输油管道问题
			
背景 想念car的GF,car就出了道水题! 描述 某石油公司计划建造一条由东向西的主输油管道.该管道要穿过一个有n 口油井的油田.从每口油井都要有一条输油管道沿最短路经(或南或北)与主管道相连.如果 ...
 - JAVASE(说出ArrayList,LinkedList的储存性能和特性)
			
说出ArrayList,和LinkedList的储存性能和特性? 答: ## ArrayList采用的是数组形式来保存对象的,这种方式将对象放在连续的位置中,优点是索引读取快,从最后插入和删除元素速 ...
 - 伪元素::before与::after的用法
			
::before与::after两个伪元素其实是CSS3中的内容,然而实际上在CSS2中就已经有了这两者的身影,只不过CSS2中是前面加一个冒号来表示(:before和:after).今天主要讲讲这两 ...
 - Install and Configure OSSEC on Debian 7&8
			
Install and Configure OSSEC on Debian 7&8 Contributed by Sunday Ogwu-Chinuwa Updated Friday, Feb ...
 - C++多线程编程(★入门经典实例★)
			
原文:http://www.cnblogs.com/codingmengmeng/p/5913068.html 多线程在编程中有相当重要的地位,我们在实际开发时或者找工作面试时总能遇到多线程的问题,对 ...
 - weex 数据绑定,动态控制组件的显示内容及样式
			
无论的原生开发还是weex开发,经常会需要我们对一些组件/控件动态赋值,在原生中,我们大家都知道,对控件setText就可以了,那么在weex中呢,我们需要怎么做呢,其实很简单,几行代码就可以搞定!首 ...
 - Monkey测试运用实例
			
测试命令是多样性的,根据个人的测试思路,设计执行你想要的测试命令 1.monkey -p com.junte -v 1000 团贷网模拟用户随机操作,无延时点击1000次 -p测试包 ...
 - Google 嘘! 嘘!
			
https://www.gufen.gq(无广告,原guso.ml,ggso.ga,guge.ga) https://c.aiguso.tk (无广告,体验良好) https://d.freedo.g ...
 - 平均负载(Load  average)
			
load average 的含义平均负载(load average)是指系统的运行队列的平均利用率,也可以认为是可运行进程的平均数. top命令中load average显示的是最近1分钟.5分钟和1 ...
 - 猴子选大王【PHP】
			
目录 猴子选大王 指针解决 数组压栈 猴子选大王 一群猴子排成一圈,按1,2,...,n依次编号.然后从第1只开始数,数到第m只,把它踢出圈,从它后面再开始数,再数到第m只,在把它踢出去...,如此不 ...