上一篇Spring学习-(1)SpringFramework官方文档翻译2
翻译了SpringFramework4.x版本的一些新特性,本篇沿着SpringFramework官方文档 继续向下做部分的翻译。这次主要是SpringFramework核心组件的内容。
三.核心技术(Core Technologies)
这部分的文档覆盖了Spring完整的技术。
在这些技术中最重要的要属Spring的控制反转(IoC)容器了,紧随其后的是全面覆盖的面向切面编程(AOP)技术。Spring有它自己的AOP框架,它很容易理解,而且成功解决了Java企业编程中80%的AOP需求。
Spring也集成了AspectsJ(目前在Java领域使用最丰富最成熟的AOP实现 )。
1
2
3
4
5
6
76, IoC 7, Resources 8, Validation, Data Binding, and Type Conversion 9, Spring Expression Language (SpEL) 10, Aspect Oriented Programming with Spring 11, 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
2
31.基于注解的配置,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配置的基础结构:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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
,等等。
1
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
在学习Spring IoC容器之后,你可能想更多地了解Spring的Resource概念,它提供了一种很便利的机制以URI语法的形式读取输入流,参考7 Resources。特别地,Resource路径可被用于构造应用上下文,请参考7.7 应用上下文与资源路径
下面的例子展示了service层的对象配置文件(services.xml
):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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
文件:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<?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定义。例如:
1
2
3
4
5
6
7
8
<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定义。
1
2
3可以使用“../”路径的形式引用父目录的文件,但这是不推荐的。这样做会在当前应用之外的文件上创建一个依赖。尤其是这种引用不推荐放在“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定义并访问它们:
1
2
3
4
5
6
7
8
9
// 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();
使用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定义
属性 | 参考… |
---|---|
class | 6.3.2, “Instantiating beans” |
name | 6.3.1, “Naming beans” |
scope | 6.5, “Bean scopes” |
constructor arguments | 6.4.1, “Dependency Injection” |
properties | 6.4.1, “Dependency Injection” |
autowiring mode | 6.4.5, “Autowiring collaborators” |
lazy-initialization mode | 6.4.4, “Lazy-initialized beans” |
initialization method | the section called “Initialization callbacks” |
destruction method | the section called “Destruction callbacks” |
除了包含创建指定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/>
元素指定别名。
1
<alias name="fromName" alias="toName"/>
在上述这个案例中,同一个容器中这个bean叫fromName
,在使用了别名定义也可以叫toName
。
例如,子系统A中的配置元数据通过subsystemA-datasource
引用一个数据源,子系统B中的配置元数据通过subsystemB-datasource
引用这个数据源,使用这两个子系统的主应用通过myApp-datasource
引用这个数据源。为了使用这三个名字引用相同的对象,可以在MyApp配置中添加如下的别名定义:
1
2
<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
2
3
4
5
61.典型地,容器直接通过反射调用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类:
1
2
3
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
关于如何为构造方法提供参数(如果需要的话)及在对象被构造之后为其设置属性,请参考注入依赖。
使用静态工厂方法实例化
当使用静态工厂方法创建一个bean时,需要使用class
属性指定那个包含静态工厂方法的类,并使用factory-method
属性指定工厂方法的名字。应该可以调用这个方法(带参数的稍后讨论)并返回一个有效的对象,之后它就像用构造方法创建的对象一样对待。一种这样的bean定义的使用方法是在代码调用静态工厂。
下面的bean定义指定了通过调用工厂方法创建这个bean。这个定义没有指定返回类型,仅仅指定了包含工厂方法的类。在这个例子中,createInstance()
方法必须是静态的。
1
2
3
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
1
2
3
4
5
6
7
8
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
属性中设置工厂方法的名字。
1
2
3
4
5
6
7
8
9
<!-- 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"/>
1
2
3
4
5
6
7
8
9
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private DefaultServiceLocator() {}
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以拥有多个工厂方法:
1
2
3
4
5
6
7
8
9
10
11
<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"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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。
1
2
3
4
5
6
7
8
9
10
11
12
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定义中参数的顺序与构造方法中的顺序保持一致。例如,下面这个类:
1
2
3
4
5
6
7
8
9
package x.y;
public class Foo {
public Foo(Bar bar, Baz baz) {
// ...
}
}
不存在潜在的歧义,假设Bar
和Baz
类没有继承关系。下面这样配置就可以了,不需要在<construct-arg/>
中显式地指定参数的索引或类型。
1
2
3
4
5
6
7
8
9
10
<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无法判断值的类型,所以没有类型就没办法匹配。例如,下面这个类:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
属性指定了参数的类型则容器会使用类型匹配。
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
使用index
属性显式地指定参数的顺序:
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单值的起义,使用索引还可以解决构造方法中存在多个相同类型的参数的问题。注意索引从0开始。
也可以使用参数的名字来消除歧义:
1
2
3
4
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
注意,为了使其可以立即使用,代码必须开启调试标记来编译,这样Spring才能从构造方法中找到参数的名字。如果没有开启调试标记(或不想)编译,可以使用JDK的注解@ConstructorProperties显式地指定参数的名字。类看起来不得不像下面这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
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。
1
2
3
4
5
6
7
8
9
10
11
12
13
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容器的实例。
1
2
3
4
5
6基于构造方法或基于setter方法的依赖注入? 因为可以混合使用基于构造方法和基于setter方法的依赖注入,所以使用构造方法注入强制依赖并使用setter方法或配置方法注入可选依赖是一个不错的原则。注意,在setter方法上使用@Required注解可以使属性变成必需的依赖。 Spring团队一般倡导使用构造方法注入,因为它会使应用程序的组件实现为不可变的对象,并保证必需的依赖不为null。另外,构造方法注入的组件总是以完全初始化的状态返回给客户端(调用)代码。注意,大量的构造方法参数是代码的坏味道,这意味着类可能有很多责任,应该被重构为合适的部分以实现关注点的分离。 setter方法应该仅仅只用于可选依赖,这些可选依赖应该在类中被赋值合理的默认值。否则,在使用这项依赖的任何地方都要做非null检查。setter方法注入的好处之一是可以使那个类的对象稍后重新配置或重新注入。使用JMX MBeans的管理是使用setter注入很好的案例。 特定的类选择最合适的依赖注入方式。有时你并没有第三方类的源码,你就需要选择使用哪种方式。例如,如果一个第三方类没有暴露任何setter方法,那么只能选择构造方法注入了。
依赖注入的过程
容器按如下方式处理依赖注入:
1
2
3
4
5·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)。
1
2
3
4
5
6循环依赖 如果你主要使用构造方法注入,很有可能创建一个无法解决的循环依赖场景。 例如,类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的部分配置如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
<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"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19public 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方法与属性匹配,下面的例子使用构造方法注入:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
工厂方法代替构造方法返回对象的实例:
1
2
3
4
5
6
7
8
<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"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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会把这些值转换成属性或参数的实际类型。
1
2
3
4
5
6
7
<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可以更简洁的表示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<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
:
1
2
3
4
5
6
7
8
9
10
11
<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/>
元素。
1
2
3
4
5
6
7
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean" />
</property>
</bean>
上面的片段在运行时与下面的片段完成一样:
1
2
3
4
5
<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。那么错误很可能会在容器启动很久之后才能发现。
1
2idref元素的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)。
1
<ref bean="someBean"/>
通过parent属性可以指定对当前容器的父容器中的bean的引用。parent属性的值可能是目标bean的id属性或name属性中的一个,而且目标bean必须在当前容器的父容器中。这种引用形式主要出现在有容器继承并且想把父容器中存在的bean包装成同样名字的代理用于子容器的时候。
1
2
3
4
<!-- in the parent context -->
<bean id="accountService" class="com.foo.SimpleAccountService">
<!-- insert dependencies as required as here -->
</bean>
1
2
3
4
5
6
7
8
<!-- 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>
1
2ref元素的local属性在4.0版本的xsd中不再支持了,因为它不提供对普通bean的引用。在升级的4.0的schema时只要把已存在的ref local改成ref bean即可。(译者注:ref local只能引用同一个文件中的bean,所以不建议使用了,改成ref bean即可。)
内部bean
在<property/>
或<constructor-arg/>
元素内部定义的bean就是所谓的内部bean。
1
2
3
4
5
6
7
8
9
<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的属性和参数。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<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的定义不太熟悉,可以先读相关章节再继续。
下面的例子展示了集合的合并:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<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的类型转换以便元素在被添加到集合之前转换成合适的类型。
1
2
3
4
5
6
7
8
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
1
2
3
4
5
6
7
8
9
10
11
<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
1
2
3
<bean class="ExampleBean">
<property name="email" value=""/>
</bean>
上面的例子与下面的Java代码是等价的:
1
exampleBean.setEmail("")
<null/>
元素处理null值。例如:
1
2
3
4
5
<bean class="ExampleBean">
<property name="email">
<null/>
</property>
</bean>
上面的配置与下面的Java代码等价:
1
exampleBean.setEmail(null)
使用p命名空间简写XML
p命名空间使你可以使用bean元素的属性,而不是内置的元素,来描述属性值和合作的bean。
Spring支持使用命名空间扩展配置,这是基于XML的schema定义的。本章讨论的bean的配置形式是定义在XML schema文档中的。然而,p命名空间不是定义在XSD文件中的,它存在于Spring的核心包中。
下面例子中的两段XML是一样的结果,第一段使用标准的XML形式,第二段使用p命名空间形式。
1
2
3
4
5
6
7
8
9
10
11
12
13
<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定义:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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: 命名空间如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<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核心包中)。
对于极少数的情况,不能获得构造方法参数名时(通常不使用调试信息编译字节码),可以退而求其次使用参数的索引。
1
2
<!-- c-namespace index declaration -->
<bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
1
2由于XML语法,索引符号需要以 _ 开头作为XML属性名,而不能以数字开头(即使在一些IDE中允许)。
经过实践,构造方法的解决机制在匹配参数方面很有效率,所以除非真的需要,我们推荐在所有配置的地方都使用名字符号。
合成属性名
可以使用合成或嵌套的属性名设置bean的属性,只要路径中除了最后的属性值的所有的组件都不为null,考虑使用如下bean定义:
1
2
3
<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,否则将会抛出空指针异常。
6.4.3 使用depends-on
如果一个bean是另一个bean的依赖,那通常意味着这个bean被设置为另一个bean的属性。典型地,在XML配置中使用<ref/>
元素来完成这件事。但是,有时bean之间的依赖并不是直接的,例如,一个静态的初始化器需要被触发,比如在数据库驱动注册时。depends-on
属性可以明确地强制一个或多个bean在使用它的bean初始化之前被初始化。
下面的例子使用depends-on属性表示对一个单例bean的依赖:
1
2
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖,可以为depends-on
属性的值提供多个名字,使用逗号,空格或分号分割:
1
2
3
4
5
6
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
1
2depends-on属性能够同时指定初始化时依赖和通信销毁时依赖,这只能运用在单例bean的情况中。依赖的bean在给定的bean销毁之前被销毁。因此,depends-on也能够控制关闭的顺序。初始化的时候依赖的对象先初始化才能注入,销毁时需要依赖的对象先销毁才能解绑。
6.4.4 延迟初始化的bean
默认地,ApplicationContext
的实现创建和配置所有单例bean作为初始化过程的一部分。通常这种预初始化是令人满意的,因为配置或环境中的错误可以立即被发现,相反则可能需要数小时甚至数天才能发现错误。如果不需要预初始化,可以把bean定义为延迟初始化来阻止预初始化。延迟初始化的bean会告诉IoC容器在第一次请求到这个bean时才初始化,而不是启动的时候。
在XML中,这种行为是通过<bean/>
元素的lazy-init
属性控制的,例如:
1
2
3
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当ApplicationContext
使用上面的配置启动时时,名为lazy的bean并不急着预初始化,而not.lazy
则会预初始化。
但是,当一个延迟初始化的bean是另一个非延迟初始化的单例bean的依赖时,ApplicationContext
会在启动时创建这个延迟初始化的bean,因为它必须用来满足那个单例的依赖。这个延迟初始化的bean被注入到非延迟初始化的单例bean中。
也可以在<beans/>
元素上设置其default-lazy-init
属性以便在容器级别控制延迟初始化。
1
2
3
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
6.4.5 自动装配合作者
Spring容器可以相互合作的bean间自动装配其关系。你可让让Spring通过检查ApplicationContext
的内容自动为你解决bean之间的依赖。自动装配有以下优点:
1
2
3·自动装配将极大地减少指定属性或构造方法参数的需要(在这点上,其它机制比如本章其它小节讲解的bean模板也是有价值的)。 ·自动装配可以更新配置当你的对象进化时。例如,如果你需要为一个类添加一个依赖,那么不需要修改配置就可以自动满足。因此,自动装配在开发期间非常有用,但不否定在代码库变得更稳定的时候切换到显式的装配。
使用XML配置时,可以为带有autowire属性的bean定义指定自动装配的模式。自动装配的功能有四种模式。可以为每个自动装配的bean指定一种模式。
表6.2 自动装配的模式
模式 | 解释 |
---|---|
no | 默认地没有自动自动装配。bean的引用必须通过ref元素定义。对于大型部署,不推荐更改默认设置,因为显式地指定合作者能够更好地控制且更清晰。在一定程度上,这也记录了系统的结构。 |
byName | 按属性名称自动装配。Spring为待装配的属性寻找同名的bean。例如,如果一个bean被设置为按属性名称自动装配,且它包含一个属性叫master(亦即,它有setMaster(…)方法),Spring会找到一个名叫master的bean并把它设置到这个属性中。 |
byType | 按属性的类型自动装配,如果这个属性的类型在容器中只存在一个bean。如果多于一个,则会抛出异常,这意味着不能为那个bean使用按类型装配。如果一个都没有,则什么事都不会发生,这个属性不会被装配。 |
constructor | 与按类型装配类似,只不过用于构造方法的参数。如果这个构造方法的参数类型在容器中不存在明确的一个bean,将会抛出异常。 |
使用按类型装配或构造方法自动装配模式,可以装配数组和集合类型。在这个情况下,容器中所有匹配期望类型的候选者都将被提供用来满足此依赖。你可以自动装配强类型的Map如果其键的类型是String。自动装配Map的值将包含所有匹配期望类型的bean实例,并且Map的键将包含对应的bean的名字。
可以联合使用自动装配行为和依赖检查,其中依赖检查会在自动装配完成后执行。
自动装配的局限性和缺点
如果一个项目一直使用自动装配,它会运行得很好。如果只是用在一两个bean上,则会出现混乱。
自动装配有如下局限性和缺点:
1
2
3
4
5·在property和constructor-arg上显式设置的依赖总是覆盖自动装配。而且,不能自动装配所谓的简单属性,如基本类型、String和Classes(也包括简单属性的数组)。这是设计层面的局限性。 ·自动装配没有显式装配精确。如上表所述,尽管Spring很小心地避免了模棱两可的猜测,但其管理的对象之间的关系并不会被明确地记录。 ·从Spring容器生成文档的工具可能找不到装配信息。 ·容器中的多个bean定义可能会匹配setter方法或构造方法参数指定的类型。对于数组、集合或Map,这未必是个问题。但是,对于那些需要单个值的依赖,这种歧义并不能随意解决。如果没有各自不同的bean定义,将会抛出异常。
在上述场景中,有如下几种选择:
1
2
3
4
5·放弃自动装配,改为显式地装配。 ·设置bean的autowire-candidate属性为false以避免自动装配,这会在下一章节讲解。 ·设置<bean/>标签的primary属性为true,从而为其指定一个单独的bean作为主要的候选者。 ·使用基于注解的配置实现更细粒度的控制,参考6.9 基于注解的容器配置。
避免bean自动装配
在单个bean上,可以避免自动装配。在xml形式中,设置<bean>
元素的autowire-candidate
属性为false,容器就不会让这个bean进入自动装配的架构中(包括注解形式的配置,比如@Autowired
)。
也可以通过定义bean名称的匹配模式避免自动装配。顶级元素<beans>
的属性default-autowire-candidates
可以接受一个或多个模式。例如,为了限制以Repository结尾的bean自动装配,提供一个值*Repository即可。如果需要提供多个模式,请以逗号分隔。bean定义上的autowire-candidate
属性优先级要更高一点,对于这类bean,匹配模式将不起作用。
这项技术对于那些你不想它们被自动注入到其它bean的bean非常有用。这并不意味着它自己不能使用自动装配,而是,它不是其它bean自动装配的候选者。
6.4.6 方法注入
在大部分应用场景中,容器中的大多数bean都是单例的。当一个单例bean需要与另一个单例bean合作,或者一个非单例bean需要与另外一个非单例bean合作的时候,仅仅定义一个bean作为另一个的属性就能解决它们的依赖关系。如果bean的生命周期不一致就会出现问题。假如,一个单例bean A 需要使用一个非单例bean B,也许在每次调用A的方法时都需要。容器仅仅创建这个单例bean A 一次,所以只有一次机会设置其属性。容器不能在 A 每次调用的时候都提供一个新的 B 的实例。
一种解决方法是放弃控制反转。你可以通过实现ApplicationContextAware
接口使 A 能连接到容器,并在每次 A 需要 B 的实例的时候调用容器的getBean(“B”)
方法取得新的 B 的实例。下面是这种方式的一种案例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
上面这种方式并不推荐使用,因为业务代码跟spring耦合了。方法注入,从某种角度来看是spring IoC容器的高级特性,允许以一种干净的方式处理这种用例。
可以在这篇博客找到更多关于方法注入的信息。
查找方法注入
查找方法注入是容器的一项能力,它可以重写容器管理的bean的方法,并返回另一个bean的查找结果。查找往往涉及到前面描述的那种原型(prototype)bean。spring利用CGLIB库动态地生成字节码子类,从而重写方法以实现查找方法注入。
1
2
3
4
5
6·为了能使动态的子类有效,被继承的类不能是final,且被重写的方法也不能是final。 ·单元测试一个具有抽象方法的类时,需要手动继承此类并重写其抽象方法。 ·组件扫描的具体方法也需要具体类。 ·一项关键限制是查找方法不能使用工厂方法和配置类中的@Bean方法,因为容器不会在运行时创建一个子类及其实例。 ·最后,方法注入的目标对象不能被序列化。
查看前面关于CommandManager
类的代码片段,可以发现spring容器会动态生成createCommand()
方法的实现。CommandManager
不会有任何的spring依赖,如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
客户端类包含了将被注入的方法(本例中的CommandManager
),被注入的方法需要如下签名:
1
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,动态生成的子类会实现这个方法。另外,动态生成的子类也会重写原类中的具体方法。例如:
1
2
3
4
5
6
7
8
9
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="command" class="fiona.apple.AsyncCommand" scope="prototype">
<!-- inject dependencies here as required -->
</bean>
<!-- commandProcessor uses statefulCommandHelper -->
<bean id="commandManager" class="fiona.apple.CommandManager">
<lookup-method name="createCommand" bean="command"/>
</bean>
commandManager
这个bean会在任何它需要command实例的时候调用其createCommand()
方法。如果实际需要,则必须把command声明为原型(prototype)。如果被声明为单例(singleton),则每次返回的都是同一个command实例。
1
2有兴趣的读者可能会发现ServiceLocatorFactoryBean(位于包org.springframework.beans.factory.config中)就是这么用的。ServiceLocatorFactoryBean中用法与其它工具类一样,ObjectFactoryCreatingFactoryBean,但是它允许你使用自己的查找接口而不是spring指定的查找接口。请参考附加信息中这些类的javadoc文档。
任意的方法替换
方法注入的一种很少使用的形式,它是使用另外的方法实现替换目标bean中的任意方法。读者可以跳过本章的剩余部分直到这项功能真正需要的时候再回来看。
以xml形式为例,可以使用replaced-method
元素指定用另一种实现替换bean中已存在的方法。如下例,我们希望重写其computeValue
方法:
1
2
3
4
5
6
7
8
9
public class MyValueCalculator {
public String computeValue(String input) {
// some real code...
}
// some other methods...
}
一个实现了org.springframework.beans.factory.support.MethodReplacer
接口的类提供了一个新的方法定义。
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* meant to be used to override the existing computeValue(String)
* implementation in MyValueCalculator
*/
public class ReplacementComputeValue implements MethodReplacer {
public Object reimplement(Object o, Method m, Object[] args) throws Throwable {
// get the input value, work with it, and return a computed result
String input = (String) args[0];
...
return ...;
}
}
原类的bean定义可能看起来像这样:
1
2
3
4
5
6
7
8
<bean id="myValueCalculator" class="x.y.z.MyValueCalculator">
<!-- arbitrary method replacement -->
<replaced-method name="computeValue" replacer="replacementComputeValue">
<arg-type>String</arg-type>
</replaced-method>
</bean>
<bean id="replacementComputeValue" class="a.b.c.ReplacementComputeValue"/>
可以使用一个或多个<arg-type/>
元素指明原方法的参数签名。参数的签名仅仅当原方法有多个重载方法时才是必要的。为了方便,string类型的参数也可以是其全路径的子串,例如,以下方式均可匹配java.lang.String
:
1
2
3
java.lang.String
String
Str
因为参数的个数往往就能够区分每一种可能了,这种简写方法可以少打几个字,所以你可以只输入最短的字符就能够匹配参数类型。
6.5 bean的作用域
当创建一个bean定义时,就给了一份创建那个类的实例的食谱。bean定义是一份食谱,这个想法很重要,因为那意味着,可以根据一份食谱创建很多实例。
不仅可以控制bean实例的各种各样的依赖关系和配置值,还可以控制这些实例的作用域(scope)。这种方式很强大且是弹性的,因为可以通过配置选择实例的作用域,而不需要在类级别控制其作用域。bean可以被部署成多个作用域之一,spring支持很多作用域,对于web应用可以使用5种。
下面的作用域都是可以直接使用的,也可以创建自定义作用域(这里直接引用英文版)。
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance per Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request; that is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext. |
session | Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext. |
global session | Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext. |
application | Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext. |
- 从Spring 3.0开始,还有一个thread作用域,但默认并没有被注册。更多信息请参考SimpleThreadScope
的文档。关于怎么注册这个作用域及任何自定义作用域,请参考使用自定义作用域。
先翻译到这里,敬请期待以下的内容
最后
以上就是繁荣往事最近收集整理的关于SpringFramework学习-(1)SpringFramework官方文档翻译3三.核心技术(Core Technologies)的全部内容,更多相关SpringFramework学习-(1)SpringFramework官方文档翻译3三.核心技术(Core内容请搜索靠谱客的其他文章。
发表评论 取消回复