[原创-翻译]Spring3.1 文档中文版--第四章:IoC容器
(原创翻译,转载请注明出处,不得用于商业目的)
4.The IoC container
这一章涵盖了Spring框架对IOC [1] 理念的实现。IoC也被称为依赖注入(DI),它是对象定义自己的依赖的方法,所谓依赖(dependency)就是与对象一起工作的其它对象。可以通过三种方式来定义所依赖的对象:构造函数的参数,工厂方法的参数,或者属性(在对象被构造出来或从工厂方法返回之后被设置)。由容器负责创建Bean对象并注入这些依赖。这个过程相对于Bean直接使用构造函数,或者使用一种类似于服务定位模式(serviceLocator Pattern)的机制, 自己来控制实例化和定位它的依赖,从根本上是颠倒过来了的,因此被称作控制反转。
org.springframework.beans
和org.springframework.context
这两个包是spring框架中IoC容器的基石。 BeanFactory
接口提供了一种高级的配置机制,能够管理任意类型的对象。ApplicationContext
是BeanFactory
的一个子接口。它增加了更多的特性,可以更方便的与Spring的AOP特性,消息资源处理(用于国际化程序),事件发布(event publication),以及应用层环境(application-layer specific contexts)集成。(应用层环境的一个典型例子是用在Web应用程序中的WebApplicationContext
)。
简而言之, BeanFactory
提供了配置框架和基本功能,而ApplicationContext
增加了更多企业级的功能。ApplicationContext
是 BeanFactory
的一个完全超集。在本章我们只使用 ApplicationContext
来讲述Spring IoC容器。如果想了解更多BeanFactory
的信息,请参考第4.15节, BeanFactory。
在Spring中,组成应用程序框架,并且由Spring IoC容器来管理的对象被称作beans。一个bean是一个对象,它由Spring IoC容器负责实例化,装配(为属性赋值),并且由容器来管理。除此之外,它只是应用程序里的一个普通对象。Beans以及它们之间的 依赖关系,都可以由容器使用的配置元数据(configuration metadata)来体现。
接口org.springframework.context.ApplicationContext
代 表Spring IoC容器,负责实例化,配置和组装前面提到的beans。容器通过读取配置元数据来知道需要实例化、配置和组装哪些对象。配置元数据可以是XML文 件,Java注解或者Java代码。配置元数据可以表达出组成应程序的对象,以及对象之间丰富的依赖关系。
Spring提供了ApplicationContext
接口的许多实现,这些实现都是“开箱即用”(out-of-the-box)的。在独立的应用程序(standalone applications)里面,通常会创建一个 ClassPathXmlApplicationContext
或者FileSystemXmlApplicationContext
的实例。尽管XML作为定义配置元数据的传统方式曾经盛极一时,但是现在你也可以使用Java注解或者Java代码来提供元数据,只需要提供一点XML配置文件来声明对这些元数据格式的支持就可以了。
在大部分应用中,程序员并不需要写代码来实例化Spring IoC 容器的实例。例如,在Web程序里,在web.xml
文件中写上大约8行J2EE Web描述语句就够用了。(参考 Section4.14.4, Convenient ApplicationContext instantiation for web applications)。如果你使用SpringSource Tool Suite(基于eclipse的开发环境),或者 Spring Roo,轻点几下鼠标和键盘,这段程式化的配置就可以轻松的被创建出来。
下图是一个Spring工作原理的顶层视图。你的应用程序类和配置元数据被结合在一起,所以在ApplicationContext
被创建和实例化之后,你就拥有一个完全可配置和可执行的系统或应用程序了。
![](http://images0.cnblogs.com/blog/412441/201303/12085632-54df564ac59b4d0c896f450c3a7dd086.png)
Spring IoC 容器
正如前面图上所示,Spring IoC 容器使用一种格式的配置元数据;通过配置元数据,开发者可以告诉Spring容器如何来实例化,配置和组装应用程序中的对象。
传统上是通过一种简单,直观的XML文件来提供配置元数据的,本章的大部分也使用这种格式来阐述Spring IoC 容器的关键概念和它的一些特性。
![]() |
Note |
---|---|
基于XML的元数据 不是唯一一种提供配置元数据的方式。Spring IoC 容器本身和配置元数据的格式是完全解耦的。 |
关于在Spring容器中使用其它格式的元数据,请参考:
-
基于注解的配置:Spring 2.5 引入了基于注解的配置元数据
-
基于Java代码的配置:从Spring 3.0 开始,很多由 Spring JavaConfig project这个项目提供的特性成为了Spring Framework 核心的一部分。这样你就可以使用Java代码,来定义独立于应用程序类的bean了。要使用这些新的特性,请参考
@Configuration
,@Bean, @Import
和@DependsOn
这几个注解。
Spring配置文件至少包括一个需要容器管理的bean,通常都会超过一个bean。基于XML的配置元数据( XML-based configuration metadata)在<bean/>
元素中配置这些bean,它位于顶层的 <beans/>
元素里面。
Bean的定义对应于组成应用程序的实际对象。通常,你都会定义多层的对象,DAO对象,表示层对象(比如Structs的Action
实例),基础设施对象(比如 Hibernate的 SessionFactories
), JMS Queues
,等等。在容器里面通常不会配置细粒度的domain对象,因为一般是由DAO层和业务逻辑来创建和加载domain对象的。然而,你可以使用Spring与AspectJ集成的特性来配置这些在IoC容器的控制范围之外创建的对象。参考 在Spring中,使用 AspectJ来依赖注入domain对象(Using AspectJ to dependency-inject domain objects with Spring).
下面的例子展示了基于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-3.0.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属性的值也可以指向协作对象。这个例子中并没有展示如何引用协作对象。要获取更多信息,请参考依赖(Dependencies)
实例化一个SpringIoC容器是很简单的事情。传递给ApplicationContext
的构造函数的路径其实就是资源字符串。容器可以从各种外部的资源来装载配置元数据,外部资源包括本地文件系统,或者java的CLASSPATH
等等。
ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"});
![]() |
Note |
---|---|
在学习了Spring的IoC容器的知识之后,你可能想了解更多关于Spring |
下面的例子演示了服务层对象 (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-3.0.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-3.0.xsd"> <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapAccountDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.ibatis.SqlMapItemDao"> <!-- additional collaborators and configuration for this bean go here --> </bean> <!-- more bean definitions for data access objects go here --> </beans>
在前面的例子中, 服务层由PetStoreServiceImpl
,和两个数据访问对象 SqlMapAccountDao
和 SqlMapItemDao 组成,这两个DAO对象基于iBatisO/R映射框架(Object/Relational mapping framework)。name属性
代表JavaBean的名称,ref
属性则指向另外一个bean定义的名称 。id和ref元素之间的这种联系表达了协作对象之间的依赖关系。 关于配置一个对象依赖关系的详细信息,请参考 依赖(Dependencies)。
通常,把bean的定义分配到多个XML文件里面是有益的。每一个XML配置文件都代表系统结构中的一个逻辑层或者模块。
可以使用ApplicationContext 的构造函数来从所有这些XML片段装载bean定义。正如前面的小节里面介绍过的,ApplicationContext 的构造函数可以处理多个Resource
。另一种处理多个XML配置文件的办法是,使用<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
。这些外部XML文件的路径都是相对于执行导入操作的文件。所以services.xml
一定是和执行导入操作的文件处于同一个目录,而messageSource.xml
和themeSource.xml
则一定是位于执行导入文件的下一级resources
目录下。可以看到,前导的斜杠'/'被忽略了,而是认为这些路径都是相对路径。最好是完全不使用前导斜杠。被导入的文件内容,包括顶层的<beans/>
元素,都必须是合法的XML bean定义,必须符合Spring Schema或DTD的格式。
![]() |
Note |
---|---|
有时候可能会使用"../"这样的相对路径来 引用父目录的文件,我们不推荐这么做。这样做会导致当前应用程序对于程序外部文件的依赖。特别不推荐对于"classpath:"类型的URL使用父目录 (比如,"classpath:../services.xml"),因为运行时的解析程序会选择"最近"的classpath根目录,然后查找它的父目 录。classpath配置的改变可能会导致选择一个和预想的不一样的错误目录。 除了相对路径之外,也可以使用完整路径:例 如,"file:C:/config/services.xml"或者"classpath:/config/services.xml"。不过,这样的 话,程序的配置就和具体的绝对路径耦合在一起了。通常会选择在这种绝对路径中保留一个占位符,通过"${...}"这种占位符,可以在运行时被解析为 JVM系统属性。 |
ApplicationContext
是一个高级工厂接口,它能够维护不同的bean和它们之间依赖关系的注册。使用T getBean(String name, Class<T>requiredType)
这个方法可以获取bean的实例。
通过ApplicationContext
,你可以读取bean定义并且访问它们的成员,如下所示:
// create and configure beans ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); // retrieve configured instance PetStoreServiceImpl service = context.getBean("petStore", PetStoreServiceImpl.class); // use configured instance List userList service.getUsernameList();
你可以使用getBean()
来获取bean的实例。ApplicationContext
接口也提供了其它的接口来获取bean,但是在你的程序代码里面,最好不要使用这些方法。实际上,最好连getBean()
方法都不要使用,因为这样你的程序就完全不依赖于Spring API。Spring与web框架的集成提供了依赖注入的方式来使用各种web框架类,比如controller和JSF-managed bean。
一个Spring IoC 容器管理着很多的bean。这些bean是根据你提供给容器的配置元数据来创建的,比如,以XML的方式定义的<bean/>
。
在容器内部,bean定义被表示为 BeanDefinition
对象,它包含下面的元数据:
-
包括包路径的完整类名:通常是所定义bean的实现类。
-
bean的行为配置元素,描述了bean在容器中的行为,包括:scope,lifecycle callbacks 等等
-
bean为了实现其功能而对其它bean的引用,这些被 引用的bean也被称作协作者(collaborators) 或者 依赖(dependencies).
-
其它的可配置项,用于新创建的对象。比如,管理连接池的bean中的连接数以及连接池的上限。
元数据会被转换成组成bean定义的属性集合。
表4.1.The bean definition
Property | Explained in... |
---|---|
class | |
name | |
scope | |
constructor arguments | |
properties | |
autowiring mode | |
lazy-initialization mode | |
initialization method | |
destruction method |
除了在bean定义中包含如何创建一个具体的bean的信息之外,ApplicationContext
实现也允许注册已经由用户在容器外部创建的对象。使用ApplicationContext的BeanFactory可以实现这一点。getBeanFactory()
方法可以返回BeanFactory的实现:DefaultListableBeanFactory
。DefaultListableBeanFactory
类的registerSingleton(..)
和 registerBeanDefinition(..)
方法 支持这种注册。不过,通常情况下,应用程序只会使用通过bean元数据定义的bean。
4.3.1 Bean的命名(Naming beans)
一个bean可以有一个或多个ID。这些ID在包含这个bean的容器中必须是唯一的。一个bean通常只有一个ID,如果它需要多个的话,多出来的ID可以被认为是别名。
在基于XML的配置元数据中,可以使用id
和/或 name
来指定一个bean的ID(s)。id
属性只允许你指定唯一的一个id(相比而言,name属性则可以指定多个)。一般情况下,name属性由字母组成(类似于"myBean","fooService"等等),但是特殊字符也是可以的。如果你想为一个bean引入其它的别名,你也可以在 name
属性里面指定别名,只需要使用逗号( ,
),分号( ;
),或者其它的空白符分隔即可。在Spring 3.1版本以前, id
属性是xsd:ID
类型,这样就限制了可使用的字符范围。从3.1版本开始,变成了xsd:string
类型。这样改变之后,XML解析器不再保证ID的唯一性,但是容器仍然强制ID的唯一性。
对于一个bean来说,name和ID不是必须的。如果没有明确的指定name或id,容器会为这个bean产生一个唯一的名称。然而,如果需要通过name来引用一个bean(使用ref
元素,或者使用Service Locator来查询),那么就必须提供一个name。不提供name的动机往往是使用内部 beans(inner beans)或者是自动装配协作者(autowiring collaborators).
在Bean的定义里面,通过使用最多一个 id
属性,以及任意数量的name
属性,可以为一个Bean指定多个名字。这些名字都等同于同一个Bean的别名。在有些情形中,这还是很有用的。比如说,可以让程序中的每一个组件都使用特定于该组件的名称来引用一个通用的Bean。
然而,在Bean真正定义的地方指定的别名有时候还不够用。有时候,确实需要为一个定义于别处的Bean引入一个别名。这往往发生在大型系统里面,配置文件被分解到每一个子系统,每一个子系统都有它自己的一个对象定义集合。在基于XML的配置元数据中,可以使用<alias/>
元素来为bean定义别名。
<alias name="fromName" alias="toName"/>
在这个例子中,同一个容器中一个名为fromName
的Bean,在使用了别名定义之后,也可以被引用为toName
。
举个例子,子系统A的配置元数据可能通过名称 'subsystemA-dataSource'来引用一个数据源。而子系统B的配置文件可能通过名称'subsystemB-dataSource'来 引用一个数据源。当编写同时使用子系统A和B的主程序的时候,主程序通过名称'myApp-dataSource'来引用数据源。为了使这三个名称指向同 一个对象,可以向MyApp程序的配置元数据中添加下面的别名定义:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/> <alias name="subsystemA-dataSource" alias="myApp-dataSource" />
这样,每一个组件以及主程序都通过一个唯一的名字引用数据源,并且保证不会互相冲突(有效的创建了一个名称空间),但都指向同一个bean。
一个Bean的定义本质上是创建对象的配方。在需要的时候,bean容器会查看这个配方,然后使用bean定义中封装的配置元数据来创建(或者获取)一个实际的Bean。
如果使用基于XML的配置元数据,可以通过<bean/>
元素的class
属性来指定需要实例化的对象类型。这个class
属性在Spring的内部其实是BeanDefinition
实例的Class
属性,通常是不可缺少的。(但是也有例外。参考4.3.2.3节,使用工厂方法来实例化Bean 和 4.7节, Bean定义的继承.)。可以按如下两种方式来使用Class
属性:
-
典型的情形是直接指定将要构造的Bean的class类型。这种情况下,容器会使用反射技术调用该Class的构造函数来直接创建Bean,就等价于使用
new
操作符的Java代码。
-
非典型的使用方法是指定一个Class,它包含了用于生成Bean对象的
static
类型的工厂方法。容器会调用这个类的一个static
类型工厂方法来生产Bean对象。返回的Bean对象可能是指定的Class类型的对象,也可能是完全不同的另外一种类型的对象。
在使用构造函数来创建一个Bean时,所有普通的类都能够用于Spring。也就是 说,所开发的类并不需要实现任何特定的接口,也不需要按照某种特定的方式来编写。只需要在Bean定义里指定class的名称就可以了。不过,也可能需要 提供一个默认的(空的)构造函数,这取决于使用哪种类型的IoC。
Spring IoC基本上可以管理你需要让它管理的任何类,并不限于标准的JavaBean。大部分用户倾向于使用真正的JavaBean,这种类仅有一个默认的(没 有参数)构造函数以及一些必要的setter和getter方法。你也可以在你的容器中使用一些更奇异的,不具有Bean的特征的类。例如,你想使用一个 旧式的连接池,它完全不满足JavaBean的规范,但是Spring照样可以管理这个Bean。
使用基于XML的配置元数据,你可以像下面这样指定Bean的class属性:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
关于为构造函数提供参数(如果需要的话)以及为一个已经构造的对象设置对象实例属性,请参考依赖注入。
当定义一个通过静态工厂方法创建的Bean时,可以使用class
属性来指定包含静态工厂方法的类,还得通过一个名为factory-method
的属性来指定工厂方法的名字。你必须能够调用这个方法(可能需要参数,参见后面的介绍),并且返回一个“活的”对象。这个对象和使用构造函数创建的对象没有任何区别。这种Bean定义方式的用处之一是调用一个遗留代码中的static
类型工厂方法。
下面的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会调用一个非静态方法来实例化Bean,这个方法隶属于容器中已存在一个的Bean。使用这种机制的话,需要把class
属性置空,并在factory-bean
属性中指定当前(父/子)容器中一个Bean的名字,这个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自身也可以被管理,并通过依赖注入(DI)的方式进行配置。参考依赖及配置详解
![]() |
Note |
---|---|
在Spring的文档中,factory bean这个术语指的是在Spring容器中配置的一个Bean,这个Bean可以通过实例工厂方法或者静态工厂方法创建对象。相比而言, |
通常一个应用程序都不会只有一个对象(在Spring的说法里叫做Bean)。即使是最 简单的应用程序也有多个对象,它们共同协作,以构成最终用户所看到的一个完整的应用程序。下面这一节就讲述如何从定义一系列独立的Bean开始,逐步形成 一个各个对象互相协作的真实应用程序。
依赖注入(DI)是对象定义它们的依赖的过程,所谓依赖就是与其协作的其它对象。只有三种方式实现依赖注入:构造函数的参数,工厂方法的参数,和对象实例的属性(这里的对象实例是由构造函数构造或者从工厂方法返回的对象)。容器在创建bean之后会注入依赖。这个过程与通常的过程是相反的,故而得名控制翻转 (Inversion of Control,IoC)。通常的过程是指,由Bean自己通过使用类的构造函数或者服务定位器(Service Locator)模式,来控制其依赖的实例化和定位。
为对象提供它的依赖时,使用依赖注入(DI)的原则,会使代码更整洁,并且解耦更 有效。对象并不查找它的依赖,也不知道所依赖对象的位置或者类是什么。这样一来,你的类就更容易测试了,特别是当依赖于接口或者抽象基类时,因为那时就可 以使用stub或者mock创建的对象来进行单元测试。
DI存在两种变体,基于构造函数的依赖注入和基于setter方法的依赖注入
基于构造函数的DI由容器调用构造函数来实现。构造函数可以有多个参数,每一个参数代表一个依赖。使用指定的参数来调用静态
工厂方法是一样的。这里的讨论认为构造函数的参数和静态
工厂方法的参数是相同的。下面的例子演示了一个实现依赖注入的类,它只能使用构造函数注入的方式来实现依赖注入。需要注意的是,这个类没有任何特别之处,它只是一个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... }
构造函数参数解析的问题在于对参数类型的匹配上( Constructor argument resolution matching occurs using the argument's type.)。如果Bean定义的构造函数参数不存在潜在的歧义的话,构造函数参数定义的顺序,就是实例化Bean时提供这些参数的顺序。考虑下面的类:
package x.y; public class Foo { public Foo(Bar bar, Baz baz) { // ... } }
因为不存在潜在的歧义,可以认为Bar
和 Baz
两个类之间没有继承关系。因此,下面的配置是可行的,你不需要在<constructor-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 { // No. of years to the 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>
指定一个序号不仅可以解决多个简单类型的歧义问题,而且可以解决有两 个相同类型的参数时的歧义问题。index的值从0开始。
对于Spring 3.0 你也可以使用构造函数参数的名称来消除歧义:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateanswer" value="42"/> </bean>
请记住,为了使这个工作开箱即用,你的代码在编译的时候必须开启debug标志位,这样的话Spring 就能够从构造函数里查找参数名称。如果你编译的时候没有开启debug标志位(或者你不想这么做), 你可以使用@ConstructorProperties
这个JDK注解来明确地为构造函数的参数命名。下面就是示例代码的样子:
package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
基于Setter方法的 DI 是这样实现的:容器在调用了一个无参数的构造函数或者无参数的static
工厂方法来实例化一个Bean之后,会对该bean调用setter方法。
下面这个例子展示了一个类只能使用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
支持基于构造函数和基于Setter方法对它所管理的Bean实施依赖注入。它也支持通过构造函数的方式实施依赖注入之后,再使用setter方法来实现依赖注入。可以使用BeanDefinition
这个类来配置依赖关系,这种情况下,你可以和PropertyEditor
一起使用来把属性从一种格式转换到另一种格式。然而,大部分的Spring用户并不直接与这些类打交道(以编程的方式),而是使用XML配置文件,在Spring的内部被转换成这些类的实例,并用于装载整个Spring IOC容器实例。
容器是这样来执行bean的依赖解析的:
-
ApplicationContext
被创建,并使用配置元数据来初始化。配置元数据描述了所有的bean,可以通过XML文件,Java代码或者注解这三种方式来指定。 -
对于每一个bean,它的依赖都是以三种方式来表示:属性,构造函数参数或者是静态工厂方法的参数。这些依赖只有在bean实际被创建的时候才提供给bean。
-
每个属性或构造函数的参数是一个将要设置的实际值,或容器中另一个bean的引用。
-
每个属性或构造器参数当以值的形式提供时,会被转换成该属性或构造器参数的实际类型。默认情况下,Spring能够把一个字符串格式的值转换成所有内置的类型,比如:
int
,long
,String
,boolean
等等。
在Spring容器被创建的时候,容器会验证每一个bean的配置,包括对bean 的引用属性是否引用到一个有效的bean进行验证。然而,直有bean实际被创建,bean属性才会被设置。单例的bean(singleton- scoped),以及被设置为预实例化(默认值)的bean会在容器被创建的时候创建。bean的范围定义在Section4.5, Bean scopes。除此之外,bean只有在需要的时候才会被创建。创建一个bean潜在的会引起一连串的bean被创建,因为bean的依赖以及它依赖的依赖(如此等等的依赖)也会被创建和赋值。
如果不存在循环依赖,当一个或多个协作bean被注入到一个依赖它们的bean时,每一个协作bean都会在注入之前被完全初 始化。这就意味着,如果bean A依赖于bean B,那么Spring IoC 容器在调用bean A 的setter方法之前会完整的配置bean B。换句话说,bean被实例化时(如果不是一个预实例化的单例bean),它的依赖会被设置,并且相关的生命周期函数(比如说配置的 init函数或者InitializingBean回调函数)都会被调用。
4.4.1.4 依赖注入的例子(Examples of dependency injection)
下面的例子使用基于XML的配置元数据来实现基于setter方法的DI。一小段Spring XML 配置文件定义了一些bean:
<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; } }
在前面的例子中,setter方法的声明和XML文件配置的属性是匹配的。下面的例子使用基于构造器的注入:
<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; } }
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
类的构造函数参数。
现在把这个例子做一些变化,不使用构造器,让Spring调用静态工厂方法来返回一个对象的实例:
<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/>
元素来提供的,这和前面使用的构造器注入是一样的。由工厂方法返回的类型并不一定就是包含静态工厂方法的类,尽管在这个例子中它们是一样的。一个实例(非静态)工厂方法在本质上是以相同的方式来使用的(除了使用factory-bean
属性来代替class
属性),细节会在后续讨论。
正如前面章节中提到的,你可以把bean的属性和构造函数参数定义为对于其他bean(也叫协作者)的引用,或者是直接定义为值。为此,Spring中基于XML的配置元数据支持在<property/>
和 <constructor-arg/>
元素中配置子元素。
<property/>
元素的value
属性把属性或构造函数的参数指定为人类可读的一个字符串。正如前面提到过的,JavaBeans的PropertyEditors
可以把这些字符串从String
类型转换为属性或构造函数参数的实际类型。
<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-名称空间来使XML配置文件更简洁。
<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-3.0.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或者SpringSource Tool Suite (STS)这样的IDE,它们支持属性自动完成,可以帮助你在创建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
实例。这是一个很好的捷径,它用嵌套的<value/>
元素取代了value
属性。
idref
是一种简单的防错方式,它把容器中另一个bean的id(字符串值--不是一个引用)传递给<constructor-arg/>
或者 <property/>
元素。
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
前面的XML片段和下面的片段完全是等价的(在运行时):
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>
第一种写法更可取,因为使用idref
标签可以让容器在在部署时就验证按名称引用的bean是否真的存在。在第二种写法中,对传递给客户端Bean的targetName
属性的值不会做任何的验证。录入错误就只有在客户端bean真正被初始化的时候才会被发现(往往是很严重的结果)。而如果客户端bean是一个原型bean的话,这种录入错误以及导致的异常可能在容器被部署很久之后才会被发现。
除此以外,如果引用的bean在相同的XML单元,并且引用的bean名称是bean的id,也可以使用local
属性,这样XML解析器自身就可以在XML文档解析阶段更早的对bean id 做验证。
<property name="targetName"> <!-- a bean with id 'theTargetBean' must exist; otherwise an exception will be thrown --> <idref local="theTargetBean"/> </property>
使用<idref/>元素传递值的一个常见例子(至少在spring 2.0版本之前)是在ProxyFactoryBean
bean定义中对AOP 拦截器的配置。在指定拦截器名称的时候,使用<idref/>元素可以防止写错拦截器的id。
ref
元素是<constructor-arg/>
或<property/>
元 素中的决定性元素。在此,你可以把bean的属性设置为对容器管理的其它bean(协作者)的引用。被引用的bean是该bean的一个依赖,它会在属性 被设置之前被初始化。(如果协作者是一个单例bean,它可能已经由容器初始化了)所有的依赖最终都是对其它对象的引用。范围和验证(重新翻译,充实此 处)取决于是否通过bean,
or local,
parent
属性来指定其它对象的id/name。
通过<ref/>
标签的bean
属性来指定目标bean,是最常见的做法。它可以指定对同一个容器或者父容器中的任意bean的引用,无论这些bean是否定义在同一个XML文件中。bean
属性的值可能就是目标bean的id
属性的值,也可以是目标bean的name
属性中的一个值。
<ref bean="someBean"/>
通过local
属性来指定目标bean,可以利用XML解析器来验证同一个XML文件中的id引用。local
属性的值必须和目标bean的id
属性一致。如果在同一个文件中没有找到匹配的元素,XML解析器会报错。因此,如果目标bean位于同一个XML文件中,使用local标签是最好的选择,这样可以尽早的发现错误。
<ref local="someBean"/>
通过parent
属性来指定目标bean将会建立对当前容器的父容器中的一个bean的引用。parent
属性的值可以是目标bean的id
属性的值,也可以是目标bean的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>
在<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,容器会忽略这些值。容器也会忽略scope
标志。内联bean总是匿名的,并且范围总是prototype。内联bean不可能被注入到协作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的key和value值,或者set的value值,也可以是下面元素中的任意一个:
bean | ref | idref | list | set | map | props | value | null
在Spring 2.0中,容器支持集合的合并。应用程序开发者可以定义一个父集合:<list/>
,<map/>
,<set/>
,<props/>
。子集合可以继承父集合,并覆盖父集合中的值。也就是说,子集合的值是父集合和子集合合并的结果,子集合中的值会覆盖父集合中对应的值。
这一节在讲述合并的时候会讨论到bean的父子机制。读者如果不熟悉父、子bean的定义,可以参考4.7 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>
读者应该注意到了,在child
bean的定义中,adminEmails
元素的<props/>
节点上,使用了merge=true
属性。当child bean
被容器解析并实例化时,得到的实例有一个Properties
集合类型的adminEmails
属性,这个集合是child bean的adminEmails
集合与parent bean的 adminEmails
集合合并的结果集。结果集的内容如下所示:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
child bean的Properties
类型的集合继承了parent bean中<props/>
的所有属性元素,并且child bean的support
项的值覆盖了父集合中的值。
这种合并的行为同样适用于<list/>
,<map/>
,和<set/>
类型的集合。而对于<list/>
类型的集合,与List
集合类型关联的语义,也就是集合元素的有序性
会被保持。父集合中的元素总是先于所有的子集合中的元素。对于Map
,Set
和Properties
类型的集合,不存在元素的有序性。
1) 不能合并两种不同类型的集合(比如Map
和List
),如果你尝试这么做,会抛出一个相关的Exception
。2) merge
属性必须在继承的孩子bean中指定,在父集合中指定merge
属性是无效的,不会产生想要的合并。3) 合并特性只在Spring 2.0及以后的版本中可用。
在Java 5 及以后版本中,你可以使用强类型的集合(使用泛型类型)。也就是说,可以声明一个Collection
类型,它只包含String
元素(打个比方)。如果使用Spring 来把一个强类型的 Collection
注入到一个bean,你可以利用Spring对类型转换的支持,这样强类型Collection
中的元素在被添加到Collection
之前,会被被转换为合适的类型。
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
这个bean中的accounts
准备注入的时候,通过反射可以得到强类型Map<String,Float>
中元素类型的泛型信息。因此Spring的类型转换机制会把value元素识别为Float
类型,字符串的值9.99, 2.75
, 和3.99
都会被转换为Float
类型。
Spring把properties中空的参数视为空字符串
。下面的基于XML的配置元数据片段把email属性设置为了空的String
值("")。
<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-名称空间可以让你使用bean
元素的属性来描述属性值和/或协作bean,而不再使用嵌套的 <property/>
元素。
Spring 2.0及以后版本支持带名称空间的可扩展配置格式。这一章讨论的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-3.0.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>
例子中展示了bean定义中位于p-名称空间的称作email的属性。这告诉Spring包含一个属性声明。正如前面提到的,p-名称空间没有schema定义,所以你可以把属性名称设置为property 的name值(指的是p:email中的email)。
下面的一个例子包含了另外两个bean定义,这两个bean都引用另一个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-3.0.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-名称空间不仅仅包含了一个property值,还有一个特殊的格式来声明property引用。第一个bean定义使用<property name="spouse" ref="jane"/>
来创建bean john
到bean jane
的引用,而第二个bean定义使用p:spouse-ref="jane"
作为属性,实现了同样的功能。在本例中,spouse
是property的名称,而-ref
这一部分表示这不是一个直接值,而是一个对其它bean的引用。
![]() |
Note |
---|---|
p-名称空间没有标准的XML格式那么灵活。例如,声明property引用会与结尾是 |
类似于P名称空间,在Spring3.1中新引入的C-名称空间可以使用内联的属性来配置构造函数参数,以取代嵌套的constructor-arg
元素。
让我们用C-名称空间来重写一下第4.4.1.1节,基于构造函数的依赖注入这一节中的例子,
<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的引用)。而同样的,它需要被声明,尽管没有在XSD schema中定义(但它存在于Spring core的内部)。
有时候(极少的情况下),构造函数参数名称是获取不到的(通常是因为字节码编译的时候没有带上debug信息),就只能退而求其次,使用参数的顺序了:
<-- 'c-namespace' index declaration --> <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz">
![]() |
Note |
---|---|
受限于XML的语法,index符号必须以一个前导的_开头,因为XML的属性名称不能以数字开头(尽管一些IDE允许这么做)。 |
在实践中,构造函数的参数解析机制是十分有效的,所以除非你确实需要使用c-名称空间,我们推荐你在配置文件中使用名称符号。
当你设置bean的property时,你可以使用复合或嵌套的属性名称(property name),只要这一路径中除了最后一部分之外的每一部分都不为null
。考虑下面的bean定义:
<bean id="foo" class="foo.Bar"> <property name="fred.bob.sammy" value="123" /> </bean>
上例中,foo
有一个fred
属性,fred
又有一个bob
属性,而bob
又有一个sammy
属性,最终sammy
属性被设置为值123
。要想让这个设置能正常工作,必须保证在bean被构造之后,foo
的fred
属性,和fred
的bob
属性都不为空,否则就会抛出一个 NullPointerException
异常。
如果一个bean是另外一个bean的依赖,通常意味着这个bean会被设置为另外一个bean的属性。你可以在基于XML的配置文件中使用<ref/>
元素来实现这一点。然而,有时候bean之间的依赖并不是那么直接。例如,一个类中的一个静态初始化器(a static initializer)需要被触发,比如数据库驱动的注册。depends-on
属性可以明确地强制一个或多个bean在自己(使用这一属性的bean)之前初始化。下面的例子使用depends-on
属性来表达对一个单例bean的依赖:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
通过为depends-on
指定多个bean名称,可以表达出对多个bean的依赖。bean名称之间使用逗号,空格或分号作为分隔符:
<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" />
![]() |
Note |
---|---|
bean定义中的 |
默认情况下,作为初始化过程的一部分,ApplicationContext
的实现会较早的创建和配置所有的单例bean。 通常情况下,这种预实例化( pre-instantiation)是很有必要的,因为可以马上发现配置文件,或是周边环境中的错误。反之,则可能在数小时,甚至数天之后才能发现这些 错误。当这种行为不是很有必要时,也可以通过把bean定义标记为延迟实例化,来禁用单例bean的预实例化。一个延迟实例化的bean告诉Ioc容器, 只在这个bean第一次被请求的时候才创建bean实例,而不是在启动的时候就实例化它。
在XML中,这种行为由<bean/>
元素的lazy-init
属性来控制,例如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/> <bean name="not.lazy" class="com.foo.AnotherBean"/>
当前面的配置内容被一个ApplicationContext
的实现读取之后,名为lazy
的bean不会在ApplicationContext
启动的时候被预实例化,而名为not.lazy
的bean会被预先实例化。
然而,当一个延迟实例化的bean是一个非延迟实例化的单例bean的依赖时,ApplicationContext
会在启动时创建延迟实例化的bean,因为它必须满足单例bean的依赖关系。这个延迟实例化的bean被注入给了一个非延迟实例化的单例bean。
你也可以通过在<beans/>
元素上使用default-lazy-init
属性来在容器级别控制延迟实例化;例如:
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
Spring容器能够自动装配协作bean之间的依赖关系。通过检查ApplicationContext
的内容,可以让Spring自动判断出bean的协作者。自动装配有下面的好处:
-
自动装配可以大大减少指定属性或构造函数参数的必要。(本章中讨论的其它机制,比如bean模版,在这方面也很有效)
-
自动装配可以随着你对象的演变而自动更新配置信息。例如,如果你需要增加对一个类的依赖,这个依赖能够自动满足,你不需要修改配置。因此自动装配在开发过程中特别有效,等到代码库变得更稳定之后,也可以再修改成显示装配。
当使用基于XML的配置元数据时[2],你可以使用<bean/>
元素的autowire
属性来指定自动装配模式。自动装配功能有5中模式。你可以为每一个bean单独指定自动装配模式。
表4.2. 自动装配模式(Autowiring modes)
Mode | Explanation |
---|---|
no |
(默认值)不使用自动装配。必须使用 |
byName |
通过属性名称来实现自动装配。Spring查找和需要自动装配的属性同名的bean。例如,一个bean定义为通过名称自动装配,而且它包含一个master属性(也就是说,它有一个setMaster(..)方法),Spring会查找一个命名为 |
byType |
如果容器中恰好只有一个指定类型的bean存在,则自动装配给该property。如果存在多个指定类型的bean,就抛出一个致命异常,这表明对于这个bean不能使用byType来自动装配。如果没有匹配的bean,什么都不会发生,属性不会被设置。 |
constructor |
类似于byType,但是应用于构造函数的参数。如果容器中不是恰好只存在一个构造函数参数类型的bean,则触发一个致命错误。 |
对于byType 或 constructor这两种自动装配模式,你可以装配数组和指定类型的集合(typed-collections)。在这种情况下,容器中所有满足所期望类型自动装配候选对象都会被用来满足依赖。你也可以自动装配强类型的Map,如果期望的key的类型是String
。一个自动装配的Map的值将会包含所有匹配期望类型的bean实例,而map的键将包含对应的bean名称。
你可以把自动装配模式和依赖检查结合起来,这在自动装配完成的时候执行。
自动装配只有在项目的全过程中都使用,才会起到好的作用。如果很少使用自动装配,偶尔只对一个两个bean使用,这会让团队中的开发者感到困惑。
考虑一下自动装配的局限和缺点:
-
property
和constructor-arg
中显示定义的依赖总会覆盖自动装配。你不能自动装配所谓的简单属性,比如基本类型,Strings
,以及Classes
(?)(以及这些简单属性的数组)。这是自动装配特性与生俱来的限制。
-
自动装配没有显示装配准确。尽管,前面的表格中已经说明了,在出现了可能会导致不可预料结果的歧义时,Spring非常小心的避免猜测;但是,由Spring管理的对象之间的关系不再是很明确的被说明。
-
对于那些可以从一个Spring容器生成文档的工具来说,装配信息可能获取不到。
-
在容器中的多个bean定义可能因匹配由setter方法或者构造函数指定的类型而被自动装配。对于数组,集合或Map,这可能不是什么问题。然而,对于只需要一个值的依赖来说,这种模糊性是不能随意处理的。如果不是只有唯一一个bean定义可用的话,就会抛出一个异常。
在后面一种情形中(需要单值,而有多个候选值可用),你有几种选择:
-
放弃自动装配,而使用显示装配。
-
通过把bean定义的
autowire-candidate
属性设置为false
来避免自动装配,在下一节会介绍这个内容。 -
选择一个bean,把它划定为主要侯选者。可以通过把
<bean/>
元素的primary
属性设置为true
来做到这一点。 -
如果你正在使用Java 5 或更新版本的话,可以使用基于注解的配置来实现更细粒度的控制,正4.9节,基于注解的容器配置如中所描述的。
你可以把一个bean从自动装配体系中排除出去。在Spring的XML格式中,把<bean/>
元素的autowire-candidate
属性设置为false
,容器就把这个bean从自动装配体系中排除掉(包括注解形式的配置,例如@Autowired
)。
你也可以在bean的名称上使用模式匹配来限制自动装配的候选对象数量。最顶层的<beans/>
在它的default-autowire-candidates
属性中接受一个或是多个匹配模式。例如,为了把自动装配的候选对象限制为名称以Repository,结尾的bean,可以把模式设置为*Repository。可以把模式的值定义为逗号分隔的列表来定义多个模式。为bean显示指定的autowire-candidate
属性的true
和false
值总是优先起作用,对于这类bean,匹配规则将不起作用。
当你不想让一些bean通过自动装配被注入给别的bean时,这些技术是很有用的。单这并不意味着,一个被排除在外的bean自身不能够被配置为自动装配。相反,只意味着这个bean自身不会作为其它bean自动装配的候选对象。
大多数情况下,容器中的bean都是单例(singletons)。 当一个单例bean需要与另一个单例bean协作,或者一个非单例bean,与另一个非单例bean协作时,通常你会把一个bean定义为另一个bean 的属性来处理这种依赖。但是,当两个bean的生命周期不一样时,问题就来了。假设一个单例bean A需要使用一个非单例(prototype)的bean B,这可能发生于对bean A的每一次方法调用上。容器只会创建bean A一次,因此只有一次机会来设置属性。容器不可能在每次需要时候都为Bea A提供一个新的bean B。
一个解决方案就是放弃使用IoC。你可以让Bean A通过实现ApplicationContextAware
接口来知道容器的存在,这样就可以在每次Bean A需要的时候,调用getBean("B")来获取新的bean B了。下面是这种方法的一个例子:
// 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 Framework耦合在一起。方法注入,一种Spring IoC容器中的稍微高级一点的玩意儿,可以以一种简洁的方式来解决这一类问题。
Lookup方法注入是容器覆盖由容器管理的bean中的方法 的能力,Lookup的结果是容器中另一个命名的bean。这种lookUp通常涉及到prototype类型的bean。在前面的小节中已经介绍过 prototype类型的bean了。Spring Framework使用CGLIB库产生的字节码来动态产生一个覆盖了父类方法的子类,这样就实现了方法注入。
![]() |
Note |
---|---|
为了是这种动态子类的方法可以奏效,你必须在classpath中包含CGLIB的jar包。Spring容器将生成子类的类不能是 |
看一下前面代码片段中的CommandManager
类,Spring容器将会动态的覆盖createCommand()
方法的实现。CommandManager
不会对Spring有任何的依赖,重写之后的代码如下面所示:
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
类)中,需要注入的方法需要下面形式的签名:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果是abstract
方法,动态产生的子类会实现这个方法。否则,动态产生的子类会覆盖原始类中的具体方法。例如:
<!-- 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类型的bean时,必须谨慎一点,考虑一下是不是真的需要这么做。如果配置为singleton,每次都会返回同一个command
实例
Tip | |
---|---|
感兴趣的读者可能发现 |
比查找方法注入更少用到的另一种方法注入形式,是替换所管理bean中任意方法为另一种方法实现的能力。读者可以跳过这一节,直到真正用到这一功能时再回来看这部分内容。
在基于XML的配置元数据中,你可以使用replaced-method
元素来把一个已存在的方法替换为另一个方法。考虑下么的类,它有一个computeValue方法,这正是我们想要覆盖的:
public class MyValueCalculator { public String computeValue(String input) { // some real code... } // some other methods... }
实现了org.springframework.beans.factory.support.MethodReplacer
接口的方法提供了一个新的方法定义。
/** 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定义如下所示:
<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"/>
你可以在<replaced-method/>
元素中使用一个或多个<arg-type/>
元素来指明被覆盖的方法签名。只有当类中存在该方法的多个重载函数的时候,才需要方法签名。为了方便使用,参数的类型字符串可以是完整类型字符串的子串。例如,下面所有的写法都能够匹配java.lang.String
类型:
java.lang.String
String
Str
因为往往通过参数的数量就足以区分不同的候选函数,所以这个简写方式可以节省很多的敲键盘的工作,只输入能够匹配一个参数类型的最短字符串就可以了。
当你创建一个bean定义的时候,你就建立了一个创建该bean定义的实际类对象的"配方"。一个bean定义是一个"配方"的观念是很重要的,因为这就意味着,对于一个类,你可以从一个配方创建出很多个对象实例。
在从一个特定bean定义来创建一个对象时,你不仅可以控制向该对象插入的各种依赖和配置值,而且可以控制该对象的范围(scope)。这种策略是很强大和灵活的,这样你就能够通过配置信息来选择创建的对象的范围,而不是把对象的范围写在Java类级别。bean可以被部署为多种范围:Spring Framework提供五种范围,其中三种需要你使用web类型的ApplicationContext
才有效。
下面的范围是开箱即用的(out of the box)。你也可以创建一个自定义的范围。
Table4.3.Bean scopes
Scope | Description |
---|---|
(默认值)每一个Spring IoC 容器中对于一个bean定义只会有唯一的一个对象实例。 |
|
一个bean定义可以有任意数量的对象实例。 |
|
每一个bean定义的生命周期只限于一次HTTP请求。也就是说,每一个HTTP请求都有它自己的bean。这只在web应用程序中有效(这里对web应用程序的表述是:web-aware Spring ApplicationContext)。 |
|
一个bean定义只存活于一个HTTP |
|
其实是将一个单例bean的生命周期定义为全局HTTP |
![]() |
线程范围的bean(Thread-scoped beans) |
---|---|
对于Spring 3.0,有一种线程范围,但是默认是没有注册的。关于更多信息,参考相关文档 SimpleThreadScope。关于如何注册这种范围,或者自定义范围,参考 4.5.5.2节, 使用自定义范围. |
单例bean就是说容器只管理着唯一的一个共享的实例。所有通过id对这个bean的请求,Spring容器都会返回这一特定bean的实例。
用另外一种方式表述,就是当你定义了一个单例bean时,Spring IoC容器只会创建唯一一个该bean的实例。这个单一的实例被存储在专门存放单例bean的缓存中。 所有后续对该bean的请求和引用都会返回这个缓存的对象。
![](http://images0.cnblogs.com/blog/412441/201303/12085815-04360f36f0f24d6d869fc115b1abee6d.png)
Spring中单例bean的概念和Gang Of Four(GoF)这本设计模式经典著作中讲述的单例模式不太一样。GoF一书中硬编码一个对象的范围,这样每一个ClassLoader
只会创建该类的唯一一个实例。而Spring中单例的概念应该被描述为每个容器中只有该类的一个实例。这意味着,如果你在Spring容器中定义一个bean,那么Spring容器只会创建唯一一个该bean的实例。单例bean是Spring中的默认范围。要在XML文件中把一个bean定义为单例bean,你可以这样写:
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- the following is equivalent, though redundant (singleton scope is the default) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
原型bean部署的结果就是每一次对该bean的请求,都会创建一个新的bean实例。请求包括,该bean被注入给另一个bean,或者你通过容器上的getBean()
方法来请求该bean。按通常的规则,对于所有有状态的bean都使用原型范围,而对于无状态的bean使用单例范围。
下图演示了Spring的原型范围。数据访问对象(data access object,DAO)通常不会被配置为原型范围,因为通常DAO不会持有任何会话状态。这里只是为了图方便,作者重用了单例范围用过的图形~~。
![](http://images0.cnblogs.com/blog/412441/201303/12085832-da0b3538334e478a9f4f5ee9d4b8f2a5.png)
下面的例子在XML文件中定义一个原型范围的bean:
<!-- using spring-beans-2.0.dtd --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
和其它范围的bean不同,Spring不会管理一个原型bean的完整生命周期:容器要么实例化,装配一个原型bean,要么组装一个原型bean,然后赋给请求方,之后再不会记录任何原型bean实例的信息了。因此,尽管任何范围bean的生命周期回调函数initialization总是会被调用,然而对于原型bean来讲,所配置的生命周期回调函数destruction不会被调用。请求方必须负责清理原型范围的对象,并且释放由原型bean持有的宝贵资源。要让Spring容器释放由原型范围的bean持有的资源,可以试一试自定义一个bean 后处理器,它会持有后续需要清理的bean的引用。
在某些方面,对于一个原型范围的bean来说,Spring容器的角色就好比是Java中的new
操作符。所有new()之后的生命周期管理都由请求方(client)来处理。(关于Spring容器中一个bean的生命周期,请参考4.6.1节, 生命周期回调函数)
当你使用依赖于原型bean的单例bean时,要明白依赖只有在实例化时被处理。因此,如果你把一个原型bean注入给一个单例bean,一个新的原型bean会被实例化并依赖注入给该单例bean。
然而,假如说你想在运行时让单例bean多次获取原型bean的新的实例,你就不能把一个原型bean注入给你的单例bean。因为注入只发生一次,也就 是发生在Spring容器实例化这个单例bean,并且解析和注入它的依赖的时候。如果在运行时你需要多个新的原型bean,参考Section4.4.6, 方法注入
request
,session
和global
这三种范围只有在web类型的Spring ApplicationContext
实现(比如:XmlWebApplicationContext
)中才能使用。如果你在普通的Spring IoC容器(比如:ClassPathXmlApplicationContext
)中使用这些范围,会出现一个IllegalStateException
异常,提示说未知的bean范围。
为了支持在request
, session
,和global session
级别定义bean的范围,在定义bean之前,需要做一些初始的配置工作。(标准的范围:单例和原型,是不需要这些初始设置的)
至于如何实现这个初始设置,取决于你特定的Servlet环境。
如果在Spring web MVC环境中访问限定范围的bean,也就是由Spring的DispatcherServlet
, 或者 DispatcherPortlet
处理的请求中,是不需要做特殊的设置的:DispatcherServlet
和 DispatcherPortlet
已经设置了所有相关的状态。
如果你使用servlet 2.4+ 版本的web容器,而请求不是由Spring的DispatcherServlet来处理 (例如,用的是JSF或Struts),那么你就需要在web应用程序的web.xml
的声明部分添加javax.servlet.ServletRequestListener
:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
如果你使用更老版本的容器(servlet2.3),可以使用Spring提供的javax.servlet.Filter
实现。如果你想在Servlet2.3版本的容器中使用web-范围的bean,而请求不是由 Spring的 DispatcherServlet来处理,那么必须在你的web应用程序的web.xml
文件中包含下面的XML配置片段:
<web-app> .. <filter> <filter-name>requestContextFilter</filter-name> <filter-class>org.springframework.web.filter.RequestContextFilter</filter-class> </filter> <filter-mapping> <filter-name>requestContextFilter</filter-name> <url-pattern>/*</url-pattern> </filter-mapping> ... </web-app>
DispatcherServlet
, RequestContextListener
和 RequestContextFilter
都做相同的事情,也就是把HTTP请求对象绑定到处理这个请求的线程
。这就可以使bean能够在后续的调用链中是request或者session的范围。
考虑下面的bean定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
对于每一个HTTP请求,Spring容器都会根据loginAction
的bean定义来生成一个新的LoginAction
实例。也就是说,loginAction
的范围是HTTP request级别。你可以按你所需,任意的改变实例的内部状态,因为从相同的loginAction
定义创建的其它实例不会看到这些状态改变;对于每一个请求,bean实例都是独立的。当完成了对请求的处理之后,request范围的bean就会被丢弃。
考虑下面的bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
对于每一个HTTP Session
,Spring容器都会根据userPreferences
的bean定义来创建一个新的UserPreferences
实例。换句话说,userPreferences
bean的作用范围是HTTP Session
级别。对于session-scoped(原文有误)
类型的bean,你可以任意改变所创建实例的内部状态,因为从同一个userPreferences
bean定义创建的其它的HTTP Session
中的实例不会看到这些状态上的改变,它们都特定于一个独立的HTTP Session
。当HTTP Session
最终被废弃时,该HTTP Session
中的bean也会被废弃。
考虑下面的bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
全局 session
范围类似于标准的HTTP Session
范围((如上所述),它只应用于基于portlet的web应用程序上下文。portlet规范定义了一个全局Session的含义:一个全局session由所有组成一个portlet应用程序的所有portlet所共享。定义在全局 session
范围内的bean在全局portlet Session
的生命周期内有效。
如果你写一个标准的基于servlet的web应用程序,而且定义了一些bean,它们是全局 session
范围的,那么Spring会使用标准的HTTP Session
范围,而且不会报任何错误。
Spring IoC 容器不只是管理着对象(bean)的实例化,而且也管理着协作者(或依赖)的装配。如果你想把一个HTTP request作用域的bean注入给另一个bean,你必须在这个限定了作用域的bean的位置注入一个AOP代理。也就是说,你需要注入一个代理对 象,它暴露和限定作用域的对象相同的接口,它也能够从相应的作用域(比如,一个HTTP请求)获取真实的目标对象,并且把方法调用委派给真实的对象。
![]() |
Note |
---|---|
你不需要为作用域是 |
下面例子中的配置文件虽然只有一行,但是理解它背后的原因和如何实现是非常重要的。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <!-- an HTTP Session-scoped bean exposed as a proxy --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- instructs the container to proxy the surrounding bean --> <aop:scoped-proxy/> </bean> <!-- a singleton-scoped bean injected with a proxy to the above bean --> <bean id="userService" class="com.foo.SimpleUserService"> <!-- a reference to the proxied userPreferences bean --> <property name="userPreferences" ref="userPreferences"/> </bean> </beans>
为了创建这样一个代理,你需要在限定作用域的bean定义处插入一个<aop:scoped-proxy/>
子元素。(如果你选择基于类的代理,你还需要在classpath中包含CGLIB类库。参考选择所创建代理的类型 和 AppendixC,基于XML Schema的配置 .)。为什么作用域为request
,session
,globalSession
和自定义范围的bean需要<aop:scoped-proxy/>
元素?让我们来看看下面singleton bean的定义,并且和对于前述的作用域你必须定义的内容做对比。(下面的userPreferences
bean定义是不完整的)
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
在上面的例子中,单例 bean userManager
被注入一个HTTP Session
作用域的bean userPreferences
的引用。此处突出的一点就是userManager
是单例bean:在每个容器中它只会被实例化一次,而它的依赖(本例中就只有一个,就是userPreferences
)也只会被注入一次。这就意味着,userManager
将只操作完全相同的userPreferences
对象,也正是那个最初注入的那个。
当你把一个作用范围较短的bean注入给一个作用范围较长的bean时,这不是你想要的行为,例如,把一个HTTP Session
作用域的bean作为依赖注入给一个单例bean。相反,你期望的行为是这样的:你需要一个单例的userManager
,而在每一个HTTP Session
的生命周期里,你需要一个专门的userPreferences
对象,它只用于该HTTP Session
。因此,容器创建一个对象,它暴露和 UserPreferences
类一样的公有接口(最好就是一个UserPreferences
实例),该对象能够从作用域机制(HTTP request,Session
,等等)中取得真实的UserPreferences
对象。容器把这个代理对象注入给userManager
,后者并不知道这个UserPreferences
引用是一个代理。在这个例子中,当UserManager
的实例调用依赖注入的UserPreferences
对象的方法时,它实际上是在调用代理对象的方法。然后代理就会从HTTP Session
中获取真实的UserPreferences
对象,然后把方法调用委派给所获取的真实对象。
因此,当把request-作用域
,session-作用域
,和globalSession-作用域
的bean注入给协作对象时,你需要进行如下的正确,完整的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <aop:scoped-proxy/> </bean> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
默认情况下,当Spring容器为一个用<aop:scoped-proxy/>
元素标记的bean创建代理时,会创建一个基于CGLIB类的代理。这就意味着,你需要在应用程序的classpath中包含CGLIB类库。
注意: CGLIB代理仅仅拦截public方法调用! 不要在调用这种代理对象的non-public方法,它们将不会被委派给目标对象。
另外,你也可以配置Spring容器来为这种限定范围的bean创建标准JDK的基于接口的代理。通过把<aop:scoped-proxy/>
元素的proxy-target-class
属性的值置为false
可 以实现这一点。使用JDK基于接口的代理意味着,为了是使这种代理有效,你不需要在你的应用程序classpath中附加额外的类库。然而,同时也意味 着,被代理的限定范围的bean必须至少实现一个接口,而需要注入该bean的所有协作者必须通过该bean的接口来引用它。
<!-- DefaultUserPreferences implements the UserPreferences interface --> <bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session"> <aop:scoped-proxy proxy-target-class="false"/> </bean> <bean id="userManager" class="com.foo.UserManager"> <property name="userPreferences" ref="userPreferences"/> </bean>
关于选择基于类或基于接口的代理的详细信息,请参考8.6节, 代理机制.
在Spring 2.0中,bean的作用域机制是可以扩展的。你可以定义自己的作用域,甚至是重新定义已存在的作用域,尽管后者被认为是不好的做法。你不能够覆盖内置的singleton
和 prototype
作用域。
为了把你自定义的作用域集成到Spring容器,你需要实现org.springframework.beans.factory.config.Scope
接口,在这一节中会介绍该接口。对于如何实现自己的作用域,参考Spring Framework自己的Scope
的实现,以及Scope Javadoc,它更详细的解释了你需要实现的方法。
Scope
接口包含四个方法,用于从作用域获取对象,从作用域删除对象,和允许对象被销毁。
下面的方法从底层的作用域返回一个对象。例如,在session作用域的实现中,它返回session-作用域的bean(如果该bean不存在,这个方法会返回一个新的bean实例,把它绑定到会话,以备将来引用)
Object get(String name, ObjectFactory objectFactory)
下面的方法从底层的作用域删除一个对象。例如,在session作用域实现中,会从底层的会话删除session-作用域的bean。必须返回该对象,而如果无法找到指定名称的对象的话,也可以返回null。
Object remove(String name)
下面的方法注册一个回调函数,当作用域被销毁,或是作用域中指定的对象被销毁时,会执行该回调函数。关于析构回调函数的更多信息,请参考Javadoc或是Spring的作用域实现。
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法获取底层的作用域的会话标识符。这个标识符对每一个作用域都是不同的。对于一个会话作用域的实现,这个标识符可以是会话的ID。
String getConversationId()
在编写并测试了自定义作用域
实现之后,你需要让Spring容器知道你的新作用域。下面的方法是向Spring容器注册一个新的作用域
的主要方法。
void registerScope(String scopeName, Scope scope);
这个方法在ConfigurableBeanFactory
接口中声明,它在大部分Spring自带的 ApplicationContext
具体实现中都是可用的。
registerScope(..)
方法的第一个参数是与作用域关联的唯一名称,在Spring容器中,这些名称的例子有singleton
和prototype
。方法的第二个参数是你要注册和使用的自定义scope
实现的真正实例。
假设你写了自定义的Scope
实现,并像下面这样注册了它:
![]() |
Note |
---|---|
下面的例子使用了 |
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
然后,你可以创建bean的定义,使用你自定义范围的作用域规则。
<bean id="..." class="..." scope="thread">
对于自定义Scope
实现,并不局限于以编程的方式来注册。使用CustomScopeConfigurer
类,你也可以做声明式作用域注册:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd"> <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer"> <property name="scopes"> <map> <entry key="thread"> <bean class="org.springframework.context.support.SimpleThreadScope"/> </entry> </map> </property> </bean> <bean id="bar" class="x.y.Bar" scope="thread"> <property name="name" value="Rick"/> <aop:scoped-proxy/> </bean> <bean id="foo" class="x.y.Foo"> <property name="bar" ref="bar"/> </bean> </beans>
![]() |
Note |
---|---|
当你在 |
4.6 自定义bean的生命过程(Customizing the nature of a bean)
为了与容器对bean生命周期的管理进行交互,你可以实现Spring的InitializingBean
和 DisposableBean
接口。容器会分别调用这两个接口的afterPropertiesSet()
和destroy()
方法,来执行bean的初始化和析构。你也可以使用对象定义元数据中的init和destroy属性来实现与容器的集成,这样你的类就不会与Spring的接口耦合在一起了。
在内部,Spring Framework使用BeanPostProcessor
实现来处理它能找到的任何回调接口,然后调用合适的方法。如果你需要自定义特性,或者其它Spring没有提供的生命周期行为,你可以自己实现BeanPostProcessor
。关于更多信息,参考4.8节, 容器扩展点
除了初始化和析构回调函数,Spring管理的对象也可以实现Lifecycle
接口,这样这些对象就能够参与到由容器自己的生命周期来驱动的开始和结束过程。
生命周期回调接口在这一节中介绍。
org.springframework.beans.factory.InitializingBean
接口允许bean在容器设置了所有必要属性之后,执行初始化工作。InitializingBean
接口只有一个方法:
void afterPropertiesSet() throws Exception;
建议你不要使用InitializingBean
接口,因为它会把你的代码和Spring的耦合在一起。替代方法 就是,指定一个POJO初始化方法。在基于XML的配置元数据中,你可以使用init-method
属性来指定初始化方法的名称。初始化方法返回值是void,而且没有参数。例如,下面的定义:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init"/> public class ExampleBean { public void init() { // do some initialization work } }
...这和下面的定义是完全等价的...
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements InitializingBean { public void afterPropertiesSet() { // do some initialization work } }
...但是不会把你的代码与Spring耦合在一起。
实现org.springframework.beans.factory.DisposableBean
接口可以让一个bean在包含它的容器被销毁时获得一个回调。DisposableBean
接口指定了唯一的一个方法:
void destroy() throws Exception;
推荐你不要使用DisposableBean
回调接口,因为它会造成你的代码和Spring产生不必要的耦合。替代方案是,指定一个普通的方法,这是bean定义支持的。对于基于XML的配置元数据,你可以使用<bean/>
的destroy-method
属性。例如,下面的定义:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/> public class ExampleBean { public void cleanup() { // do some destruction work (like releasing pooled connections) } }
...这和下面的定义是完全等价的...
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/> public class AnotherExampleBean implements DisposableBean { public void destroy() { // do some destruction work (like releasing pooled connections) } }
...但是避免了把你的代码与Spring耦合在一起。
当你不使用Spring指定的InitializingBean
和DisposableBean
回调接口,而是自己来写初始化和销毁的回调函数时,你写出的函数的名称通常是这样的:init()
, initialize()
, dispose()
,等等。理想情况下,在一个项目中,这种生命周期回调函数的名字是标准化的,所有的开发者都使用相同的函数名称,保证一致性。
你可以配置Spring容器在每一个bean上查找
已命名的初始化和销毁回调函数名称。这就意味着,你作为一个应用程序开发者,在写你的应用程序类时,可以使用一个名为init()
的初始化回调函数,而不需要为每一个bean定义配置一个init-method="init"
属性。Spring IoC容器会在bean被创建时调用这个方法(并依据前面介绍过的标准的生命周期回调函数约定)。这个特性同时强制推行了一个统一的初始化和销毁回调函数的命名规范。
假定你的初始化回调函数命名为init()
,而销毁回调函数命名为destroy()
,你的类将会类似于下面这样:
public class DefaultBlogService implements BlogService { private BlogDao blogDao; public void setBlogDao(BlogDao blogDao) { this.blogDao = blogDao; } // this is (unsurprisingly) the initialization callback method public void init() { if (this.blogDao == null) { throw new IllegalStateException("The [blogDao] property must be set."); } } }
<beans default-init-method="init"> <bean id="blogService" class="com.foo.DefaultBlogService"> <property name="blogDao" ref="blogDao" /> </bean> </beans>
顶层的<beans/>
元素上的default-init-method
属性会使Spring IoC容器把一个名为init
的方法识别为初始化回调函数。当bean被创建并组装时,如果bean的类有这样一个方法,那么它会在合适的时机被调用。
类似的,你可以在顶层的<beans/>
元素上使用default-destroy-method
属性来配置销毁回调函数。
如果已存在的bean类已经有了回调函数,但是命名上和惯例不一致时,你可以在该<bean/>上使用init-method
和 destroy-method
属性来指定方法名称,以覆盖默认值。
(重新翻译)Spring容器确保在为一个bean提供了所有依赖之后,一个已配置的 初始化回调函数会立即被调用。因此,初始化回调函数是调用在最原始的bean上的,也就意味着AOP拦截,以及后续的处理还没有应用于该bean。目标 bean被完全创建(被容器初始化完成,并调用了初始化回调函数)之后,AOP代理(举个例子)以及它的拦截才会被作用于该bean。如果目标bean和 代理是分别定义的,你的代码就可以与最初的目标bean交互,绕过了代理对象。因此,对init方法应用拦截器可能是不合逻辑的,因为这样做会使目标 bean的生命周期与它的代理/拦截器耦合在一起,当你的代码与最初的目标bean直接交互的时候,就会导致很奇怪的语义。
在Spring 2.5中,你有三种方式可以控制bean的生命周期行为:InitializingBean
和DisposableBean
回调接口;自定义init()
和 destroy()
函数;以及@PostConstruct
和 @PreDestroy
注解。你可以对一个bean组合使用这三种方式。
![]() |
Note |
---|---|
如果为一个bean配置了多种生命周期控制机制,而且每一种机制都配置为不同的函数名称,那么每一个所配置的函数会按照下面描述的顺序被执行。然而,如果同一函数名称-比如,用于初始化方法的 |
为同一个bean配置了多个生命周期机制,并且初始化方法都不一样时,按如下的顺序来调用:
-
@PostConstruct
注解的方法 -
InitializingBean
回调接口定义的afterPropertiesSet()
方法 -
一个自定义配置的init()方法。
Destroy方法按照同样的顺序被调用:
-
@PreDestroy
标记的方法。 -
DisposableBean
回调接口定义的destroy()
方法。 -
一个自定义配置的
destroy()
方法。
Lifecycle
接口定义了一些关键的函数,以满足对象管理自己生命周期的需求(比如,启动和停止一些后台进程)
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
Spring-管理的任意对象都可以实现这个接口。这样的话,当ApplicationContext本身启动和停止时,它会逐级调用该容器中定义的所有Lifecycle实现。它将调用委派给一个LifecycleProcessor
实例:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
注意,LifecycleProcessor
本身是Lifecycle
接口的一个扩展。它又增加了两个方法,用于对容器的刷新和关闭行为进行响应。
启动和停止的调用顺序很重要。如果两个对象之间存在"依赖"关系,依赖方会在它的依赖启动之后启动,并且在它的依赖停止之前停止。然而,有时侯精确的依赖关系是未知的。你可能只知道一种类型的对象必须在另一种类型的对象之前启动。在这种情况下,SmartLifecycle
接口提供了另一种途径:分阶段控制
,就是它的父接口中定义的getPhase()
方法。
public interface Phased { int getPhase(); } public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }
在启动时,phase值最低的对象会先启动;而停止时,phase值最高的对象会先停止。因此,如果一个对象实现了SmartLifecycle
接口,并且getPhase()方法返回了 Integer.MIN_VALUE
,那么这个对象会是最先启动并最后停止的对象中的一个。相反,phase的值为Integer.MAX_VALUE
则表明这个对象会最后启动并且最先停止(可能是因为它需要其它的进程保持运行)。对于phase值,必须要知道,对于那些没有实现SmartLifecycle
接口的普通Lifecycle
对象来说,默认的phase值为0。因此,任何负的phase值就表明这个对象会在标准组件之前启动(并且在它们之后停止),对于任何正的phase值,则是相反的含义(在标准组件之后启动,并在它们之前停止)。
正如你所看到的,SmartLifecycle
定义的stop函数接受一个回调(callback)。任何实现都必须在关闭处理完成之后,调用回调(callback)的run()方法。这可以实现异步的关闭,这是必须的。因为LifecycleProcessor
接口的默认实现,DefaultLifecycleProcessor
会 等待一段时间,直到超时,以便每一个phase中的对象能够调用该回调。每一个phase的默认超时时间是30秒。你可以通过在容器中定义一个名 为"lifecycleProcessor"的bean来覆盖默认的生命周期处理器实例。如果你只想修改超时的时间长度,按下面的方式定义就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>
正如前面提到的,LifecycleProcessor
接口也为环境(容器,context)的刷新和关闭定义了回调函数。只要stop()函数被显示的调用,'close'回调只是驱动关闭处理流程,需要注意的是这一过程是在环境关闭的过程中发生的。相反,'refresh'回调则开启了SmartLifecycle
类型的bean的另一个特性。当环境被刷新时(在所有对象都被实例化并初始化完成之后),这个回调会被调用,就在此时,默认的lifecycle处理器将检查每一个SmartLifecycle
对象的isAutoStartup()
方 法返回的boolean值。如果为"true",那么这个对象会在此刻被启动,而不是等到对环境(context)或该bean的start()方法的显 示调用(与环境的刷新不同,对于一个标准的context实现,环境的启动不会自动发生)。"phase"值,以及"依赖"关系将会以前面所描述的方式来 决定启动的顺序。
![]() |
Note |
---|---|
这一节只适用于非web应用程序。当web应用程序关闭的时候,Spring的基于Web的 |
如果你在一个非web应用程序的环境中使用Spring的IoC容器;例如,在一个富 客户端桌面环境中;你可以向JVM注册一个shutdown钩子。这样做可以保证一个优雅的关闭,并且会调用你的单例bean的相关destroy函数。 这样,所有的资源会被释放。当然,你必须正确的配置和实现这些destroy回调函数。
你可以调用registerShutdownHook()
方法来注册一个shutdown钩子,这个方法在类AbstractApplicationContext
上声明:
import org.springframework.context.support.AbstractApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public final class Boot { public static void main(final String[] args) throws Exception { AbstractApplicationContext ctx = new ClassPathXmlApplicationContext(new String []{"beans.xml"}); // add a shutdown hook for the above context... ctx.registerShutdownHook(); // app runs here... // main method exits, hook is called prior to the app shutting down... } }
当ApplicationContext
创建了一个实现org.springframework.context.ApplicationContextAware
接口的类时,这个类会被提供一个到该ApplicationContext
的引用。
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
因此bean能够在代码中操作创建该bean的ApplicationContext
,通过ApplicationContext
接口,或者把这个引用转换成一个已知的子接口,例如ConfigurableApplicationContext
, 子接口会提供更多的功能。一个用处就是以编程的方式来获取其它bean。有时候这个功能是有用的;然而,通常你要避免这样做,因为它会把你的代码和 Spring耦合在一起,同时这也违背了IoC的初衷,因为协作bean应当以属性的方式来提供。ApplicationContext提供的其它方法可 以访问文件资源,发布应用程序事件,以及访问一个MessageSource。这些附加的特性在4.14节, ApplicationContext的更多功能中有描述。
在Spring 2.5中,自动装配是另一种获取ApplicationContext
引用的方式。"传统的"通过构造函数和通过类型的自动装配模式(在4.4.5节, 自动装配协作者中有描述)可以分别为构造函数的参数和setter方法的参数提供ApplicationContext
类型的依赖。为了更具灵活性,包括自动装配字段和多个参数的方法,可以使用新的基于注解的自动装配特性。如果字段,构造函数参数,或者setter方法参数带上了@Autowired
注解的话,ApplicationFactory
会自动装配给期望得到BeanFactory
类型的字段,构造函数参数,或者方法参数。更多信息,见4.9.2节, @Autowired
当ApplicationContext创建了一个实现org.springframework.beans.factory.BeanNameAware
接口的类时,该类会拥有一个到它相关对象定义中的名称的引用
public interface BeanNameAware { void setBeanName(string name) throws BeansException; }
这个接口会在普通bean属性生成之后,在初始化回调函数之前(比如:InitializingBean
, afterPropertiesSet或者一个自定义的init-方法)被调用。
除了上面讨论的ApplicationContextAware
和BeanNameAware
接口,Spring提供一系列的Aware
接口,来方便bean在需要一个特定的 基础设施(infrastructure)依赖时,能够获得容器的引用。最重要的Aware
接口总结在下面--按常规,单看名字可以对依赖的类型略知一二了:
Table4.4.Aware
interfaces
Name | Injected Dependency | Explained in... |
---|---|---|
|
声明 |
|
|
Event publisher of the enclosing |
|
|
用于装载bean类的类装载器 |
|
|
声明 |
|
|
所声明bean的名称 |
|
|
|
|
|
定义weaver,用于在装载时处理类定义。 |
Section8.8.4, Load-time weaving with AspectJ in the Spring Framework |
|
可配置策略,用于解析消息(支持参数化和国际化) |
|
|
Spring JMX notification publisher |
|
|
包含当前容器的 |
|
|
包含当前容器的 |
|
|
可配置的装载器,用于对资源的底层访问。 |
|
|
包含当前容器的 |
|
|
包含当前容器的 |
请再次注意,这些接口的使用会把你的代码与Spring API绑定在一起,这违背了控制翻转的初衷。因此,建议你只把它们用在基础设施bean中,它们通常需要以编程的方式访问容器。
一个bean定义可能包含很多的配置信息,包括构造函数参数,属性值,以及容器特定的信息,比如初始化方法,静态工厂方法名称,等等。一个child bean的定义可以从parent定义中继承配置数据。child定义可以覆盖一些值,或者按需要增加其它的一些值。使用parent和child bean定义可以节省大量的录入工作。实际上,这是模版方式的一种体现。
如果你以编程的方式使用一个ApplicationContext
接口,那么child bean定义以ChildBeanDefinition
类来表示。大部分用户并不会在这个层次使用它们,相反,会在类似于ClassPathXmlApplicationContext
中显示的配置bean 定义。当你使用基于XML的配置元数据时,你通过parent
属性来指明一个child bean定义,通过这个属性的值来指定parent bean。
<bean id="inheritedTestBean" abstract="true" class="org.springframework.beans.TestBean"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithDifferentClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBean" init-method="initialize"> <property name="name" value="override"/> <!-- the age property value of 1 will be inherited from parent --> </bean>
child bean的定义中如果没有指定class,则可以使用parent中定义的class,但也可以覆盖它。在后一种情况下,child bean的类必须与parent匹配,也就是,它必须能接受parent的属性值。
child bean的定义从parent继承了构造函数参数值,属性值,并可以覆盖parent的方法,也可以增加新的值。你指定的所有初始化方法,销毁方法,和/或静态
工厂方法,都将覆盖parent设置中对应的内容。
下面的配置总是从child定义中取得: depends on, autowire mode, dependency check, singleton, scope, lazy init。
前面的例子通过使用abstract
属性,显示的把parent bean定义为abstract。如果parent定义没有指定一个类,就需要显示的把parent bean定义为abstract
,如下所示:
<bean id="inheritedTestBeanWithoutClass" abstract="true"> <property name="name" value="parent"/> <property name="age" value="1"/> </bean> <bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean" parent="inheritedTestBeanWithoutClass" init-method="initialize"> <property name="name" value="override"/> <!-- age will inherit the value of 1 from the parent bean definition--> </bean>
这个parent bean自身不能被实例化,因为它是不完整的,并且被显示的标记为abstract
。当一个bean像这样被标记为abstract
时,它只能作为一个纯模版bean定义,用于child定义的父定义。尝试使用这样的一个abstract
类型的parent bean自身,把它赋给另外一个bean的属性,或者以parent bean的id来显示调用getBean()
方法,都会返回一个错误。类似的,容器内部的preInstantiateSingletons()
方法会忽略被定义为abstract的bean。
![]() |
Note |
---|---|
默认情况下, |
4.8 容器扩展点(Container Extension Points)
通常,应用程序开发者无需实现一个 ApplicationContext
的子类。相反,Spring IoC 容器可以通过插入一些特殊集成接口的实现来达到被扩展的目的。下面几节描述这些集成接口。
BeanPostProcessor
接口定义了一些回调函数,你可以实现这些回调函数,提供你自己的(或覆盖容器默认的)实例化逻辑,依赖解析逻辑,等等。如果你想实现在Spring容器完成实例化、配置和初始化bean之后的一些逻辑,你可以插入一个或更多的 BeanPostProcessor
实现。
你可以配置多个BeanPostProcessor
实例,也可以通过设置order
属性来控制这些BeanPostProcessor
执行的顺序。只有当BeanPostProcessor
实现了Ordered
接口时,你才可以设置这个属性。如果你写了自己的BeanPostProcessor
,那么你也要考虑实现Ordered
接口。请参阅下面的注意事项,关于 以编程的方式注册BeanPostProcessors
![]() |
Note |
---|---|
要想改变真正的bean定义(也就是,定义bean的蓝图),你可以使用 |
org.springframework.beans.factory.config.BeanPostProcessor
接口只有两个回调函数。当实现BeanPostProcessor接口的类被注册为一个容器的一个后处理器时,对于由容器创建的每一个bean实例,在容器的初始化方法(比如,InitializingBean接口的afterPropertiesSet() 方法,以及任何声明的初始化方法)调用之前以及所有bean初始化回调函数调用之后,后处理器的回调函数都会被调用。后处理器可以对bean实例做任何处 理,甚至可以完全忽略回调函数。bean的后处理器通常检查回调接口,或者用一个代理把bean包装起来。一些Spring AOP基础设施类就被实现为bean的后处理器,以便提供代理包装逻辑。
ApplicationContext
会自动检测所有在配置元数据中定义的实现了BeanPostProcessor
接口的bean。ApplicationContext
把这些bean注册为后处理器(post-processor),这样它们会在bean创建之后被调用。bean的后处理器(post-processor)在容器中的部署和其它任何bean都是一样的。
![]() |
以编程的方式注册bean后处理器(Programmatically registering BeanPostProcessors) |
---|---|
尽管注册 |
![]() |
BeanPostProcessors and AOP auto-proxying |
---|---|
实现了 对于所有这种bean,你可能会看到这样的log信息: Bean foo 不能被任何BeanPostProcessor接口处理(例如,不符合自动代理的条件)。 |
下面的例子演示了如何在一个ApplicationContext
中编写,注册,以及使用BeanPostProcessors
。
第一个例子演示了基本的用法。这个例子展示了一个自定义的BeanPostProcessor
实现,当每一个bean被容器创建的时候,它会调用bean的toString()
方法,并且向系统控制台打印结果字符串。
下面是自定义BeanPostProcessor
实现类的定义:
package scripting; import org.springframework.beans.factory.config.BeanPostProcessor; import org.springframework.beans.BeansException; public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor { // simply return the instantiated bean as-is public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; // we could potentially return any object reference here... } public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { System.out.println("Bean '" + beanName + "' created : " + bean.toString()); return bean; } }
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:lang="http://www.springframework.org/schema/lang" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang-3.0.xsd"> <lang:groovy id="messenger" script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy"> <lang:property name="message" value="Fiona Apple Is Just So Dreamy."/> </lang:groovy> <!-- when the above bean (messenger) is instantiated, this custom BeanPostProcessor implementation will output the fact to the system console --> <bean class="scripting.InstantiationTracingBeanPostProcessor"/> </beans>
注意到 InstantiationTracingBeanPostProcessor
的定义时很简单的。它甚至没有一个名字,因为它是一个bean,所以它能够像其它bean一样被依赖注入。(前面的配置也定义了一个由Groovy脚本支持的bean。Spring 2.0动态语言支持在第27章, 动态语言支持中有详细介绍)
下面简单的Java应用程序执行了前面的代码和配置:
import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.scripting.Messenger; public final class Boot { public static void main(final String[] args) throws Exception { ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml"); Messenger messenger = (Messenger) ctx.getBean("messenger"); System.out.println(messenger); } }
前面应用程序的输出类似于下面这样:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
我们要探究的下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
。这个接口的语义和BeanPostProcessor
是类似的,一个主要的区别在于:BeanFactoryPostProcessor
作用于bean 的配置元数据;也就是,Spring IoC 容器允许BeanFactoryPostProcessors
读取配置元数据,并且有可能在容器实例化除了BeanFactoryPostProcessors
之外的任何bean之前修改它。
你可以配置多个BeanFactoryPostProcessors
,并且可以通过设置order
属性来控制这些BeanFactoryPostProcessors
的执行顺序。不过,只有在BeanFactoryPostProcessor
实现了Ordered
接口时,你才能够设置这个属性。如果你编写自己的BeanFactoryPostProcessor
实现,你也需要考虑实现Ordered
接口。关于BeanFactoryPostProcessor
和 Ordered
接口的更多细节信息,请参考Javadoc。
![]() |
Note |
---|---|
如果你想改变实际的bean实例(也就是,根据配置元数据来创建的对象),你需要使用
|
当bean factory post-processor 被声明在一个ApplicationContext
内部时,它会被自动执行,以便对定义容器的配置元数据应用改变。Spring自带了很多预定义的bean factory post-processor,例如PropertyOverrideConfigurer
和 PropertyPlaceholderConfigurer
。也可以使用自定义的BeanFactoryPostProcessor
,例如,可以用于注册自定义属性编辑器。
ApplicationContext
会自动检测所有定义其中,并实现了 BeanFactoryPostProcessor
接口的bean。它在合适的时机把这些bean用作bean factory post-processors。你可以像部署任何其它bean一样的部署这些 post-processor bean。
![]() |
Note |
---|---|
像 |
你可以用PropertyPlaceholderConfigurer
来把属性值从bean定义中拆出来,放在一个独立的文件中,使用标准Java中的Properties
格式。这样做可以使部署应用程序的人能够定制特定于环境的属性,比如数据库URL和密码,而不需要做一些复杂的工作,来冒险修改主XML定义文件,或是容器的配置文件。
考虑下面基于XML的配置元数据片段,其中定义了一个DataSource
,它带有一个占位符(placeholder)。例子演示了在一个外部的Properties
文件中配置的属性。在运行时,PropertyPlaceholderConfigurer
被应用于元数据,数据源的属性会被替换。需要被替换的值被标记为这种占位符格式:${property-name}。这和 Ant / log4j / JSP EL所使用的格式是一样的。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations" value="classpath:com/foo/jdbc.properties"/> </bean> <bean id="dataSource" destroy-method="close" class="org.apache.commons.dbcp.BasicDataSource"> <property name="driverClassName" value="${jdbc.driverClassName}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
真实的值来自于另一个文件,该文件的格式是标准Java的Properties
格式:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
因此,字符串${jdbc.username}
在运行时被替换为值'sa',对于其它的占位符,也会被替换为属性文件中匹配的key所对应的值。PropertyPlaceholderConfigurer
会检查bean定义中的绝大多数属性中的占位符。此外,占位符的前缀和后缀是可以自定义的。
在Spring2.5中引入了context
名称空间之后,可以用一个专门的配置元素来配置属性占位符。可以在location
属性中提供一个逗号分隔的location列表。
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
不仅会在你指定的Properties
文件中查找属性。默认情况下,如果它在指定的Properties
文件中查找不到一个属性的话,它也会检查Java系统
变量。你可以通过设置configurer的systemPropertiesMode
属性的值来自定义这种行为,可设置为下面三种整数值之一:
-
never (0): 从不检查系统属性
-
fallback (1): 如果在指定的properties文件中找不到属性值,则检查系统属性。这是默认值。
-
override (2): 在尝试指定的properties文件之前,先检查系统属性。这就会使系统属性覆盖任何其它的属性来源。
关于PropertyPlaceholderConfigurer
的更多信息,请参考Javadoc。
Class name substitution | |
---|---|
你可以使用 <bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/foo/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.foo.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/> 如果占位符中被替换的class名称在运行时不能被解析为一个有效的类,那么bean的解析就会失败。对于一个非延迟初始化的bean来说,这是 |
PropertyOverrideConfigurer
是另一个bean factory post-processor,类似于PropertyPlaceholderConfigurer
,区别在于,bean属性的原始定义可能有默认值,也可能根本没有值。如果一个覆盖的Properties
文件中不存在某一bean属性中的一项,那么就会使用原始定义的默认值。
注意,bean定义并不知道会被覆盖,所以从XML定义文件并不能马上知晓覆盖配置会被使用。对于存在多个PropertyOverrideConfigurer
实例,并且对于同一个bean属性定义了多个不同的值的情况,因为采用的是覆盖的机制,所以最后一个值会被使用。
Properties文件中的配置行采用这个格式:
beanName.property=value
例如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
这个示例文件可能被用于这样的一个容器定义,它包含了一个名为dataSource的bean,该bean有两个属性: driver 和 url
复合的property也被支持,只要路径中除了最后一个将要被覆盖的属性之外的每一个组件已经为非空(想必是被构造器初始化了)。在下面这个例子中......:
foo.fred.bob.sammy=123
foo
bean的fred
属性的bob
属性的sammy
属性被赋值为数量值123
。
![]() |
Note |
---|---|
指定的覆盖值总是字面值,它们不会被翻译为bean的引用。即使XML中bean定义的原始值指定了一个bean引用,这个规则也是适用的。也就是说,bean引用会被一个字面值所覆盖。 |
自从Spring 2.5 引入了context
名称空间之后,可以使用一个专门的配置元素来配置属性覆盖:
<context:property-override location="classpath:override.properties"/>
对于那些自身是工厂的对象实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是对Spring IoC 容器实例化逻辑进行扩展的一个切入点。如果你有复杂的初始化代码,用XML配置语句来表达可能会很冗长,而用Java代码却能够更好的表达你的逻辑,那么你可以创建自己的FactoryBean
,在这个类里来写复杂的初始化代码,然后把自定义的FactoryBean
类插入到容器中。
FactoryBean
接口提供三个方法:
-
Object getObject()
:返回该工厂创建的对象的一个实例。这个实例可能是共享的,取决于工厂方法返回的是一个singleton或者prototype。 -
boolean isSingleton()
:如果FactoryBean
返回的是singleton,该方法返回true
,否则返回false
。 -
Class getObjectType()
:返回getObject()
方法返回的对象类型,如果该类型提前还不知道的话,返回null
。
FactoryBean
的概念以及接口在Spring框架中的很多地方被用到;Spring自身带有超过50个FactoryBean
接口的实现。
当你需要向容器请求一个FactoryBean
自身的实例,而不是它所生产的bean时,你可以调用ApplicationContext
的getBean()
方法,参数是bean的id,并以&
符号作为前缀。例如,对于一个id为myBean
的bean,调用容器的getBean("myBean")
会返回这个bean(FactoryBean
的产品),而调用getBean("&myBean")
会返回FactoryBean
自身的实例。
除了使用基于XML的配置信息之外,还可以使用基于注解的配置,它不再使用尖括号形式的声明,而是字节码元数据来组装组件。通过使用注解,开发者不再使用XML来描述装配信息,而是在相关的类、方法、字段上使用注解,把配置信息移动到组件类的代码里。 正如在4.8.1.2节, 例子: RequiredAnnotationBeanPostProcessor中提到的,把BeanPostProcessor
和注解配合使用是扩展Spring IoC 容器的一种方式。例如,在Spring 2.0中引入了@Required注解,用于强制必要属性被赋值。Spring 2.5 实现了通常用于依赖注入的XML配置方式都有对应的注解方式。本质上,@Autowired
注解提供了和4.4.5节, 自动装配所描述的相同的功能,而且提供更加细粒度的控制,以及更广泛的适用性。Spring 2.5也增加了对JSR-250 注解的支持,比如@PostConstruct
和@PreDestroy
。Spring 3.0增加了对JSR-330(对Java的依赖注入)注解的支持(包含在 javax.inject 包中),例如@Inject
和 @Named
。这些注解的详细信息可以在4.11 使用 JSR 330 标准注解中找到。
![]() |
Note |
---|---|
注解注入在XML注入之前执行。因此对于使用了两种配置方式的对象来说,基于XML的配置信息会覆盖基于注解的配置。 |
和通常一样,你可以把它们注册为独立的bean定义,但是通过在基于XML的配置文件中使用下面的标签,它们也可以被显示的注册(注意,包含了context
名称空间):
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> </beans>
(这句话注册的后处理器(post-processors)包括AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
, PersistenceAnnotationBeanPostProcessor
,以及后续将要提到的RequiredAnnotationBeanPostProcessor
。)
![]() |
Note |
---|---|
|
@Required
应用于bean属性的setter方法,正如下面的例子所示:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
这个注解的含义很简单,它指明受影响的bean属性必须在配置时被填充,通过bbean定义中的一个显示的属性值,或者通过自动装配。如果受影响的bean没有被填充,容器会抛出一个异常;这就可以及早的发现问题,避免后续的处理中抛出NullPointerException
等类似的错误。仍然建议你在bean类自身中添加断言,例如,添加到init方法中。这样做可以强制那些必须的引用和值被赋值,即使你在容器外面使用这个类。
正如你所想,你可以把@Autowired
注解用于“传统”的setter方法:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
![]() |
Note |
---|---|
JSR 330 的@inject注解可以用于Spring的 |
你也可以把注解应用于方法,方法可以有任意的名称,和/或者多个参数:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
你能够把@Autowired
应用于构造函数和字段:
public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
在期望某种类型数组的字段或方法上添加@Autowired注解,可以装配ApplicationContext
中所有指定类型的bean:
public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... }
同样也适用于集合类型:
public class MovieRecommender { private Set<MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
只要期望的键值类型是String
,甚至是Map类型也能够被自动装配。Map的值将包含期望类型的所有bean,而键就是对应的bean的name。
public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
默认情况下,当没有候选bean可用时,自动装配会失败;被注解的方法、构造函数和字段默认情况下会被视为必须的(required)依赖。这个行为可以被改变,如下面所示:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required=false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
![]() |
Note |
---|---|
一个类中只能有一个被注解的构造函数可以被标记为required,但是可以注解多个non-required构造函数。这种情况下,拥有的参数数量最多的构造函数的依赖会被优先满足。 相比于 |
你也可以对那些众所周知可解析的依赖使用@Autowired
: BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
, ApplicationEventPublisher
, and MessageSource
。这些接口以及它们的扩展接口,例如ConfigurableApplicationContext
或 ResourcePatternResolver
,都是自动解析的,不需要专门建立依赖关系。
public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... }
![]() |
Note |
---|---|
|
因为类型的自动装配,可能会导致存在多个候选bean,所以往往需要对选择过程有更多的控制。做到这一点的方法之一是使用Spring的 @Qualifier注解。你可以关联特定参数的限定词,缩小匹配类型的范围,这样的话对每一个参数都只有一个特定的bean被选中。在最简单的情况 下,@Qualifier可以是一个简单的描述值:
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... }
@Qualifier
注解也可以用于单个构造函数参数或方法参数:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(@Qualifier("main") MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
对应的bean定义如下所示。限定值为"main"的bean会被装配为有着同样限定值的构造函数参数。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier value="main"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier value="action"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
作为一种退化匹配方式(For a fallback match),bean的名字会被认为是一个默认的限定值。因此,你可以定义一个id为"main"的bean,而不必使用嵌套的qualifier元 素,这样会有同样的匹配结果。然而,尽管你可以使用这种方式通过名称来引用一个特定的bean,但是@Autowired
本 质上是一种有着可选的限定值的类型驱动注入。这意味着,限定值通常在类型匹配集合中可以缩小选择的范围,它们并不是要从语义上表达出对唯一一个bean的 引用。好的限定值,如"main",或者"EMEA",或者"persistent”,表达出特定组件的特性,这独立于bean 的id。在匿名bean定义中,bean id是可以自动产生的,正如前面的例子中所描描述的。(这段话总的来讲,就是说qualifier元素的使用和bean id的使用是两种思路。你虽然可以用bean id来起到qualifier的作用,但是最好不要这样做,这不是最佳实践。qualifier的本质是描述一个bean的特性,比如这个bean是用于 持久层的,还是用于web层的。qualifier可以用于匿名bean中。)
Qualifier也适用于集合类型,例如上面所讨论的Set<MovieCatalog>
。在这种情况下,与声明的限定符相匹配的所有bean都会被注入到这个集合。这意味着,限定符不必非得是限定唯一的bean;它们的过滤标准是很粗放的。例如,你可以定义多个MovieCatalog
类型的bean,它们可以有相同的限定值"action";这样所有的bean都会被注入给一个带有@Qualifier("action")
注解的集合:Set<MovieCatalog>
。
Tip | |
---|---|
如果你想表达通过注解驱动的按名称注入,不要首先考虑使用 这种语义上的区别带来的一个结果就是,自身被定义为集合或者map类型的bean不能够通过
|
你可以创建你自己的自定义qualifier注解。只需简单的定义一个注解,并在你的定义中加上@Qualifier
注解即可:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }
然后,你就可以在自动装配的字段和参数上提供自定义的qualifier:
public class MovieRecommender { @Autowired @Genre("Action") private MovieCatalog actionCatalog; private MovieCatalog comedyCatalog; @Autowired public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) { this.comedyCatalog = comedyCatalog; } // ... }
接下来,提供候选bean定义的信息。你可以把<qualifier/>
标签添加为<bean/>
标签的子元素,然后指定type
和value
的值来匹配你的自定义qualifier注解。type匹配为注解的完整类名称。或者,在不存在命名冲突的情况下,为方便起见,你可以使用短的类名。这两种方式在下面的例子中都有演示。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="Genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="example.Genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> <bean id="movieRecommender" class="example.MovieRecommender"/> </beans>
在4.10节, Classpath扫描和管理的组件,"http://static.springsource.org/spring /docs/3.1.x/spring-framework-reference/html/beans.html#beans-classpath- scanning",将会看到一种替代在XML中提供qualifier元数据的方式,它是基于注解的。请看4.10.7节, 使用注解来提供qualifier元数据
有些情况下,可以使用一个没有值的注解就可以满足需求。当注解服务于一个更通用的目的,并且可以应用于多种类型的依赖时,这是很有用的。例如,你可能会提供一个offline类型,用于在没有网络访问时作为搜索结果返回。首先,定义一个简单的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
然后为自动装配的字段或者属性添加注解:
public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }
现在bean定义只需要一个限定符type
:
<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!-- inject any dependencies required by this bean --> </bean>
你也可以定义接受命名属性的自定义qualifier注解,可以补充或取代value
属性。如果为一个自动装配的字段或参数指定了多个属性值,那么一个bean定义如果想成为自动装配的候选bean,必须匹配所有的属性值。作为一个例子,考虑下面的注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }
在这个例子中, Format
是一个枚举:
public enum Format { VHS, DVD, BLURAY }
自动装配的字段都带有自定义的qualifier注解,并且都包含了两个属性genre
和 format
。
public class MovieRecommender { @Autowired @MovieQualifier(format=Format.VHS, genre="Action") private MovieCatalog actionVhsCatalog; @Autowired @MovieQualifier(format=Format.VHS, genre="Comedy") private MovieCatalog comedyVhsCatalog; @Autowired @MovieQualifier(format=Format.DVD, genre="Action") private MovieCatalog actionDvdCatalog; @Autowired @MovieQualifier(format=Format.BLURAY, genre="Comedy") private MovieCatalog comedyBluRayCatalog; // ... }
最终,bean定义需要包含匹配的qualifier值。这个例子也演示了,可以使用bean的meta属性来取代<qualifier/>
子元素。如果存在<qualifier/>
子元素的话,<qualifier/>
子元素和它的属性会优先用于匹配。但是在没有<qualifier/>
元素存在的时候,自动装配机制会退而求其次,使用<meta/>
标签的值,就像下面例子中的最后两个bean定义所示。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:annotation-config/> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Action"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <qualifier type="MovieQualifier"> <attribute key="format" value="VHS"/> <attribute key="genre" value="Comedy"/> </qualifier> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="DVD"/> <meta key="genre" value="Action"/> <!-- inject any dependencies required by this bean --> </bean> <bean class="example.SimpleMovieCatalog"> <meta key="format" value="BLURAY"/> <meta key="genre" value="Comedy"/> <!-- inject any dependencies required by this bean --> </bean> </beans>
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,它可以让你注册自定义的qualifier注解类型,即使它们没有Spring的@Qualifier
注解。
<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier</value> </set> </property> </bean>
应用程序上下文中所使用的AutowireCandidateResolver
具体实现,取决于Java的版本。在Java 5版本之前,qualifier注解还未被支持,因此自动装配候选bean只由每个bean定义的autowire-candidate
来决定,或者由<beans/>
元素上的default-autowire-candidates
模式来决定。在Java 5以及后续版本中,@Qualifier
注解的出现,以及通过CustomAutowireConfigurer
注册的任意自定义注解,都将发挥作用。
无论什么版本的Java,当有多个bean都符合qualify的条件而成为候选bean时,“主”候选bean的选取是一样的:如果在候选bean中,只有一个bean定义的primary
属性被设置为true
,那么这个bean会被选中。
Spring也支持在字段或bean属性的setter方法上使用JSR-250中的@Resource
注解来实现注入。在Java EE 5 以及6中,这是一个通用的模式,例如对于JSF 1.2管理的bean,或者JAX-WS 2.0 终端。Spring也支持在Spring管理的对象上使用这种模式。
@Resource
使用一个name属性,默认情况下Spring把这个值解释为待注入bean的名称。换句话说,它遵循by-name语义,正如这个例子所示的:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
如果没有显示的指定名称,默认名称就从字段名或setter方法中获取。对于一个字段来说,会使用字段名称;而对于setter方法来说,会使用属性名。所以,下面的例子会把名为"movieFinder"的bean注入给它的setter方法。
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
![]() |
Note |
---|---|
注解提供的名称被 |
如果使用@Resource
注解时,没有显示的指定名称,那么类似于@Autowired
,在查找不到指定名称的bean时,@Resource
会找到一种主匹配类型,同时会解析众所周知的可解析依赖,包括:BeanFactory
, ApplicationContext,
ResourceLoader, ApplicationEventPublisher
, 以及 MessageSource
接口。
因此,在下面的例子中,customerPreferenceDao
字段首先查找一个名为customerPreferenceDao的bean,如果找不到的话,就退而求其次,查找一个CustomerPreferenceDao
类型的bean作为主类型来匹配。"context"字段会基于众所周知的可解析依赖类型,被注入一个ApplicationContext
类型的bean。
public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... }
CommonAnnotationBeanPostProcessor
不仅能够识别出@Resource
注解,也能够识别出JSR-250的生命周期(lifecycle)注解。生命周期注解是从Spring 2.5版本开始引入的,这些注解是initialization callbacks和destruction callbacks的一种替代方案。前提条件是CommonAnnotationBeanPostProcessor
注册在Spring的ApplicationContext
中,在对应的Spring生命周期接口方法或者显示声明的回调方法被调用时,带有这些注解的方法会被调用。下面的例子中,缓存在初始化时会被预先生成(pre-populated),并且在析构时被清理。
public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... } }
![]() |
Note |
---|---|
关于组合各种生命周期机制的具体细节,参考Section4.6.1.4, 组合生命周期机制。 |
本章中的大多数例子都是用XML来指定配置元数据,用于生成Spring容器中的每一个BeanDefinition
。前一章(4.9节, 基于注解的容器配置)演示了如何通过源码级别的注解来提供很多的配置元数据。即使在上一章的例子中,"基础"bean定义信息也是显示定义在XML文件中的,注解只是驱动着依赖注入。这一节描述了一个新的选择:通过扫描classpath来隐式的检测候选组件。 候选组件是这样的类:它满足一定的过滤条件,并且在容器中注册有对应的bean定义。这就减少了使用XML来执行bean注册的必要性,因为可以使用注解 来代替(例如,@component),AspectJ类型表达式,或者你的自定义过滤条件来选择哪一个类将在容器中注册一个bean定义。
![]() |
Note |
---|---|
从Spring3.0开始,许多由Spring JavaConfig 项目提供的特性成为了Spring核心框架的一部分。这就让你可以使用java来定义bean,以取代使用传统的XML文件。对于如何使用这些新特性的例子,请看一下 |
在Spring2.0以及后续版本中,@Repository
注解可用于标记任何实现repository角色或模式(也被称为Data Access Object或者DAO)的类。这个标记的用途之一是对异常的自动翻译,请参考14.2.2节, Exception translation中的介绍。
Spring 2.5 引入了更多的模式注解:@Component
, @Service
, 和 @Controller
. @Component
是一个通用模式,可用于任何Spring管理的组件。 @Repository
, @Service
, 和 @Controller
是@Component
的专门化注解,用于更具体的用例,它们分别用于持久化层、服务层和展现层。因此,相比于使用@Component
注解,选择使用@Repository
, @Service
, 或@Controller
注解,你的类会更适合于被开发工具处理,以及关联到某一个方面。也许在Spring框架的未来版本中,@Repository
, @Service
, 和 @Controller
会包含更多的语义。因此,如果你在@Component
和 @Service
中选择你的service层注解, @Service
很明显是更好的选择。类似的,@Repository
也是DAO层的首选注解,同时它已经被实现为一个自动异常翻译的标记(a marker for automatic exception translation)。
Spring 能够自动检测模式化(stereotyped)的类,并向ApplicationContext注册对应BeanDefinition
。例如,下面的两个类就符合这种自动检测的条件:
@Service public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } } @Repository public class JpaMovieFinder implements MovieFinder { // implementation elided for clarity }
为了自动检测类,并且注册对应的bean,你需要在XML中包括下面的元素,其中的base-package元素是这两个类的共用父包(parent package)。(另外,你也可以指定一个逗号分隔的列表来包含每一个类的父包(parent package))。
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd"> <context:component-scan base-package="org.example"/> </beans>
![]() |
Note |
---|---|
classpath包的扫描需要classpath中对应的路径必须存在。在使用Ant来构建JAR包时,需确保不要开启JAR任务的files-only开关。 |
此外,当使用component-scan元素时,AutowiredAnnotationBeanPostProcessor
和 CommonAnnotationBeanPostProcessor
都会被隐式的包含进来。这就意味着,这两个组件会被自动检测和装配-不需要在XML中做任何bean配置。
![]() |
Note |
---|---|
你可以通过把annotation-config属性置为false来禁用 |
默认情况下,只有@Component
, @Repository
, @Service
, @Controller
,或者自身由@Component
标注的自定义注解标注的类才会被检测为候选组件。不过,你可以使用自定义过滤器来修改并扩展这种行为。把要包含或排除的类添加为component-scan
的include-filter或者exclude-filter子元素。每一个过滤器元素需要type
和expression
属性。下面的表描述了过滤器选项。
Table4.5.Filter Types
Filter Type | Example Expression | Description |
---|---|---|
annotation | org.example.SomeAnnotation |
An annotation to be present at the type level in target components. |
assignable | org.example.SomeClass |
A class (or interface) that the target components are assignable to (extend/implement). |
aspectj | org.example..*Service+ |
An AspectJ type expression to be matched by the target components. |
regex | org\.example\.Default.* |
A regex expression to be matched by the target components class names. |
custom | org.example.MyTypeFilter |
A custom implementation of the org.springframework.core.type .TypeFilter interface. |
下面的例子演示了XML配置信息,例子中排出了所有的@Repository
注解,并使用"stub" repository来代替。
<beans> <context:component-scan base-package="org.example"> <context:include-filter type="regex" expression=".*Stub.*Repository"/> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Repository"/> </context:component-scan> </beans>
![]() |
Note |
---|---|
你也可以禁用默认的过滤器,只需要把<component-scan/>元素增加一个use-default-filters="false"属性即可。这会禁用由 |
Spring组件也能够向容器提供bean定义元数据。你需要使用@Bean
注解,这和在@Configuration
标注的类中用来定义bean元数据的@Bean
注解是一样的。
@Component public class FactoryMethodComponent { @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } public void doWork() { // Component method implementation omitted } }
这个类是一个Spring组件,它有应用程序特定的代码,包含在它的doWork方法中。然而,它也贡献了一个bean定义,该bean引用一个工厂方法,即publicInstance()
方法。@Bean
注解用在工厂方法上,还定义了其它的bean属性,例如@Qualifier
注解指定的限定值。其它可指定的方法级别的注解包括@Scope
, @Lazy
,以及自定义的qualifier注解。也支持自动装配字段和方法,另外也支持@bean方法的自动装配。
@Component public class FactoryMethodComponent { private static int i; @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } // use of a custom qualifier and autowiring of method parameters @Bean protected TestBean protectedInstance(@Qualifier("public") TestBean spouse, @Value("#{privateInstance.age}") String country) { TestBean tb = new TestBean("protectedInstance", 1); tb.setSpouse(tb); tb.setCountry(country); return tb; } @Bean @Scope(BeanDefinition.SCOPE_SINGLETON) private TestBean privateInstance() { return new TestBean("privateInstance", i++); } @Bean @Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS) public TestBean requestScopedInstance() { return new TestBean("requestScopedInstance", 3); } }
这个例子把String类型的方法参数country装配为另一个名为privateInstance
的bean的Age
属性。一个Spring的表达式语言元素通过#{ <expression> }
符号来定义了属性的值。对于@Value
注解,一个表达式解析器会被预先配置,以便在析解表达式文本时查找bean名称。
在Spring component组件中,@Bean
方法的处理和@Configuration
类中的@Bean不一样。区别就在于@Component
注解的类不会使用CGLIB来拦截方法和字段的调用。所谓CGLIB代理,就是说@Configuration
类中的@Bean
方法,在调用协作对象的方法或字段时,会创建协作对象的bean元数据引用(create bean metadata references)。方法并不是按照普通Java语法来调用的。相反,在@Component
的@Bean
方法中调用方法或字段,有着标准的Java语义。
当一个组件被自动检测为扫描进程的一部分时,它的名称由这个扫描进程的BeanNameGenerator
策略来产生。默认情况下,任意包含name
值的Spring模式(stereotype)注解( @Component
, @Repository
, @Service
, 和 @Controller
)会把这个name值提供给对应的bean定义。
如果这种注解没有包含name
值,或者是对于其它类型的检测组件(比如由自定义过滤器 检测到的组件),默认bean的名称生成器会返回小写字母开头的非限定类名(也就是不包括完整包路径的类名)。例如,如果下面两个组件被检测到,bean 名称将是myMovieLister和movieFinderImpl:
@Service("myMovieLister") public class SimpleMovieLister { // ... } @Repository public class MovieFinderImpl implements MovieFinder { // ... }
![]() |
Note |
---|---|
如果你不想依赖于默认的bean命名策略,你可以提供一个自定义bean命名策略。首先,实现 |
<beans> <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" /> </beans>
作为一个通用规则,只要其它组件有可能显示的引用一个bean,就应该考虑在注解中指定name。否则的话,自动产生的名称就足够了。
与Spring通常管理的组件一样,对于自动检测组件来说,默认的,也是最常用的作用域是单例(singleton)。然而,有时候你需要其它的作用域,Spring 2.5 提供了一个新的@Scope
注解。在注解中填入作用域的名称即可:
@Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... }
![]() |
Note |
---|---|
如果想实现一个自定义的作用域解析策略,不依赖基于注解的方法,需要实现 |
<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" /> </beans>
当使用某些非singleton的作用域时,可能需要为限定作用域的对象生成代理。原因在Section4.5.4.5, Scoped beans as dependencies有描述。为此,在组建扫描元素上,需要使用一个scoped-proxy属性,可以取三个值:no, interfaces, 以及 targetClass。例如,下面的配置会使用标准的JDK动态代理
<beans> <context:component-scan base-package="org.example" scoped-proxy="interfaces" /> </beans>
@Qualifier
注解在Section4.9.3,Section4.9.3使用@Qualifier优化基于注解的自动装配这一节中有讨论。那一节中的例子演示了当你解析自动装配候选对象是,如何使用@Qualifier
注解,以及自定义限定符注解来提供细粒度的控制。因为这些例子是基于XML的bean定义,限定符元数据是通过bean
元素的qualifier
或 meta
子元素来提供给候选bean定义的。依赖classpath扫描来自动检测组件时,您可以使用候选类的类型级别的注解来提供限定符元数据。下面的三个例子演示了这种方式:
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... } @Component @Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... } @Component @Offline public class CachingMovieCatalog implements MovieCatalog { // ... }
![]() |
Note |
---|---|
与大多数基于注解的方法一样,请牢记注解元数据是绑定到类定义本身,而通过XML的方式可以为相同类型的多个bean提供限定符元数据的不同变化,因为元数据提供给实例,而不是提供给类。 |
从Spring 3.0开始,Spring提供对JSR-330标准注解(依赖注入)的支持。这些注解的扫描方式和Spring注解相同。你只需要在你的classpath中包含相关的jar包。
![]() |
Note |
---|---|
如果你使用Mave, <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> |
类似于@Autowired
,@javax.inject.Inject
可以按如下的方式来使用:
import javax.inject.Inject; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
和@Autowired
一样,可以在类级别,字段级别,方法级别和构造函数参数级别使用@Inject
.如果你想为需要注入的依赖使用一个限定符名称,你应该使用@Named
注解,如下所示:
import javax.inject.Inject; import javax.inject.Named; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(@Named("main") MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
类似于@Component
,@javax.inject.Named
可以按如下方式来使用:
import javax.inject.Inject; import javax.inject.Named; @Named("movieListener") public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
一种很普遍的现象是,在使用@Component
注解的时候,并不会指定组件的名称。@Named
可以以一种相同的方式来使用:
import javax.inject.Inject; import javax.inject.Named; @Named public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
在使用 @Named
的时候,使用组件扫描的方式和使用Spring注解是完全一样的:
<beans> <context:component-scan base-package="org.example"/> </beans>
在使用标准注解的时候,必须要知道的一点是,一些很重要的功能是不可用的,如下表所示:
Table4.6.Spring annotations vs. standard annotations
Spring | javax.inject.* | javax.inject restrictions / comments |
---|---|---|
@Autowired | @Inject | @Inject has no 'required' attribute |
@Component | @Named | ---- |
@Scope("singleton") | @Singleton |
JSR-330默认的作用域类似于Spring的
|
@Qualifier | @Named | ---- |
@Value | ---- | no equivalent |
@Required | ---- | no equivalent |
@Lazy | ---- | no equivalent |
Spring支持基于Java的配置这一新特性的核心是由@Configuration
注解的类。这些类主要是由@Bean
注解的方法组成,这些方法用于Spring IoC容器管理的对象的实例化,配置,以及初始化逻辑。
用@Configuration
注解一个类就表明这个类可以被Spring IoC 容器用于bean定义的来源。最简单的@Configuration
配置类内容大概是这样的:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
AppConfig
类等价于下面的XML配置:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
如你所见,@Bean
注解和<bean/>
元素起着相同的作用。后续的小节中会详细讨论@Bean
注解。首先,我们会讨论使用基于Java的配置方式创建Spring容器的各种途径。
下面的小节对Spring中的AnnotationConfigApplicationContext
做了说明,这是Spring 3.0引入的新特性。这种多用途的ApplicationContext
实现不仅能接受@Configuration
注解的类作为输入,也可以接受普通的@Component
类,以及由JSR-330元数据注解的类。
当@Configuration
类作为输入的时候,@Configuration
类自身会被注册为一个bean定义,并且该类中所有声明的@Bean
方法也被注册为bean定义。
当@Component
和JSR-330注解的类作为输入时,它们会被注册为bean定义,并且假定DI元数据@Autowired
或者@Inject
在必要时会被用于这些类。
在实例化ClassPathXmlApplicationContext
时,使用XML文件作为输入。同样的道理,实例化AnnotationConfigApplicationContext
时,可以使用@Configuration
作为输入。这就可以在Spring容器中完全不使用XML文件。
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
前面提到过,AnnotationConfigApplicationContext
不仅可以处理@Configuration
注解的类。所有@Component
或者JSR-330注解的类都可以作为它的输入。例如:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
上面的例子假定MyServiceImpl
, Dependency1
和 Dependency2
使用Spring依赖注入注解,例如@Autowired
。
4.12.2.2 使用register(Class<?>...)以编程的方式创建容器(Building the container programmatically using register(Class<?>...)
)
register(Class<?>...)
)
可以使用一个无参数的构造函数来实例化一个AnnotationConfigApplicationContext
,然后使用register()
方法来配置它。以编程的方式来构造AnnotationConfigApplicationContext
,这种方法特别有用。
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.register(AppConfig.class, OtherConfig.class); ctx.register(AdditionalConfig.class); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
Experienced Spring users will be familiar with the following commonly-used XML declaration from Spring's context:
namespace
经验丰富的Spring用户一定会对下面的常用XML声明非常熟悉,它声明了Spring的context:
名称空间:
<beans> <context:component-scan base-package="com.acme"/> </beans>
上面的例子中,com.acme
包会被扫描,查找所有@Component
注解的类,这些类会被注册为容器中的Spring bean定义。AnnotationConfigApplicationContext
提供scan(String...)
方法,来实现相同的组件扫描功能:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
![]() |
Note |
---|---|
请记住, |
AnnotationConfigApplicationContext的一个WebApplicationContext变体就是AnnotationConfigWebApplicationContext。这个实现可用于配置Spring的ContextLoaderListener
servlet listener,Spring MVC DispatcherServlet
,等等。下面是一个web.xml
片段,配置了一个典型的Spring MVC web应用程序。注意contextClass
中context-param 和 init-param的使用:
<web-app> <!-- Configure ContextLoaderListener to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- Configuration locations must consist of one or more comma- or space-delimited fully-qualified @Configuration classes. Fully-qualified packages may also be specified for component-scanning --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- Bootstrap the root application context as usual using ContextLoaderListener --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- Declare a Spring MVC DispatcherServlet as usual --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext instead of the default XmlWebApplicationContext --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- Again, config locations must consist of one or more comma- or space-delimited and fully-qualified @Configuration classes --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- map all requests for /app/* to the dispatcher servlet --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
类似于<import/>
元素用在Spring XML文件中,以帮助模块化配置,@Import
注解可以从另一个配置类中加载@Bean
定义。
@Configuration public class ConfigA { public @Bean A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { public @Bean B b() { return new B(); } }
现在,在实例化context时,不需要同时指定ConfigA.class
和 ConfigB.class
,只有ConfigB需要明确定义:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class); // now both beans A and B will be available... A a = ctx.getBean(A.class); B b = ctx.getBean(B.class); }
这种方法简化了容器的实例化,因为在构造的时候只有一个类需要处理,而不是要求开发人员加上大量的@Configuration
类。
上面的例子是可行的,但是过于简化了。在大多数实际情况下,跨越多个配置类中的bean会互相依赖。当使用XML时,这不是一个问题,因为没有编译器介入,只需要简单的声明ref="someBean"
,Spring就会在容器初始化时处理好一切。当使用@Configuration
类时,java编译器就限制了配置模型,也就说是对其它bean的引用必须是有效的Java对象。
幸运的是,解决这个问题也很简单。请记住@Configuration
类本质上只是容器中的另一个bean-这就意味着它们可以像其它任何bean一样来使用@Autowired
注入元数据。
让我们考虑一个更真实的场景,有若干个@Configuration
类,每一个都依赖于声明于其它类中的bean:
@Configuration public class ServiceConfig { private @Autowired AccountRepository accountRepository; public @Bean TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { private @Autowired DataSource dataSource; public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { public @Bean DataSource dataSource() { /* return new DataSource */ } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); // everything wires up across configuration classes... TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
在上述情形中,使用@Autowired运作良好,并提供所需的模块化,不过要想准确的知道自动装配的bean定义于何处还是有点困难的。例如,作为一个开发者,你审视着ServiceConfig
类时,你怎么知道@Autowired AccountRepository
bean在哪里定义的呢?在代码中,这并不是很明显就能看出来的,不过这并不是问题。因为开发工具(IDE)可以帮助解决此问题。 SpringSource Tool Suite提供了能够展示bean之间如何组装在一起的图形的工具。而且,你的IDE可以轻松找到所有AccountRepository
类型的对象的声明和使用,这样你就能快速找到返回该类型的@Bean
方法。
如果这种模糊的状况你无法接受,而你需要在IDE中直接从一个@Configuration
类跳到另一个,那么考虑自动装配configuration类自身(尽可能直接使用configuration自身,这样更容易理清楚依赖注入关系):
@Configuration public class ServiceConfig { private @Autowired RepositoryConfig repositoryConfig; public @Bean TransferService transferService() { // navigate 'through' the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); } }
在上面的代码中,很容易就能定位AccountRepository定义于何处。而现在,ServiceConfig与RepositoryConfig紧密耦合在一起了,这也是一种折衷:得到一些,就会失去一些。通过使用基于接口或基于抽象类的@Configuration
类,可以在一定程度上缓解这种紧密耦合。考虑下面的代码:
@Configuration public class ServiceConfig { private @Autowired RepositoryConfig repositoryConfig; public @Bean TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { public @Bean DataSource dataSource() { /* return DataSource */ } } public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class); TransferService transferService = ctx.getBean(TransferService.class); transferService.transfer(100.00, "A123", "C456"); }
现在,ServiceConfig
与DefaultRepositoryConfig
是松耦合的,并且内置的IDE工具还可以派上用场:开发者可以很容易的生成RepositoryConfig
实现的继承树。这样的话就可以从@Configuration
类,找到具体依赖的子类。
Spring的@Configuration
类的目标并不是要100%的完全取代XML配置。一些工具,比如XML名称空间仍然是一种配置容器的理想工具。在XML方式更便利或者必要的情形下,你有两个选择:要么以XML为中心实例化容器,例如ClassPathXmlApplicationContext
,或者以Java为中心,使用AnnotationConfigApplicationContext
,并且使用@ImportResource
注解来导入必要的XML。
从XML来启动Spring容器,并辅助的使用@Configuration
类,这种方式可能更可取。例如,在一个已经存在的,大型的使用Spring XML的代码库中,只在必要的时候使用@Configuration
类,并且在已存在的XML文件中包含它们,这更容易做到一些。下面,你会看到这种以XML为中心的方式来使用@Configuration
类:
要记住@Configuration
类本质上来讲只是容器中的bean定义。在这个例子中,我们创建了一个名为AppConfig
的@Configuration
类,并且把作为一个<bean/>
定义,包含在system-test-config.xml
文件中。因为<context:annotation-config/>
被开启了,容器会识别@Configuration
注解,并且能够很好的处理声明在AppConfig
中的两个@Bean
方法。
@Configuration public class AppConfig { private @Autowired DataSource dataSource; public @Bean AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } public @Bean TransferService transferService() { return new TransferService(accountRepository()); } } system-test-config.xml <beans> <!-- enable processing of annotations such as @Autowired and @Configuration --> <context:annotation-config/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="com.acme.AppConfig"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans> jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= public static void main(String[] args) { ApplicationContext ctx = new ClassPathXmlApplicationContext("classpath:/com/acme/system-test-config.xml"); TransferService transferService = ctx.getBean(TransferService.class); // ... }
![]() |
Note |
---|---|
在上面的 |
因为@Configuration
注解在元数据级别自动包含了@Component
注解,所以 @Configuration
注解的类自动成为组件扫描(component scanning)的候选对象。依然使用上面的例子,可以定义system-test-config.xml
文件来利用组件扫描。注意,在这个例子中,我们不需要显示的声明<context:annotation-config/>
,因为<context:component-scan/>
已经开启了所有同样的功能。
system-test-config.xml <beans> <!-- picks up and registers AppConfig as a bean definition --> <context:component-scan base-package="com.acme"/> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> <bean class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean> </beans>
在@Configuration
作为主要配置容器的方式的应用中,仍然有可能需要使用少许的XML配置。在这种情形中,可以使用@ImportResource
,只定义所需要的XML。这样做就实现了以Java为中心的配置容器方式,并且保持XML配置信息的最少化。
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { private @Value("${jdbc.url}") String url; private @Value("${jdbc.username}") String username; private @Value("${jdbc.password}") String password; public @Bean DataSource dataSource() { return new DriverManagerDataSource(url, username, password); } } properties-config.xml <beans> <context:property-placeholder location="classpath:/com/acme/jdbc.properties"/> </beans> jdbc.properties jdbc.url=jdbc:hsqldb:hsql://localhost/xdb jdbc.username=sa jdbc.password= public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); TransferService transferService = ctx.getBean(TransferService.class); // ... }
@Bean
是一个方法级别的注解,它模拟了XML中<bean/>
元素。注解也支持<bean/>
提供的一些属性,比如:init-method
,destroy-method
,autowiring
以及name
。
你可以在一个@Configuration
注解的,或是@Component
注解的类中使用@Bean
注解。
要想声明一个bean,只需要对一个方法使用@Bean
注解。你可以使用这种方式,向ApplicationContext
注册一个该方法返回类型的bean。默认情况下,bean名称和方法名相同。下面是一个声明@Bean
方法的简单例子:
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
前面的配置完全等价于下面的Spring XML配置:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
两种声明都会在ApplicationContext
中构造一个名称为transferService
的bean,它们都是TransferServiceImpl
类的实例:
transferService -> com.acme.TransferServiceImpl
如果@Bean
依赖于其它bean,可以像bean方法之间的调用一样来表达依赖关系:
@Configuration public class AppConfig { @Bean public Foo foo() { return new Foo(bar()); } @Bean public Bar bar() { return new Bar(); } }
在上面的例子中,foo
bean通过构造函数来注入一个bar
对象的引用。
定义在@Configuration
注解的类中的bean支持常规的生命周期回调。任何使用@Bean
注解定义的类都可以使用JSR-250中的@PostConstruct
和@PreDestroy
注解,更多信息,请参考 JSR-250注解
常规的Spring 生命周期回调函数也是完全支持的。如果一个bean实现了 InitializingBean
, DisposableBean
, 或Lifecycle
接口,它们各自的方法会被容器调用。
标准的*Aware
系列接口也完全支持,比如:BeanFactoryAware
,BeanNameAware
, MessageSourceAware
, ApplicationContextAware
等等。
@Bean
支持指定任意的初始化和析构回调函数,非常类似于Spring XML中bean
元素的init-method
和destroy-method
属性。
public class Foo { public void init() { // initialization logic } } public class Bar { public void cleanup() { // destruction logic } } @Configuration public class AppConfig { @Bean(initMethod = "init") public Foo foo() { return new Foo(); } @Bean(destroyMethod = "cleanup") public Bar bar() { return new Bar(); } }
当然,对于上面例子中的Foo
类来说,直接在构造函数里面调用init()
方法,是完全等价的。
@Configuration public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... }
Tip | |
---|---|
在用Java编程时,你可以对你的对象做任何事情,不必总依赖于容器的生命周期。 |
你可以指定通过@Bean
定义的bean的作用域。在 Bean的作用域这一节中定义的作用域都可以使用。
默认的作用域是singleton
,但是你可以使用@Scope
注解来覆盖默认值:
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
通过使用scoped proxies,Spring提供了一种很方便的方式来使用限定作用域的依赖(scoped dependencies)。使用XML配置时,创建这样的一个代理的最简单方式是<aop:scoped-proxy/>
元素。用@Scope注解来配置bean同样也支持代理模式(proxyMode)。默认是不使用代理( ScopedProxyMode.NO
),你也可以指定ScopedProxyMode.TARGET_CLASS
或者ScopedProxyMode.INTERFACES
。
如果把代理的例子从XML中移植到在Java代码中使用@Bean
注解的方式,代码类似于下面这样:
// an HTTP Session-scoped bean exposed as a proxy @Bean @Scope(value = "session", proxyMode = ScopedProxyMode.TARGET_CLASS) public UserPreferences userPreferences() { return new UserPreferences(); } @Bean public Service userService() { UserService service = new SimpleUserService(); // a reference to the proxied userPreferences bean service.setUserPreferences(userPreferences()); return service; }
前面已经提示过了,查询方法注入(lookup method injection)是一种高级特性,你很少会用到。它常用于一个单例作用域(singleton-scoped)的bean依赖一个原型作用域(prototype-scoped)的bean。使用Java来配置这种依赖是实现这种模式的很自然的方式。
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(); }
使用Java-configuration,你可以创建CommandManager
的子类,抽象的createCommand()
会被覆盖,以查询一个新的(prototype)command对象:
@Bean @Scope("prototype") public AsyncCommand asyncCommand() { AsyncCommand command = new AsyncCommand(); // inject dependencies here as required return command; } @Bean public CommandManager commandManager() { // return new anonymous implementation of CommandManager with command() overridden // to return a new prototype Command object return new CommandManager() { protected Command createCommand() { return asyncCommand(); } } }
默认情况下,configuration类使用@Bean
方法的名称作为结果bean的名称,而这个功能可以被name
属性覆盖。
@Configuration public class AppConfig { @Bean(name = "myFoo") public Foo foo() { return new Foo(); } }
正如Section4.3.1, 为bean命名这一节讨论的,有时候需要为一个bean赋予多个名字,也被称作是别名。@Bean
注解的name
属性接受一个字符串数组,以支持别名:
@Configuration public class AppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
下面的例子演示了一个@Bean
注解的方法被调用两次:
@Configuration public class AppConfig { @Bean public ClientService clientService1() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientService clientService2() { ClientServiceImpl clientService = new ClientServiceImpl(); clientService.setClientDao(clientDao()); return clientService; } @Bean public ClientDao clientDao() { return new ClientDaoImpl(); } }
clientDao()
方法在clientService1()
方法中被调用了一次,在clientService2()
方法中又被调用一次。这个方法创建ClientDaoImpl
对象的一个新实例并返回该实例。你可能会认为有两个实例(每一次调用都返回一个)。这完全是错误的理解:在Spring中,实例化的bean默认是有singleton
作用域的,也就是每次返回的其实是同一个对象。这种魔法般的功能是这样实现的:在启动时@Configuration
类都会被创建子类(使用CGLIB)。在子类中,子类的方法在调用父类的方法创建新的实例之前,会首先检查容器,以查找缓存的(限定作用域的)bean。
![]() |
Note |
---|---|
不同作用域的bean的行为是不同的,这里讨论的是单例作用域。 |
![]() |
Note |
---|---|
要知道,为了JavaConfig能正常工作,你必须在你的依赖列表中包含CGLIB jar包。 |
![]() |
Note |
---|---|
CGLIB在启动时动态添加了特性,也带来了一些限制:
|
Spring 2.5引入的context
名称空间提供了一个load-time-weaver
元素。
<beans> <context:load-time-weaver/> </beans>
向基于XML的Spring配置文件中添加这个元素会在ApplicationContext
中激活一个LoadTimeWeaver
。ApplicationContext
中的任何bean都可以实现LoadTimeWeaverAware
接口,从而获得一个到LoadTimeWeaver实例的引用。这在与Spring JPA结合起来很有用,因为JAP类的转换可能需要LoadTimeWeaver实例。更多细节,可以参考LocalContainerEntityManagerFactoryBean
的Javadoc。关于更多 AspectJ load-time weaving的信息,参考Section8.8.4, Load-time weaving with AspectJ in the Spring Framework
posted on 2013-03-02 11:56 seeker2012 阅读(1064) 评论(0) 编辑 收藏 举报