Spring学习-(1)SpringFramework官方文档翻译3
来源:互联网 发布:mac 导入图片位置 编辑:程序博客网 时间:2024/05/21 07:15
上一篇Spring学习-(1)SpringFramework官方文档翻译2
翻译了SpringFramework4.x版本的一些新特性,本篇沿着SpringFramework官方文档 继续向下做部分的翻译。这次主要是SpringFramework核心组件的内容。
三.核心技术(Core Technologies)
这部分的文档覆盖了Spring完整的技术。
在这些技术中最重要的要属Spring的控制反转(IoC)容器了,紧随其后的是全面覆盖的面向切面编程(AOP)技术。Spring有它自己的AOP框架,它很容易理解,而且成功解决了Java企业编程中80%的AOP需求。
Spring也集成了AspectsJ(目前在Java领域使用最丰富最成熟的AOP实现 )。
6, IoC7, Resources8, Validation, Data Binding, and Type Conversion9, Spring Expression Language (SpEL)10, Aspect Oriented Programming with Spring11, Spring AOP APIs
6. IoC容器
6.1 Spring的IoC容器和bean的简介
本章主要介绍Spring关于控制反转的实现。控制反转(IoC)又被称作依赖注入(DI)。它是一个对象定义其依赖的过程,它的依赖也就是与它一起合作的其它对象,这个过程只能通过构造方法参数、工厂方法参数、或者被构造或从工厂方法返回后通过settter方法设置其属性来实现。然后容器在创建bean时注入这些依赖关系。这个过程本质上是反过来的,由bean本身控制实例化,或者直接通过类的结构或Service定位器模式定位它自己的依赖,因此得其名曰控制反转。
org.springframework.beans
和org.springframework.context
两个包是Spring IoC容器的基础。BeanFactory
接口提供了一种先进的管理任何类型对象的配置机制。ApplicationContext
是BeanFactory
的子接口,它使得与Spring AOP特性集成更简单,添加了消息资源处理(用于国际化)、事件发布,还添加了应用层特定的上下文,比如用于web应用的WebApplicationContext
。
简而言之,BeanFactory
提供了配置框架和基础功能,ApplicationContext
在此基础上添加了更多的企业级特定功能。ApplicationContext
是BeanFactory
的完整超集,且仅用于本章描述Spring的IoC容器。更多使用BeanFactory
代替ApplicationContext
的信息请参考6.16 BeanFactory。
在Spring中,形成应用主干且被Spring IoC容器管理的对象称为beans。一个bean就是被Spring IoC容器实例化、装配和管理的对象。简单来说,一个bean就是你的应用中众多对象中一个。Beans和它们之间的依赖被容器中配置的元数据反射。
6.2 容器概述 org.springframework.context.ApplicationContext
代表了Spring的IoC容器,并且负责实例化、配置和装配前面提及的bean。容器通过读取配置元数据获取指令来决定哪些对象将被实例化、配置和装配。配置元数据反映在XML、Java注解或者Java代码中。它允许你表达组合成应用的对象及它们之间丰富的依赖关系。
Spring提供了几个可以直接使用的ApplicationContext
接口的实现。在独立的应用中通常创建ClassPathXmlApplicationContext
或者FileSystemXmlApplicationContext
的实例。XML是定义配置元数据的传统形式,也可以通过添加一小段XML配置让容器声明式地支持Java注解或Java代码配置元数据的形式。
在大部分应用场景中,显式的代码不需要实例化一个或多个Spring IoC容器。例如,在web应用中,在web.xml
中引用简单的八行(大约)样板XML描述符足够了(参考6.15.4 web应用中方便地实例化ApplicationContext)。如果使用Spring工具套件Eclipse开发环境,样板配置只需要简单地点击几次鼠标或键盘就可以被创建。
下面的图表展示了Spring是怎么工作的。应用程序的类文件和配置元数据是绑定在一起的,所以在ApplicationContext
被创建和初始化后,你将拥有完整的已配置好的和可执行的系统或应用。
图片6.1. Spring IoC容器
6.2.1 配置元数据
如前述图表所述,Spring IoC容器是消费配置元数据的一种形式,配置元数据代表开发者告诉Spring容器怎么去实例化、配置和装配应用中的对象。
配置元数据习惯上使用简单直观的XML形式,本章大部分地方也使用这种形式来传达Spring IoC容器的关键概念和特性。
基于XML的元数据不是配置元数据的唯一方式,Spring IoC容器本身是与实际写配置元数据的形式完全解耦的,现在许多开发者选择基于Java的配置形式来配置Spring应用程序。
关于使用其它形式配置元数据,参考:
1.基于注解的配置,Spring 2.5引入了基于注解配置元数据的形式。2.基于Java的配置,从Spring 3.0开始,Spring JavaConfig项目提供的许多特性成为了Spring框架核心的一部分。因此,可以在应用程序类的外部通过Java定义bean而不是在XML文件中。使用这些新特性,请参考@Configuration, @Bean, @Import和@DepensOn注解。
Spring配置包含了至少一个必须容器管理的bean定义。基于XML的配置元数据显示为被顶级<beans/>
元素包含的<bean/>
元素。Java配置典型地使用@Bean
注解@Configuration
类中方法。
这些bean定义与组成应用的实际对象通信。典型地,可以定义service层对象、数据访问对象(DAO)、诸如Struts Action实例的视图层对象、基础对象如Hibernate SessionFactories
、JMS Queues,等等,也不需要在容器中定义细粒度的领域对象,因为通常由DAO层和业务逻辑来创建和加载领域对象。然而,可以使用与AspectJ的集成来配置在IoC容器控制之外创建的对象。参考使用AspectJ依赖注入领域对象。
下面展示了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的类型并且需要使用全路径。id属性的值指向了合作的对象。XML指定合作者XML并没有显示在上述例子中,具体可参考依赖。
6.2.2 实例化容器
实例一个Spring IoC容器很简单,提供给ApplicationContext
构造方法的一个或多个路径是实际的资源字符串,这使得容器可以从不同的外部文件加载配置元数据,比如本地的文件系统,Java的CLASSPATH
,等等。
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
在学习Spring IoC容器之后,你可能想更多地了解Spring的Resource概念,它提供了一种很便利的机制以URI语法的形式读取输入流,参考7 Resources。特别地,Resource路径可被用于构造应用上下文,请参考7.7 应用上下文与资源路径
下面的例子展示了service层的对象配置文件(services.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"> <!-- services --> <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="itemDao" ref="itemDao"/> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for services go here --></beans>
下面的例子展示了数据访问对象daos.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="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for data access objects go here --></beans>
在前面的例子中,service层包含类PetStoreServiceImpl
和两个数据访问对象JpaAccountDao
和JpaItemDao
(基于JPA 对象关系映射标准)。property name
属性指向了JavaBean属性的名字,ref
属性指向另一个bean定义的名字。id
和ref
之间的链接表达了两个合作者间的依赖关系。详细的配置和对象依赖请参考依赖。
组合基于XML的配置元数据
跨越多个XML文件配置bean定义是很有用的,通常一个独立的XML配置文件代表架构中的一个逻辑层或模块。
可以使用ApplicationContext的构造方法加载所有这些XML片段的bean定义。构造方法传入多个Resource
路径即可,就像前面章节介绍的那样。替代方案,也可以使用一个或多个<import/>
元素从其它文件加载bean定义。例如:
<beans> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/></beans>
上面的例子中,外部的bean定义从三个文件中加载进来:services.xml
, messageSource.xml
和themeSource.xml
。所有的路径对于当前文件都是相对路径,所以services.xml
必须在同一个目录或当前路径所在的classpath下,而messageSource.xml
和themeSource.xml
则必须在resources路径下,resources必须当前文件目录的下一级。如你所见,开头的斜杠被忽略了,即使给了路径也是相对的,不使用开头的斜杠格式还更好点。被导入文件的内容,包括顶级的<beans/>
元素,必须是Spring Schema验证通过的XML bean定义。
可以使用“../”路径的形式引用父目录的文件,但这是不推荐的。这样做会在当前应用之外的文件上创建一个依赖。尤其是这种引用不推荐放在“classpath:”URL中(例如,“classpath:../services.xml”),在运行时会选择最近的classpath的根目录,然后再在其父目录中寻找。classpath配置改变可能会导致选择了不同的错误的目录。 可以总是使用全路径定位资源代替相对路径,例如“file:C:/config/services.xml”或“classpath:/config/services.xml”。然而,注意这样会把应用程序的配置与特定的绝对路径耦合了。一般地,可以间接配置那个绝对路径,例如,通过JVM系统属性“${…}”占位符在运行时加载。
6.2.3 使用容器 ApplicationContext
接口是一个先进的工厂,用于维护不同的bean及其依赖关系的注册。使用T getBean(String name, Class requiredType)
方法可以获取bean实例。
ApplicationContext
允许你可以像下面这样读取bean定义并访问它们:
// create and configure beansApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});// retrieve configured instancePetStoreService service = context.getBean("petStore", PetStoreService.class);// use configured instanceList<String> userList = service.getUsernameList();
使用getBean()
获取bean实例。ApplicationContext
接口还有几个其它方法用于获取bean,但是理想状态下应用程序代码应该从来都不会用到它们。应用程序代码的解应该不会调用getBean()
方法,而且不应该依赖于Spring的API。例如,Spring的web框架集成为不同的web框架类提供了依赖注入,比如controller和JSF管理的bean。
6.3 Bean概述
Spring IoC容器管理了至少一个bean,这些bean通过提供给容器的配置元数据来创建,例如,XML形式的<bean/>
定义。
在容器本身内部,这些bean定义表示为BeanDefinition对象,它包含以下元数据:
包限定的类名:被定义bean的实际实现类。
bean行为的配置元素,在容器中bean以哪种状态表现(scope,lifecycle,callback,等等)。
需要一起工作的其它对象的引用,这些引用又被称作合作者(collaborator)或依赖。
设置到新创建对象中的其它配置,例如,在bean中使用的连接数,用于管理连接池或池的大小。
元数据转化成了一系列的属性,组成了每个bean的定义。
表 6.1. bean定义
除了包含创建指定bean的信息的bean定义外,ApplicationContext
实现也允许注册用户在容器外创建的已存在的对象。可以通过ApplicationContext
的getBeanFactory()
方法获取其BeanFactory的实现DefaultListableBeanFactory
。DefaultListableBeanFactory
通过registerSingleton(…)
和registerBeanDefinition(..)
方法支持这样的注册。然而,典型的应用都是通过元数据定义bean。
bean元数据和手动提供的单例实例需要尽早注册,为了容器在自动装配时正确推断它们和其它内省的步骤。在一定程度上支持重写已存在的元数据和已存在的单例实例,然而在运行时注册新bean(并发访问工厂)并不正式地支持,并且可能在容器中导致并发访问异常和/或不一致的状态。
6.3.1 命名bean
每一个bean都有一个或多个标识符。这些标识符在托管bean的容器中必须唯一。一个bean通常只有一个标识符,但如果需要更多标识符,可以通过别名来实现。
在XML中,可以使用id
和/或name
属性来指定bean的标识符。id
属性允许显式地指定一个id
。按照约定,这些名字是字母数字的(’myBean’, ‘fooService’, 等),但是也可以包含一些特殊字符。如果你想引入其它别名,也可以在name
属性中指定,用逗号(,
)、分号(;
)或空格分割。作为历史记录,Spring 3.1之前的版本,id属性被定义为xsd:ID
,它会限制一些可能的字符。3.1开始,它被定义为xsd:string
类型。注意id的唯一性是被容器强制执行的,而不再是XML解析器。
给bean指定一个名字或id并不是必需的。如果没有显式地指定名字或id,容器会为那个bean生成一个唯一的名字。但是,如果要根据名字引用那个bean,比如通过ref元素或Service定位器查找,那么必须指定一个名字。不提供名字一般用于内部bean和自动装配合作者。
bean命名的约定
命名bean时采用标准Java对字段的命名约定。bean名字以小写字母开头,然后是驼峰式。例如,(不带引号)‘accountManager’
, ‘accountService’
, ‘userDao’
, ‘loginController’
, 等等。
按统一的规则命名bean更易于阅读和理解,而且,如果使用Spring AOP,当通过名字应用advice到一系列bean上将会非常有帮助。
在bean定义外给bean起别名
在bean的定义中,可以为其提供多个名字,通过与id属性指定的名字或name属性里的其它名字组合起来。这些名字对同一个bean是等价的,在有些情况下很有用,比如,允许应用中的每个组件通过其本身指定的名字引用共同的依赖。
在bean定义的地方指定所有的别名并不足够。有时候希望在其它地方为bean指定一个别名。这在大型系统中很常见,它们的配置被很多子系统分割,每个子系统有它自己的一系列bean定义。在XML配置中,可以使用<alias/>
元素指定别名。
<alias name="fromName" alias="toName"/>
在上述这个案例中,同一个容器中这个bean叫fromName
,在使用了别名定义也可以叫toName
。
例如,子系统A中的配置元数据通过subsystemA-datasource
引用一个数据源,子系统B中的配置元数据通过subsystemB-datasource
引用这个数据源,使用这两个子系统的主应用通过myApp-datasource
引用这个数据源。为了使用这三个名字引用相同的对象,可以在MyApp配置中添加如下的别名定义:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/><alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每个组件和主应用都可以通过不同的名字引用这个数据源了,并且可以保证不会与任何其它的定义冲突(有效地创建了一个命名空间),它们还引用了同一个bean。
Java配置
如果使用Java配置,@Bean
注解可以用于提供别名,参考6.12.3 章节使用@Bean注解。
6.3.2 实例化bean
一个bean的定义本质上就是创建一个或多个对象的食谱。容器查看这个食谱并使用被bean定义封装的配置元数据来创建(或获取)实际的对象。
如果使用XML配置,在<bean/>
元素的class
属性中指定被实例化的对象的类型(或类)即可。class
属性通常是必需的,它实际是BeanDefinition
实例的Class
属性(例外,参考使用实例的工厂方法实例化和6.7 Bean定义继承)。有两种使用Class
属性的方式:
1.典型地,容器直接通过反射调用bean的构造方法来创建一个bean,这个Java代码中使用new操作符是等价的。2.容器通过调用工厂类中的static工厂方法来创建一个bean。通过调用static工厂方法返回的对象类型可能是同一个类,也可能是完全不同的另一个类。 内部类名字。如果为一个static嵌套类配置bean定义,必须使用嵌套类的二进制名字。 例如,如果在com.example包中有一个类叫Foo,并且Foo类中有一个static嵌套类叫Bar,那么bean定义中的class属性的值应该是com.example.Foo$Bar。 注意使用$字符分割嵌套类和外部类。
使用构造方法实例化
用构造方法创建bean的方式,在Spring中所有正常的类都可以使用并兼容。也就是说,被开发的类不需要实现任何特定的接口或以特定的形式编码。仅仅指定bean类就足够了。但是,依靠什么样的类型来指定bean,你可能需要默认的空构造方法。
Spring的IoC窗口几乎可以管理所有的类,并不仅限于真的JavaBean。大部分用户更喜欢带有默认(无参)构造方法和适当setter/getter方法的JavaBean。你也可以拥有不同的非bean风格的类。例如,如果你需要使用不是JavaBean格式的连接池,Spring一样可以管理它。
在XML中,可以像下面这样指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/><bean name="anotherExample" class="examples.ExampleBeanTwo"/>
关于如何为构造方法提供参数(如果需要的话)及在对象被构造之后为其设置属性,请参考注入依赖。
使用静态工厂方法实例化
当使用静态工厂方法创建一个bean时,需要使用class
属性指定那个包含静态工厂方法的类,并使用factory-method
属性指定工厂方法的名字。应该可以调用这个方法(带参数的稍后讨论)并返回一个有效的对象,之后它就像用构造方法创建的对象一样对待。一种这样的bean定义的使用方法是在代码调用静态工厂。
下面的bean定义指定了通过调用工厂方法创建这个bean。这个定义没有指定返回类型,仅仅指定了包含工厂方法的类。在这个例子中,createInstance()
方法必须是静态的。
<bean id="clientService" class="examples.ClientService" factory-method="createInstance"/>
public class ClientService { private static ClientService clientService = new ClientService(); private ClientService() {} public static ClientService createInstance() { return clientService; }}
关于如何为工厂方法提供参数及在对象从工厂返回之后为其设置属性,请参考依赖与配置详解。
使用实例的工厂方法实例化(非静态)
与使用静态工厂方法实例化类似,使用实例的工厂方法实例化是调用一个已存在的bean的非静态方法来创建一个新的bean。为了使用这种机制,请把class
属性置空,在factory-bean
属性中指定被调用的用来创建对象的包含工厂方法的那个bean的名字,并factory-method
属性中设置工厂方法的名字。
<!-- the factory bean, which contains a method called createInstance() --><bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --></bean><!-- the bean to be created via the factory bean --><bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; }}
一个工厂类可以拥有多个工厂方法:
<bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- inject any dependencies required by this locator bean --></bean><bean id="clientService" factory-bean="serviceLocator" factory-method="createClientServiceInstance"/><bean id="accountService" factory-bean="serviceLocator" factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator { private static ClientService clientService = new ClientServiceImpl(); private static AccountService accountService = new AccountServiceImpl(); private DefaultServiceLocator() {} public ClientService createClientServiceInstance() { return clientService; } public AccountService createAccountServiceInstance() { return accountService; }}
工厂bean本身也可以通过依赖注入管理和配置,参考依赖与配置详解。
在Spring的文档中,工厂bean引用Spring容器配置的通过实例或静态工厂方法创建对象的bean。相比之下,FactoryBean引用的是Spring特定的FactoryBean。
6.4 依赖
一个典型的企业级应用包含了不止一个对象(或者用Spring的说法叫bean)。即使是最简单的应用也需要几个对象一起工作以展现给最终用户看到的连贯应用。下一节介绍怎么样定义一系列独立的bean使它们相互合作实现一个共同的目标。
6.4.1 依赖注入
依赖注入是一个对象定义其依赖的过程,它的依赖也就是与它一起合作的其它对象,这个过程只能通过构造方法参数、工厂方法参数、或者被构造或从工厂方法返回后通过setter方法设置其属性来实现。然后容器在创建这个bean时注入这些依赖。这个过程本质上是反过来的,由bean本身控制实例化,或者直接通过类的结构或Service定位器模式定位它自己的依赖,因此得其名曰控制反转。
使用依赖注入原则代码更干净,并且当对象提供了它们的依赖时解耦更有效。对象不查找它的依赖,且不知道依赖的位置或类。比如,类变得更容易测试,尤其是当依赖是基于接口或抽象基类时,这允许在单元测试时模拟实现。
依赖注入有两种主要的方式,基于构造方法的依赖注入和基于setter方法的依赖注入。
基于构造方法的依赖注入
基于构造方法的依赖注入,由容器调用带有参数的构造方法来完成,每个参数代表一个依赖。调用带有特定参数的静态工厂方法创建bean几乎是一样的,这里把构造方法的参数与静态工厂方法的参数同等对待。下面的例子展示了一个只能通过构造方法注入依赖的类。注意,这个类并没有什么特殊的地方,它仅仅只是一个没有依赖容器的特定接口、基类或注解的POJO。
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted...}
构造方法参数的解决
构造方法参数的解决匹配时使用参数的类型。如果没有潜在的歧义存在于bean定义的构造方法参数中,那么在bean实例化时bean定义中参数的顺序与构造方法中的顺序保持一致。例如,下面这个类:
package x.y;public class Foo { public Foo(Bar bar, Baz baz) { // ... }}
不存在潜在的歧义,假设Bar
和Baz
类没有继承关系。下面这样配置就可以了,不需要在<construct-arg/>
中显式地指定参数的索引或类型。
<beans> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> </bean> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/></beans>
当另一个bean被引用时,类型是已知的并且匹配可能出现(像上例一样)。
当使用简单类型时,比如<value>
true</value>
,Spring无法判断值的类型,所以没有类型就没办法匹配。例如,下面这个类:
package examples;public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}
在上述场景中,如果使用type
属性指定了参数的类型则容器会使用类型匹配。
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/></bean>
使用index
属性显式地指定参数的顺序:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/></bean>
除了解决多个简单值的起义,使用索引还可以解决构造方法中存在多个相同类型的参数的问题。注意索引从0开始。
也可以使用参数的名字来消除歧义:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/></bean>
注意,为了使其可以立即使用,代码必须开启调试标记来编译,这样Spring才能从构造方法中找到参数的名字。如果没有开启调试标记(或不想)编译,可以使用JDK的注解@ConstructorProperties显式地指定参数的名字。类看起来不得不像下面这样:
package examples;public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}
基于setter方法的依赖注入
基于setter方法的依赖注入,由容器在调用无参构造方法或无参静态工厂方法之后调用setter方法来实例化bean。
下面的例子展示了一个只能通过纯净的setter方法注入依赖的类。这个类符合Java的约定,它是一个没有依赖容器的特定接口、基类或注解的POJO。
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on the MovieFinder private MovieFinder movieFinder; // a setter method so that the Spring container can inject a MovieFinder public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted...}
ApplicationContext
对它管理的bean支持基于构造方法和基于setter方法的依赖注入,也支持在使用构造方法注入依赖之后再使用setter方法注入依赖。以BeanDefinition
的形式配置依赖,可以与PropertyEditor
实例一起把属性从一种形式转化为另一种形式。但是,大多数Spring用户不直接(编程式地)使用这些类,而是使用XML bean定义、注解的组件(例如,以@Component
、@Controller
注解的类)或基于Java的@Configuration
类的@Bean
方法。这些资源然后都被内部转化为了BeanDefinition
的实例,并用于加载完整的Spring IoC容器的实例。
基于构造方法或基于setter方法的依赖注入? 因为可以混合使用基于构造方法和基于setter方法的依赖注入,所以使用构造方法注入强制依赖并使用setter方法或配置方法注入可选依赖是一个不错的原则。注意,在setter方法上使用@Required注解可以使属性变成必需的依赖。 Spring团队一般倡导使用构造方法注入,因为它会使应用程序的组件实现为不可变的对象,并保证必需的依赖不为null。另外,构造方法注入的组件总是以完全初始化的状态返回给客户端(调用)代码。注意,大量的构造方法参数是代码的坏味道,这意味着类可能有很多责任,应该被重构为合适的部分以实现关注点的分离。 setter方法应该仅仅只用于可选依赖,这些可选依赖应该在类中被赋值合理的默认值。否则,在使用这项依赖的任何地方都要做非null检查。setter方法注入的好处之一是可以使那个类的对象稍后重新配置或重新注入。使用JMX MBeans的管理是使用setter注入很好的案例。 特定的类选择最合适的依赖注入方式。有时你并没有第三方类的源码,你就需要选择使用哪种方式。例如,如果一个第三方类没有暴露任何setter方法,那么只能选择构造方法注入了。
依赖注入的过程
容器按如下方式处理依赖注入:
·ApplicationContext被创建并初始化描述所有bean的配置元数据。配置元数据可以是XML、Java代码或注解。·对于每一个bean,它的依赖以属性、构造方法参数或静态工厂方法参数的形式表示。这些依赖在bean实际创建时被提供给它。·每一个属性或构造方法参数都是将被设置的值的实际定义,或容器中另一个bean的引用。·每一个值类型的属性或构造方法参数都会从特定的形式转化为它的实际类型。默认地,Spring可以把字符串形式的值转化为所有的内置类型,比如int, long, String, boolean,等等。
Spring容器在创建时会验证每个bean的配置。但是,bean的属性本身直到bean实际被创建时才会设置。单例作用域的或被设置为预先实例化(默认)的bean会在容器创建时被创建。作用域的定义请参考6.5 bean的作用域。否则,bean只在它被请求的时候才会被创建。创建一个bean会潜在地引起一系列的bean被创建,因为bean的依赖及其依赖的依赖(等等)会被创建并赋值。注意,那些不匹配的依赖可能稍后创建,比如,受影响的bean的首次创建(译者注:这句可能翻译的不到位,有兴趣的自己翻译下,原文为 Note that resolution mismatches among those dependencies may show up late, i.e. on first creation of the affected bean)。
循环依赖 如果你主要使用构造方法注入,很有可能创建一个无法解决的循环依赖场景。 例如,类A使用构造方法注入时需要类B的一个实例,类B使用构造方法注入时需要类A的一个实例。如果为类A和B配置bean互相注入,Spring IoC容器会在运行时检测出循环引用,并抛出异常BeanCurrentlyInCreationException。 一种解决方法是把一些类配置为使用setter方法注入而不是构造方法注入。作为替代方案,避免构造方法注入,而只使用setter方法注入。换句话说,尽管不推荐,但是可以通过setter方法注入配置循环依赖。 不像典型的案例(没有循环依赖),A和B之间的循环依赖使得一个bean在它本身完全初始化之前被注入了另一个bean(经典的先有鸡/先有蛋问题)。
你通常可以相信Spring做正确的事。它会在容器加载时检测配置问题,比如引用不存在的bean和循环依赖。Spring尽可能晚地设置属性和解决依赖,在bean实际被创建之后。这意味着Spring正确加载了,之后如果在创建对象或它的依赖时出现问题那么Spring又会产生异常。例如,因为一个缺少或失效的属性导致bean抛出了异常。这潜在地会推迟一些配置问题的可视性,这就是为什么ApplicationContext
的实现类默认预先实例化单例bean。在bean实际需要之前会花费一些时间和内存成本,因此在ApplicationContext
创建时会发现配置问题,而不是之后。你也可以重写默认的行为让单例bean延迟初始化,而不是预先实例化。
如果不存在循环依赖,当一个或多个合作的bean被注入依赖的bean时,每个合作的bean都将在注入依赖的bean之前被完全实例化。这意味着如果A依赖于B,那么在调用A的setter方法注入B之前会完全实例化B。换句话说,bean被实例化(如果不是预先实例化的单例),它的依赖被设置,然后相关的生命周期方法被调用(比如,配置的初始化方法或初始化bean的回调方法)。
依赖注入的示例
下面的例子使用XML配置setter方法注入。XML的部分配置如下:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/></bean><bean id="anotherExampleBean" class="examples.AnotherBean"/><bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; }}
上面的例子,XML中setter方法与属性匹配,下面的例子使用构造方法注入:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <!-- constructor injection using the neater ref attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/></bean><bean id="anotherExampleBean" class="examples.AnotherBean"/><bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; }}
bean定义中指定的构造方法参数将用作ExampleBean
的构造方法参数。
现在考虑使用另外一种方式,调用static
工厂方法代替构造方法返回对象的实例:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance"> <constructor-arg ref="anotherExampleBean"/> <constructor-arg ref="yetAnotherBean"/> <constructor-arg value="1"/></bean><bean id="anotherExampleBean" class="examples.AnotherBean"/><bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { // a private constructor private ExampleBean(...) { ... } // a static factory method; the arguments to this method can be // considered the dependencies of the bean that is returned, // regardless of how those arguments are actually used. public static ExampleBean createInstance ( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { ExampleBean eb = new ExampleBean (...); // some other operations... return eb; }}
通过<constructor-arg/>
元素为static
工厂方法提供参数,与实际使用构造方法时一样。工厂方法返回的类型不必与包含工厂方法的类的类型一致,即使在这个例子中是一样的。实例(非静态)工厂方法完全一样的使用方法(除了使用factory-bean
代替class
属性),细节不会在这里讨论。
6.4.2 依赖与配置详解
如前所述,可以定义bean的属性和构造方法的参数引用其它的bean(合作者),或设置值。在XML配置中,可以使用<property/>
或<constructor-arg/>
的属性达到这个目的。
直接设置值(原始类型,String,等等)
<property/>
的value
属性可以为属性或构造方法参数指定人类可读的字符串形式的值。Spring的转换器service会把这些值转换成属性或参数的实际类型。
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- results in a setDriverClassName(String) call --> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="masterkaoli"/></bean>
下面的例子使用p-namespace可以更简洁的表示:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="com.mysql.jdbc.Driver" p:url="jdbc:mysql://localhost:3306/mydb" p:username="root" p:password="masterkaoli"/></beans>
上述XML更简洁了,但是错误只能在运行时被发现,而不是设计的时候,除非使用类似IntelliJ IDEA或Spring工具套件(STS)的开发工具,它们可以在创建bean定义时自动完成属性的拼写。强烈推荐使用这类IDE。
也可以像下面这样配置java.util.Properties
:
<bean id="mappings" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <!-- typed as a java.util.Properties --> <property name="properties"> <value> jdbc.driver.className=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql://localhost:3306/mydb </value> </property></bean>
Spring容器会使用JavaBean的PropertyEditor机制把<value/>
中的内容转换成java.util.Properties实例。这样更简短,Spring团队也更喜欢使用内置的<value/>
元素代替value属性。
idref元素
idref元素是一种很简单的错误检查方式,可以把容器中另一个bean的id(字符串值-不是引用)传递给<constructor-arg/>
或<property/>
元素。
<bean id="theTargetBean" class="..."/><bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property></bean>
上面的片段在运行时与下面的片段完成一样:
<bean id="theTargetBean" class="..." /><bean id="client" class="..."> <property name="targetName" value="theTargetBean" /></bean>
第一种形式比第二种形式更好,因为使用idref标签时容器会在部署时验证引用的命名的bean是否实际存在。第二种形式不会对client bean的targetName属性的值执行任何验证。错误只能在client bean实际实例化时才能发现(可能导致致命的结果)。如果client bean是一个prototype类型的bean。那么错误很可能会在容器启动很久之后才能发现。
idref元素的local属性在4.0版本的xsd中不再支持了,因为它不提供对普通bean的引用。在升级的4.0的schema时只要把已存在的idref local改成idref bean即可。
一个常见的地方(至少在比Spring 2.0版本更早的版本中),在ProxyFactoryBean bean的定义中,元素的作用是在AOP拦截器的配置中。当您指定拦截器名称时,使用作用的元素可以防止您错误地拼写拦截器id。
引用其它bean(合作者)
ref是<constructor-arg/>
或<property/>
中的最后一个元素。可以使用它让一个bean的指定属性引用另一个bean(合作者)。被引用的bean就是这个bean的一个依赖,并且会在此属性设置前被初始化(如果此合作者是单例的,它可能已经被初始化了)。所有的引用最终都是对另一个对象的引用。作用域和检验由通过bean, local或parent属性指定的另一个对象的id或name决定。
通过bean的<ref/>
标签指定目标bean是最常用的形式,并且可以引用同一个或父容器中的任何bean,不论是否在同一个XML文件中。引用的值可能是目标bean的id属性,或name属性中的一个(译者注:因为name属性可以指定多个name)。
<ref bean="someBean"/>
通过parent属性可以指定对当前容器的父容器中的bean的引用。parent属性的值可能是目标bean的id属性或name属性中的一个,而且目标bean必须在当前容器的父容器中。这种引用形式主要出现在有容器继承并且想把父容器中存在的bean包装成同样名字的代理用于子容器的时候。
<!-- in the parent context --><bean id="accountService" class="com.foo.SimpleAccountService"> <!-- insert dependencies as required as here --></bean>
<!-- in the child (descendant) context --><bean id="accountService" <!-- bean name is the same as the parent bean --> class="org.springframework.aop.framework.ProxyFactoryBean"> <property name="target"> <ref parent="accountService"/> <!-- notice how we refer to the parent bean --> </property> <!-- insert other configuration and dependencies as required here --></bean>
ref元素的local属性在4.0版本的xsd中不再支持了,因为它不提供对普通bean的引用。在升级的4.0的schema时只要把已存在的ref local改成ref bean即可。(译者注:ref local只能引用同一个文件中的bean,所以不建议使用了,改成ref bean即可。)
内部bean
在<property/>
或<constructor-arg/>
元素内部定义的bean就是所谓的内部bean。
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property></bean>
内部bean不需要定义id或name,即使指定了,容器也不会使用它作为标识符。容器在创建内部bean时也会忽略其作用域标志,内部bean总是匿名的且总是随着外部bean一起创建。不可能把内部bean注入到除封闭bean以外的合作bean,也不可能单独访问它们。
有一种边界情况,可以从自定义作用域中接收销毁方法的回调,例如,对于包含在一个单例bean中的request作用域的内部bean,它的创建依赖于包含它的bean,但是销毁方法的回调允许它参与到request作用域的生命周期中。这并不是一种常见的情况,内部bean一般共享包含它的bean的作用域。
集合
在<list/>
, <set/>
, <map/>
和<props/>
元素中,可以分别设置Java集合类型List, Set, Map和Properties的属性和参数。
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property></bean>
map的键值或set的值也可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合的合并
Spring容器同样支持集合的合并。开发者可以定义一个父类型的<list/>
, <set/>
, <map/>
或<props/>
元素,然后让子类型继承它,也可以重写父集合里的值。也就是说,子集合的值是父集合与子集合值合并的结果,且子集合的元素重写了父集合中同样的元素。
这节主要讨论父子集合的合并机制,读者如果对父子bean的定义不太熟悉,可以先读相关章节再继续。
下面的例子展示了集合的合并:
<beans> <bean id="parent" abstract="true" class="example.ComplexObject"> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.com</prop> <prop key="support">support@example.com</prop> </props> </property> </bean> <bean id="child" parent="parent"> <property name="adminEmails"> <!-- the merge is specified on the child collection definition --> <props merge="true"> <prop key="sales">sales@example.com</prop> <prop key="support">support@example.co.uk</prop> </props> </property> </bean><beans>
注意子bean的adminEmails
属性上的<props/>
元素的merge=true
。当<child/>
被实例化时,它拥有一个adminEmails
Properties
集合,这个集合包含了父子两个集合adminEmails
合并的结果。
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
子集合Properties的值集继承了所有父集合的元素,并且子集合中的support值重写了父集合的值。
<list/>
, <map/>
和 <set/>
集合的合并行为也是类似的。在<list/>
元素合并的时候,因为List集合维护的是有序的值,所以父集合的值在子集合值的前面。在Map, Set和Properties中的值则不存在顺序。没有顺序的集合类型更有效,因此容器内部更倾向于使用Map, Set和Properties。
集合合并的局限性
不能合并不同的集合类型(比如,Map和List),如果试图这么做将会抛出异常。merge属性必须指定在子集合的定义上,在父集合上指定merge属性将是多余的且不会合并集合。
强类型集合
Java5引入了泛型类型,所以可以使用强类型的集合。也就是说,例如,可以声明一个只能包含String元素的集合类型。如果使用Spring把强类型的集合注入到一个bean,可以利用Spring的类型转换以便元素在被添加到集合之前转换成合适的类型。
public class Foo { private Map<String, Float> accounts; public void setAccounts(Map<String, Float> accounts) { this.accounts = accounts; }}
<beans> <bean id="foo" class="x.y.Foo"> <property name="accounts"> <map> <entry key="one" value="9.99"/> <entry key="two" value="2.75"/> <entry key="six" value="3.99"/> </map> </property> </bean></beans>
当foo的accounts属性准备注入的时候,会通过反射获得强类型Map
<bean class="ExampleBean"> <property name="email" value=""/></bean>
上面的例子与下面的Java代码是等价的:
exampleBean.setEmail("")
<null/>
元素处理null值。例如:
<bean class="ExampleBean"> <property name="email"> <null/> </property></bean>
上面的配置与下面的Java代码等价:
exampleBean.setEmail(null)
使用p命名空间简写XML
p命名空间使你可以使用bean元素的属性,而不是内置的元素,来描述属性值和合作的bean。
Spring支持使用命名空间扩展配置,这是基于XML的schema定义的。本章讨论的bean的配置形式是定义在XML schema文档中的。然而,p命名空间不是定义在XSD文件中的,它存在于Spring的核心包中。
下面例子中的两段XML是一样的结果,第一段使用标准的XML形式,第二段使用p命名空间形式。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="foo@bar.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/></beans>
这个例子展示了p命名空间中一个叫做email的属性。这会告诉Spring包含了一个属性声明。如前所述,p命名空间没有schema定义,所以可以把xml的属性名设置成java的属性名。
下面的例子包含两个引用了其它对象的bean定义:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" ref="jane"/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" p:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean></beans>
如你所见,此例不仅使用p命名空间定义属性值,还使用特定的形式声明了属性的引用。第一个bean定义使用<property name=”spouse” ref=”jane”/>
创建了从john到jane的引用,第二个bean定义使用p:spouse-ref=”jane”作为属性做了完全相同的事。这个例子中spouse是属性名,凭借-ref表明这不是直接的值而是对另一个对象的引用。
p命名空间并没有标准的XML形式灵活。例如,属性如果以Ref结尾则会与属性的引用冲突,标准的XML形式就不会有这样的问题。我们推荐仔细地选择自己的方式,并和团队成员交流,以避免XML文档中同时出现三种不同的方式。
使用c命名空间简写XML
与使用p命名空间简写XML类似,c命名空间是Spring3.1新引入的,允许使用内联属性配置构造方法的参数,而不用嵌套constructor-arg元素。
让我们一起回顾下基于构造方法的依赖注入这节,使用c: 命名空间如下:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- traditional declaration --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/></beans>
c: 命名空间与p: 命名空间使用一样的转换(后面的-ref用于bean引用),通过名字设置构造方法的参数。同样地,c命名空间也没有定义在XSD文件中(但是存在于Spring核心包中)。
对于极少数的情况,不能获得构造方法参数名时(通常不使用调试信息编译字节码),可以退而求其次使用参数的索引。
<!-- c-namespace index declaration --><bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
由于XML语法,索引符号需要以 _ 开头作为XML属性名,而不能以数字开头(即使在一些IDE中允许)。
经过实践,构造方法的解决机制在匹配参数方面很有效率,所以除非真的需要,我们推荐在所有配置的地方都使用名字符号。
合成属性名
可以使用合成或嵌套的属性名设置bean的属性,只要路径中除了最后的属性值的所有的组件都不为null,考虑使用如下bean定义:
<bean id="foo" class="foo.Bar"> <property name="fred.bob.sammy" value="123" /></bean>
foo有一个属性叫fred,fred有一个属性叫bob,bob有一个属性叫sammy,并且最后的sammy属性的值被设置为123。其中,foo的fred属性和fred的bob属性在bean构造后必须不为null,否则将会抛出空指针异常。
先翻译到这里,敬请期待以下的内容