SLF4J所提供的核心API是一些接口以及一个LoggerFactory的工厂类。SLF4J提供了统一的记录日志的接口,只要按照其提供的方法记录即可,最终日志的格式、记录级别、输出方式等通过具体日志系统的配置来实现,因此可以在应用中灵活切换日志系统。
配置SLF4J是非常简单的一件事,只要将与你打算使用的日志系统对应的jar包加入到项目中,SLF4J就会自动选择使用你加入的日志系统。
日志系统绑定原理:
下面说的是slf4j+logback组合示例:
我们在应用slf4j日志门面的时候都是先在类中声明Logger
private static final Logger logger = LoggerFactory.getLogger(Test.class);
Logger是slf4j中的一个接口,slf4j又是日志的门面,我想大家都有一个疑惑,Logger怎么会根据你导入项目的log4j或者logback去调用不同项目中的日志框架,我们进LoggerFactory的源码包里一探究竟,其实so easy! 我这里是slf4j-1.7.5.jar
public static Logger getLogger(Class clazz) {
return getLogger(clazz.getName());
}
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
public static ILoggerFactory getILoggerFactory() {
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://bugzilla.slf4j.org/show_bug.cgi?id=106
return TEMP_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();检查版本
}
}
private final static void bind() {
Set staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
emitSubstituteLoggerWarning();
}
private static String STATIC_LOGGER_BINDER_PATH = "org/slf4j/impl/StaticLoggerBinder.class";
private static Set findPossibleStaticLoggerBinderPathSet() {
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;
}
上面这一步是非常关键的一步,其中 loggerFactoryClassLoader.getResources(STATIC_LOGGER_BINDER_PATH); 意思是查找所有给定名称的资源 ,详见JDK参考文档,意思是查找项目根路径下 org/slf4j/impl/StaticLoggerBinder.class 此文件,然后你可以查看logback或者slf4j-log4j的jar包,会发现这两个包都有这个目录和文件,说明,如果项目导入这两个其中一个文件的话,getResources就会找到这个实现类
现在我们回过头来再看之前的方法,据需往下走 performInitialization()方法执行完后 INITIALIZATION_STATE现在是成功状态,会进入StaticLoggerBinder.getSingleton().getLoggerFactory();方法。此时应该注意,这个方法会最终返回Logger实现类。但是此时又是在slf4j包里,怎么能返回slf4j或者log4j的实现类呢,我一开始以为用的是反射,因为我们已经获取到其他包里的StaticLoggerBinder ,但是其实不是,我自己找过之后才发现slf4j.jar里根本没有StaticLoggerBinder.class。此时用 StaticLoggerBinder调用getSingleton().方法,其实这个StaticLoggerBinder已经是其他日志实现框架中的类了。(slf4j的源码中是有StaticLoggerBinder类的)