日本综合一区二区|亚洲中文天堂综合|日韩欧美自拍一区|男女精品天堂一区|欧美自拍第6页亚洲成人精品一区|亚洲黄色天堂一区二区成人|超碰91偷拍第一页|日韩av夜夜嗨中文字幕|久久蜜综合视频官网|精美人妻一区二区三区

RELATEED CONSULTING
相關(guān)咨詢
選擇下列產(chǎn)品馬上在線溝通
服務(wù)時(shí)間:8:30-17:00
你可能遇到了下面的問(wèn)題
關(guān)閉右側(cè)工具欄

新聞中心

這里有您想知道的互聯(lián)網(wǎng)營(yíng)銷解決方案
從Slf4j源碼角度分析阿里開發(fā)手冊(cè)日志規(guī)約

本文轉(zhuǎn)載自微信公眾號(hào)「JAVA前線」,作者IT徐胖子 。轉(zhuǎn)載本文請(qǐng)聯(lián)系JAVA前線公眾號(hào)。

 1 日志規(guī)約

《阿里巴巴開發(fā)手冊(cè)》日志規(guī)約章節(jié)有一條強(qiáng)制規(guī)定:應(yīng)用中不可直接使用日志系統(tǒng)(Log4j、Logback)API,而應(yīng)依賴使用日志框架SLF4J中的API。使用門面模式的日志框架,有利于維護(hù)和各個(gè)類的日志處理方式統(tǒng)一:

 
 
 
 
  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3. private static final Logger logger = LoggerFactory.getLogger(Abc.class); 

我們?cè)谑褂萌罩究蚣苓^(guò)程中會(huì)發(fā)現(xiàn),日志框架種類很多如slf4j、log4j、logback等等,在引入依賴時(shí)很容易混淆。那么這些框架是什么關(guān)系、應(yīng)該如何使用就是本文需要回答的問(wèn)題。

2 實(shí)例分析

在編寫代碼之前我們首先了解slf4j全稱,我認(rèn)為這會(huì)對(duì)理解這個(gè)框架有所幫助:

 
 
 
 
  1. Simple Logging Facade for Java 

全稱的含義就是Java簡(jiǎn)單日志門面,我們知道有一種設(shè)計(jì)模式被稱為門面模式,其本質(zhì)是化零為整,通過(guò)一個(gè)對(duì)象將散落在各處的功能整合在一起,這樣外部只要通過(guò)與這個(gè)對(duì)象交互,由該對(duì)象選擇具體實(shí)現(xiàn)細(xì)節(jié)。slf4j就是這樣一個(gè)門面,應(yīng)用程序只需要和slf4j進(jìn)行交互,slf4j選擇使用哪一個(gè)日志框架的具體實(shí)現(xiàn)。

2.1 slf4j-jdk14

(1) 引入依賴

 
 
 
 
  1.  
  2.    
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.    
  10.    
  11.     org.slf4j 
  12.     slf4j-jdk14 
  13.     1.7.30 
  14.    
  15.  

(2) 代碼實(shí)例

 
 
 
 
  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 輸出日志

 
 
 
 
  1. LogTest 
  2. 三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main 
  3. 信息: info message 
  4. 三月 14, 2021 11:39:14 上午 com.my.log.test.jdk14.LogTest main 
  5. 嚴(yán)重: error message 

2.2 slf4j-simple

(1) 引入依賴

 
 
 
 
  1.  
  2.    
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.    
  10.    
  11.     org.slf4j 
  12.     slf4j-simple 
  13.     1.7.30 
  14.    
  15.  

(2) 代碼實(shí)例

 
 
 
 
  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 輸出日志

 
 
 
 
  1. [main] INFO com.my.log.test.simple.LogTest - info message 
  2. LogTest 
  3. [main] ERROR com.my.log.test.simple.LogTest - error message 

2.3 logback

(1) 引入依賴

 
 
 
 
  1.  
  2.    
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.    
  10.    
  11.     ch.qos.logback 
  12.     logback-core 
  13.     1.2.3 
  14.    
  15.    
  16.     ch.qos.logback 
  17.     logback-classic 
  18.     1.2.3 
  19.    
  20.  

(2) 代碼實(shí)例

 
 
 
 
  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 輸出日志

 
 
 
 
  1. 11:40:53.406 [main] INFO com.my.log.test.logbck.LogTest - info message 
  2. LogTest 
  3. 11:40:53.410 [main] ERROR com.my.log.test.logbck.LogTest - error message 

2.4 slf4j-log4j12

(1) 引入依賴

 
 
 
 
  1.  
  2.    
  3.    
  4.     org.slf4j 
  5.     slf4j-api 
  6.     1.7.30 
  7.    
  8.    
  9.    
  10.    
  11.     org.slf4j 
  12.     slf4j-log4j12 
  13.     1.7.30 
  14.    
  15.  

