上一篇文章我们使用SpringMVC搭建了一个简单WEB项目 - HelloWorld,注意到我们在pom.xml中仅仅加了一个依赖(dependency):
?
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.0.RELEASE</version> </dependency> </dependencies>
?
?所有被依赖加载进来的jar包有:
?
spring-webmvc-3.1.0.RELEASE.jar spring-asm-3.1.0.RELEASE.jar spring-beans-3.1.0.RELEASE.jar spring-context-3.1.0.RELEASE.jar spring-aop-3.1.0.RELEASE.jar spring-context-support-3.1.0.RELEASE.jar spring-core-3.1.0.RELEASE.jar commons-logging-1.1.1.jar spring-expression-3.1.0.RELEASE.jar spring-web-3.1.0.RELEASE.jar aopalliance-1.0.RELEASE.jar
?
在上篇文章最后我们分析了各个spring模块的依赖关系,注意到commons-logging-1.1.1.jar被依赖进来了,它是由spring-core模块依赖的,spring-core是spring的核心模块,任何其他模块都要加载它,所以common-logging理所当然的加载进来了。
下面开始正题,日志,通常我们谈到Java中的日志,首先想到的就是Log4j,对不对,这货知名度之广,生命力之强,在Java开源软件中也是非常罕见的。但其实在Log4j诞生之前,已经有一个日志框架出现在Java开源社区,它就是commons-logging,Rod Jonson先生在设计Spring框架时还没有log4j,所以他只有选择commons-logging,我们看一下它的类结构,非常简单:
?
org.apache.commons.logging |- Log.class |- LogConfigurationException.class |- LogFactory.class |- LogSource.class org.apache.commons.logging.impl |- 一些具体的实现类
?
?所以我们在Spring源代码中可以看到大量的类似于如下的代码片段:
org.springframework.core.env.AbstractEnvironment.java
?
package org.springframework.core.env; ... import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; ... public abstract class AbstractEnvironment implements ConfigurableEnvironment { ... protected final Log logger = LogFactory.getLog(getClass()); ... public void addActiveProfile(String profile) { logger.debug(String.format("Activating profile '%s'", profile)); ... } }
?
整个Spring框架都是这样使用commons-logging的。
问题来了,既然Spring框架已经原生支持commons-logging了,为什么我们在实际开发中很少使用它呢?以及如果我想使用其他日志框架,Spring如何配置呢?
下面我先翻译spring官方手册中有关日志的一段内容,看看Spring团队是如何解释的:
?
1.3.2 日志
日志是Spring中非常重要的依赖,因为 a)它是唯一的强制性的外部依赖,b)大家都喜欢从他们使用的工具中看到一些输出,c)Spring集成了非常多的第三方工具,这些工具也会选择一种日志依赖。应用开发者的目标之一经常是想把日志统一配置在整个应用的一个中央地方,包括所有外部组件。因为有如此众多的日志框架可供选择,所以这个目标比它看起来要困难得多。
Spring中强制性的日志依赖为Jakarta Commons Logging API(缩写JCL)。我们在Spring框架中编译了JCL,同时我们也使JCL的Log对象对于继承Spring框架的所有类都是可见的。对使用者来说很重要的一点是,Spring的所有版本都使用相同的日志库:这样就让迁移就变得很简单了,因为向后兼容性甚至在继承于Spring的应用中都得到了保护(自己意会啊,我是字面翻译 译者)。我们这么做的目的是使Spring中的任何模块都明确地依赖于commons-logging(JCL的官方实现),然后使所有其他的模块在编译时(at compile time)依赖于它。举个例子,如果你正在使用Maven,想知道你从哪里捡起(pick up)的common-logging依赖,我们说它来自于Spring,更明确的就是来自于核心模块spring-core。
commons-logging很好的一点就是你不再需要任何其他的东西来使你的应用工作起来。它有一个运行时发现(runtime discovery)算法来寻找其他日志框架,通过广为人知的classpath路径,和使用任何一个它合适的路径(或者你可以告诉它你需要的哪个路径)。如果以上说的都没有的话,你会很高兴的看到它实际使用了来自JDK(java.util.logging或者缩写为JUL)的日志。(即如果我们只加载了commons-logging,它实际上是通过JDK的类库里面的具体日志类来做日志输出的,JDK的日志类在java.util.logging包下,JDK类库真是事无巨细啊,不是吗? 译者)你应该发现你的Spring应用在大多数情况下都正常工作着,并很愉快地记录日志到控制台上。这是很重要的。
?
1.3.2.1 不要使用Commons Logging!
不幸的是,commons-logging虽然对于终端用户来说非常方便的,但这货的运行时发现算法是有问题的!如果我们能把时间倒流到Spring框架刚刚创建那会,我们会使用一个不同的日志依赖。第一选择也许会是Java的Simple Logging Facade(缩写SLF4J),这东东同样被很多其他工具使用着,这些其他工具和Spring一起工作在他们的应用里。
将commons-logging关掉非常简单:只要确保它不出现在运行时的classpath路径下即可。在Maven的术语中,你可以排除(exclude)依赖(dependency),因为Spring组件之间的依赖关系图已经明确了,所以你只需要做一次排除依赖即可:
?
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.0.RELEASE</version> <scope>runtime</scope> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> </dependencies>
?
?现在我们的应用很可能已经坏掉了,因为classpath路径下已经没有JCL API的实现了,所以为了修复它我们不得不提供一个新的替代品。在下一个小节中我们将向你展示一个例子,该例子提供了一个使用SLF4J的JCL替代实现。
?
1.3.2.2 使用SLF4J
SLF4J相比commons-logging是一个在运行时(at runtime)更干净(clearner)的依赖并且更有效的,因为它使用编译时(compile-time)绑定替代了运行时发现(runtime discoveery),同时它也能集成这些基于运行时发现的其他日志框架。这也意味着你必须明确的知道你在运行时想要什么,并且声明它或者相应的配置它。SFL4J提供了和很多通用日志框架的绑定,所以你通常还是可以选择那个你已经在使用的日志框架,通过配置和管理将它绑定起来。
SLF4J提供了同许多通用日志框架的绑定,包括JCL,它也做反转(reverse):在其他日志框架和它自身之间做桥接。所以通过Spring使用SLF4J,你需要将commons-logging依赖替换为SLF4J-JCL桥(bridge)。一旦你完成了这点,来自Spring框架内部的日志响应会自动的转成来自SFL4J API的日志响应,因此如果你应用中的其他库也使用SLF4J的API,那么你会有一个单独的地方配置和管理日志。
一个通用的选择也许就是将Spring和SLF4J桥接起来,然后提供一个明确的SLF4J到Log4J的绑定。你需要提供4个依赖(同时排除已经存在的commons-logging):桥接(bridge),SLF4J API,到Log4J的绑定,最后是Log4J自身的实现。在Maven中,你这样做看起来象:
?
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.0.RELEASE</version> <scope>runtime</scope> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.5.8</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> <scope>runtime</scope> </dependency> </dependencies>
?
?哇,看起来仅仅为了得到一些日志,我们搞了好多依赖啊。恩,的确是,但这个是可选的(optional),而且它在遵守classload的问题上(with respect to classloader issues 实在是翻不出啊 译者)的日志表现形式比乏味的commons-logging要好多了,注意如果你在一个非常严格的容器中,比如OSGi平台,据说SLF4J也有性能上的优势,因为SLF4J是绑定在编译时(compile-time)而不是运行时的(runtime)。
SFJ4J使用者中有一个更通用的选择,这个选择使用更少的步骤,生成更少的依赖,它直接绑定到Logback,这会少了一些额外的绑定步骤,因为Logback直接实现了SLF4J,所以你只需要依赖两个库而不是4个(jcl-over-slf4j和logback)。如果你这样做了,你就也需要排除来自其他外部依赖(并非Spring)对slf4j-api的依赖,因为你在classpath下只能拥有一个版本的API。
?
1.3.2.3 使用Log4J
很多开发者使用Log4j这个日志框架来配置和管理日志。Log4j很有效并且口碑很好,事实上在我们团队内部构建和测试Spring时运行时使用的就是Log4J。Spring框架内部提供了一些工具来配置和初始化Log4J,所以在一些Spring模块的Maven的POM文件中你会看到一个可选的(optional)的编译时(compile-time)依赖的Log4j。
为了使Logj同默认的JCL依赖(commons-logging)一起工作,所有你需要做的就使将Log4j放到classpath路径下,然后提供一个配置文件(classpath的根路径下,log4j.properties或者log4j.xml)(什么是classpath的根路径? 译者)(log4j.properties和log4j.xml相比区别?优缺点? 译者)。所以对于Maven使用者下面这个就是你的依赖声明:
?
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>3.0.0.RELEASE</version> <scope>runtime</scope> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.14</version> <scope>runtime</scope> </dependency> </dependencies>
?
?这里有一个log4j.properties的简单sample,它将日志打到控制台上:
?
log4j.rootCategory=INFO, stdout log4j.appender.stdout=org.apache.log4j.ConsoleAppender log4j.appender.stdout.layout=org.apache.log4j.PatternLayout log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %t %c{2}:%L - %m%n log4j.category.org.springframework.beans.factory=DEBUG
?
?采用原生JCL的运行时容器(Runtime Containers with Native JCL)
很多开发者在一个容器中运行他们的Spring应用,而这些容器本身就提供JCL的实现类。IBM Websphere Application Server(缩写WAS)就是一个原型(archetype)。这经常会导致问题出现,不幸的是并没有一个银弹(silver bullet 软件行业术语,通过《人月神话》一书广为人知,不过我没看过,这货太古老了,没意思 译者)解决方案;简单的从你的应用中排除commons-logging在大多数情形下是不够的。
让我们把这个说的更清楚一点:问题报告通常并不是跟JCL相关,甚至不是commons-logging;而是他们将commons-logging绑定到另外一个框架(经常是Log4J)的做法。这就会失败,因为commongs-logging在旧的版本之间改变了运行时发现(runtime discovery)的方式,比如一些容器中还存在的(1.0)版本,和更现代的版本如大多数人现在使用的(1.1)版本。Spring不使用JCL API中任何不通用的部分,所以Spring框架内部不会出问题,但是一旦Spring或者你的应用试图做任何日志,你会发现绑定到Log4J不起作用了。
在这种WAS的条件下,我们可以做的最简单的事情就是反转(invert)类加载层级(class loader hierarchy)(IBM管它叫“parent last”),因此应用控制JCL依赖,而不是容器。这个选择并不总是开放的,但是有在开源社区(public domain)(是这样翻译的么? 译者)中大量的其他建议的替代方案,你的本钱(mileage)可能极度依赖于容器中精确的版本和特性。
?
ok,翻译完了,我们看以下具体的例子,基于上一篇的samples-web
pom.xml更新为:
?
<dependencies> <!-- spring mvc --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc</artifactId> <version>3.1.0.RELEASE</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <!-- logging --> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-api</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>jcl-over-slf4j</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> <version>1.6.1</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.16</version> </dependency> </dependencies>
?
?在src/main/resources中加入log4j.xml
?
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE log4j:configuration PUBLIC "-//APACHE//DTD LOG4J 1.2//EN" "log4j.dtd"> <log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/"> <!-- Appenders --> <appender name="console" class="org.apache.log4j.ConsoleAppender"> <param name="Target" value="System.out" /> <layout class="org.apache.log4j.PatternLayout"> <param name="ConversionPattern" value="%-5p: %c - %m%n" /> </layout> </appender> <!-- Root Logger --> <root> <priority value="info" /> <appender-ref ref="console" /> </root> </log4j:configuration>
?
?搞定,这就是所有要做的工作,增加了
?
slf4j-api-1.6.1.jar jcl-over-slf4j-1.6.1.jar slf4j-log4j12-1.6.1.jar log4j-1.2.16.jar
?
?上面的译文中并没有详细解释这几个jar包,我来补充一下:
我们注意到commons-logging-1.1.1.jar没有了,但是注意看jcl-over-slf4j-1.6.1.jar这个jar包,你会发现里面的结构和类名同commons-logging-1.1.1.jar一模一样!
slf4j-api-1.6.1.jar为slf4j的api,slf4j-log4j12-1.6.1.jar就是slf4j同log4j连接的桥接器,及上文说的bridge,注意slf4j是一个日志门店(logging facade),它可以同各种各样的其他第三方日志框架对接。它并不提供具体日志实现。log4j-1.2.16.jar就不用多说了,它就是日志的具体实现,它会到classpath下发现log4j.xml的配置并初始化log4j配置参数。
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?
?