Commons
Logging庫包含了commons-logging-api.jar和commons-logging.jar.
查看這兩個文件的內容, 你會發現它們的差別并不大: org.apache.commons.logging包下的內容是相同的,差異的地方僅在org.apache.commons.logging.impl包. 這兩個包內容的描述可以參考官方網站:
http://commons.apache.org/logging/guide.html#commons-logging.jar
簡單地說, commons-logging-api.jar僅包含定義接口的類, 而commons-logging.jar是其的一種實現。由于commons-logging.jar實現類較少且簡單, 為了方便部署, 于是將commons-logging-api.jar中對于接口的定義也一并打包在commons-logging.jar中. 這樣一來, 對于要采用commons-logging為實現類的應用程序,只要讓commons-logging.jar在classpath中就好。類似地, 如果不計劃使用commons-logging為實現(但采用commons-logging-api的另一種實現),則需要將commons-logging-api.jar放到classpath, commons-logging.jar則是不需要(也不能添加)的.
ps:
commons-logging-api接口又被稱為JCL(Jakarta Commons Logging). 實現JCL接口的庫除了Commons Logging自己外, 另一個很有名的就是SLF4J(http://www.slf4j.org/)了.
SLF4J的官方網站指明SLF4J擁有絕佳的性能. http://www.slf4j.org/faq.html#logging_performance. SLF4J提供的API甚至達到其它API性能的30倍:
… the
second form will outperform the first form by a factor of at least 30, in case
of a disabled logging statement …
性能的提升關鍵在于減少將對象(特別是非常復雜的對象且其toString()方法實現得比較耗時)轉換為String的開銷. JCL定義的log方法(trace, debug, info, warn和error)只包含一個Object類型的參數. 這樣, 執行這些方法之前就必須將所有要log的東西轉化為一個Object類型的對象(通常是String), 例如:
logger.debug("The
new entry is "+entry+".");
如果entry的轉換比較復雜, 就會比較耗時.
特別是如果在上述例子中, debug level的log被禁用了, 這種轉換的開銷則是完全沒有必要的(白白浪費CPU和內存). 當然, 加上對相應log level的啟用/禁用的判斷,然后再調用log方法, 也是可以避免這些開銷的. 例如:
if (logger.isDebugEnabled()) {
logger.debug("The new entry is
"+entry+".");
}
在這種情況下, 轉換的開銷就被避免了.在上述代碼中,
對于log level的啟用/禁用的判斷實際上是執行了兩次: 一次是在if語句中的顯式調用, 另一次則是log方法內部在做真正的log動作之前進行了調用. 對于log level的啟用/禁用的判斷產生的開銷相當小, 很多時候是可以忽略不計的. 但是,
SLF4J比較計較, 它認為仍然有一次調用是沒有必要的. SLF4J解決這個問題的方法也很簡單: 利用與String.format()類似的方法, 需要的參數無需轉換, 而是作為函數的參數傳入, 函數在實現時, 先判斷log level是否被啟用. 如果沒有, 直接返回. 否則, 轉換, 然后log. 從這種實現思路可以知道, SLF4J需要定義另一套API, 在JCL所有log方法的基礎上增加一個(或多個)用來表示傳入對象的參數。例如:
public void
debug(String format, Object arg) {
if (logger.isLoggable(Level.FINE)) { //判斷log level的啟用情況
String msgStr = MessageFormatter.format(format,
arg); //format,轉化
log(SELF, Level.FINE, msgStr, null); //log動作
}
}
這下, 調用這樣的API自然就能在性能上勝人一籌了:
logger.debug("The
entry is {}.", entry); //直接調用, 無需判斷log level是否啟用. 簡潔,高效
需要說明的是:
l log方法的第一個參數(format), 是SLF4J自定義的,
不是String.format()所支持的那些. SLF4J的format更簡單.
l SLF4J本身也是接口和實現分離的. 不是SLF4J的所有實現能具有性能上的優勢, 比如SLF4J的simple實現版本就完全沒有性能上的優化, 所以選擇SLF4J的實現前, 需要考察一下.
Spring從一開始就依賴JCL(同時出于后向兼容的考慮, 在可以預見的未來, 也會持續依賴JCL), 而不是SLF4J.
在Spring的官方文檔中可以看到這樣的敘述:
If we could
turn back the clock and start Spring now as a new project it would use a
different logging dependency. The first choice would probably be the Simple
Logging Facade for Java (SLF4J), which is also used by a lot of other tools
that people use with Spring inside their applications.
可見, Spring的開發人員也意識到JCL的默認實現沒有SLF4J優秀. 幸運的是,盡管Spring對JCL默認實現的依賴如此嚴重, 在Spring中將其替換也不是不可能. 事實上,
Spring官方就給出了一種方法, 這種方法本質上是將SLF4J作為JCL的一種實現:
l 需要保留commons-logging-api.jar.
很多Spring Module都依賴JCL. 同時排除commons-logging.jar, 不要使用這種實現.
l 添加jcl-over-slf4j.jar.
這作為JCL和SLF4J之間的bridge, 實際上就是采用SLF4J來實現JCL.
l 添加slf4j-api.jar.
這是SLF4J的接口包.
l 添加SLF4J的一種實現(以及這種實現需要的其他包). 比如計劃使用log4j, 則添加slf4j-log4j12.jar(以及log4j.jar).