Spring 中的日志依赖

工程 | Dave Syer | 2009年12月4日 | ...

本文讨论了 Spring 做出的选择以及开发者在使用 Spring 构建的应用程序中进行日志记录的选项。本文的发布时间与 Spring 3.0 的即将发布同时,并非因为我们做了很大的改动(尽管我们现在对依赖项元数据更加谨慎),而是为了让您可以做出关于如何在应用程序中实现和配置日志记录的明智决策。首先,我们将简要介绍 Spring 中的强制依赖项,然后更详细地讨论如何设置您的应用程序以使用一些常用日志库的示例。例如,我将展示使用 Maven Central 风格的构件命名约定进行依赖项配置。

Spring 依赖项和依赖于 Spring

尽管 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 编译,则范围可以声明为 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 不可浏览,但在 http://www.springsource.com/repository 有一个用户界面,可用于搜索和下载依赖项。它还提供方便的 Maven 和 Ivy 配置片段,如果您使用这些工具,可以复制和粘贴。

如果您更喜欢使用 Ivy 来管理依赖项,那么那里有类似的名称和配置选项(请参阅您的依赖项管理系统的文档,或查看一些示例代码——Spring 本身在构建时使用 Ivy 来管理依赖项)。

日志记录

日志记录对于 Spring 来说是一个非常重要的依赖项,因为 a) 它是唯一强制性的外部依赖项,b) 每个人都喜欢看到他们正在使用的工具的一些输出,以及 c) Spring 集成了许多其他工具,所有这些工具也都做出了日志记录依赖项的选择。应用程序开发人员的目标之一通常是在一个中心位置为整个应用程序(包括所有外部组件)配置统一的日志记录。由于有如此多的日志框架可供选择,这比以前更难。

Spring 中强制的日志记录依赖项是 Jakarta Commons Logging API (JCL)。我们针对 JCL 编译,并且我们还使 JCL 日志对象对扩展 Spring Framework 的类可见。对于用户来说,所有版本的 Spring 都使用相同的日志库非常重要:迁移很容易,因为即使对于扩展 Spring 的应用程序,向后兼容性也能得到保留。我们做到这一点的方法是使 Spring 中的一个模块显式依赖于 commons-logging(JCL 的规范实现),然后使所有其他模块在编译时依赖于它。例如,如果您正在使用 Maven,并且想知道您在哪里获得了对 commons-logging 的依赖,那么它来自 Spring,特别是来自名为 spring-core 的中心模块。

commons-logging 的好处是您不需要任何其他东西就能使您的应用程序工作。它有一个运行时发现算法,可以在类路径上的知名位置查找其他日志框架,并使用它认为合适的框架(或者如果您需要,您可以告诉它哪个框架)。如果没有其他可用,您只需从 JDK(java.util.logging 或简称 JUL)获得非常漂亮的日志。您应该发现您的 Spring 应用程序开箱即用地快乐地记录到控制台,这很重要。

不使用 Commons Logging

不幸的是,commons-logging 最糟糕的事情,以及使其不受新工具欢迎的事情,也是运行时发现算法。如果我们可以倒回时间,现在开始将 Spring 作为一个新项目,它将使用不同的日志记录依赖项。首先选择可能是 Java 的简单日志记录外观 (SLF4J),许多其他人们在应用程序中与 Spring 一起使用的工具也使用它。

关闭 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

SLF4J 比 commons-logging 更干净的依赖项,并且在运行时更高效,因为它使用编译时绑定而不是运行时发现它集成的其他日志框架。这也意味着您必须更明确地说明您希望运行时发生什么,并相应地声明或配置它。SLF4J 提供对许多常用日志框架的绑定,因此您通常可以选择一个您已经使用的框架,并将其绑定以进行配置和管理。

SLF4J 提供对许多常用日志框架的绑定,包括 JCL,它也反过来做:在其他日志框架和自身之间建立桥梁。因此,要在 Spring 中使用 SLF4J,您需要将 commons-logging 依赖项替换为 SLF4J-JCL 桥。完成此操作后,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>

这似乎仅仅为了获得一些日志记录就需要很多依赖项。是的,但是它可选的,并且在类加载器问题方面,它的行为应该比普通的 commons-logging 更好,特别是如果您在一个严格的容器(如 OSGi 平台)中。据称,由于绑定是在编译时而不是运行时,因此性能也有所提高。

在 SLF4J 用户中更常见的选择是直接绑定到 Logback,它使用更少的步骤并生成更少的依赖项。这消除了额外的绑定步骤,因为 Logback 直接实现 SLF4J,因此您只需要依赖两个库而不是四个(jcl-over-slf4j 和 logback)。如果您这样做,您可能还需要从其他外部依赖项(而不是 Spring)中排除 slf4j-api 依赖项,因为您只希望类路径上有一个版本的该 API。

使用 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 应用程序运行在一个本身提供 JCL 实现的容器中。IBM WebSphere Application Server (WAS) 是原型。这经常导致问题,不幸的是,没有万能的解决方案;在大多数情况下,仅仅从应用程序中排除 commons-logging 就还不够。

明确一点:报告的问题通常不是 JCL 本身的问题,甚至不是 commons-logging 的问题:而是与将 commons-logging 绑定到另一个框架(通常是 Log4J)有关。这可能会失败,因为 commons-logging 在某些容器中找到的较旧版本 (1.0) 和大多数人现在使用的现代版本 (1.1) 之间更改了它们执行运行时发现的方式。Spring 没有使用 JCL API 的任何不寻常的部分,因此那里没有任何内容中断,但是一旦 Spring 或您的应用程序尝试执行任何日志记录,您就会发现到 Log4J 的绑定不起作用。

在这种情况下,使用 WAS 最简单的方法是反转类加载器层次结构(IBM 称其为“父级最后”),以便应用程序控制 JCL 依赖项,而不是容器。该选项并非总是开放的,但公共领域中有很多其他建议可以采用替代方法,而您的里程数可能会因容器的确切版本和功能集而异。

(注意:Spring 和 slf4j 版本已从原文更新,以保持其可复制粘贴性。)

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加快您的进步。

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供对 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

查看 Spring 社区中所有即将举行的活动。

查看全部