欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我我的微信「java_front」一块儿交流学习java
1 日志规约
阿里巴巴开发手册日志规约章节有一条强制规定:应用中不可直接使用日志系统(Log4j、Logback)API,而应依赖使用日志框架SLF4J中的API。使用门面模式的日志框架,有利于维护和各个类的日志处理方式统一:git
import org.slf4j.Logger; import org.slf4j.LoggerFactory; private static final Logger logger = LoggerFactory.getLogger(Abc.class);
咱们在使用日志框架过程当中会发现,日志框架种类不少如slf4j、log4j、logback等等,在引入依赖时很容易混淆。那么这些框架是什么关系、应该如何使用就是本文须要回答的问题。github
2 实例分析
在编写代码以前咱们首先了解slf4j全称,我认为这会对理解这个框架有所帮助:apache
Simple Logging Facade for Java设计模式
全称含义就是Java简单日志门面,咱们知道有一种设计模式称为门面模式,其本质是化零为整,经过一个对象将散落在各处的功能整合在一块儿,这样外部只要经过与这个对象交互,由该对象选择具体实现细节。slf4j就是这样一个门面,应用程序只须要和slf4j进行交互,slf4j选择使用哪个日志框架的具体实现。api

2.1 slf4j-jdk14
(1) 引入依赖微信
<dependencies> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- jdk14 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-jdk14</artifactId> <version>1.7.30</version> </dependency> </deendencies>
(2) 代码实例架构
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogTest { private final static Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { logger.info("info message"); System.out.println("LogTest"); logger.error("error message"); } }
(3) 输出日志app
LogTest
三月 14, 2021 11:39:14 上午
com.my.log.test.jdk14.LogTest main
信息: info message
三月 14, 2021 11:39:14 上午
com.my.log.test.jdk14.LogTest main
严重: error message框架
2.2 slf4j-simple
(1) 引入依赖
<dependencies> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- simple --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-simple</artifactId> <version>1.7.30</version> </dependency></dependencies>
(2) 代码实例
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogTest { private final static Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { logger.info("info message"); System.out.println("LogTest"); logger.error("error message"); }
(3) 输出日志
[main] INFO com.my.log.test.simple.LogTest - info message
LogTest
[main] ERROR com.my.log.test.simple.LogTest - error message
2.3 logback
(1) 引入依赖
<dependencies> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- logback --> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-core</artifactId> <version>1.2.3</version> </dependency> <dependency> <groupId>ch.qos.logback</groupId> <artifactId>logback-classic</artifactId> <version>1.2.3</version> </dependency> </depedencies>
(2) 代码实例
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogTest { private final static Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { logger.info("info message"); System.out.println("LogTest"); logger.error("error message"); } }
(3) 输出日志
11:40:53.406 [main] INFO com.my.log.test.logbck.LogTest - info message
LogTest
11:40:53.410 [main] ERROR com.my.log.test.logbck.LogTest - error message
2.4 slf4j-log4j12
(1) 引入依赖
<dependencies> <!-- slf4j --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.7.30</version> </dependency> <!-- log4j12 --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.7.30</version> </dependency> </dependencies>
(2) 代码实例
import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class LogTest { private final static Logger logger = LoggerFactory.getLogger(LogTest.class); public static void main(String[] args) { logger.info("info message"); System.out.println("LogTest"); logger.error("error message"); } }
(3) 日志配置
<log4j:configuration xmlns:log4j='http://jakarta.apache.org/log4j/'> <appender name="myConsoleAppender" class="org.apache.log4j.ConsoleAppender"> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="[%d{dd HH:mm:ss,SSS\} %-5p] [%t] %c{2\} - %m%n" /> </layout> <!--过滤器设置输出级别 --> <filter class="org.apache.log4j.varia.LevelRangeFilter"> <param name="levelMin" value="debug" /> <param name="levelMax" value="error" /> <param name="AcceptOnMatch" value="true" /> </filter> </appender> <root> <priority value="debug" /> <appender-ref ref="myConsoleAppender" /> </root> </log4j:configuration>
(4) 输出日志
[14 11:41:39,198 INFO ] [main] log4j.LogTest - info message
LogTest
[14 11:41:39,201 ERROR] [main] log4j.LogTest - error message
3 源码分析
咱们发现上述实例中Java代码并无变化,只是将引用具体日志框架实现进行了替换,例如依赖从simple替换为log4j,具体日志服务实现就替换成了log4j,这究竟是怎么实现的?咱们经过阅读源码回答这个问题。
3.1 阅读准备
(1) 源码地址
目前最新版本2.0.0-alpha2-SNAPSHOT
https://github.com/qos-ch/slf4j
(2) 项目结构
咱们从项目结构能够看出一些信息:门面是api模块,具体实现包括jdk1四、log4j十二、simple模块,须要注意logback是同一个做者的另外一个项目不在本项目。

(3) 阅读入口
package org.slf4j; public class NoBindingTest { public void testLogger() { Logger logger = LoggerFactory.getLogger(NoBindingTest.class); logger.debug("hello" + diff); assertTrue(logger instanceof NOPLogger); } }
3.2 源码分析
LoggerFactory.getLogger
public final class LoggerFactory { public static Logger getLogger(Class<?> clazz) { Logger logger = getLogger(clazz.getName()); if (DETECT_LOGGER_NAME_MISMATCH) { Class<?> autoComputedCallingClass = Util.getCallingClass(); if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), autoComputedCallingClass.getName())); Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); } } return logger; } }
getLogger(clazz.getName())
public final class LoggerFactory { public static Logger getLogger(String name) { ILoggerFactory iLoggerFactory = getILoggerFactory(); return iLoggerFactory.getLogger(name); } }
getILoggerFactory()
public final class LoggerFactory { public static ILoggerFactory getILoggerFactory() { return getProvider().getLoggerFactory(); } }
getProvider()
public final class LoggerFactory { static SLF4JServiceProvider getProvider() { if (INITIALIZATION_STATE == UNINITIALIZED) { synchronized (LoggerFactory.class) { if (INITIALIZATION_STATE == UNINITIALIZED) { INITIALIZATION_STATE = ONGOING_INITIALIZATION; performInitialization(); } } } switch (INITIALIZATION_STATE) { case SUCCESSFUL_INITIALIZATION: return PROVIDER; case NOP_FALLBACK_INITIALIZATION: return NOP_FALLBACK_FACTORY; case FAILED_INITIALIZATION: throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); case ONGOING_INITIALIZATION: return SUBST_PROVIDER; } throw new IllegalStateException("Unreachable code"); } }
performInitialization()
public final class LoggerFactory { private final static void performInitialization() { bind(); if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { versionSanityCheck(); } } }
bind()
public final class LoggerFactory { private final static void bind() { try { // 核心代码 List<SLF4JServiceProvider> providersList = findServiceProviders(); reportMultipleBindingAmbiguity(providersList); if (providersList != null && !providersList.isEmpty()) { PROVIDER = providersList.get(0); PROVIDER.initialize(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(providersList); } // 省略代码 } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } } }
findServiceProviders()
这是加载具体日志实现的核心方法,使用SPI机制加载全部SLF4JServiceProvider实现类:
public final class LoggerFactory { 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; } }
SPI(Service Provider Interface)是一种服务发现机制,本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件加载实现类,这样能够在运行时动态为接口替换实现类,经过SPI机制能够为程序提供拓展功能。本文以log4j为例说明使用SPI功能的三个步骤:
(a) 实现接口
public class Log4j12ServiceProvider implements SLF4JServiceProvider
(b) 配置文件
文件位置:
src/main/resources/META-INF/services/
文件名称:
org.slf4j.spi.SLF4JServiceProvider
文件内容:
org.slf4j.log4j12.Log4j12ServiceProvider
(c) 服务加载
public final class LoggerFactory { 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; } }
只要各类日志实现框架按照SPI约定进行代码编写和配置文件声明,便可以被LoggerFactory加载,slf4j会获取第一个做为实现。
public final class LoggerFactory { private final static void bind() { try { // 使用SPI机制加载具体日志实现 List<SLF4JServiceProvider> providersList = findServiceProviders(); reportMultipleBindingAmbiguity(providersList); if (providersList != null && !providersList.isEmpty()) { // 获取第一个实现 PROVIDER = providersList.get(0); PROVIDER.initialize(); INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; reportActualBinding(providersList); } // 省略代码 } catch (Exception e) { failedBinding(e); throw new IllegalStateException("Unexpected initialization failure", e); } } }
分析到这里咱们的问题应该能够获得解答:假设咱们项目只引入了slf4j和log4j,至关于只有log4j这一个具体实现,那么本项目就会使用log4j框架。若是将log4j依赖换为logback,那么项目在不改动代码的状况下会使用logback框架。
4 文章总结
本文咱们从阿里开发手册日志规约出发,首先分析了如何使用不一样的日志框架,而后咱们从问题出发(不修改代码便可替换具体日志框架)进行slf4j源码阅读,从源码中咱们知道实现核心是SPI机制,这个机制能够动态加载具体日志实现。关于SPI源码分析请参看笔者文章DUBBO系列(1)什么是SPI机制 ,但愿本文对你们有所帮助。
欢迎你们关注公众号「JAVA前线」查看更多精彩分享文章,主要包括源码分析、实际应用、架构思惟、职场分享、产品思考等等,同时欢迎你们加我我的微信「java_front」一块儿交流学习