slf4j介绍以及实现原理窥探
一、概述
slf4j(全稱是Simple Loging Facade For Java)是一個(gè)為Java程序提供日志輸出的統(tǒng)一接口,并不是一個(gè)具體的日志實(shí)現(xiàn)方案,就好像我們經(jīng)常使用的JDBC一樣,只是一種規(guī)則而已。因此單獨(dú)的slf4j是不能工作的,它必須搭配其他具體的日志實(shí)現(xiàn)方案,比如apache的org.apache.log4j.Logger,jdk自帶的java.util.logging.Logger等等。
其中對與jar包:
slf4j-log4j12-x.x.x.jar是使用org.apache.log4j.Logger提供的驅(qū)動(dòng)
slf4j-jdk14-x.x.x.jar是使用java.util.logging提供的驅(qū)動(dòng)
slf4j-simple-x.x.x.jar直接綁定System.err
slf4j-jcl-x.x.x.jar是使用commons-logging提供的驅(qū)動(dòng)
logback-classic-x.x.x.jar是使用logback提供的驅(qū)動(dòng)
二、slf4j優(yōu)勢
1.與客戶端很好的解耦
比如:我們發(fā)現(xiàn)了一位大牛開發(fā)了一個(gè)非常好而且又剛好能夠滿足自己需求的類庫,類庫里使用了apache的org.apache.log4j.Logger,然而你自己的程序在開發(fā)的時(shí)候使用的是jdk自帶的java.util.logging.Logger,那么現(xiàn)在憂傷的問題來了:如果你想要使用,你是不是需要同時(shí)支持log4j和jdk兩種日志系統(tǒng)?這樣的話,你就需要添加兩個(gè)實(shí)現(xiàn)同樣功能的jar包并且維護(hù)兩套日子配置,你是不是需要耗費(fèi)更多的精力來進(jìn)行維護(hù)?此時(shí)寶寶心里苦,寶寶不說。
2.節(jié)省內(nèi)存
log4j這些傳統(tǒng)的日志系統(tǒng)里面并沒有占位符的概念,當(dāng)我們需要打印信息的時(shí)候,我們需要如下方式進(jìn)行使用。
1 package com.hafiz.zhang; 2 3 import org.apache.log4j.Logger; 4 5 /** 6 * @author hafiz.zhang 7 * @date 16/5/12 18:01 8 */ 9 public class TestLog4j { 10 private static final Logger LOGGER = Logger.getLogger(TestLog4j.class); 11 12 public static void main(String[] args) { 13 String message = "服務(wù)器出錯(cuò)啦."; 14 LOGGER.info("Error message is : " + message); 15 } 16 }查看源碼,我們發(fā)現(xiàn)了log4j的info函數(shù)有兩種方式可供選擇:
1 public void info(Objectmessage) 2 public void info(Objectmessage, Throwable t)第一個(gè)參數(shù)是要輸出的信息,假設(shè)我們要輸出的是一個(gè)字符串,并且字符串中包含變量,則Objectmessage參數(shù)就必須使用字符串相加操作,就比如上面測試代碼的14行一樣。姑且不說字符串相加是一個(gè)比較消耗性能的操作,字符串是一個(gè)不可變對象,一旦創(chuàng)建就不能被修改,創(chuàng)建的字符串會(huì)保存在String池中,占用內(nèi)存。更糟糕的是如果配置文件中配置的日志級別是ERROR的話,這行info日志根本不會(huì)輸出,則相加得到的字符串對象是一個(gè)非必須對象,白白浪費(fèi)了內(nèi)存空間。這時(shí)候有人會(huì)說了,那我可以這樣寫啊:
1 package hafiz.zhang; 2 3 import org.apache.log4j.Logger; 4 5 /** 6 * @author hafiz.zhang 7 * @date 16/5/12 18:04 8 */ 9 public class TestLog4j { 10 private static final Logger LOGGER = Logger.getLogger(TestLog4j.class); 11 12 public static void main(String[] args) { 13 String message = "服務(wù)器出錯(cuò)啦."; 14 if (LOGGER.isInfoEnabled()) { 15 LOGGER.info("Error message is: " + message); 16 } 17 } 18 }?
這樣不就解決了白白浪費(fèi)內(nèi)存的問題了嗎?沒錯(cuò),這是一個(gè)變通方案,但是這樣的代碼太繁瑣,不直觀!
下面再來看看slf4j的打日志的方式:(爽爆了)
1 package com.hafiz.zhang; 2 3 4 import org.slf4j.Logger; 5 import org.slf4j.LoggerFactory; 6 7 /** 8 * @author hafiz.zhag 9 * @date 15/8/26 21:54 10 */ 11 public class TestLog4j { 12 private static final Logger LOGGER = LoggerFactory.getLogger(TestLog4j.class); 13 14 public static void main(String[] args) { 15 String message = "服務(wù)器出錯(cuò)啦."; 16 LOGGER.info("Error message is: {}", message); 17 } 18 }看到?jīng)]有,打日志的時(shí)候使用了{(lán)}占位符,這樣就不會(huì)有字符串拼接操作,減少了無用String對象的數(shù)量,節(jié)省了內(nèi)存。并且記住,在生產(chǎn)最終日志信息的字符串之前,這個(gè)方法會(huì)檢查一個(gè)特定的日志級別是不是打開了,這不僅降低了內(nèi)存消耗而且預(yù)先降低了CPU去處理字符串連接命令的時(shí)間。這里是使用SLF4J日志方法的代碼,來自于slf4j-log4j12-1.6.1.jar中的Log4j的適配器類Log4jLoggerAdapter。
三、slf4j的使用方法以及實(shí)現(xiàn)原理
上面我們提到了slf4j是不能夠獨(dú)立工作的,要想使用我們必須帶上其他的具體日志實(shí)現(xiàn)方案,下面我們就以log4j為例進(jìn)行使用slf4j,我們需要做的工作如下:(下面的xxx表示jar包具體版本號)
1.將slf4j-api-xxx.jar加入工程classpath中
2.將slf4j-log4jxx-xxx.jar加入工程classpath中
3.將log4j-xxx.jar加入工程classpath中
4.將log4j.properties(log4j.xml)文件加入工程classpath中(與spring繼承 還能使用自定義文件位置的方式指定,后續(xù)博客中我會(huì)介紹)
注:如果項(xiàng)目是maven項(xiàng)目,則前三步就變成一步,在pom.xml文件中添加以下依賴。(如果沒有更高版本的slf4j-api和log4j要求,則只添加第一條依賴就可以,因?yàn)閟lf4j-log4j12依賴會(huì)包含slf4j-api和log4j依賴)
1 <dependency> 2 <groupId>org.slf4j</groupId> 3 <artifactId>slf4j-log4j12</artifactId> 4 <version>1.6.4</version> 5 </dependency> 6 <dependency> 7 <groupId>org.slf4j</groupId> 8 <artifactId>slf4j-api</artifactId> 9 <version>1.6.4</version> 10 </dependency> 11 <dependency> 12 <groupId>log4j</groupId> 13 <artifactId>log4j</artifactId> 14 <version>1.2.16</version> 15 </dependency>slf4j工作原理窺探
首先,slf4j-api作為slf4j的接口類,使用在程序代碼中,這個(gè)包提供了一個(gè)Logger類和LoggerFactory類,Logger類用來打日志,LoggerFactory類用來獲取Logger;slf4j-log4j是連接slf4j和log4j的橋梁,怎么連接的呢?我們看看slf4j的LoggerFactory類的getLogger函數(shù)的源碼:
1 /** 2 * Return a logger named according to the name parameter using the statically 3 * bound {@link ILoggerFactory} instance. 4 * 5 * @param name 6 * The name of the logger. 7 * @return logger 8 */ 9 public static Logger getLogger(String name) { 10 ILoggerFactory iLoggerFactory = getILoggerFactory(); 11 return iLoggerFactory.getLogger(name); 12 } 13 14 /** 15 * Return a logger named corresponding to the class passed as parameter, using 16 * the statically bound {@link ILoggerFactory} instance. 17 * 18 * @param clazz 19 * the returned logger will be named after clazz 20 * @return logger 21 */ 22 public static Logger getLogger(Class clazz) { 23 return getLogger(clazz.getName()); 24 } 25 26 /** 27 * Return the {@link ILoggerFactory} instance in use. 28 * 29 * <p> 30 * ILoggerFactory instance is bound with this class at compile time. 31 * 32 * @return the ILoggerFactory instance in use 33 */ 34 public static ILoggerFactory getILoggerFactory() { 35 if (INITIALIZATION_STATE == UNINITIALIZED) { 36 INITIALIZATION_STATE = ONGOING_INITILIZATION; 37 performInitialization(); 38 39 } 40 switch (INITIALIZATION_STATE) { 41 case SUCCESSFUL_INITILIZATION: 42 return StaticLoggerBinder.getSingleton().getLoggerFactory(); 43 case NOP_FALLBACK_INITILIZATION: 44 return NOP_FALLBACK_FACTORY; 45 case FAILED_INITILIZATION: 46 throw new IllegalStateException(UNSUCCESSFUL_INIT_MSG); 47 case ONGOING_INITILIZATION: 48 // support re-entrant behavior. 49 // See also http://bugzilla.slf4j.org/show_bug.cgi?id=106 50 return TEMP_FACTORY; 51 } 52 throw new IllegalStateException("Unreachable code"); 53 }查找到現(xiàn)在,我們發(fā)現(xiàn)LoggerFactory.getLogger()首先獲取一個(gè)ILoggerFactory接口,然后使用該接口獲取具體的Logger。獲取ILoggerFactory的時(shí)候用到了一個(gè)StaticLoggerBinder類,仔細(xì)研究我們會(huì)發(fā)現(xiàn)StaticLoggerBinder這個(gè)類并不是slf4j-api這個(gè)包中的類,而是slf4j-log4j包中的類,這個(gè)類就是一個(gè)中間類,它用來將抽象的slf4j變成具體的log4j,也就是說具體要使用什么樣的日志實(shí)現(xiàn)方案,就得靠這個(gè)StaticLoggerBinder類。
再看看slf4j-log4j包種的這個(gè)StaticLoggerBinder類創(chuàng)建ILoggerFactory長什么樣子:
1 /** 2 * The ILoggerFactory instance returned by the {@link #getLoggerFactory} 3 * method should always be the same object 4 */ 5 private final ILoggerFactory loggerFactory; 6 7 private StaticLoggerBinder() { 8 loggerFactory = new Log4jLoggerFactory(); 9 try { 10 Level level = Level.TRACE; 11 } catch (NoSuchFieldError nsfe) { 12 Util 13 .report("This version of SLF4J requires log4j version 1.2.12 or later. See also http://www.slf4j.org/codes.html#log4j_version"); 14 } 15 } 16 17 public ILoggerFactory getLoggerFactory() { 18 return loggerFactory; 19 } 20 21 public String getLoggerFactoryClassStr() { 22 return loggerFactoryClassStr; 23 }可以看到slf4j-log4j中的StaticLoggerBinder類創(chuàng)建的ILoggerFactory其實(shí)是一個(gè)org.slf4j.impl.Log4jLoggerFactory,這個(gè)類的getLogger函數(shù)代碼如下:
1 /* 2 * (non-Javadoc) 3 * 4 * @see org.slf4j.ILoggerFactory#getLogger(java.lang.String) 5 */ 6 public Logger getLogger(String name) { 7 Logger slf4jLogger = null; 8 // protect against concurrent access of loggerMap 9 synchronized (this) { 10 slf4jLogger = (Logger) loggerMap.get(name); 11 if (slf4jLogger == null) { 12 org.apache.log4j.Logger log4jLogger; 13 if(name.equalsIgnoreCase(Logger.ROOT_LOGGER_NAME)) { 14 log4jLogger = LogManager.getRootLogger(); 15 } else { 16 log4jLogger = LogManager.getLogger(name); 17 } 18 slf4jLogger = new Log4jLoggerAdapter(log4jLogger); 19 loggerMap.put(name, slf4jLogger); 20 } 21 } 22 return slf4jLogger; 23 }就在其中創(chuàng)建了真正的org.apache.log4j.Logger,也就是我們需要的具體的日志實(shí)現(xiàn)方案的Logger類。就這樣,整個(gè)綁定過程就完成了。
轉(zhuǎn)載于:https://www.cnblogs.com/hafiz/p/5486858.html
與50位技術(shù)專家面對面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的slf4j介绍以及实现原理窥探的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 一个奇怪的sql异常
- 下一篇: Js String转Int(Number