总的入口就是这句 LoggerFactory.getLogger(Main.class)
,我们从这里入手。
public class Main {
private static final Logger LOGGER = LoggerFactory.getLogger(Main.class);
public static void main(String[] args) {
LOGGER.info("\\{} {} test", "hello");
LOGGER.info("\\\\{}", "place");
}
}
1.8.0 之前
在 SLF4J 1.8.0 版本之前,它依赖于 Java 类加载机制,通过特定的类名来查找并加载具体框架的绑定实现。我们看下具体的实现:
getLogger
会使用传入的类名进行查找,它首先需要找到具体的 ILoggerFactory
实例,然后调用 getLogger(name)
方法获取指定名称的 Logger 实例。ILoggerFactory
的实现类会根据具体的日志框架实现来创建和返回 Logger 实例。
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
return iLoggerFactory.getLogger(name);
}
顺着 getILoggerFactory()
往下追,能够找到 bind()
方法,它是整个加载的核心。
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()) {
// 查找可能的 `StaticLoggerBinder` 的路径集合
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) {
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);
}
}
在 bind()
方法中,findPossibleStaticLoggerBinderPathSet()
查找可能的 StaticLoggerBinder
的路径集合。主要逻辑就是通过类加载器,获取所有 org/slf4j/impl/StaticLoggerBinder.class
的资源。可以看到,所有支持 SLF4J 的框架,都在指定路径定义了这个类。
StaticLoggerBinder.getSingleton();
这里会进行实际的绑定。在有多个实现的情况下,会依据类加载顺序,选择第一个。如果 SLF4J 在类路径中找不到任何 SLF4J 绑定,那么它将默认使用 NOP(No Operation)绑定,就是说没有任何日志输出。也就是下面的 NOP_FALLBACK_FACTORY。
public static ILoggerFactory getILoggerFactory() {
...
switch (INITIALIZATION_STATE) {
case SUCCESSFUL_INITIALIZATION:
return StaticLoggerBinder.getSingleton().getLoggerFactory();
case NOP_FALLBACK_INITIALIZATION:
return NOP_FALLBACK_FACTORY;
...
}
throw new IllegalStateException("Unreachable code");
}
1.8.0 及之后
从 SLF4J 1.8.0 版本开始,SLF4J 使用了 ServiceLoader 机制来加载日志框架的实现。当应用程序启动时,ServiceLoader 会扫描所有的类路径,查找并加载可用的日志框架实现。
依旧是最关键的 bind()
:
private final static void bind() {
try {
List<SLF4JServiceProvider> providersList = findServiceProviders();
reportMultipleBindingAmbiguity(providersList);
...
} catch (Exception e) {
failedBinding(e);
throw new IllegalStateException("Unexpected initialization failure", e);
}
}
在findServiceProviders()
,提供了两种方式对服务提供者进行加载。
- 通过系统变量
slf4j.provider
来指定。 - 通过
ServiceLoader.load()
加载SLF4JServiceProvider.class
对象列表。这里使用了 Java 的 SPI 机制,该机制会扫描META-INF/services
下的文件,文件名为接口全限定名,内容是实现了这个接口的类的全限定名。关于 SPI,大家可以看看这篇文章剖析 SPI 在 Spring 中的应用。
为什么有这样的改变?
- 在 SLF4J 1.8.0 版本之前,对于其他日志框架,需要在自己项目实现一个包名为
org.slf4j.impl
的类,这会导致代码与 SLF4J 框架耦合。 - 对于 SLF4J 自身,它自身不能有类
StaticLoggerBinder
,但是为了编译通过,还是需要提供一个简单的实现,然后在打包阶段删除。 - SLF4J 2004就开始发布,而 Java SPI 直到 2006 年才正式加入标准库。