slf4j初始化绑定源码分析

通过阅读源码研究一下 Slf4j 是如何在运行时绑定具体的log api实现。

我们来看看slf4j的源代码,看当这段常见的写日志代码在第一次执行时,slf4j会如何工作

Logger logger = LoggerFactory.getLogger(SomeClass.class);
logger.debug(“first log”);

打开类 org.slf4j.LoggerFactory的源码,看到getLogger()方法的代码如下:

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

继续看 getILoggerFactory()方法的代码实现:

static int INITIALIZATION_STATE = UNINITIALIZED;

public static ILoggerFactory getILoggerFactory() {
if (INITIALIZATION_STATE == UNINITIALIZED) {
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
performInitialization();
}

switch (INITIALIZATION_STATE) {
    case SUCCESSFUL_INITIALIZATION:
        return StaticLoggerBinder.getSingleton().getLoggerFactory();
...

}

这里涉及到一个名为INITIALIZATION_STATE 的静态变量,用来记录当前初始化的状态。默认是UNINITIALIZED,第一次调用getILoggerFactory()方法时,检查到INITIALIZATION_STATE == UNINITIALIZED,就会调用performInitialization()方法来进行初始化。

performInitialization()方法在初始化完成时,会设置INITIALIZATION_STATE为SUCCESSFUL_INITIALIZATION。这样后面的switch语句就会调用StaticLoggerBinder.getSingleton().getLoggerFactory()来返回需要的ILoggerFactory。

我们继续深入看performInitialization()方法的代码实现:

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

继续看bind()方法,忽略错误处理的代码:

private final static void bind() {
try {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
emitSubstituteLoggerWarning();
} ……
}

这里是关键代码了,findPossibleStaticLoggerBinderPathSet()方法用来查找当前classpath下可能的StaticLoggerBinder的实现,如果有多个的话,则reportMultipleBindingAmbiguity()和reportActualBinding()方法会在绑定前后打印相应的信息。

看findPossibleStaticLoggerBinderPathSet()里面干了什么:

private static String STATIC_LOGGER_BINDER_PATH = “org/slf4j/impl/StaticLoggerBinder.class”;

private static Set findPossibleStaticLoggerBinderPathSet() {
// use Set instead of list in order to deal with bug #138
// LinkedHashSet appropriate here because it preserves insertion order during iteration
Set staticLoggerBinderPathSet = new LinkedHashSet();
try {
ClassLoader loggerFactoryClassLoader = LoggerFactory.class
.getClassLoader();
Enumeration paths;
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 ioe) {
Util.report(“Error getting resources from path”, ioe);
}
return staticLoggerBinderPathSet;
}

忽略细节,findPossibleStaticLoggerBinderPathSet()方法其实就是通过classloader的getResources()方法找到所有的名为”org/slf4j/impl/StaticLoggerBinder.class”的resource。

我们再回来看bind()方法,其实真正绑定的代码只有一行

import org.slf4j.impl.StaticLoggerBinder;

private final static void bind() {
……
// the next line does the binding
StaticLoggerBinder.getSingleton();
……
}

这里调用了StaticLoggerBinder.getSingleton()方法。我们看StaticLoggerBinder类的权限定名,恰好和findPossibleStaticLoggerBinderPathSet()方法中查找的一致。

StaticLoggerBinder类是从哪里来的?我们看代码的时候,可以发现在slf4j-api的源代码中,的确有对应的package和类存在。

ImplClassInSlf4jApiSource.jpgImplClassInSlf4jApiSource.jpg

但是打开打包好的slf4j-api.jar,却发现根本没有这个implpackage。

ImplClassNotInSlf4jApiJar.jpgImplClassNotInSlf4jApiJar.jpg

而且这个StaticLoggerBinder类的代码也明确说这个类不应当被打包到slf4j-api.jar:

private StaticLoggerBinder() {
throw new UnsupportedOperationException(
“This code should have never made it into slf4j-api.jar”);
}

在slf4j-api项目的pom.xml文件中,我们可以找到下面的内容:


org.apache.maven.plugins
maven-antrun-plugin


process-classes

run





Removing slf4j-api’s dummy StaticLoggerBinder and StaticMarkerBinder



这里通过调用ant在打包为jar文件前,将package org.slf4j.impl和其下的class都删除掉了。

实际上这里的impl package内的代码,只是用来占位以保证可以编译通过(所谓dummy)。需要在运行时再进行绑定。

具体的log api的源码

我们再来看,具体的log api实现要如何做才能和slf4j绑定和装载。

slf4j自带了一个极度简化的log实现slf4j-simple,这里我们可以找到slf4j-api需要的”org/slf4j/impl/StaticLoggerBinder.class”:

StaticLoggerBinderInSlf4jSimple.jpgStaticLoggerBinderInSlf4jSimple.jpg

同样在slf4j-log4j12, slf4j-jkd14, slf4j-jcl的项目中可以找到类似的”org/slf4j/impl/StaticLoggerBinder.class”,这三个项目分别用于集成java社区最常见的几个log实现:log4j, jdk logging, apache common logging.

继续看回StaticLoggerBinder的代码,以slf4j-simple为例:

private final ILoggerFactory loggerFactory;

private StaticLoggerBinder() {
loggerFactory = new SimpleLoggerFactory();
}

public ILoggerFactory getLoggerFactory() {
return loggerFactory;
}

这里的getLoggerFactory()方法会返回slf4j-simple实现的SimpleLoggerFactory。

public class SimpleLoggerFactory implements ILoggerFactory {

ConcurrentMap<String, Logger> loggerMap;

public Logger getLogger(String name) {
    Logger simpleLogger = loggerMap.get(name);
    if (simpleLogger != null) {
        return simpleLogger;
    } else {
        Logger newInstance = new SimpleLogger(name);
        Logger oldInstance = loggerMap.putIfAbsent(name, newInstance);
        return oldInstance == null ? newInstance : oldInstance;
    }
}

}

SimpleLoggerFactory实现slf4j定义的ILoggerFactory interface,getLogger()方法中负责创建SimpleLogger对象并返回(为了提高性能做了cache)。

类似的slf4j-log4j12中会返回Log4jLoggerFactory,而Log4jLoggerFactory中通过调用log4j的LogManager来创建log4j的Logger对象并通过Log4jLoggerAdapter类来包装为slf4j的Logger(adapter模式)。

log4jLogger = LogManager.getLogger(name);
Logger newInstance = new Log4jLoggerAdapter(log4jLogger);

初始化流程中涉及到的类:

org.slf4j.Logger
org.slf4j.LoggerFactory
org.slf4j.ILoggerFactory
org.slf4j.impl.StaticLoggerBinder
org.slf4j.ILoggerFactory
具体log api的Logger实现类

Logger和LoggerFactory是slf4j定义好的,业务代码通过LoggerFactory来创建Logger对象,并调用这个logger对象来写日志。业务代码在此时是无需知道(也无法知道)具体底层是哪个log api实现,从而摆脱对具体log api的依赖。

LoggerFactory通过装载StaticLoggerBinder类来绑定具体的log api实现,得到该log api实现ILoggerFactory接口的类示例。

这个ILoggerFactory接口的类示例调用底层log api的实现来获取需要logger。

这里有个细节,如果该Logger类已经实现了org.slf4j.Logger这个interface,就直接返回。比如绑定slf4j-simple时:

slf4j-bind-simple.pngslf4j-bind-simple.png

如果没有,比如Log4j的Logger,肯定不会实现org.slf4j.Logger这个interface,这时就需要包装为slf4j的Logger。在slf4j-log4jl2中,有一个Log4jLoggerAdapter类,实现了org.slf4j.Logger, 然后将方法调用转发给log4j的Logger:

slf4j-bind-log4j.pngslf4j-bind-log4j.png

通过翻看Slf4j的代码,我们可以清楚的看到slf4j在运行时绑定具体的log api实现的方式。其实非常简单,关键之处就在于 org.slf4j.impl.StaticLoggerBinder 。

转载来源:http://skyao.github.io/2014/07/21/slfj4-binding/

相关优秀文章:
http://my.oschina.net/xianggao/blog/519199
http://my.oschina.net/u/1469592/blog/533621
http://www.blogjava.net/DLevin/archive/2012/11/08/390991.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值