(2) 代碼實(shí)例

 
 
 
 
  1. import org.slf4j.Logger; 
  2. import org.slf4j.LoggerFactory; 
  3.  
  4. public class LogTest { 
  5.     private final static Logger logger = LoggerFactory.getLogger(LogTest.class); 
  6.     public static void main(String[] args) { 
  7.         logger.info("info message"); 
  8.         System.out.println("LogTest"); 
  9.         logger.error("error message"); 
  10.     } 

(3) 日志配置

 
 
 
 
  1.  
  2.    
  3.      
  4.        
  5.      
  6.      
  7.      
  8.        
  9.        
  10.        
  11.      
  12.    
  13.    
  14.      
  15.      
  16.    
  17.  

(4) 輸出日志

 
 
 
 
  1. [14 11:41:39,198 INFO ] [main] log4j.LogTest - info message 
  2. LogTest 
  3. [14 11:41:39,201 ERROR] [main] log4j.LogTest - error message 

3 源碼分析

我們發(fā)現(xiàn)上述實(shí)例中Java代碼并沒(méi)有變化,只是將引用具體日志框架實(shí)現(xiàn)進(jìn)行了替換,例如依賴從simple替換為log4j,具體日志服務(wù)實(shí)現(xiàn)就替換成了log4j,這到底是怎么實(shí)現(xiàn)的?我們通過(guò)閱讀源碼回答這個(gè)問(wèn)題。

3.1 閱讀準(zhǔn)備

(1) 源碼地址

目前最新版本2.0.0-alpha2-SNAPSHOT

 
 
 
 
  1. https://github.com/qos-ch/slf4j 

(2) 項(xiàng)目結(jié)構(gòu)

我們從項(xiàng)目結(jié)構(gòu)可以看出一些信息:門面是api模塊,具體實(shí)現(xiàn)包括jdk14、log4j12、simple模塊,需要注意logback是同一個(gè)作者的另一個(gè)項(xiàng)目不在本項(xiàng)目。

(3) 閱讀入口

 
 
 
 
  1. package org.slf4j; 
  2.  
  3. public class NoBindingTest { 
  4.     public void testLogger() { 
  5.         Logger logger = LoggerFactory.getLogger(NoBindingTest.class); 
  6.         logger.debug("hello" + diff); 
  7.         assertTrue(logger instanceof NOPLogger); 
  8.     } 

3.2 源碼分析

LoggerFactory.getLogger

 
 
 
 
  1. public final class LoggerFactory { 
  2.     public static Logger getLogger(Class clazz) { 
  3.         Logger logger = getLogger(clazz.getName()); 
  4.         if (DETECT_LOGGER_NAME_MISMATCH) { 
  5.             Class autoComputedCallingClass = Util.getCallingClass(); 
  6.             if (autoComputedCallingClass != null && nonMatchingClasses(clazz, autoComputedCallingClass)) { 
  7.                 Util.report(String.format("Detected logger name mismatch. Given name: \"%s\"; computed name: \"%s\".", logger.getName(), 
  8.                                           autoComputedCallingClass.getName())); 
  9.                 Util.report("See " + LOGGER_NAME_MISMATCH_URL + " for an explanation"); 
  10.             } 
  11.         } 
  12.         return logger; 
  13.     } 

getLogger(clazz.getName())

 
 
 
 
  1. public final class LoggerFactory { 
  2.     public static Logger getLogger(String name) { 
  3.         ILoggerFactory iLoggerFactory = getILoggerFactory(); 
  4.         return iLoggerFactory.getLogger(name); 
  5.     } 

getILoggerFactory()

 
 
 
 
  1. public final class LoggerFactory { 
  2.     public static ILoggerFactory getILoggerFactory() { 
  3.         return getProvider().getLoggerFactory(); 
  4.     } 

getProvider()

 
 
 
 
  1. public final class LoggerFactory { 
  2.     static SLF4JServiceProvider getProvider() { 
  3.         if (INITIALIZATION_STATE == UNINITIALIZED) { 
  4.             synchronized (LoggerFactory.class) { 
  5.                 if (INITIALIZATION_STATE == UNINITIALIZED) { 
  6.                     INITIALIZATION_STATE = ONGOING_INITIALIZATION; 
  7.                     performInitialization(); 
  8.                 } 
  9.             } 
  10.         } 
  11.         switch (INITIALIZATION_STATE) { 
  12.         case SUCCESSFUL_INITIALIZATION: 
  13.             return PROVIDER; 
  14.         case NOP_FALLBACK_INITIALIZATION: 
  15.             return NOP_FALLBACK_FACTORY; 
  16.         case FAILED_INITIALIZATION: 
  17.             throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 
  18.         case ONGOING_INITIALIZATION: 
  19.             return SUBST_PROVIDER; 
  20.         } 
  21.         throw new IllegalStateException("Unreachable code"); 
  22.     } 

performInitialization()

 
 
 
 
  1. public final class LoggerFactory { 
  2.     private final static void performInitialization() { 
  3.         bind(); 
  4.         if (INITIALIZATION_STATE == SUCCESSFUL_INITIALIZATION) { 
  5.             versionSanityCheck(); 
  6.         } 
  7.     } 

bind()

 
 
 
 
  1. public final class LoggerFactory { 
  2.     private final static void bind() { 
  3.         try { 
  4.             // 核心代碼 
  5.             List providersList = findServiceProviders(); 
  6.             reportMultipleBindingAmbiguity(providersList); 
  7.             if (providersList != null && !providersList.isEmpty()) { 
  8.              PROVIDER = providersList.get(0); 
  9.              PROVIDER.initialize(); 
  10.              INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 
  11.                 reportActualBinding(providersList); 
  12.             } 
  13.             // 省略代碼 
  14.         } catch (Exception e) { 
  15.             failedBinding(e); 
  16.             throw new IllegalStateException("Unexpected initialization failure", e); 
  17.         } 
  18.     } 

findServiceProviders()

這是加載具體日志實(shí)現(xiàn)的核心方法,使用SPI機(jī)制加載所有SLF4JServiceProvider實(shí)現(xiàn)類:

 
 
 
 
  1. public final class LoggerFactory { 
  2.     private static List findServiceProviders() { 
  3.         ServiceLoader serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); 
  4.         List providerList = new ArrayList(); 
  5.         for (SLF4JServiceProvider provider : serviceLoader) { 
  6.             providerList.add(provider); 
  7.         } 
  8.         return providerList; 
  9.     } 

SPI(Service Provider Interface)是一種服務(wù)發(fā)現(xiàn)機(jī)制,本質(zhì)是將接口實(shí)現(xiàn)類的全限定名配置在文件中,并由服務(wù)加載器讀取配置文件加載實(shí)現(xiàn)類,這樣可以在運(yùn)行時(shí)動(dòng)態(tài)為接口替換實(shí)現(xiàn)類,通過(guò)SPI機(jī)制可以為程序提供拓展功能。本文以log4j為例說(shuō)明使用SPI功能的三個(gè)步驟:

(a) 實(shí)現(xiàn)接口

 
 
 
 
  1. public class Log4j12ServiceProvider implements SLF4JServiceProvider 

(b) 配置文件

 
 
 
 
  1. 文件位置:src/main/resources/META-INF/services/ 
  2. 文件名稱:org.slf4j.spi.SLF4JServiceProvider 
  3. 文件內(nèi)容:org.slf4j.log4j12.Log4j12ServiceProvider 

(c) 服務(wù)加載

 
 
 
 
  1. public final class LoggerFactory { 
  2.     private static List findServiceProviders() { 
  3.         ServiceLoader serviceLoader = ServiceLoader.load(SLF4JServiceProvider.class); 
  4.         List providerList = new ArrayList(); 
  5.         for (SLF4JServiceProvider provider : serviceLoader) { 
  6.             providerList.add(provider); 
  7.         } 
  8.         return providerList; 
  9.     } 

只要各種日志實(shí)現(xiàn)框架按照SPI約定進(jìn)行代碼編寫和配置文件聲明,即可以被LoggerFactory加載,slf4j會(huì)獲取第一個(gè)作為實(shí)現(xiàn)。

 
 
 
 
  1. public final class LoggerFactory { 
  2.     private final static void bind() { 
  3.         try { 
  4.             // 使用SPI機(jī)制加載具體日志實(shí)現(xiàn) 
  5.             List providersList = findServiceProviders(); 
  6.             reportMultipleBindingAmbiguity(providersList); 
  7.             if (providersList != null && !providersList.isEmpty()) { 
  8.                 // 獲取第一個(gè)實(shí)現(xiàn) 
  9.                 PROVIDER = providersList.get(0); 
  10.                 PROVIDER.initialize(); 
  11.                 INITIALIZATION_STATE = SUCCESSFUL_INITIALIZATION; 
  12.                 reportActualBinding(providersList); 
  13.             } 
  14.             // 省略代碼 
  15.         } catch (Exception e) { 
  16.             failedBinding(e); 
  17.             throw new IllegalStateException("Unexpected initialization failure", e); 
  18.         } 
  19.     } 

分析到這里我們的問(wèn)題應(yīng)該可以得到解答:假設(shè)我們項(xiàng)目只引入了slf4j和log4j,相當(dāng)于只有l(wèi)og4j這一個(gè)具體實(shí)現(xiàn),那么本項(xiàng)目就會(huì)使用log4j框架。如果將log4j依賴換為logback,那么項(xiàng)目在不改動(dòng)代碼的情況下會(huì)使用logback框架。

4 文章總結(jié)

本文我們從阿里開發(fā)手冊(cè)日志規(guī)約出發(fā),首先分析了如何使用不同的日志框架,然后我們從問(wèn)題出發(fā)(不修改代碼即可替換具體日志框架)進(jìn)行slf4j源碼閱讀,從源碼中我們知道實(shí)現(xiàn)核心是SPI機(jī)制,這個(gè)機(jī)制可以動(dòng)態(tài)加載具體日志實(shí)現(xiàn)。關(guān)于SPI源碼分析請(qǐng)參看筆者文章JDK SPI機(jī)制,希望本文對(duì)大家有所幫助。


分享標(biāo)題:從Slf4j源碼角度分析阿里開發(fā)手冊(cè)日志規(guī)約
當(dāng)前鏈接:http://www.dlmjj.cn/article/ccdjcoc.html