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).