看源码,搞明白Logback是如何自动生效的?
## 导语: - ``private static final Logger logger = Lo
渲染中...
## 导语:
- ``private static final Logger logger = LoggerFactory.getLogger(ClassName.class);``
- 对于使用``Springboot``的``Java``开发的人员来说,当你使用``Slf4j``+``Logback``的日志框架时,这行代码一定不陌生。细心的人会发现,``Logger ``类和``LoggerFactory``类都是``slf4j``的,那么``Logback``有什么用呢?我也有这个疑问,所以我抽空看了一下源码,发现了他是如何使用``Logback``的。
- 第一次写源码相关的文章,有不对的地方欢迎批评指正。
## Slf4j和Logback的关系
通过百度可以知道。。。
- ``Slf4j``是一种日志框架接口设计,是没有具体的业务实现的,想要使用``Slf4j``记录日志:1、自己实现``Slf4j``相关接口;2、直接使用实现了``Slf4j``的相关日志框架,如``Log4j``何``Logback``。
- ``Logback``是``Slf4j``原生实现的日志框架。由于前段时间``Log4j``接连被爆出多个高危漏洞,让使用``Logback``的人变多了。
<!-- more -->
## 确定是否使用了Logback相关的类
首先,我们要确定记录日志时候,是否使用了Logback相关的实现类。
1. 找到项目中任意一个`` LoggerFactory.getLogger(ClassName.class);``这样的代码,点进`` getLogger();``方法内部,会发现只有两行代码,我们在``return``那里打个断点(代码中用``【断点】``表示该行打了断点)。
```java
public static Logger getLogger(String name) {
ILoggerFactory iLoggerFactory = getILoggerFactory();
【断点】return iLoggerFactory.getLogger(name);
}
```
2. 然后启动项目,等到进入断点。进入断点后,看一下``ILoggerFactory ``类,他已经是``Logback``中的一个实现类``ch.qos.logback.classic.LoggerContext``,如下图:


