翻译自Spring官方文档 4.1.2版本
一、IoC容器
1.1 Spring IoC容器和beans简介
这个章节涵盖了Spring Framework<em>控制反转</em>(Inversion of Control 缩写IoC)的原理。IoC也被称为依赖注入(dependency injection)。它是一个流程由此对象定义他们的依赖,也就是说,定义与某个对象一起工作的其他对象,只有通过构造器参数、工厂方法的参数或者将属性通过set到它被构造后或从一个工厂方法返回后的对象实例 。当容器创建bean时会<em>注入</em>(injects )那些依赖。相对于bean自己来控制其依赖的实例化或位置的做法,这个过程从根本上是反转的,因此叫做<em>控制反转。</em>
org.springframework.beans
和org.springframework.context
这两个包是Spring框架的IoC容器的基础。BeanFactory
接口提供了一个先进的能够配置的机制来管理任何类型的对象。ApplicationContext
是BeanFactory
的一个子接口。它使以下几个方面更容易集成:Spring AOP特性、消息资源处理(用来国际化)、事件发布、应用层特殊的上下文比如用在web应用程序的WebApplicationContext
。
在Spring里,组成你应用程序骨干的对象并且被Spring IoC容器管理,它们被称作beans
(这个词相当难翻译)。一个bean是一个已经实例化的、组装的除此之外还是被Spring IoC容器管理的对象。beans和它们之间的依赖关系反映在一个容器使用的<em>配置元数据</em>(configuration metadata)里。
1.2 容器概览
org.springframework.context.ApplicationContext
这个接口代表了Spring IoC容器,并且还负责实例化、配置和装配前面提到的beans。容器获取其有关哪些对象实例化的说明,设置、然后通过读取配置元数据装配beans。配置元数据被表现在XML、Java annotations或者Java code。它允许你去表达构成你的应用程序的对象并且丰富这些对象间的依赖关系。
有几个ApplicationContext接口的实现提供开箱即用(out-of-the-box )的功能。在独立的应用程序通常会创建一个ClassPathXmlApplicationContext
或者FileSystemXmlApplicationContext
的实例。虽然XML已经成为传统的形式来定义配置元数据,你还可以指示容器来使用Java annotations或者code作为元数据的形式但是仍旧需要一小段XML配置来声明启动这些额外元数据形式的支持。
在绝大多数应用程序的场景里,显式的用户代码不需要去实例化一个或多个Spring IoC容器。例如,在web应用程序场景中,在web.xml文件中几行简单的配置就通常足够了。
1.2.1 配置元数据
如上图所示,Spring IoC容器消费了一份配置元数据,配置元数据表示你作为一名程序开发者告诉Spring容器去实例化、设置和装配哪些对象。
Spring 2.5开始引进基于注解来配置元数据的支持。
Spring 3.0开始支持基于JavaConfig来配置元数据。
Spring配置至少包含一个(通常都是多个)容器必须管理的bean定义。基于XML的配置元数据显示了这些bean被配置为bean
元素,而它在一个顶级元素beans
下。Java类的配置方式通常使用@Bean
注解在方法上,方法所在的类使用@Configuration
注解。
这些bean定义对应组成你应用程序真实的对象。通常你会定义服务层的对象;数据访问对象(DAOs);展示对象(presentation objects)例如,Struts Action
接口;基础结构对象(infrastructure objects)例如,Hibernate SessionFactories
;JMS Queues
等等。通常有一个没有在容器里配置为bean的域对象(domain objects),因为这通常是DAOs和业务逻辑来创建和加载域对象。但是你可以用Spring集成的AspectJ来配置这些在IoC容器控制范围外被创建的对象。
下面的例子展示了基于XML配置元数据的基本框架:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
id
属性是一个字符串,你可以用它来识别独特的bean定义。class
属性定义了bean的类型并且使用完全限定类名(fully qualified classname)。
1.2.2 实例化一个容器
实例化一个Spring IoC是很容易的。一个路径或者多个路径提供给ApplicationContext
的构造函数允许容器从多种多样的外界资源加载配置元数据 ,例如本地文件系统、从java的CLASSPATH
等等。
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
1.2.3 使用容器
ApplicationContext是一个维护不同beans和它们的依赖的先进工厂,使用方法:
T getBean(String name, Class<T> requiredType);
你可以取回你的beans的实例。
例如:
// create and configure beans
ApplicationContext context =
new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
1.3 Bean概览
一个Spring IoC容器管理这一个或多个bean。这些bean通过你提供给容器的配置元数据被创建,例如在基于XML配置的<bean/>
元素下定义。
在容器内部,这些bean定义通过BeanDefinition
对象表示。
1.3.1 命名beans
每一个bean都有一个或者多个标识符。这些标识符在容器管理的所有bean里必须是唯一的。一个bean通常只有一个标识符,但是如果需要更多的,这个额外的标识符可以考虑别名。
你并没有被要求为一个bean提供一个名称或id。如果没有显示的提供名称或者id,容器会为这个bean生成一个唯一的名称。
Bean命名约定使用的是标准的Java实例成员变量的命名。bean的名称开头以小写字母开头,后面的字母遵循驼峰大小写形式。例如:'accountManager', 'accountService', 'userDao', 'loginController'等等。
在基于XML的配置文件里,可以使用<alias/>
元素来为bean定义别名:
<alias name="fromName" alias="toName"/>
1.3.2 实例beans
一个bean定义基本上就是一个配方(recipe)用来创建一个或多个对象。当一个被命名的bean被请求时容器去查询配方,同时使用配置元数据封装的bean定义来创建(或取得)一个真是的对象。
如果你使用基于XML的配置方式,你要在<bean/>
元素的class属性里具体指定要被实例的对象的类。这个class属性在一个BeanDefinition
实例的内部是一个类属性。
当你通过构造器方法创建一个bean,对于所有普通的类都是适用的并且与Spring兼容。也就是说类在开发时不需要去实现任何特殊接口或者用特殊的方式编码。简单的指定bean的类就足够了。但是根据你用的IoC的类型,你可能需要一个默认的构造器。
1.4 依赖关系
一个典型的企业应用程序不是只包含一个对象(或者用Spring的说法,一个bean)。即使是最简单的应用程序也包含了几个一起工作的对象来让最终的用户看起来这是一个合乎逻辑的应用程序。下面一部分来解释你定义的许多孤立的bean是怎么变成一个对象们一起协作去到达一个目标的真正的应用程序。
1.4.1 依赖注入
Dependency injection
(DI)依赖注入是一个流程由此对象定义他们的依赖,也就是说,定义与某个对象一起工作的其他对象,只有通过构造器参数、工厂方法的参数或者将属性通过set到它被构造后或从一个工厂方法返回后的对象实例 。当容器创建bean时会将这些依赖注入(injects )。相对于bean自己来控制其依赖的实例化或位置的做法,这个过程从根本上是反转的,因此叫做控制反转Inversion of Control (IoC)。
通过DI的原则代码变得更整洁,并且当对象提供了它们的依赖后解耦更有效。对象不用查找它的依赖关系也不知道这些依赖的位置或者类。正因如此你的类变得更容易测试,尤其当这些依赖是接口或者抽象基类时,允许stub或者mock的实现在单元测试中被使用。
DI存在两种主要变体,基于构造器的依赖注入和基于Setter方法的依赖注入。
基于构造器的依赖注入
基于构造器的依赖注入由容器调用一个含有一些参数的构造器来完成,每一个参数代表一个依赖。调用一个含有一些特殊参数的静态工厂方法来构造bean
和基于构造器的依赖注入是基本等效的,这个论述对待一个构造器的参数和一个静态工厂方法的参数是相似的。下面的例子展示了一个类只能通过基于构造器的方式注入依赖。注意这是一个没有一点特殊的类,它是一个POJO,没有依赖容器的特殊接口,父类或者注解。
public class SimpleMovieLister {
// SimpleMovieLister有一个依赖MovieFinder
private MovieFinder movieFinder;
// 一个构造器,这样Spring容器才能注入一个MovieFinder
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
构造器参数解析