问题复现
在项目中引入一个二方包后在后台日志诡异的不见了,于是使用
mvn dependency:tree -l tree.txt
输出依赖关系树,并定位到新引入的二方包部分,二方包引入了spring-boot-starter-logging其依赖了logback-classic与项目中的log4j产生了冲突,所以将前者排掉就项目就可以正常启动了,这个排包的过程不算难,那slf4j是如何实现绑定的呢?
slf4j源码浅析
这里使用到的slf4j-api版本是1.7.25
,在老版本中是存在一些线程安全问题的,通常我们打日志的时候都会写一段这样的代码
1 | private static final Logger logger = LoggerFactory.getLogger(XXX.class) |
我们从LoggerFactory这个类开始分析源码,首先看到
getLogger
方法1
2
3
4
5
6
7
8public static Logger getLogger(Class<?> clazz) {
Logger logger = getLogger(clazz.getName());
.....
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}从上面源码可以看到会调用
getLogger(String name)
这个方法,通过LoggerFactory最终获取Logger对象,
所以重点就是获取LoggerFactory.我们重点看下
getILoggerFactory
这个方法
1 | public static ILoggerFactory getILoggerFactory() { |
从源码中可以看到INITIALIZATION_STATE
是一个静态的volatile变量,在之前的版本中没有volatile修饰,在这个方法中主要就是调用了performInitialization
方法完成初始化,在该方法中主要完成绑定工作并进行检查
1 | private final static void performInitialization() { |
其中有个重要的方法findPossibleStaticLoggerBinderPathSet
顾名思义就是找可能存在的StaticLoggerBinder路径
1 | private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class"; |
其实就是找日志实现包中的的StaticLoggerBinder,如slf4j-log4j12中的,将这些类的类路径添加到上面的set中,接着通过reportMultipleBindingAmbiguity
方法检查是不是存在多个日志实现绑定产生冲突, 即看一下binderPathSet中元素个数是不是大于1,很简单,因此当项目中logback和slf4j-log4j同时存在时会打印出多个”Found binding in …”
1 | private static boolean isAmbiguousStaticLoggerBinderPathSet(Set<URL> binderPathSet) { |
回到bind
方法,看到StaticLoggerBinder.getSingleton();
,其实就是创建一个单例的StaticLoggerBinder对象,而这个对象中含有一个LoggerFactory,针对不同的日志框架有不同的实现
- log4j
1
2
3
4
5
6
7
8
9
10
11private final ILoggerFactory loggerFactory;
private StaticLoggerBinder() {
loggerFactory = new Log4jLoggerFactory();
try {
Level level = Level.TRACE;
} catch (NoSuchFieldError nsfe) {
Util
.report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version");
}
} - logback-classic通过这种绑定的方式就可以实现LoggerFactory的获取,如引入了log4j,就会利用log4j实现的
1
2
3
4
5
6
7private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}StaticLoggerBinder
类来获取log4j的LoggerFactory,而LoggerFactory可以简单地理解为一个Map,key为loggerName,value为Logger对象