3. <span id="step3"></span>第一个断点我们就能确定,我们系统中确实用到了``Logback``的相关实现,但是不是说``Logback``是``Slf4j``的实现类吗,那么``Slf4j``是如何引用``Logback``的类的?下面继续一探究竟。接下来点进第1步两行代码中,``getILoggerFactory()``,看到如下代码,刚看到可能有点蒙蔽,不要怕,咱们就在第一行打断点,看看他到底怎么执行的就完事了。
```java
public static ILoggerFactory getILoggerFactory() {
【断点】if (INITIALIZATION_STATE == UNINITIALIZED) {
synchronized (LoggerFactory.class) {
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://jira.qos.ch/browse/SLF4J-97
return SUBST_FACTORY;
}
throw new IllegalStateException("Unreachable code");
}
```
4. 通过第3不得断点,单步执行,我们会发现如下两件事:
- 第一个``if``进去了(应该是系统首次启动运行才会进入,至于为啥往下看你应该能明白)
- 在``switch--case``中,进入了第一个``case``并成功返回
- 后边我们重点要搞明白这两点。
## 深入分析
> 经过以上4步的初步看源码,我们发现了两件事,简称``4-1``和``4-2``。
> 之所以在这令起一个标题叫``深入分析``,是因为经过分析这两件事,就可以搞明白【``Slf4j``是如何引用``Logback``的类的?】这个问题
### INITIALIZATION_STATE
> 从字面意思,咱们能猜到这个变量的含义是【初始化状态】,可以看做一个状态码
> 首先,咱们看看``INITIALIZATION_STATE ``值得变化过程
- 初始值:``INITIALIZATION_STATE``就是``UNINITIALIZED``,声明时就初始化,表示【未初始化】
```java
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
```
- 正在初始化:仔细看上边看[第3步](#step3)贴出的代码中,``if``中有一行代码如下
```java
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
```
- 初始化成功:仍然是[第3步](#step3)的代码,我们看到``INITIALIZATION_STATE``的值,在到``switch--case``中时,已经变成了``SUCCESSFUL_INITIALIZATION``,所以才进入第一个``case``。那么为什么会变成``SUCCESSFUL_INITIALIZATION``就是需要继续深入探究的事情。
### bind()
> 还是回到[第3步](#step3),有一个方法是``performInitialization()``,之所以我们这个标题叫做``bind()``,是因为在``performInitialization()``方法中,最主要的就是``bind()``。
> 两个方法内部代码如下:
- performInitialization()
```java
private final static void performInitialization() {
bind();
if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) {
versionSanityCheck();
}
}
```
- bind()
```java
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()) {
staticLoggerBinderPathSet = findPossibleStaticLoggerBinderPathSet();
reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);
}
// the next line does the binding
StaticLoggerBinder.getSingleton();
INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION;
reportActualBinding(staticLoggerBinderPathSet);
} 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);
} finally {
postBindCleanUp();
}
}
```
### 晕了
> ok,一般情况下,看到``bind()``里的代码,尤其是``findPossibleStaticLoggerBinderPathSet()``、``reportMultipleBindingAmbiguity()``两个方法内部的代码,已经是晕了,不要慌,咱们慢慢捋,就算他是弹簧,咱也要把它捋直了。
> 接下来就不那么多废话了,提高下效率,按顺序来
### findPossibleStaticLoggerBinderPathSet()
先进入方法内部打断点,看到如下信息,重要:


### 总结1
根据上面的两个截图,经过了一些不可描述的过程(由于我的水平有限,暂时理解不了),虽然我不知道细节和原理,但是在整体上我有一点点理解,通俗的总结以下两点:
1. 先找到``Slf4j``的加载器
2. 找到本地使用的,实现了``Slf4j``接口的,jar包目录
``reportMultipleBindingAmbiguity(staticLoggerBinderPathSet);``这行代码咱们就不看了,经过断点发现,该方法内部的逻辑就没走(没进到内部的``if``中)
### 重点:StaticLoggerBinder.getSingleton();
> ``bind()``方法内部,执行了``StaticLoggerBinder.getSingleton();``该方法,由于这块一些代码互相调用,且都是写在一起的,我就一起把这一套代码贴出来
```java
/**
* The unique instance of this class.
*/
private static StaticLoggerBinder SINGLETON = new StaticLoggerBinder();
private static Object KEY = new Object();
static {
SINGLETON.init();
}
private boolean initialized = false;
private LoggerContext defaultLoggerContext = new LoggerContext();
private final ContextSelectorStaticBinder contextSelectorBinder = ContextSelectorStaticBinder.getSingleton();
private StaticLoggerBinder() {
defaultLoggerContext.setName(CoreConstants.DEFAULT_CONTEXT_NAME);
}
public static StaticLoggerBinder getSingleton() {
return SINGLETON;
}
```
### 总结2
看到上班``getSingleton()``中直接返回了一个类实例,而这个类好巧不巧,就是``findPossibleStaticLoggerBinderPathSet()``方法中第一个截图时的那个类:

这时候,仔细看你会发现,这时已经进入``Logback``内部了,如果不仔细,只看包路径,你还以为仍然在``Slf4j``体(包)内乱撞那,等你回过神来,熟不知已经进入了贤者模式。。。。
## 猜想
> ``水平有限,且没有过于深究,所以不敢叫结论,只敢叫猜想,如果猜想有错,甚至完全错误,请您狠狠地纠正我!``
1. ``Slf4j``包有他自己写好的类实例化逻辑
2. ``Slf4j``可能已经写死了一些信息,比如他的实现类的包路径和类名,如``org.slf4j.impl.StaticLoggerBinder``
3. 一个项目同时存在多个``Slf4j``的``org.slf4j.impl.StaticLoggerBinder``实现类,会发生冲突,冲突原因可能是:``Slf4j``不知道该让谁在他体(包)内乱撞。。。。
其中第三点猜想经过百度大概率证实了,因为好像项目中同时引用``Log4j``和``Logback``会发生冲突,而这两个日志工具都是基于``Slf4j``去实现的。
- OK,结束!
- 本文还是有很多地方没有真正搞清楚,想搞清楚的大佬可以自行去看源码学习了解。
## 赞助请求V3
**建站因为热爱,生活需要Money,请屏幕前的大佬动动您发财的小手,点击一次以示鼓励,祝您生活愉快!**
<!-- 文章内嵌广告位 -->
<div class="article-ads"></div>
> PS:就目前的访问量,即便每个访客都点一次广告,收入也不足以支付运营成本。`如果看不到广告,可能是网络原因或被拦截了,那就算了吧。再次祝您生活愉快~~`END
评论
登录后查看和发表评论
前往登录