1.3 使用方案
前面描述的模块使得 Spring 可以在很多方案中作为业务逻辑实现的选择,从 applet 到 使用了 Spring 的事务管理功能和 Web 框架整合,功能完善的企业级应用程序。
典型地功能完善的 Spring Web 应用
Spring 的声明式事务管理特性(11.5 节)使得 Web 应用程序可以全部事务化,就好像
使用了 EJB 容器管理的事务。全部的自定义业务逻辑可以使用简单的 POJO 来实现并且被 Spring 的 IoC 容器来管理。其它的服务包含对发送邮件的支持,验证对 Web 层独立,这可以 让你选择在哪里执行验证规则。Spring 的 ORM 支持对 JPA,Hibernate,JDO 和 iBatis 进行了整合;比如,当使用 Hibernate 时,你可以继续使用已有的映射文件和标准的 Hibernate 的 SessionFactory 配置 。表 单控 制器 无缝 地整 合了 Web 层 和领 域模 型, 也去 除了 ActionForm 或其它为领域模型转换 HTTP 参数的类的需要。
使用了第三方 Web 框架的 Spring 中间层 有时,开发和运行环境并不允许你完全转换到一个不同的框架中。Spring Framework 不强制你使用其中的部分;它并不是一个所有或没有的方案。已有使用了 WebWork,Struts, Tapestry 或其它 UI 框架构建的前端应用程序也可以使用基于 Spring 的中间层来进行整合, 这 就 允 许 你 去 使用 Spring 的 事 务 特 性 。 你 仅 仅 需 要 做 的 是 给 业 务 逻 辑 装 上 ApplicationContext,对整合的 Web 层使用 WebApplicationContext。
远程调用使用方案
当你需要通过 Web Service 访问已有的代码时,你可以使用 Spring 的 Hessian-, Burlap-,Rmi-或 JaxPpcProxyFactory 类。对已有的应用程序开启远程访问并不困难。
EJB – 包装已有的 POJO
Spring Framework 也提供了对企业级 Java Bean(EJB,译者注)的访问和抽象层(第 21 章),这就可以重用已有的 POJO,可以扩展、包装它们到无状态会话 bean,不安全的 Web 应用程序可能也需要声明式安全。
1.3.1 依赖管理和命名规约
依赖管理和依赖注入是不同的概念。要在应用程序中添加 Spring 的优雅特性(比如依 赖注入),你需要放置所需的类库(jar 文件),并且添加到运行时环境的类路径中,通常在 编译时也是需要这些类库文件的。这些依赖不是注入的虚拟组件,而是文件系统(通常来说 是这样)上的物理资源。依赖管理的过程包括定位资源,存储它们,并将它们添加到类路径 中。依赖可以是直接的(比如应用程序在运行时需要 Spring),或者是间接的(比如应用程 序需要的 commons-dbcp 组件还依赖于 commons-pool 组件)。间接的依赖也被认为是“过 度的”,而且那些依赖本身就难以识别和管理。
如果你决定使用 Spring,那么你需要获得 Spring 的 jar 包,其中要包括你所需要使用的 Spring 的模块。为了使用的便捷,Spring 被打包成各个模块的集合,尽可能地分离其中的相 互依赖,那么如果你不想编写 Web 应用程序,就不需要添加 spring-web 模块的 jar 包。要参 考 Spring 模 块 的 库 , 本 指 南 使 用 了 一 个 可以 速记 的 命 名 规 约 , spring- 或者 spring-.jar ,这里的“ * ” 代 表 了 各 模 块 的 短 名 称 ( 比 如 , spring-core , spring-webmvc,spring-jms 等)。实际上 jar 文件命名或许就是这种形式的(参考下 面的示例),但也有可能不是这种情况,通常它的文件名中还包含一个版本号(比如, spring-core-3.0.0.RELEASE.jar)。
通常,Spring 在四个不同的地方发布组件:
在社区下载点 http://www.springsource.org/dow nloads/comm unity。在这儿你可以找到所 有的 Spring jar 包,它们被压缩到一个 zip 文件中,可以自由下载。这里 jar 包的命名从 3.0 版本开始,都以 org.springframework.*-
.jar 格式进行。 Maven 的中央库,也是 Maven 默认检索的资源库,它并不会检索特殊的配置来使用。 很多 Spring 所依赖的常用类库也可以从 Maven 的中央库中获得,同时 Spring 社区的绝大多数用户都使用 Maven 作为依赖管理工具,这对于他们来说是很方便的。这里的 jar 包的命名规则是 spring-*-
.jar 格式的,并且 Maven 里的 groupId 是 org.springframework。 企业级资源库(Enterprise Bundle Repository,EBR),这是由 SpringSource 组织运营的, 同时也提供了和 Spring 整合的所有类库。对于所有 Spring 的 jar 包及其依赖,这里也有 Maven 和 Ivy 的资源库,同时这里还有很多开发人员在使用 Spring 编写应用程序时能用到的其它大量常用类库。而且,发布的版本,里程碑版本和开发版本也都部署在这里。 这 里 jar 文 件 的 命 名 规 则 和 从 社 区 下 载 的 (
org.springframework.*-<version>.jar
)一致,并且所依赖的外部类库(不 是来自 SpringSource 的)也是使用的这种“长命名”的形式,并以 com.springsource 作为前缀。可以参考 FAQ 部分获取更多信息。在 Amazon S3 为开发和里程碑版本发布(最终发布的版本这里也会有)而设置的公共Maven 资源库。Jar 文件的名称和 Maven 中央库是一样的,那么这里是获取 Spring 开发版本的地方,其它的类库是部署于 Maven 中央库的。 那么,首先你要决定的事情是如何管理这些依赖:很多人使用自动化的系统,比如 Maven 和 Ivy,但是你也可以手动下载所有的 jar 文件。当使用 Maven 或 Ivy 获取 Spring 时,之后你就需要决定从哪里来获取它们。通常来说,如果你关注 OSGi 的话,那么就使用 EBR,因为 它对所有的 Spring 依赖兼容 OSGi,比如 Hibernate 和 Freemarker。如果对 OSGi 不感兴趣, 那么使用哪个下载都是可以的,但是他们也各有利弊。通常来讲,为你的项目选择一个或者备用的一个;但是请不要混用。因为相对于 Maven 中央库而言,EBR 组件非常需要一个不同的命名规则,那么这就特别重要了。
表 1.1 Maven 中央库和 SpringSource EBR 资源库的比较
特性 | Maven 中央库 | EBR |
---|---|---|
OSGi 的兼容 | 不明确 | 是 |
组件数量 | 成千上万;所有种类 | 几百个;是 Spring 整合中会用到的 |
一致的命名规约 | 没有 | 有 |
命名规约:GroupId | 相异。新组件通常使用域名,比如 org.slf4j。老组件 通常仅仅使用组件名,比 如 log4j。 | 原 始 的 域 名 或 主 包 的 根 路 径 , 比 如 org.springframework |
命名规约:ArtifactId | 相异。通常是项目或模块 名,使用连字符“-”分隔, 比如 spring-core,log4j。 | 捆绑符号名称,从主包的路径分离,比如 org.springframework.beans。如果 jar 需要修补以保证兼容 OSGi,那么就附加 com.springsource , 比 如 com.springsource.org.apache.log4j |
命名规约:Version | 相异。很多新的组件使用 m.m.m 或 m.m.m.X(m 是 数字,X 是文本)。老的使 用 m.m。有一些也不是。 顺序是确定的但通常并不 可靠,所以不是严格的可 靠。 | OSGi 版本数字 m.m.m.X,比如 3.0.0.RC3。 文本标识符使用相同的数字值规定版本 的字母顺序。 |
发布 | 通常是自动通过 rsync 或源码控制更新。项目作者 可以上传独立的 jar 文件到 JIRA。 | 手动(由 SpringSource 控制的 JIRA) |
质量保证 | 根据政策。精确度是作者的责任。 | 宽泛的 OSGi 清单,Maven POM 和 Ivy 元数据。QA 由 Spring 团队执行。 |
主机 | Contegix 提供。由 Sonatype 和一些镜像构成。 | 由 SpringSource 的 S3 构成 |
搜索工具 | 很多 | http://www.springsource.com/repository |
和 SpringSource 工具整合 | 通过和 Maven 依赖管理的 STS 来整合 | 通过 Maven ,Roo ,CloudFoundry 的STS 进行宽泛整合 |
1.3.1.1 Spring 依赖和基于 Spring
尽管 Spring 提供对整合的支持,以及对大量企业级应用及其外部工具的支持,那么它 也有心保持它的强制依赖在一个绝对小的数目上:你不需要为了简单的使用去定位并下载
(甚至是自动地)大量的 jar 来使用 Spring。对于基本的依赖注入那只需要一个必须的外部 依赖,就是日志(可以参考下面关于日志的深入介绍)。
下面,我们来概述一下配置一个基于 Spring 的应用程序所需的基本配置,首先使用
Maven 和 Ivy。在所有的示例中,如果有哪一点不清楚,可以参考你所使用的依赖管理系统 的相关文档,或者参考一些示例代码 – Spring 本身在构建时使用了 Ivy 来管理依赖,而我们
大多数的示例却使用 Maven。
1.3.1.2 Maven 依赖管理
如果你正使用 Maven 来进行依赖管理,那么你就不需要明确地提供日志依赖。比如, 要为应用程序配置创建应用上下文并使用依赖注入特性,那么,你的 Maven 依赖配置文件 可以是这样的:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>3.0.0.RELEASE</version>
<scope>runtime</scope>
</dependency>
</dependencies>
就是这么简单。要注意的是,如果你在编译代码时不需要使用 Spring 的 API,那么 scope
元素可以声明为 runtime,这就可以用于典型的依赖注入用例。 在上面的示例中,我们使用了 Maven 中央库中的命名规约,那么它就可以在 Maven 中
央库或 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>
要使用 SrpingSource 的 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/repositor y,这可以用来搜索和下 载依赖。它也有 Maven 和 Ivy 配置的代码片段,你可以复制下来并粘贴到你所使用的工具中, 很方便使用。
1.3.1.3 Ivy 依赖管理
如果你使用 Ivy 来管理依赖,那么有一些简单的命名规约和配置选项。
要配置 Ivy 定位到 SpringSource EBR 中,需要添加如下的解析器元素到你的配置文件
ivysettings.xml 中:
<resolvers>
<url name="com.springsource.repository.bundles.release">
<ivy pattern="http://repository.springsource.com/ivy/bundles/release
/
[organisation]/[module]/[revision]/[artifact] -[revision].[ext]"
/>
<artifact pattern="http://repository.springsource.com/ivy/bundles/release
/
[organisation]/[module]/[revision]/ [artifact]-[revision].[ext]"
/>
</url>
<url name="com.springsource.repository.bundles.external">
<ivy pattern="http://repository.springsource.com/ivy/bundles/externa l/
[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
/>
<artifact pattern="http://repository.springsource.com/ivy/bundles/externa l/
[organisation]/[module]/[revision]/[artifact]-[revision].[ext]"
/>
</url>
</resolvers>
上面的 XML 并不是合法的格式,因为行太长了 – 如果你要复制粘贴,那么要移除中部
url 模式部分结尾额外的行。(本文档中已经去除)
一旦 Ivy 配置好去查看 EBR,那么添加依赖就很简单了。只要拉起在资源库浏览器中的 捆绑详细信息页面,你就会发现为你准备好的 Ivy 代码片段,那么就可以将它包含到你的依
赖部分中了。比如(在 ivy.xml 中):
<dependency org="org.springframework" name="org.springframework.core" rev="3.0.0.RELEASE"
conf="compile->runtime"/>
1.3.2 日志
对于 Spring 来说,日志是一个非常重要的依赖,因为 a)这是唯一强制的外部依赖,b) 开发人员都会想看到他们所使用的工具的一些输出内容,而且 c)Spring 整合了多种实用工具, 它们都会选择一种日志依赖包。应用程序开发人员的目标之一就是在核心位置对整个应用程 序有一个统一的日志配置,包括对所有的外部组件。因为日志框架有多种选择,那么这就可 能有些难以确定了。
Spring 中强制的日志依赖包是 Jakarta 的 Commons Logging API(JCL)。我们对 Spring 的 编译是基于 JCL 的,而且我们使扩展了 Spring Framework 的类对 JCL 包的 Log 对象都是可见 的。对于用户来说,所有 Spring 的版本都使用相同的日志包也是很重要的:因为保留了向 后兼容的特性,那么迁移是很容易进行的,同理,扩展 Spring 的应用程序也是这样。我们
这样做就是使得 Spring 中的模块明确地使用基于 commons-logging(JCL 的典型实现)的 日志实现,在编译时也使得其它模块都基于这个日志包。如果你正在使用 Maven 的话,同 时 想知道 在 哪儿 获 取 到的 commons-logging 依赖 ,那就是从 Spring 中被称作是 spring-core 的核心模块中获取的。
关于 commons-logging 比较好的做法是你不需要做其它的步骤就可以使应用程序运 行起来。它有一个运行时的查找算法,在我们都知道的类路径下寻找其它的日志框架,并且
使用 Spring 认为是比较合适的(或者告诉 Spring 你需要使用的具体是哪一个)一个。如果
没有找到可用的,那么你会得到一个来自 JDK(java.util.logging 或者简称为 JUL)本身看起来 还不错的日志依赖。那么,你会发现在很多时候,Spring 应用程序运行中会有日志信息打印 到控制台上,这点是很重要的。
1.3.2.1 不使用 Commons Logging
不幸的是,commons-logging 的运行时查找算法对最终用户方便是有些问题的。如 果我们可以让时光倒流并让 Spring 从现在开始作为一个新的项目进行,那么我们会使用不 同的日志依赖。首选的日志依赖可能就是 Java 简单的日志门面(SLF4 J),SLF4J 也被 Spring 的开发人员在他们其它应用程序中的很多工具所使用。
关闭 commons-logging 也很简单:仅仅要确认在运行时,它的包不在类路径下就可 以了。在 Maven 中要去掉这个依赖,因为 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>
目前这个应用程序可能就不能运行了,因为在类路径中已经没有了 JCL API 的实现了, 所以要修复这个问题的话,就要提供另外一种日志的实现了。在下一节中,我们来说明如何 提供一个其它的 JCL 的实现,这里,我们使用 SLF4J 作为示例。
1.3.2.2 使用 SLF4J
SLF4J 是一个整洁的日志依赖,在运行时的效率也比 commons-logging 更高,因为
SLF4J 使用了编译时构建,而不是运行时去查找其它整合的日志框架。这也就意味着你可以
更明确地在运行时去做些什么,并且去声明或配置。SLF4J 为很多通用的日志框架提供的绑 定,所以通常你可以选择已有的一个,并去绑定配置或管理。
SLF4J 为很多通用日志框架提供绑定,包括 JCL,并且也可以反向进行:桥接其它的日志 框架和 SLF4J 本身。所以如果要在 Spring 中使用 SLF4J,那么就需要使用 SLF4J-JCL 桥来代替
commons-logging 依赖。只要配置好了,那么来自 Spring 的日志调用就会被翻译成调用
SLF4J 的 API 了,如果在应用程序中的其它类库使用了原有的 API,那么会有一个单独的地方 来进行配置和管理的日志。
一个常用的选择就是桥接 Spring 到 SLF4J API,之后提供从 SLF4J 到 Log4J 的明确绑定。 你需要提供 4 种依赖(并排除已经存在的 commons-logging 依赖):桥接工具,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>
这样,看起来好像很多依赖都需要日志了。情况确实是这样,但 SLF4J 是可选的,而且 它的性能要比含有类加载器问题的 commons-logging 依赖好很多,尤其是如果你使用了 一个严格限制的容器,比如 OSGi 平台。据称那也会有性能优势,因为绑定是编译时进行的, 而不是在运行时。
另外,在 SLF4J 的用户中一个较为常见的选择是使用很少步骤,同时产生更少的依赖, 那也就是直接绑定到 Logback 上。这会去掉很多额外的绑定步骤,因为 Logback 本身直接实 现了 SLF4J , 这 样 的 话, 你 就仅 需 要两个依赖的类库 ,而不 原先 的 是四 个 了( 就是 jcl-over-slf4j 和 logback)。如果你也确实那么来做了,你可能还需要从其它外部依 赖(而不是 Spring)中去掉 slf4j-api 的依赖,因为在类路径中只保留一个 API 的一个版本就 行了。
1.3.2.3 使用 Log4J
很多用户出于配置和管理的目的而使用 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.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 打印到控制台的日志的配置示例:
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 的 Webshphere 应用服务器(WAS)就是这种类型的。这通常就会引起问题,而不幸的是没 有什么解决的方法;很多情况下仅仅从应用程序中去掉 commons-logging 依赖是不够的。
我们要 清楚 地认 识这一 点: 这个 问题 通常和 JCL 本 身一同 报告 出来 ,或 者是和
commons-logging:而不是绑定 commons-logging 到另外的框架(这里,通常是 Log4J)。 这就可能会引发问题,因为 commons-logging 改变了它们在运行时环境里,在一些容器 中查找老版本(1.0)的方式,而现在很多人使用的是新版本(1.1)。Spring 不会使用任何不 通用的 JCL API 部分,所以这里不会有问题,但是 Spring 本身或应用程序尝试去进行日志记 录,你可以发现绑定到 Log4J 是不起作用的。
这种在 WAS 上的情况,最简单的做法就是颠倒类加载器的层次(IBM 称之为“parent last”),那么就是使应用程序来控制,而不是容器来控制 JCL 的依赖。这种选择并不总是开 放的,但是在公共领域中的替代方法也有其它的建议,根据确切的版本和容器的特性集,您所要求的效果可能会有不同。