转载请联系JAVA前线公众号。1日志协议《阿里巴巴开发手册》日志协议章节有一个强制性规定:应用程序不能直接使用日志系统(Log4j、Logback)API,而应该依赖日志框架SLF4J中的API。使用门面模式的日志框架,有利于各类日志处理方式的维护和统一:我们在使用日志框架过程中发现,日志框架的种类有很多,比如slf4j、log4j、logback等,在引入依赖的时候很容易混淆。那么这些框架之间到底是什么关系,如何使用,是本文需要解答的问题。2实例分析在写代码之前,我们先了解一下slf4j的全称。我觉得对理解这个框架会有帮助:SimpleLoggingFacadeforJava的全称是Java的简单日志门面的意思。我们知道有一种设计模式叫做门面模式。化部分为整体,将分散在各处的功能通过一个对象进行整合,让外界只需要与这个对象进行交互,具体的实现细节将由对象自行选择。slf4j就是这样一个门面。应用只需要和slf4j交互,slf4j选择使用哪个日志框架的具体实现。2.1slf4j-jdk14(1)导入依赖org.slf4jslf4j-api1.7.30org.slf4jslf4j-jdk141.7.30(2)代码示例");System.out.println("LogTest");logger.error("errormessage");}}(3)输出日志LogTest2021年3月14日11:39:14amcom.my.log.test.jdk14.LogTestmain信息:infomessageMarch14,202111:39:14amcom.my.log.test.jdk14.LogTestmain严重:errormessage2.2slf4j-simple(1)importdependency<依赖关系>org.slf4jslf4j-api1.7.30org.slf4jslf4j-simple1.7.30(2)代码实例importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;publicclassLogTest{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(LogTest.class);publicstaticvoidmain(String[]args){logger.info("infomessage");System.out.println("LogTest");logger.error("errormessage");}}(3)输入日志[main]INFOcom.my.log.test.simple.LogTest-infomessageLogTest[main]ERRORcom.my.log.test.simple.LogTest-errormessage2.3logback(1)引入依赖!--slf4j-->org.slf4jslf4j-api1.7.30ch.qos.logbacklogback-core1.2.3ch.qos.logbacklogback-classic1.2.3(2)代码示例importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;publicclassLogTest{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(LogTest.class);publicstaticvoidmain(String[]args){logger.info("infomessage");System.out.println("LogTest");logger.error("errormessage");}}(3)输出日志11:40:53.406[main]INFOcom.my.log.test.logbck.LogTest-infomessageLogTest11:40:53.410[main]ERRORcom.my。log.test.logbck.LogTest-errormessage2.4slf4j-log4j12(1)导入依赖org.slf4jslf4j-api1.7.30org.slf4jslf4j-log4j121.7.30(2)代码实例importorg.slf4j.Logger;importorg.slf4j.LoggerFactory;publicclassLogTest{privatefinalstaticLoggerlogger=LoggerFactory.getLogger(LogTest.class);publicstaticvoidmain(String[]args){logger.info("infomessage");System.out.println("LogTest");logger.error("errormessage");}}(3)日志配置(4)输出日志[1411:41:39,198INFO][main]log4j.LogTest-infomessageLogTest[1411:41:39,201ERROR][main]log4j.LogTest-errormessage3源码分析我们发现上面例子中的Java代码没有变化,只是替换了对具体日志框架实现的引用。比如依赖从simple替换为log4j,具体的日志服务实现替换为log4j。这是如何实现的?我们阅读源码来回答这个问题3.1阅读准备(一)源码地址最新版本2.0.0-alpha2-SNAPSHOThttps://github.com/qos-ch/slf4j(二)项目结构我们可以看到一些资料从项目结构看:facade是一个api模块,具体实现包括jdk14、log4j12、simple模块。需要注意的是,logback是同一作者的另一个项目,不包含在本项目中。(3)阅读入口packageorg.slf4j;publicclassNoBindingTest{publicvoidtestLogger(){Loggerlogger=LoggerFactory.getLogger(NoBindingTest.class);logger.debug("hello"+diff);assertTrue(loggerinstanceofNOPLogger);}}3.2源代码分析LoggerFactory.getLoggerpublicclassactoryfinal{publicstaticLoggergetLogger(Class>clazz){Loggerlogger=getLogger(clazz.getName());if(DETECT_LOGGER_NAME_MISMATCH){Class>autoComputedCallingClass=Util.getCallingClass();if(autoComputedCallingClass!=null&&nonMatchingClasses(clazz,autoComputedCallingClass)){Util.report(String.format("Detectedloggernamemismatch.Givenname:\"%s\";computedname:\"%s\".,logger.getName(),autoComputedCallingClass.getName()));Util.report("See"+LOGGER_NAME_MISMATCH_URL+"foranexplanation");}}returnlogger;}}getLogger(clazz.getName())publicfinalclassLoggerFactory{publicstaticLoggergetLogger(Stringname){ILoggerFactoryiLoggerFactory=getILoggerFactory();returniLoggerFactory.getLogger(name);}}getILoggerFactory()publicfinalclassLoggerFactory{publicstaticILoggerFactorygetILoggerFactory(){returngetProvider().getLoggerFactory();}}getProvider()publicfinalclassLoggerFactory{staticSLF4JServiceProvidergetProvider(){if(INITIALIZATION_STATE==UNINITIALIZED){同步(LoggerFactory.class){if(INITIALIZATION_STATE==UNINITIALIZED){INITIALIZATION_STATE=ONGOING_INITIALIZATION;performInitialization();}}}switch(INITIALIZATION_STATE){caseSUCCESSFUL_INITIALIZATION:returnPROVIDER;caseNOP_FALLBACK_INITIALIZATION:returnNOP_FALLBACK_FACTORY;caseFAILED_INITIALIZATION:thrownewIllegalStateException(UNSUCCESSFUL_INIT_MSG);caseONGOING_INITIALIZATION:returnSUBST_PROVIDER;}thrownewIllegalStateException("Unreachablecode");}}performInitialization()publicfinalclassLoggerFactory{privatefinalstaticvoidperformInitialization(){bind();if(INITIALIZATION_STATE==SUCCESSFUL_INITIALIZATION){versionSanityCheck();}}}bind()publicfinalclassLoggerFactory{privatefinalstaticvoidbind(){try{//核心代码ListprovidersList=findServiceProviders();reportMultipleBindingAmbiguity(providersList);if(providersList!=null&&!providersList.isEmpty()){PROVIDER=providersList.get(0);PROVIDER.initialize();INITIALIZATION_STATE=SUCCESSFUL_INITIALIZATION;reportActualBinding(providersList);}//省略代码}catch(Exceptione){failedBinding(e);thrownewIllegalStateException("Unexpectedinitializationfailure",e);}}}findServiceProviders()这是加载具体日志实现的核心方法,使用SPI机制加载所有SLF4JServiceProvider实现类:=newArrayList();for(SLF4JServiceProviderprovider:serviceLoader){providerList.add(provider);}重新turnproviderList;}}SPI(ServiceProviderInterface)是一种服务发现机制,本质是在文件中配置接口实现类的全限定名,服务加载器读取配置文件加载实现类,使得接口可以被在运行时动态替换实现类,可以通过SPI机制为程序提供扩展功能。本文以log4j为例,说明使用SPI功能的三个步骤:(a)实现接口publicclassLog4j12ServiceProviderimplementsSLF4JServiceProvider(b)配置文件文件位置:src/main/resources/META-INF/services/文件名:org.slf4j.spi.SLF4JServiceProvider文件内容:org.slf4j.log4j12.Log4j12ServiceProvider(c)服务加载();for(SLF4JServiceProviderprovider:serviceLoader){providerList.add(provider);}returnproviderList;}}各种日志实现框架只要遵循SPI约定进行代码编写和配置文件声明,即可通过LoggerFactory和slf4j将获得第一个作为实现。publicfinalclassLoggerFactory{privatefinalstaticvoidbind(){try{//使用SPI机制加载具体日志实现ListprovidersList=findServiceProviders();reportMultipleBindingAmbiguity(providersList);if(providersList!=null&&!providersList.isEmpty()){//获取第一个实现PROVIDER=providersList.get(0);PROVIDER.initialize();INITIALIZATION_STATE=SUCCESSFUL_INITIALIZATION;reportActualBinding(providersList);}//省略代码}catch(Exceptione){failedBinding(e);thrownewIllegalStateException("Unexpectedinitializationfailure",e);}}}分析到这里应该可以回答我们的问题了:假设我们的项目只引入了slf4j和log4j,相当于只引入了log4j的具体实现,那么这个项目就会使用log4j框架。如果将log4j依赖改成logback,项目将使用logback框架,无需更改代码。4文章小结本文从阿里开发手册的日志规范入手,先分析不同的日志框架如何使用,然后从问题入手(具体的日志框架可以替换,无需修改代码)来阅读slf4j源代码。从源码中我们知道实现的核心是SPI机制,可以通过动态加载特定的日志来实现。SPI源码的分析可以参考作者的文章JDKSPI机制。希望这篇文章对大家有所帮助。