众所周知,slf4j 是日志的门面。在阿里的《Java 开发手册》中有相关的记载:
那么,他是如何完成这个事情的呢?
根据官网的介绍,我们跑个 Demo
HelloWorld.java
import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 日志测试类 * * @author xisha * @date 2022/5/9 7:33 下午 */public class HelloWorld { public static void main(String[] args) { Logger logger = LoggerFactory.getLogger(HelloWorld.class); logger.info("Hello World"); }}
pom.xml
<dependencies> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.36</version> </dependency> </dependencies>
运行结果
注意,这个地方除了依赖 slf4j-simple 之外,还可以依赖 slf4j-log4j12 、slf4j-reload4j,slf4j-jdk14,slf4j-nop,slf4j-jcl,logback-classic 等等,相当于每个 依赖都是一个实现。
下载源码,可以看到每个model 都依赖了 slf4j-api
官网上的图也表明 slf4j-api 应该是个核心包
从上面两个图,再根据我多年来写bug的经验盲猜一波,这这个应该是有个什么什么东西注册上去了,然后 LoggerFactory 去拿的时候再把这个注册过的 Logger 拿出来
打个断点进去,那么很明显
StaticLoggerBinder --> JVM 注册LogFactory --> 反射出 StaticLoggerBinder通过 StaticLoggerBinder 获取 ILoggerFactory通过 ILoggerFactory 获取 Logger
代码很简单,我就不帖了,听上去很简单啊这,你以为故事到这里就结束了么? 桥豆麻袋!StaticLoggerBinder 是在哪里定义的? 可以看到,StaticLoggerBinder 在 slf4j-api 是有定义的,且每个实现里也都定义了一个。那么问题来了,我同时引入了 slf4j-api 和一个具体的实现,这玩意儿难道不会冲突么?
但是在我的 Demo 工程里搜的的话,只有实现包里有 StaticLoggerBinder 这个类。哦豁?这是什么骚操作?slf4j-api 里的 StaticLoggerBinder 去哪里了呢?还是说我的源码下载的有问题?
奥~ 看起来好像是被这个框架给删掉了
但是这是个啥玩意儿?
一番搜索之后了解到这是个ant的插件,phase 表示的是生命周期,这个生命周期我就不展开了(毕竟我也不大懂),我们只需要知道 process-classes 是在 compile 之后执行的,这段代码的含义大概是 处理 classes 的时候(在编译之后),把 target/classes/org/slf4j/impl 目录下的文件都删掉的意思。
而我们的 slf4j-api 里的 StaticLoggerBinder ,也正好在这个包下面,你说巧不巧? 狗头.jpg
删掉之后,具体实现类里的相关实现就能趁虚而入,占领高地了。学到了学到了。
你以为故事到这里就结束了么?
你看红框框起来的那行代码
// the next line does the bindingStaticLoggerBinder.getSingleton();
其他的代码都很好理解,但是这个是个什么鬼……
get 了一个单例,返回值也没用上,这个又是为啥呢,怎么就 dose the binding 了呢?这里就涉及到亿点点的类加载知识了。首先我们看一段Demo代码
// StatisticClass.javapublic class StatisticClass { static { // 输出1 System.out.println("StatisticClass init!"); } private static final StatisticClass SINGLETON = new StatisticClass(); public static StatisticClass getSingleton() { return SINGLETON; } private StatisticClass() { }}// Demo.javapublic class Demo { public static void main(String[] args) { // 注释一 // StatisticClass statisticClass; // 注释二 //StatisticClass.getSingleton(); }}
来一道面试题,问在不打开注释一和注释二,只打开注释一,只打开注释二的三种情况下,输出1的结果分别是怎么样的?
如果你看过《深入理解java虚拟机》的话,对这个问题应该并不陌生,答案我就不贴了,简单说就是:我们的静态变量 SINGLETON = new StatisticClass()
应该是在类加载的初始化这步完成的。但是如果没有地方显示的执行 StatisticClass
的方法,那么StatisticClass
是不会被初始化的。
从上面的例子我们就可以理解 slf4j 里那句the next line does the binding
的含义了。
到这里似乎问题就结束了。但是这里又又又有个问题啊,这里他为啥不用接口呢?就 StaticLoggerBinder
这个类,为啥不设计成接口呢?这里明显是搞成接口更合理啊,想到这里,我是不是可以提一个issue,然后改吧改吧,混一波知名项目的PR?
太简单了少年……看最新的分支,这个确实已经设计成接口了,代码也很简单,我就不贴了,大概是下面这么个逻辑:
之前的findClass 也改成了如下代码的 findServiceProviders:
private static List<SLF4JServiceProvider> findServiceProviders() { ServiceLoader<SLF4JServiceProvider> serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); List<SLF4JServiceProvider> providerList = new ArrayList<SLF4JServiceProvider>(); for (SLF4JServiceProvider provider : serviceLoader) { providerList.add(provider); } return providerList; }
奇奇怪怪的 StaticLoggerBinder.getSingleton(); 也改成了 PROVIDER.initialize();
好了,故事到这里就大概结束了,虽然给知名项目提PR的机会没有了,但是好歹是学到了一波知识,这波不亏,不亏不亏~
原文:https://juejin.cn/post/7098185851347140639