2024-05-25
班门弄斧
00
请注意,本文编写于 105 天前,最后修改于 105 天前,其中某些信息可能已经过时。

目录

导语:
Slf4j和Logback的关系
确定是否使用了Logback相关的类
深入分析
INITIALIZATION_STATE
bind()
晕了
findPossibleStaticLoggerBinderPathSet()
总结1
重点:StaticLoggerBinder.getSingleton();
总结2
猜想
赞助请求V3

导语:

  • private static final Logger logger = LoggerFactory.getLogger(ClassName.class);
  • 对于使用SpringbootJava开发的人员来说,当你使用Slf4j+Logback的日志框架时,这行代码一定不陌生。细心的人会发现,Logger 类和LoggerFactory类都是slf4j的,那么Logback有什么用呢?我也有这个疑问,所以我抽空看了一下源码,发现了他是如何使用Logback的。
  • 第一次写源码相关的文章,有不对的地方欢迎批评指正。

Slf4j和Logback的关系

通过百度可以知道。。。

  • Slf4j是一种日志框架接口设计,是没有具体的业务实现的,想要使用Slf4j记录日志:1、自己实现Slf4j相关接口;2、直接使用实现了Slf4j的相关日志框架,如Log4jLogback
  • LogbackSlf4j原生实现的日志框架。由于前段时间Log4j接连被爆出多个高危漏洞,让使用Logback的人变多了。

确定是否使用了Logback相关的类

首先,我们要确定记录日志时候,是否使用了Logback相关的实现类。

  1. 找到项目中任意一个 LoggerFactory.getLogger(ClassName.class);这样的代码,点进 getLogger();方法内部,会发现只有两行代码,我们在return那里打个断点(代码中用【断点】表示该行打了断点)。
java
public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); 【断点】return iLoggerFactory.getLogger(name); }
  1. 然后启动项目,等到进入断点。进入断点后,看一下ILoggerFactory 类,他已经是Logback中的一个实现类ch.qos.logback.classic.LoggerContext,如下图:

alt

alt

  1. 第一个断点我们就能确定,我们系统中确实用到了Logback的相关实现,但是不是说LogbackSlf4j的实现类吗,那么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"); }
  1. 通过第3不得断点,单步执行,我们会发现如下两件事:
    • 第一个if进去了(应该是系统首次启动运行才会进入,至于为啥往下看你应该能明白)
    • switch--case中,进入了第一个case并成功返回
  • 后边我们重点要搞明白这两点。

深入分析

经过以上4步的初步看源码,我们发现了两件事,简称4-14-2。 之所以在这令起一个标题叫深入分析,是因为经过分析这两件事,就可以搞明白【Slf4j是如何引用Logback的类的?】这个问题

INITIALIZATION_STATE

从字面意思,咱们能猜到这个变量的含义是【初始化状态】,可以看做一个状态码 首先,咱们看看INITIALIZATION_STATE 值得变化过程

  • 初始值:INITIALIZATION_STATE就是UNINITIALIZED,声明时就初始化,表示【未初始化】
java
static volatile int INITIALIZATION_STATE = UNINITIALIZED;
  • 正在初始化:仔细看上边看第3步贴出的代码中,if中有一行代码如下
java
INITIALIZATION_STATE = ONGOING_INITIALIZATION;
  • 初始化成功:仍然是第3步的代码,我们看到INITIALIZATION_STATE的值,在到switch--case中时,已经变成了SUCCESSFUL_INITIALIZATION,所以才进入第一个case。那么为什么会变成SUCCESSFUL_INITIALIZATION就是需要继续深入探究的事情。

bind()

还是回到第3步,有一个方法是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()

先进入方法内部打断点,看到如下信息,重要:

alt

alt

总结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()方法中第一个截图时的那个类:

alt

这时候,仔细看你会发现,这时已经进入Logback内部了,如果不仔细,只看包路径,你还以为仍然在Slf4j体(包)内乱撞那,等你回过神来,熟不知已经进入了贤者模式。。。。

猜想

水平有限,且没有过于深究,所以不敢叫结论,只敢叫猜想,如果猜想有错,甚至完全错误,请您狠狠地纠正我!

  1. Slf4j包有他自己写好的类实例化逻辑
  2. Slf4j可能已经写死了一些信息,比如他的实现类的包路径和类名,如org.slf4j.impl.StaticLoggerBinder
  3. 一个项目同时存在多个Slf4jorg.slf4j.impl.StaticLoggerBinder实现类,会发生冲突,冲突原因可能是:Slf4j不知道该让谁在他体(包)内乱撞。。。。

其中第三点猜想经过百度大概率证实了,因为好像项目中同时引用Log4jLogback会发生冲突,而这两个日志工具都是基于Slf4j去实现的。

  • OK,结束!

  • 本文还是有很多地方没有真正搞清楚,想搞清楚的大佬可以自行去看源码学习了解。

赞助请求V3

建站因为热爱,生活需要Money,请屏幕前的大佬动动您发财的小手,点击一次以示鼓励,祝您生活愉快!

PS:就目前的访问量,即便每个访客都点一次广告,收入也不足以支付运营成本。如果看不到广告,可能是网络原因或被拦截了,那就算了吧。再次祝您生活愉快~~

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:DingDangDog

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!