Spring 可以使你从“简单的Java对象”(POJO)构建应用程序,并且将企业服务非侵入性的应用到 POJO。此功能适用于 Java SE 编程模型和完全或者部分的 Java EE 。
- 在不用处理事务API的情况下,可以操作数据库事务
- 在不用处理远程API的情况下,可以用本地方法执行远程过程
- 在不用处理JMX API的情况下,可以用本地方法执行管理操作
- 在不用处理JMS API的情况下,可以用本地方法执行消息的处理
依赖注入和控制反转
背景
“问题是,[他们]哪些方面的控制被反转?”这个问题由 Martin Fowler在他的 Inversion of Control (IoC) 网站在 2004 年提出。 Fowler 建议重新命名这个说法,使得他更加好理解,并且提出了 Dependency Injection(依赖注入)(这个新的说法)。
模块
核心容器
Core Container 由 spring-core, spring-beans, spring-context, spring-context-support, 和 spring-expression (Spring Expression Language) 模块组成
spring-core 和 spring-beans 提供框架的基础部分,包括 IoC 和 Dependency Injection 功能。BeanFactory 是一个复杂的工厂模式的实现。不需要可以编程的单例,并允许您将配置和特定的依赖从你的实际程序逻辑中解耦。
Context (spring-context) 模块建立且提供于在Core 和 Beans 模块的基础上,它是一种在框架类型下实现对象存储操作的手段,有一点像 JNDI 注册。Context 继承了 Beans 模块的特性,并且增加了对国际化的支持(例如用在资源包中)、事件广播、资源加载和创建上下文(例如 一个 Servlet 容器)。Context 模块也支持例如 EJB,JMX 和基础远程这样的 JavaEE 特性。ApplicationContext 是 Context 模块的焦点。spring-context-support 提供对常见第三方库的支持集成进 Spring 应用上下文,如缓存 (EhCache, Guava, JCache), 通信 (JavaMail), 调度 (CommonJ, Quartz) 和 模板引擎 (FreeMarker, JasperReports, Velocity).
spring-expression 模块提供了一个强大的Expression Language(表达式语言)用来在运行时查询和操作对象图。这是作为JSP2.1 规范所指定的统一表达式语言(unified EL)的一种延续。这种语言支持对属性值、属性参数、方法调用、数组内容存储、收集器和索引、逻辑和算数操作及命名变量,并且通过名称从 Spring 的控制反转容器中取回对象。表达式语言模块也支持 List 的映射和选择,正如像常见的列表汇总一样。
AOP 及 Instrumentation
spring-aop 模块提供 AOP Alliance-compliant(联盟兼容)的面向切面编程实现,允许你自定义,比如,方法拦截器和切入点完全分离代码。使用源码级别元数据的功能,你也可以在你的代码中加入 behavioral information (行为信息),在某种程度上类似于 .NET 属性。
单独的 spring-aspects 模块提供了集成使用 AspectJ。
spring-instrument 模块提供了类 instrumentation 的支持和在某些应用程序服务器使用类加载器实现。spring-instrument-tomcat 用于 Tomcat Instrumentation 代理。
消息
数据访问和集成
Data Access/Integration 层由 JDBC, ORM, OXM, JMS, 和 Transaction 模块组成。
spring-jdbc 模块提供了不需要编写冗长的JDBC代码和解析数据库厂商特有的错误代码的JDBC-抽象层。
spring-tx 模块的支持可编程和声明式事务管理,用于实现了特殊的接口的和你所有的POJO类(Plain Old Java Objects)。
spring-orm 模块提供了流行的 object-relational mapping(对象-关系映射)API集成层,其包含JPA,JDO,Hibernate。使用ORM包,你可以使用所有的 O/R 映射框架结合所有Spring 提供的特性,比如前面提到的简单声明式事务管理功能。
spring-oxm 模块提供抽象层用于支持 Object/XML mapping (对象/XML映射)的实现,如 JAXB、Castor、XMLBeans、JiBX 和 XStream 的。
spring-jms 模块(Java Messaging Service)包含生产和消费信息的功能。 从 Spring Framework 4.1 开始提供集成 spring-messaging 模块。
Web
Web 层由 spring-web, spring-webmvc, spring-websocket, 和 spring-webmvc-portlet 组成 。
spring-web 模块提供了基本的面向 web 开发的集成功能,例如多方文件上传、使用 Servlet listeners 和 Web 开发应用程序上下文初始化 IoC 容器。它也包含 HTTP 客户端以及 Spring 远程访问的支持的 web 相关的部分。
spring-webmvc 模块(也被称为 Web Servlet 模块)包含 Spring 的model-view-controller (模型-视图-控制器(MVC)和 REST Web Services 实现的Web应用程序。Spring 的 MVC 框架提供了domain model(领域模型)代码和 web form (网页) 之间的完全分离,并且集成了 Spring Framework 所有的其他功能。
spring-webmvc-portlet 模块(也被称为 Web-Portlet 模块)提供了MVC 模式的实现是用一个 Portlet 的环境和 spring-webmvc 模块功能的镜像。
Test
使用场景
前面的构建模块描述在很多的情况下使 Spring 是一个合理的选择,从资源受限的嵌入式程序到成熟的企业应用程序都可以使用 Spring 事务管理功能和 web 框架集成。
Figure 2.2. Typical full-fledged Spring web application
Spring 声明式事务管理特性使 Web 应用程序拥有完全的事务,就像你使用 EJB 容器管理的事务。所有的自定义业务逻辑可以用简单的 POJO 实现,用 Spring 的 IoC 容器进行管理。额外的服务包括发送电子邮件和验证,是独立的网络层的支持,它可以让你选择在何处执行验证规则。Spring 的 ORM 支持集成 JPA,Hibernate,JDO;例如,当使用 Hibernate,您可以继续使用现有的映射文件和 标准的 Hibernate 的 SessionFactory 配置。表单控制器将 Web 层和领域模型无缝集成,消除 ActionForms 或其他类用于变换 HTTP 参数成为您的域模型值的需要。
Figure 2.3. Spring middle-tier using a third-party web framework
有时,不允许你完全切换到一个不同的框架。Spring Framework 不强制使用它,它不是一个全有或全无的解决方案。现有的前端 Struts, Tapestry, JSF 或其他 UI 框架,可以集成一个基于 Spring 中间件,它允许你使用 Spring 事务的功能。你只需要将业务逻辑连接使用 ApplicationContext 和使用WebApplicationContext 集成到你的 web 层。
Figure 2.4. Remoting usage scenario
当你需要通过 web 服务访问现有代码时,可以使用 Spring 的 Hessian-, Burlap-, Rmi- 或 JaxRpcProxyFactory 类。启用远程访问现有的应用程序并不难。
Figure 2.5. EJBs - Wrapping existing POJOs
Spring Framework 也提供了 Enterprise JavaBeans 访问和抽象层 使你能重用现有的 POJOs,并且在需要声明安全的时候打包他们成为无状态的 bean 用于可伸缩,安全的 web 应用里。
依赖关系关系和命名约定
依赖管理和依赖注入是不同的概念。为了让 Spring 的这些不错的功能运用到运用程序中(比如依赖注入),你需要收集所有的需要的库(JAR文件),并且在编译、运行的时候将它们放到你的类路径中。这些依赖不是虚拟组件的注入,而是物理的资源在文件系统中(通常)。依赖管理过程包括定位这些资源,存储它们并添加它们到类路径。依赖可以是直接(如我的应用程序运行时依赖于 Spring ),或者是间接(如我的应用程序依赖 commons-dbcp ,而 commons-dbcp 又依赖于 commons-pool)。间接的依赖也被称为 “transitive (传递)”,它是最难识别和管理的依赖。
如果你将使用 Spring,你需要复制哪些包含你需要的 Spring 功能的 jar包。为了使这个过程更加简单,Spring 被打包为一组模块,这些模块尽可能多的分开依赖关系。例如,如果不想写一个 web 应用程序,你就不需要引入 Spring-web 模块。为了在本指南中标记 Spring 库模块我们使用了速记命名约定 spring- 或者 spring-.jar ,其中*代表模块的短名(比如 spring-core,spring-webmvc, spring-jms 等)。实际的jar 文件的名字,通常是用模块名字和版本号级联(如spring-core-4.1.4.BUILD-SNAPSHOT.jar)
每个 Spring Framework 发行版本将会放到下面的位置:
- Maven Central (Maven 中央库),这是 Maven 查询的默认库,而不需要任何特殊的配置就能使用。许多常用的 Spring 的依赖库也存在与Maven Central ,并且 Spring 社区的很大一部分都使用 Maven 进行依赖管理,所以这是最方便他们的。jar 命名格式是 spring-*-<version>.jar , Maven groupId 是org.springframework
- 公共 Maven 仓库还拥有 Spring 专有的库。除了最终的 GA 版本,这个库还保存开发的快照和里程碑。JAR 文件的名字是和 Maven Central 相同的形式,所以这是让 Spring 的开发版本使用其它部署在 Maven Central 库的一个有用的地方。该库还包含一个用于发布的 zip 文件包含所有Spring jar,方便下载。
所以首先,你要决定用什么方式管理你的依赖,通常建议你使用自动系统像 Maven, Gradle 或 Ivy, 当你也可以下载 jar
下面是 Spring 中的组件列表。更多描述,详见2.2. Modules (模块)
Table 2.1. Spring Framework Artifacts
GroupId | ArtifactId | Description |
---|---|---|
org.springframework | spring-aop | Proxy-based AOP support |
org.springframework | spring-aspects | AspectJ based aspects |
org.springframework | spring-beans | Beans support, including Groovy |
org.springframework | spring-context | Application context runtime, including scheduling and remoting abstractions |
org.springframework | spring-context-support | Support classes for integrating common third-party libraries into a Spring application context |
org.springframework | spring-core | Core utilities, used by many other Spring modules |
org.springframework | spring-expression | Spring Expression Language (SpEL) |
org.springframework | spring-instrument | Instrumentation agent for JVM bootstrapping |
org.springframework | spring-instrument-tomcat | Instrumentation agent for Tomcat |
org.springframework | spring-jdbc | JDBC support package, including DataSource setup and JDBC access support |
org.springframework | spring-jms | JMS support package, including helper classes to send and receive JMS messages |
org.springframework | spring-messaging | Support for messaging architectures and protocols |
org.springframework | spring-orm | Object/Relational Mapping, including JPA and Hibernate support |
org.springframework | spring-oxm | Object/XML Mapping |
org.springframework | spring-test | Support for unit testing and integration testing Spring components |
org.springframework | spring-tx | Transaction infrastructure, including DAO support and JCA integration |
org.springframework | spring-web | Web support packages, including client and web remoting |
org.springframework | spring-webmvc | REST Web Services and model-view-controller implementation for web applications |
org.springframework | spring-webmvc-portlet | MVC implementation to be used in a Portlet environment |
org.springframework | spring-websocket | WebSocket and SockJS implementations, including STOMP support |
Spring 的依赖以及基于 Spring
虽然 Spring 提供了集成在一个大范围的企业和其他外部工具的支持,它故意保持其强制性依赖关系降到最低:在简单的用例里,你无需查找并下载(甚至自动)一大批 jar 库来使用 Spring 。基本的依赖注入只有一个外部强制性的依赖,这是用来做日志的(见下面更详细地描述日志选项)。
接下来我们将一步步展示如果配置依赖 Spring 的程序,首先用 Maven 然后用 Gradle 和最后用 Ivy。在所有的情况下,如果有不清楚的地方,查看的依赖性管理系统的文档,或看一些示例代码。Spring 本身是使用 Gradle 来管理依赖的,我们的很多示例也是使用 Gradle 或 Maven。
译者注:有关 Gradle 的使用,可以参见笔者的另外一部作品《Gradle 2 用户指南》
Maven 依赖管理
如果您使用的是 Maven 的依赖管理你甚至不需要明确提供日志依赖。例如,要创建一个应用程序的上下文和使用依赖注入来配置应用程序,你的Maven 依赖将看起来像这样:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.2.1.RELEASE</version>
<scope>runtime</scope>
</dependency>
</dependencies>
就是它。注意 scope 可声明为 runtime (运行时)如果你不需要编译 Spring 的API,这通常是基本的依赖注入使用的案例。
以上与 Maven Central 存储库工程实例。使用 Spring Maven 存储库(如里程碑或开发者快照),你需要在你的 Maven 配置指定的存储位置。如下:
<repositories>
<repository>
<id>io.spring.repo.maven.release</id>
<url>http://repo.spring.io/release/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
里程碑:
<repositories>
<repository>
<id>io.spring.repo.maven.milestone</id>
<url>http://repo.spring.io/milestone/</url>
<snapshots><enabled>false</enabled></snapshots>
</repository>
</repositories>
以及快照:
<repositories>
<repository>
<id>io.spring.repo.maven.snapshot</id>
<url>http://repo.spring.io/snapshot/</url>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Maven "Bill Of Materials" 依赖
有可能不小心使用不同版本的 Spring JAR 在使用 Maven 时。例如,你可能发现一个第三方的库,或另一 Spring 的项目,拉取了一个在传递依赖较早的发布包。如果你自己忘记了显式声明一个直接依赖,各种意想不到的问题出现。
为了克服这些问题,Maven 支持 "bill of materials" (BOM) 的依赖的概念。你可以在你的 dependencyManagement 部分引入 spring-framework-bom 来确保所有 spring依赖(包括直接和传递的)是同一版本。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-framework-bom</artifactId>
<version>4.2.1.RELEASE</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
使用 BOM 后,当依赖 Spring Framework 组件后,无需指定<version>
属性
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<dependencies>
Gradle 依赖管理
用 Gradle 来使用 Spring ,在 repositories 中填入适当的 URL :
repositories {
mavenCentral()
// and optionally...
maven { url "http://repo.spring.io/release" }
}
可以适当修改 URL 从 /release 到 /milestone 或 /snapshot 。库一旦配置,就能声明 Gradle 依赖:
dependencies {
compile("org.springframework:spring-context:4.2.1.RELEASE")
testCompile("org.springframework:spring-test:4.2.1.RELEASE")
}
Ivy 依赖管理
如果你更喜欢用Ivy管理依赖也有类似的配置选项。
配置 Ivy ,将指向 Spring 的库 添加下面的 resolver(解析器)到你ivysettings.xml:
<resolvers>
<ibiblio name="io.spring.repo.maven.release"
m2compatible="true"
root="http://repo.spring.io/release/"/>
</resolvers>
可以适当修改 root URL 从 /release 到 /milestone 或 /snapshot 。
一旦配置,就能添加依赖,举例 (在 ivy.xml):
<dependency org="org.springframework"
name="spring-core" rev="4.2.1.RELEASE" conf="compile->runtime"/>
分发的 zip 文件
虽然使用构建系统,支持依赖管理是推荐的方式获得了 Spring Framework,,它仍然是可下载分发的 zip 文件。
分发的 zip 文件是发布到 Spring Maven Repository (这是为了我们的便利,在下载这些文件的时候你不需要 Maven 或者其他的构建系统)。
下载一个 Zip,在web 浏览器打开 http://repo.spring.io/release/org/springframework/spring ,选择适当的文件夹的版本。下载完毕文件结尾是 -dist.zip,例如, spring-framework-{spring-version}-RELEASE-dist.zip 。分发也支持发布里程碑和快照。
日志
对于 Spring 日志是非常重要的依赖,因为:a)它是唯一的外部强制性的依赖;b)每个人都喜欢从他们使用的工具看到一些输出;c)Spring 结合很多其他工具都选择了日志依赖。应用开发者的一个目标就是往往是有统一的日志配置在一个中心位置为了整个应用程序,包括所有的外部元件。这就更加困难,因为它可能已经有太多选择的日志框架。
在 Spring 强制性的日志依赖 是 Jakarta Commons Logging API(JCL)。我们编译 JCL,我们也使得 JCLLog
对象对 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 项目并且他使用了不同的日志依赖。第一个选择很可能是Simple Logging Facade for Java ( SLF4J),过去也曾被许多其他工具通过 Spring 使用在他们的应用程序。
基本上有两种方法可以关闭commons-logging:
- 通过 spring-core 模块排除依赖(因为它是唯一的显示依赖于 commons-logging 的模块)。
- 依赖特殊的 commons-logging 依赖,用空的jar(更多的细节可以在SLF4J FAQ中找到)替换掉库。
排除 commons-logging,添加以下的内容到 dependencyManagement 部分:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.2.1.RELEASE</version>
<exclusions>
<exclusion>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
现在,这个应用程序可以打破,因为在类路径上没有实现 JCL API,因此要修复它就必须提供有一个新的。在下一节我们将向你展示如何提供另一种实现 JCL,使用 SLF4J 作为例子的另一种实现。
使用 SLF4J
SLF4J 是一个更加简洁的依赖,在运行时相对于 commons-logging 更加的有效因为它使用编译时绑定来代替运行时发现其他日志框架的集成。这也意味着,你不得不更加明确你想在运行时发生什么,并相应的声明它或者配置它。SLF4J 提供绑定很多的常见日志框架,因此你可以选择一个你已经使用的,并且绑定到配置和管理。
SLF4J 提供了绑定很多的常见日志框架,包括 JCL,它也做了反向工作:是其他日志框架和它自己之间的桥梁。因此在 Spring 中使用 SLF4J 时,你需要使用 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-core</artifactId>
<version>4.2.1.RELEASE</version>
<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>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.5.8</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</dependency>
</dependencies>
这似乎是一个很大的依赖性,其仅仅是为了得到一些日志文件。那就这样吧,但是它是可选的,它在关于类加载器的问题上应该比 commons-logging 表现的更加的好,值得注意的是,如果你在一个严格的容器中像OSGi 平台。据说也有一个性能优势应为绑定是在编译时而不是在运行时。
SLF4J 是用户中一个常见的选择,使用它可以更少的步骤和产生更少的依赖,直接绑定 Logback。这消除了多余的绑定步骤,因为 Logback 直接实现了 SLF4J,因此你只需要依赖两个库而不是4个(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-core</artifactId>
<version>4.2.1.RELEASE</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.14</version>
</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本地运行时容器
很多的人在提供 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 或者你的应用程序视图做一些日志时,你会发现绑定的 Log4j 不工作了。
在这种情况下,WAS 最容易的是转化类加载器的层次结构(IBM 称之为“parent last”),使应用程序控制 JCL 的依赖关系,而不是容器。该选项不是总是开放的,但是有很多其他的建议在公共领域的替代方法,你的里程可能取决于确切的版本和特定的容器。