抢先一步
VMware 提供培训和认证,助您加速前进。
了解更多本文讨论了 Spring 在日志方面的选择以及开发者在使用 Spring 构建应用程序时可用的选项。本文发布时间恰逢 Spring 3.0 即将发布,这不是因为我们做了很多改动(尽管现在我们对依赖元数据更加谨慎),而是为了让您可以就如何在应用程序中实现和配置日志做出明智的决定。首先,我们简要介绍 Spring 中的强制性依赖,然后更详细地讨论如何设置应用程序以使用一些常见日志库的示例。作为一个例子,我将使用 Maven Central 风格的 Artifact 命名约定来展示依赖配置。
尽管 Spring 为各种企业及其他外部工具提供了集成和支持,但它有意将其强制性依赖项保持在最低限度:您不应该为了使用 Spring 处理简单用例而需要定位和下载(即使是自动下载)大量 jar 库。对于基本的依赖注入,只有一个强制性的外部依赖项,那就是日志(有关日志选项的更详细描述,请参见下文)。如果您使用 Maven 进行依赖管理,甚至无需明确提供日志依赖项。例如,要创建应用程序上下文并使用依赖注入配置应用程序,您的 Maven 依赖项将如下所示
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.2.RELEASE</version>
<scope>runtime</scope>
</dependency>
</dependencies>
就是这样。请注意,如果您不需要针对 Spring API 进行编译,可以将 scope 声明为 runtime,这通常适用于基本的依赖注入用例。
我们在上面的示例中使用了 Maven Central 的命名约定,因此它适用于 Maven Central 或 SpringSource S3 Maven 仓库。要使用 S3 Maven 仓库(例如用于里程碑版本或开发者快照),您需要在 Maven 配置中指定仓库位置。对于正式发布版本
<repositories>
<repository>
<id>com.springsource.repository.maven.release</id>
<url>http://maven.springframework.org/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于里程碑版本
<repositories>
<repository>
<id>com.springsource.repository.maven.milestone</id>
<url>http://maven.springframework.org/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
对于快照版本
<repositories>
<repository>
<id>com.springsource.repository.maven.snapshot</id>
<url>http://maven.springframework.org/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
要使用 SpringSource EBR,您需要对依赖项使用不同的命名约定。名称通常很容易猜到,例如在这种情况下是
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>org.springframework.context</artifactId>
<version>3.0.0.RELEASE</version>
<scope>runtime</scope>
</dependency>
</dependencies>
您还需要明确声明仓库的位置(只有 URL 重要)
<repositories>
<repository>
<id>com.springsource.repository.bundles.release</id>
<url>http://repository.springsource.com/maven/bundles/release/</url>
</repository>
</repositories>
如果您手动管理依赖项,上面仓库声明中的 URL 是无法直接浏览的,但在 <a href="http://www.springsource.com/repository">http://www.springsource.com/repository 有一个用户界面,可用于搜索和下载依赖项。它还提供了方便的 Maven 和 Ivy 配置代码片段,如果您使用这些工具,可以直接复制粘贴。
如果您更喜欢使用 Ivy 来管理依赖项,那么那里也有类似的名称和配置选项(请参考您的依赖管理系统的文档,或查看一些示例代码 - Spring 本身在构建时就使用 Ivy 来管理依赖项)。
日志对于 Spring 来说是一个非常重要的依赖项,原因如下:a) 它是唯一强制性的外部依赖项;b) 每个人都喜欢看到他们使用的工具输出一些内容;c) Spring 集成了许多其他工具,这些工具也都选择了自己的日志依赖项。应用程序开发人员的目标之一通常是将整个应用程序(包括所有外部组件)的日志配置集中在一个地方。这比想象的要困难,因为日志框架的选择太多了。
Spring 中强制性的日志依赖项是 Jakarta Commons Logging API (JCL)。我们针对 JCL 进行编译,并且还使得扩展 Spring Framework 的类可以访问 JCL Log 对象。对用户来说很重要的一点是,所有版本的 Spring 都使用相同的日志库:迁移很容易,因为即使对于扩展 Spring 的应用程序,我们也保留了向后兼容性。我们这样做的方式是让 Spring 中的一个模块明确依赖于 commons-logging(JCL 的规范实现),然后在编译时让所有其他模块都依赖于它。例如,如果您使用 Maven,并想知道您是从哪里获取 commons-logging 依赖项的,那么它来自 Spring,具体来说是来自名为 spring-core 的核心模块。
commons-logging 的好处在于您不需要其他任何东西就能让您的应用程序工作。它有一个运行时发现算法,会在类路径上已知的位置查找其他日志框架,并使用它认为合适的那个(或者如果您需要,可以指定一个)。如果没有其他可用的日志框架,您也可以只从 JDK (java.util.logging,简称 JUL) 获得相当不错的日志输出。您会发现在大多数情况下,您的 Spring 应用程序开箱即用就能正常工作并将日志愉快地输出到控制台,这很重要。
不幸的是,commons-logging 最糟糕的地方,也是导致它在新工具中不受欢迎的原因,恰恰是其运行时发现算法。如果时光倒流,现在重新开始 Spring 项目,它会使用不同的日志依赖项。首选很可能是 Simple Logging Facade for Java (SLF4J),许多在应用程序中与 Spring 一起使用的其他工具也使用了 SLF4J。
关闭 commons-logging 很简单:只需确保它在运行时不在类路径上即可。用 Maven 的术语来说,就是排除该依赖项,并且由于 Spring 依赖项的声明方式,您只需要排除一次。
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.2.RELEASE</version>
<scope>runtime</scope>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
现在,这个应用程序很可能已经无法正常工作,因为类路径上没有 JCL API 的实现。因此,为了解决这个问题,必须提供一个新的实现。在下一节中,我们将以 SLF4J 为例,展示如何提供 JCL 的替代实现。
SLF4J 是一种更简洁的依赖项,并且在运行时比 commons-logging 更高效,因为它使用编译时绑定,而不是对它集成的其他日志框架进行运行时发现。这也意味着您必须更明确地说明在运行时希望发生什么,并据此声明或配置。SLF4J 提供了与许多常见日志框架的绑定,因此您通常可以选择一个您已经在使用的框架,并绑定到它进行配置和管理。
SLF4J 提供了与许多常见日志框架的绑定,包括 JCL,它也提供了反向功能:其他日志框架与 SLF4J 之间的桥接。因此,要将 SLF4J 与 Spring 一起使用,您需要用 SLF4J-JCL 桥接替换 commons-logging 依赖项。完成此操作后,来自 Spring 内部的日志调用将被转换为对 SLF4J API 的日志调用,因此如果应用程序中的其他库也使用该 API,那么您就可以在一个地方配置和管理日志了。
一种常见的选择可能是将 Spring 桥接到 SLF4J,然后提供从 SLF4J 到 Log4J 的显式绑定。您需要提供 4 个依赖项(并排除现有的 commons-logging):桥接器、SLF4J API、到 Log4J 的绑定以及 Log4J 实现本身。在 Maven 中,您可以这样做
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.2.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.7.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
<scope>runtime</scope>
</dependency>
</dependencies>
仅仅为了获取一些日志输出,这看起来可能需要很多依赖项。确实如此,但它
在 SLF4J 用户中,一个更常见的选择是直接绑定到 Logback,这种方法步骤更少,生成的依赖项也更少。这省去了额外的绑定步骤,因为 Logback 直接实现了 SLF4J,所以您只需要依赖两个库而不是四个(jcl-over-slf4j 和 logback)。如果您这样做,您可能还需要从其他外部依赖项(非 Spring)中排除 slf4j-api 依赖项,因为您只希望类路径上存在该 API 的一个版本。
许多人使用 <a href="https://logging.apache.ac.cn/log4j">Log4j 作为日志框架进行配置和管理。它高效且成熟,事实上,我们在构建和测试 Spring 时运行时使用的就是它。Spring 还提供了一些用于配置和初始化 Log4j 的工具类,因此在某些模块中它对 Log4j 有可选的编译时依赖。
要让 Log4j 与默认的 JCL 依赖项 (commons-logging) 一起工作,您只需将 Log4j 放到类路径上,并为其提供一个配置文件(在类路径的根目录下放置 log4j.properties 或 log4j.xml)。所以对于 Maven 用户来说,这是您的依赖声明
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.1.2.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 示例
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 实现的容器中运行 Spring 应用程序。IBM Websphere Application Server (WAS) 是典型的例子。这经常会导致问题,不幸的是,没有放之四海而皆准的解决方案;在大多数情况下,仅仅从您的应用程序中排除 commons-logging 是不够的。
需要明确的是:报告的问题通常与 JCL 本身无关,甚至与 commons-logging 也无关:相反,它们与将 commons-logging 绑定到另一个框架(通常是 Log4J)有关。这可能会失败,因为 commons-logging 在某些容器中发现的旧版本(1.0)与大多数人现在使用的现代版本(1.1)之间改变了运行时发现的方式。Spring 没有使用 JCL API 的任何非寻常部分,所以在 Spring 内部不会出现问题,但是一旦 Spring 或您的应用程序尝试进行任何日志记录,您可能会发现与 Log4J 的绑定无法正常工作。
在这种使用 WAS 的情况下,最简单的做法是反转类加载器层次结构(IBM 称之为“父级优先”的相反:“parent last”),以便应用程序控制 JCL 依赖项,而不是容器。这个选项并非总是可用,但公共领域有很多其他替代方法的建议,具体效果取决于容器的确切版本和功能集。
(注意:Spring 和 slf4j 的版本已从原文更新,以便可以直接复制粘贴。)