1. 概述
slf4j(全稱是Simple Loging Facade For Java)是一個為Java程序提供日志輸出的統(tǒng)一接口,并不是一個具體的日志實現(xiàn)方案,就好像我們經(jīng)常使用的JDBC一樣,只是一種規(guī)則而已。因此單獨的slf4j是不能工作的,它必須搭配其他具體的日志實現(xiàn)方案,比如apache的org.apache.log4j.Logger,jdk自帶的java.util.logging.Logger等等。
其中對與jar包:
slf4j-log4j12-x.x.x.jar是使用org.apache.log4j.Logger提供的驅(qū)動
slf4j-jdk14-x.x.x.jar是使用java.util.logging提供的驅(qū)動
slf4j-simple-x.x.x.jar直接綁定System.err
slf4j-jcl-x.x.x.jar是使用commons-logging提供的驅(qū)動
logback-classic-x.x.x.jar是使用logback提供的驅(qū)動
2. slf4j優(yōu)勢
1.與客戶端很好的解耦
比如:我們發(fā)現(xiàn)了一位大牛開發(fā)了一個非常好而且又剛好能夠滿足自己需求的類庫,類庫里使用了apache的org.apache.log4j.Logger,然而你自己的程序在開發(fā)的時候使用的是jdk自帶的java.util.logging.Logger,那么現(xiàn)在憂傷的問題來了:如果你想要使用,你是不是需要同時支持log4j和jdk兩種日志系統(tǒng)?這樣的話,你就需要添加兩個實現(xiàn)同樣功能的jar包并且維護兩套日子配置,你是不是需要耗費更多的精力來進行維護?此時寶寶心里苦,寶寶不說。
2.節(jié)省內(nèi)存
log4j這些傳統(tǒng)的日志系統(tǒng)里面并沒有占位符的概念,當我們需要打印信息的時候,我們需要如下方式進行使用。
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ù)器出錯啦.";
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)
第一個參數(shù)是要輸出的信息,假設(shè)我們要輸出的是一個字符串,并且字符串中包含變量,則Objectmessage參數(shù)就必須使用字符串相加操作,就比如上面測試代碼的14行一樣。姑且不說字符串相加是一個比較消耗性能的操作,字符串是一個不可變對象,一旦創(chuàng)建就不能被修改,創(chuàng)建的字符串會保存在String池中,占用內(nèi)存。更糟糕的是如果配置文件中配置的日志級別是ERROR的話,這行info日志根本不會輸出,則相加得到的字符串對象是一個非必須對象,白白浪費了內(nèi)存空間。這時候有人會說了,那我可以這樣寫?。?/p>
1 package hafiz.zhang;
2
3 import org.apache.log4j.Logger;
4
5 /**
6 * @author geekjc
7 * @date 19/10/15 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ù)器出錯啦.";
14 if (LOGGER.isInfoEnabled()) {
15 LOGGER.info("Error message is: " + message);
16 }
17 }
18 }
這樣不就解決了白白浪費內(nèi)存的問題了嗎?沒錯,這是一個變通方案,但是這樣的代碼太繁瑣,不直觀!
下面再來看看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ù)器出錯啦.";
16 LOGGER.info("Error message is: {}", message);
17 }
18 }
看到?jīng)]有,打日志的時候使用了{}占位符,這樣就不會有字符串拼接操作,減少了無用String對象的數(shù)量,節(jié)省了內(nèi)存。并且記住,在生產(chǎn)最終日志信息的字符串之前,這個方法會檢查一個特定的日志級別是不是打開了,這不僅降低了內(nèi)存消耗而且預先降低了CPU去處理字符串連接命令的時間。這里是使用SLF4J日志方法的代碼,來自于slf4j-log4j12-1.6.1.jar中的Log4j的適配器類Log4jLoggerAdapter。
3. slf4j的使用方法以及實現(xiàn)原理
上面我們提到了slf4j是不能夠獨立工作的,要想使用我們必須帶上其他的具體日志實現(xiàn)方案,下面我們就以log4j為例進行使用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ù)博客中我會介紹)
注:如果項目是maven項目,則前三步就變成一步,在pom.xml文件中添加以下依賴。(如果沒有更高版本的slf4j-api和log4j要求,則只添加第一條依賴就可以,因為slf4j-log4j12依賴會包含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的接口類,使用在程序代碼中,這個包提供了一個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()首先獲取一個ILoggerFactory接口,然后使用該接口獲取具體的Logger。獲取ILoggerFactory的時候用到了一個StaticLoggerBinder類,仔細研究我們會發(fā)現(xiàn)StaticLoggerBinder這個類并不是slf4j-api這個包中的類,而是slf4j-log4j包中的類,這個類就是一個中間類,它用來將抽象的slf4j變成具體的log4j,也就是說具體要使用什么樣的日志實現(xiàn)方案,就得靠這個StaticLoggerBinder類。 再看看slf4j-log4j包種的這個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其實是一個org.slf4j.impl.Log4jLoggerFactory,這個類的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,也就是我們需要的具體的日志實現(xiàn)方案的Logger類。就這樣,整個綁定過程就完成了。