SLF4J:我这可是日志的门面啊
众所周知,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 binding
StaticLoggerBinder.getSingleton();
其他的代码都很好理解,但是这个是个什么鬼……
get 了一个单例,返回值也没用上,这个又是为啥呢,怎么就 dose the binding 了呢?这里就涉及到亿点点的类加载知识了。首先我们看一段Demo代码
// StatisticClass.java
public 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.java
public 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