委派模式——从SLF4J说起

作者:vivo 互联网服务器团队- Xiong yangxin


将某个通用解决方案包装成成熟的工具包,是每一个技术建设工作者必须思考且必须解决的问题。本文从业内流行的既有工具包入手,解析实现思路,沉淀一般方法。为技术建设的初学者提供一些实践思路的参考。尤其是文中提倡的“去中心化”的协作模式,和“关键链路+开发接口”的开发模式,具有一定的实际落地意义。当然本文在行文中,不可避免存在一定主观偏见性,读者可酌情阅读。


一、前言


熟悉JAVA服务器开发的同学应该都使用过日志模块,并且大概率使用过”log4j-over-slf4j”和“slf4j-log4j”这两个包。那么这两个包的区别是什么?为什么会互相引用包含呢?这篇文章会解释下这几个概念的区别。


首先说一下SLF4J


二、从SLF4J开始


SLF4J全称”Simple Logging Facade for Java (SLF4J) “, 它诞生之初的目的,是为了针对不同的log解决方案,提供一套统一的接口适配标准,从而让业务代码无须关心使用到的第三方模块都使用了哪些log方案。


举个例子, Apache Dubbo和RabbitMQ使用到的日志模块便不相同。从某种意义上而言,SLF4J只是一个facade,类似于当年的ODBC(针对不同的数据库厂商而制定的统一接口标准, 下文会涉及到)。而这个facade对应的包名,是 “slf4j-api-xxx.xxx.xxx.jar”。所以,当你应用了”slf4j-api-xxx.jar”的包时,其实只是引入了一个日志接口标准,而并没有引入日志具体实现


2.1、业内实现


SLF4J标准在应用层的核心类,就是两个: org.slf4j.Logger 和 org.slf4j.LoggerFactory。其中,自版本1.6.0后,如果并没有具体的实现,slf4j-api会默认提供一个啥也不干的Logger实现(org.slf4j.helpers.NOPLogger)。


在当前(本稿件于2022-03-01拟制)的市面上,既有的实现SLF4J的方案有以下几种:

委派模式——从SLF4J说起


整体层次如下图:


委派模式——从SLF4J说起

综上而言:以SLF4J-开头的jar包,一般指的是采用某种第三方框架实现的slf4j解决方案。


2.2 工作机制


那么整个SLF4J的工作机制是如何运作的呢,换句话说,系统是如何知道应该使用哪个实现方案的呢?


对于那种不需要适配器的原生实现方式,直接引入对应的包即可。


对于那种需要适配器的委托式实现方式,则需要通过另外的一个渠道来告知SLF4J应该使用哪个实现类: SPI机制。


举个例子,我们看一下slf4j-log4j的包结构:

委派模式——从SLF4J说起

我们先看pom文件,就包含两个依赖:

dependency>groupId>org.slf4jgroupId>artifactId>slf4j-apiartifactId>dependency>
dependency>groupId>log4jgroupId>artifactId>log4jartifactId>dependency>


slf4j-log4j同时引入了slf4j-api和log4j。那么slf4j-log4j本身的作用不言而喻:使用LOG4J的功能,实现SLF4J的接口标准。


整体的接口/类关系路径如下图:

委派模式——从SLF4J说起

但是这仍然没有解决本章节开始提出的问题(程序怎么知道应该用哪个Logger)。


可以从源码入手:(slf4j/slf4j-log4j12 at master · qos-ch/slf4j · GitHub),我们看到了以下关键的文件:

委派模式——从SLF4J说起

也就是说:slf4j-log4j使用了java的SPI机制告知JVM在运行时调用具体哪一个实现类。由于SPI机制暂不属于本文章讨论范围,读者可以去官网获取信息。


读者可以去GitHub – qos-ch/slf4j: Simple Logging Facade for Java看其他的实现方式的适配器是如何工作的。


那么本章开始的问题答案便是:

  1. SLF4J制定一套日志打印流程,然后把核心类抽象出接口给外部去实现;

  2. 适配器使用第三方日志组件实现了这些核心类接口,并采用SPI机制,让JAVA运行时意识到核心接口的具体实现类。