本章介绍了Spring框架实现控制反转(IoC) [1] 的原理。IoC也被称作 依赖注入 (DI)。它是一个处理对象依赖项的过程,也就是说,和他们一起工作的其他的对象,只有通过构造参数、工厂方法参数或者(属性注入)通过构造参数实例化或通过工厂方法返回对象后再设置属性。当创建bean后,IoC容器再将这些依赖项注入进去。这个过程基本上是反转的,因此得名 控制反转 (IoC), This process is fundamentally the inverse, hence the name Inversion of Control (IoC), of the bean itself controlling the instantiation or location of its dependencies by using direct construction of classes, or a mechanism such as the Service Locator pattern.
org.springframework.beans
和org.springframework.context
包是Spring框架IoC容器的基础。
BeanFactory
接口提供了一个先进的配置机制能够管理任何类型的对象。
ApplicationContext
(应用上下文) 是BeanFactory
的一个子接口。它增加了更方便的集成Spring的AOP功能、消息资源处理(使用国际化)、事件发布和特定的应用层,如在web应用层中使用的WebApplicationContext
。
总之,BeanFactory
提供了配置框架和基本功能,ApplicationContext
则添加了更多的企业特定的功能。ApplicationContext
是BeanFactory
的一个完整的超集,并且在本章专门用于指代Spring容器。关于更多使用BeanFactory
替代ApplicationContext
的信息,参考Section 5.16, “The BeanFactory”。
在Spring中,被Spring IoC 容器 管理的这些来自于应用主干的这些对象称作 beans 。bean是一个由Spring IoC容器进行实例化、装配和管理的对象。此外,bean只是你应用中许多对象中的一个。Beans以及他们之间的 依赖关系 是通过容器使用 配置元数据 反应出来。
org.springframework.context.ApplicationContext
接口代表了Spring
IoC容器,并且负责上面提到的Beans的实例化、配置和装配。容器通过读取配置元数据获取对象如何实例化、配置和装配的指示。配置元数据可以用XML、Java注解或Java代码来描述。它允许你表示组成你应用的对象,以及对象间丰富的依赖关系。
Spring提供了几个开箱即用的ApplicationContext
接口的实现。在独立的应用程序中,通常创建
ClassPathXmlApplicationContext
或
FileSystemXmlApplicationContext
的实例。
虽然XML是定义配置元数据的传统格式,但是你可以指示容器使用Java注解或者代码作为元数据格式,你需要通过提供少量XML配置声明支持这些额外的元数据格式。
在大多数的应用场景,不需要显式的代码来实例化一个或多个Spring IoC容器。例子,在wei应用中,在应用的web.xml
文件中,简单的8行样板式的xml配置文件就足够了。如果你使用 Spring Tool Suite 的Eclipse开发环境,你只需要点几下鼠标或者键盘就可以轻松的创建这个配置。
下面的图表是一个Spring工作的高级别视图。你的应用程序类都通过配置元数据进行关联,所以在ApplicationContext
创建和初始化后,你就有了一个完全配置和可执行的系统或应用程序。
如上图所示,Spring IoC容器使用了一种 配置元数据 的形式,这些配置元数据代表了你作为一个应用开发者告诉Spring容器如何去实例化、配置和装备你应用中的对象。
配置元数据通常使用一个简单和直观的XML格式,本章大部分都使用这种格式来表达Spring IoC容器概念和特性。
Note | |
---|---|
基于XML配置的元数据 不是 唯一允许用来配置元数据的一种形式。Spring IoC容器本身是 完全 和元数据配置书写的形式解耦的。这些天,许多开发者在他们的Spring应用中选择使用基于Java的配置的元数据形式。 |
更多有关在Spring容器中使用其他形式的元数据的内容,请查阅:
Spring配置包括至少一个且通常多个由容器管理的bean定义。在基于XML配置的元数据中,这些beans配置成一个<bean/>
元素,这些<bean/>
元素定义在顶级元素<beans/>
的里面。在Java配置中通常在一个@Configuration
注解的类中,在方法上使用@Bean
注解。
这些bean定义对应的实际对象组成了你的应用。通常你会定义服务层对象、数据访问层对象(DAO),展现层对象如Struts的Action
实例,底层对象如Hibernate的SessionFactories
,JMS的Queues
等等。一般很少会在容器中配置细粒度的领域对象,因为通常是DAO和业务逻辑负责创建和加载领域对象。但是你可以使用Spring集成AspectJ来配置IoC容器之外创建的对象。查看在Spring中使用AspectJ依赖注入领域对象。
下面的例子演示了基于XML的配置元数据的基础结构:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="..." class="..."> <!-- 在这里写 bean 的配置和相关引用 --> </bean> <bean id="..." class="..."> <!-- 在这里写 bean 的配置和相关引用 --> </bean> <!-- 更多bean的定义写在这里 --> </beans>
id
属性是一个用来识别每一个独立bean定义的字符串。class
属性定义了bean的类型,这个属性需要使用bean类的全限定名称。id属性的值可以被其他的bean对象引用。这个例子中没有引用其他bean,查看bean依赖获取更多信息。
实例化Spring IoC容器很容易。将一个或多个位置路径提供给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.xsd"> <!-- services --> <bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl"> <property name="accountDao" ref="accountDao"/> <property name="itemDao" ref="itemDao"/> <!-- 在这里写额外的bean的配置和相关引用 --> </bean> <!-- 更多Service层的bean定义写在这里 --> </beans>
下面的例子是数据访问层daos.xml
文件:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="accountDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao"> <!-- 在这里写额外的bean的配置和相关引用 --> </bean> <bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao"> <!-- 在这里写额外的bean的配置和相关引用 --> </bean> <!-- 更多数据访问层的bean定义写在这里 --> </beans>
在上面的例子中,服务层包含了PetStoreServiceImpl
类和两个类型为JpaAccountDao
和JpaItemDao
(基于JPA对象/关系映射标准)的数据访问对象。property name
元素指代JavaBean属性的名称,ref
元素引用了另一个bean定义的名称。id
和ref
直接的这种关系表达出了这两个合作对象间的依赖关系。配置对象直接依赖关系的详细信息,请参见依赖关系。
bean定义可以跨越多个XML文件是非常有用的。通常每个独立的XML配置文件表示一个逻辑层或者是你架构中的一个模块。
你可以使用应用上下文的构造方法从多个XML片段中加载bean的定义。像上面例子中出现过的一样,构造方法可以接收多个Resource
位置。或者可以在bean定义中使用一个或多个<import/>
从其他的配置文件引入bean定义。例如:
<beans> <import resource="services.xml"/> <import resource="resources/messageSource.xml"/> <import resource="/resources/themeSource.xml"/> <bean id="bean1" class="..."/> <bean id="bean2" class="..."/> </beans>
上面的例子中,外部的bean定义从services.xml
、messageSource.xml
和themeSource.xml
这三个文件中加载。所有的位置路径都是相对于定义执行导入的文件,所以 services.xml
必须和当前定义导入的文件在相同的路径下。而messageSource.xml
和themeSource.xml
必须在当前定义导入的文件路径下的resources
路径下。你可以看到,这里忽略了反斜杠,由于这里的路径是相对的,因此建议 不使用反斜杠。这些被引入文件的内容会被导入进来,包含顶层的<beans/>
元素,它必须是一个符合Spring架构的有效的XML bean定义。
Note | |
---|---|
使用一个相对"../"路径引用父目录中的配置是允许的,但是不推荐这么做。如果这么做就产生了一个当前应用外的引用依赖。特别不推荐在使用"classpath:"路径的时候,在运行的时候解析选择“最近”的classpath跟路径,然后在找父目录。Classpath配置的更改可能会导致选择一个不同的、错误的目录。 通常情况下,你可以使用完全限定的资源位置来代替相对路径,例如:"file:C:/config/services.xml"或"classpath:/config/services.xml"。但是请注意,你的应用可能和一个特定的绝对路径耦合了。通常更合适的方式是通过间接的方式来使用绝对路径,例如通过"${…}"占位符,在运行时解析JVM的系统属性。 |
ApplicationContext
是智能的工厂接口,它能够维护注册不同beans和它们的依赖。通过使用 T getBean(String
name, Class<T> requiredType)
方法,你可以取得这些beans的实例。
'ApplicationContext`使您可以读取的bean定义和像下面这样使用:
// 创建并配置beans ApplicationContext context = new ClassPathXmlApplicationContext(new String[] {"services.xml", "daos.xml"}); // 取得配置的实例 PetStoreService service = context.getBean("petStore", PetStoreService.class); // 使用实例 List<String> userList = service.getUsernameList();
使用getBean()
来获取您beans的实例,ApplicationContext
接口还有几个其他的可以获取beans的方法,但是理想情况下,你最好不要使用这些方法。
事实上,您的应用程序代码不应调用getBean()所有方法,因而不会和Spring的接口产生依赖。
例如,Spring的与web框架的集成提供了对各种web框架类的依赖注入,如控制器和JSF管理的bean。
一个Spring IoC容器管理了一个或者多个 beans。这些beans通过你提供给容器的配置元数据进行创建,例如通过XML形式的<bean/>
定义。
在容器内本身,这些bean定义表示为BeanDefinition
对象,它包含了如下的元数据:
这些元数据转换成组成每个bean定义的一组属性。
Table 5.1. bean定义
属性名 | 说明… |
---|---|
class | |
name | |
scope | |
constructor arguments | |
properties | |
autowiring mode | |
lazy-initialization mode | |
initialization method | |
destruction method |
除了bean定义外,它还包含有关如何创建特定bean的信息,ApplicationContext
实现还允许由用户在容器外创建注册现有的对象。
这是通过访问ApplicationContext的工厂方法,通过getBeanFactory()
返回DefaultListableBeanFactory
工厂方法的实现。
DefaultListableBeanFactory
支持通过registerSingleton(..)
方法和registerBeanDefinition(..)
方法进行注册。
然而,典型的应用程序的工作仅仅通过元数据定义的bean定义beans。
每个bean都有一个或多个标识符,这些bean的标识符在它所在的容器中必须唯一。
一个bean通常只有一个标识符,但如果它有一个以上的id
标识符,多余的标识符将被认为是别名。
基于xml的配置元数据中,你可以使用id
或(和) name
属性来指定bean的标识符。
id
属性允许您只指定一个id。通常这些名字是字母数字组成的(myBean,fooService等等),但也可能包含特殊字符。
如果你想给bean添加其他的别名,你可以通过name
属性来指定这些别名,可以使用逗号(,
),分号(;
)或者空格来分割这些别名。
这里需要特别注意的是,在Spring3.1版本以前,id
属性被定义成xsd:ID
类型(可以通过xml规则限制唯一),
在Spring3.1以及以后的版本中,id
被定义成了xsd:string
类型,id
属性不在通过XML解析器限制为唯一,而是通过容器强制限制为唯一。
bean的id和name不是必须的。如果没有明确的name或者id,容易会给bean生成一个唯一的名字。
但是,如果你想通过名称引用这个bean,通过使用ref
元素或服务定位器模式 查找,你就必须提供一个名字。
在对bean定义时,除了使用id
属性指定一个唯一的名称外,为了提供多个名称,需要通过name
属性加以指定,所有这个名称都指向同一个bean,在某些情况下提供别名非常有用,比如为了让应用每一个组件都能更容易的对公共组件进行引用。然而,在定义bean时就指定所有的别名并不总是很恰当。有时我们期望能够在当前位置为那些在别处定义的bean引入别名。在XML配置文件中,可以通过<alias/>
元素来完成bean别名的定义,例如:
<alias name="fromName" alias="toName"/>
在这种情况下,如果容易中存在名为fromName
的bean定义,在增加别名定义后,也可以用toName
来引用。
例如,在子系统A中通过名字subsystemA-dataSource
配置的数据源。在子系统B中可能通过名字subsystemB-dataSource
来引用。当两个子系统构成主应用的时候,主应用可能通过名字myApp-dataSource
引用数据源,将全部三个名字引用同一个对象,你可以将下面的别名定义添加到应用配置中:
<alias name="subsystemA-dataSource" alias="subsystemB-dataSource"/> <alias name="subsystemA-dataSource" alias="myApp-dataSource" />
现在每个子系统和主应用都可以通过唯一的名称来引用相同的数据源,并且可以保证他们的定义不与任何其他的定义冲突。
bean定义基本上就是用来创建一个或多个对象的配方(recipe,有更合适的翻译?),当需要一个bean的时候,容器查看配方并且根据bean定义封装的配置元数据创建(或获取)一个实际的对象。
如果你使用基于XML的配置,你可以在<bean/>
元素通过class
属性来指定对象的类型。这个class
属性,实际上是BeanDefinition
实例中的一个Class
属性。这个class
属性通常是必须的(例外情况,查看the section called “使用实例工厂方法实例化” 和 Section 5.7, “Bean定义的继承”),使用Class
属性的两种方式:
new
有点像。
Class
一样,也可能完全不一样。
当你使用构造方法来创建bean的时候,Spring对class没有特殊的要求。也就是说,正在开发的类不需要实现任何特定的接口或者以特定的方式进行编码。但是,根据你使用那种类型的IoC来指定bean,你可能需要一个默认(无参)的构造方法。
Spring IoC 容器可以管理几乎所有你想让它管理的类,它不限于管理POJO。大多数Spring用户更喜欢使用POJO(一个默认无参的构造方法和setter,getter方法)。但在容器中使用非bean形式(non-bean style)的类也是可以的。比如遗留系统中的连接池,很显然它与JavaBean规范不符,但Spring也能管理它。
当使用基于XML的元数据配置文件,可以这样来指定bean类:
<bean id="exampleBean" class="examples.ExampleBean"/> <bean name="anotherExample" class="examples.ExampleBeanTwo"/>
给构造方法指定参数以及为bean实例化设置属性将在后面的依赖注入中详细说明。
当采用静态工厂方法创建bean时,除了需要指定class属性外,还需要通过factory-method属性来指定创建bean实例的工厂方法。Spring将调用此方法(其可选参数接下来介绍)返回实例对象,就此而言,跟通过普通构造器创建类实例没什么两样。
下面的bean定义展示了如何通过工厂方法来创建bean实例。注意,此定义并未指定返回对象的类型,仅指定该类包含的工厂方法。在此例中,createInstance()
必须是一个static方法。
<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实例设置属性的详细内容请查阅依赖和配置详解。
与通过 静态工厂方法 实例化类似,通过调用工厂实例的非静态方法进行实例化。 使用这种方式时,class属性必须为空,而factory-bean属性必须指定为当前(或其祖先)容器中包含工厂方法的bean的名称,而该工厂bean的工厂方法本身必须通过factory-method属性来设定。
<!-- 工厂bean,包含createInstance()方法 --> <bean id="serviceLocator" class="examples.DefaultServiceLocator"> <!-- 其他需要注入的依赖项 --> </bean> <!-- 通过工厂bean创建的ben --> <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"> <!-- 其他需要注入的依赖项 --> </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容器中配置的工厂类通过
实例 或
静态 工厂方法来创建对象。相比而言,
|
典型的企业应用不会单一得由一个对象组成(或者说Spring术语中的bean)。即便是最简单的系统也需要多个对象共同协作来展示给终端用户一个条理分明的应用。接下来的这一节内容会阐述如何定义多个独立于应用程序的bean一起协同工作完成目标。
依赖注入 (DI) 是指对象之间的依赖关系,也就是说,一起协作的其他对象只通过构造器的参数、工厂方法的参数或者由构造函数或者工厂方法创建的对象设置属性。因此容器的工作就是创建bean并注入那些依赖关系。这个过程实质通过直接使用类的构造函数或者服务定位模式来反转控制bean的实例或者其依赖关系的位置,因此它有另外一个名字叫控制反转 (IoC)。
运用了DI原理代码会更加清晰并且由依赖关系提供对象也将使各层次的松耦合变得更加容易。对象不需要知道其依赖关系,也不需要知道它的位置或者类之间的依赖。因此,你的类会更容易测试,尤其是当依赖关系是在接口或者抽象基本类,
基于构造器注入 DI通过调用带参数的构造器来实现,每个参数代表着一个依赖关系。此外,还可通过给 静态
工厂方法传参数来构造bean。接下来的介绍将认为给构造器传参数和给静态工厂方法传参数是类似的。下面展示了只能使用构造器来注入依赖关系的例子。请注意这个类并没有什么 特别 之处,它只是个普通的POJO,不依赖于特殊的接口,抽象类或者注解。
public class SimpleMovieLister { // the SimpleMovieLister has a dependency on a MovieFinder private MovieFinder movieFinder; // a constructor so that the Spring container can inject a MovieFinder public SimpleMovieLister(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // business logic that actually uses the injected MovieFinder is omitted... }
构造器参数通过参数类型进行匹配。如果构造器参数的类型定义没有潜在的歧义,那么bean被实例化的时候,bean定义中构造器参数的定义顺序就是这些参数的顺序并依次进行匹配。看下面的代码:
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并不能知道该值的类型,不借助其他帮助Spring将不能通过类型进行匹配。看下面的类:
package examples; public class ExampleBean { // Number of years to calculate the Ultimate Answer private int years; // The Answer to Life, the Universe, and Everything private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
针对上面的场景可以使用type
属性来显式指定那些简单类型那个的构造参数类型,比如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg type="int" value="7500000"/> <constructor-arg type="java.lang.String" value="42"/> </bean>
使用index
属性来显式指定构造参数的索引,比如:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg index="0" value="7500000"/> <constructor-arg index="1" value="42"/> </bean>
使用索引可以解决多个简单值的混淆,还能解决构造方法有两个相同类型的参数的混淆问题,注意index是从0开始的。
你也可以使用构造器参数命名来指定值的类型:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg name="ultimateAnswer" value="42"/> </bean>
请记住为了使这个起作用,你的代码编译时要打开编译模式,这样Spring可以检查构造方法的参数。如果你不打开调试模式(或者不想打开),也可以使用 @ConstructorProperties JDK注解明确指出构造函数的参数。下面是简单的例子:
package examples; public class ExampleBean { // Fields omitted @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; } }
在调用了无参的构造方法或者无参的静态
工厂方法实例化bean之后,容器通过回调bean的setter方法来完成setter注入。接下来的例子将展示只使用setter注入依赖。这个类是个普通的Java类
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
所管理的beans支持构造函数注入和setter注入,在一些依赖已经使用构造器注入之后它还支持setter注入。你可以以BeanDefinition
的形式配置依赖,它能根据指定的PropertyEditor
实现将属性从一种格式转化为另外一种格式。但是,大多数Spring的使用者不会直接使用这些类(也就是通过编程的形式),而是采用XML配置这些bean
,注解的组件(即用@Component
,@Controller
等注解类),或者基于@Configuration
类的@Bean
方法。本质上这些资源会转换成BeanDefinition
的实例并且用于加载整个Spring IoC容器实例。
容器解决依赖问题通常有以下几个步骤:
ApplicationContext
创建并根据配置的元数据初始化。配置的元数据可以通过置顶的XML,Java代码或者注解。
int
,long
, String
, boolean
等。
当Spring容器创建时容器会校验每个bean的配置。但是在bean被实际创建 前,bean的值并不会被设置。那些单例类型的bean和被设置为预安装(默认)的bean会在容器创建时与容器同时创建。Scopes是在Section 5.5, “Bean作用域”中定义的。同时,另外的bean只会在被需要时创建。伴随着bean被实际创建,作为该bean的依赖和它的依赖的依赖(以此类推)会被创建和分配。注意 这些依赖之间的解决会显示迟一些
通常你可以信赖Spring。在容器加载时Spring会检查配置,比如不存在的bean和循环依赖。当bean创建时,Spring尽可能迟得设置属性和依赖关系。这意味着即使Spring正常加载,在你需要一个存在问题或者它的依赖存在问题的对象时,Spring会报出异常。举个例子,bean因设置缺少或者无效的属性会抛出一个异常。因为一些配置问题存在将会导致潜在的可见性被延迟,所以默认ApplicationContext
的实现bean采用提前实例化的单例模式。在实际需要之前创建这些bean会带来时间和内存的开销,当ApplicationContext
创建完成时你会发现配置问题,而不是之后。你也可以重写默认的行为使得单例bean延迟实例化而不是提前实例化。
如果不存在循环依赖,当一个或者多个协助bean会被注入依赖bean时,每个协助bean必须在注入依赖bean之前 完全 配置好。这意味着如果bean A对bean B存在依赖关系, 那么Spring Ioc容器在调用bean A的setter方法之前会完全配置bean B。换句话说,bean会被实例化(如果不是采用提前实例化的单例模式),相关的依赖会被设置好,相关的lifecycle方法(比如configured init 方法或者InitializingBean callback 方法)会被调用
接下来的Setter注入例子使用基于XML的配置元数据的方式。相应的Spring XML配置文件:
<bean id="exampleBean" class="examples.ExampleBean"> <!-- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <!-- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; } }
在前面的例子,我们看到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; } }
在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的依赖(协作),
或者是内联的bean。基于XML的Spring配置元数据支持使用<property/>
和 <constructor-arg/>
元素定义。
<property/>
元素的value
值通过可读的字符串形式来指定属性和构造器参数。Spring的conversion service
把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.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更加简洁;但是,编码的错误只有在运行时才会被发现而不是编码设计的时候,除非你在定义bean的时候, 使用 IntelliJIDEA 或者 Spring Tool Suite (STS) 支持动态属性补全的IDE。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容器使用JavaBeans PropertyEditor
把元素<value/>
内的文本转换为java.util.Properties
实例。由于这种做法非常简单,所以这是Spring团队在很多地方采用内嵌的<value/>
元素代替value属性。
idref
元素用来将容器内其他bean的id(值是字符串-不是引用)传给元素<constructor-arg/>
或者 <property/>
<bean id="theTargetBean" class="..."/> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean" /> </property> </bean>
上面的bean定义片段完全等同于(在运行时)下面片段:
<bean id="theTargetBean" class="..." /> <bean id="client" class="..."> <property name="targetName" value="theTargetBean" /> </bean>
第一种形式比第二种形式更好,因为使用idref
标签允许容器在部署时验证引用的bean是否存在。
在第二种形式中,传给 client
bean中属性targetName
的值并没有被验证。
只有当 client
bean完全实例化的时候错误才会被发现(可能伴随着致命的结果)。如果 client
bean是原型 bean。那么这个错误和异常可能只有再容器部署很长一段时间后才能被发现。
Note | |
---|---|
|
与ProxyFactoryBean
bean定义中使用<idref/>
元素指定AOP 拦截器配置 (版本不低于Spring 2.0)相同之处在于:当你指定拦截器名称的时候使用<idref/>
元素可以防止你拼错拦截器的id。
在 <constructor-arg/>
或者 <property/>
可以使用ref
元素。该元素用来将bean中指定属性的值设置为对容器的另外一个bean(协作者)的引用。
该引用bean将被作为依赖注入,而且再注入之前会被初始化(如果协作者是单例)。所有的引用最终都是另一个对象的引用。
bean的范围和验证依赖于你指定的bean
,local
,或者 parent
属性的id/name。
通过<ref/>
标签的bean
属性定义目标bean是最常见的形式,通过该标签可以引用同一容器或者父容器任何bean,无论是否在
相同的xml文件中。xml的bean
元素的值既可以目标bean的id
属性也可以是其中一个目标bean的name
属性值。
<ref bean="someBean"/>
通过parent
属性指定目标bean来创建bean的引用,该bean是当前容器下的父级容器。parent
属性值既可以是目标bean的id也可以是
name属性值。而且目标bean必须在当前容器的父级容器中。使用parent
属性的主要用途是为了用某个与父级容器中的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>
Note | |
---|---|
|
A <bean/>
element inside the <property/>
or <constructor-arg/>
elements defines a
so-called inner bean.
所谓的内部bean就是指在 <property/>
或者 <constructor-arg/>
元素内部使用<bean/>
定义bean。
<bean id="outer" class="..."> <!-- instead of using a reference to a target bean, simply define the target bean inline --> <property name="target"> <bean class="com.example.Person"> <!-- this is the inner bean --> <property name="name" value="Fiona Apple"/> <property name="age" value="25"/> </bean> </property> </bean>
内部bean的定义不需要id或者name属性。容器会忽略这些属性值。同时容器也会忽略scope
标志位。内部bean 总是 匿名的
并且他们总是伴随着外部bean创建。同时将内部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容器也支持集合的 合并。开发者可以定义parent-style`<list/>, `<map/>
, <set/>
或者<props/>
元素并,
child-style 的<list/>
, <map/>
, <set/>
或者 <props/>
元素继承和覆盖自父集合。也就是说。父集合元素合并后的值就是子集合的最终结果,而且子集中的元素值将覆盖父集中对应的值。
关于合并的章节涉及到了parent-child bean机制。不熟悉父子bean的读者可参见relevant section.
接下来的例子展示了集合的合并:
<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的定义中
<props/>元素上的`merge=true
属性的用法。当child`bean被解析并且被容器初始化,产生的实例包含了`adminEmails
,Properties
集合,其adminEmails将与父集合的adminEmails属性进行合并。
administrator=administrator@example.com sales=sales@example.com support=support@example.co.uk
子bean的Properties
集合将从父<props/>
集成所有属性元素。同时子bean的support值将覆盖父集合的相应值。
<list/>
, <map/>
, 和 <set/>
集合类型的合并处理都基本类似。<list/>
元素某个方面有点特殊,这和List
集合类型的语义学有关
换句话说,比如维护一个有序
集合的值,父bean的列表内容将排在子bean李彪内容的前面。对于Map
, Set
, 和 Properties
集合类型没有顺序的概念,
因此作为相关的Map
, Set
, 和 Properties
实现基础的集合类型在容器内部排序的语义。
你不能合并两种不能类型的集合(比如Map
和 List
),如果你这么做了将会抛出相应的异常
。merge
属性必须在继承
的子bean中定义。定义在父bean的集合属性上指定的merge
属性是多余的并且得不到期望的合并结果。
Java 5 引入了泛型,这样你可以使用强类型集合。换句话说绳命一个只能包含String类型元素的Collection
是可能的(比如)。
如果使用Spring来给bean注入强类型的Collection
,你可以利用Spring的类型转换,在向强类型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会把空属性当做空字符串处理。以下的基于XML配置的片段将email属性设置为空字符串。
<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
元素的属性代替<property/>` 元素来描述属性值或者协作bean。
Spring支持名称空间的可扩展配置with namespaces,这些名称空间基于一种XML Schema定义。这章节涉及到的beans
配置都是定义在一个XML Schema文档理。但是p命名空间不是定义在XSD文件而是存在于Spring内核中。
下面的例子展示了两种XML片段,其结果是一样的:第一个使用了标准XML格式,第二种使用了p命名空间。
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean name="classic" class="com.example.ExampleBean"> <property name="email" value="foo@bar.com"/> </bean> <bean name="p-namespace" class="com.example.ExampleBean" p:email="foo@bar.com"/> </beans>
在例子中,使用p命名空间的bean定义有了一个叫email的属性。这告诉Spring要包含这个属性的声明。正如前面所说的, p命名空间不需要schema定义,因此你可以设置属性的名字作为bean的property的名字。
接下来的例子包括了两种以上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.xsd"> <bean name="john-classic" class="com.example.Person"> <property name="name" value="John Doe"/> <property name="spouse" ref="jane"/> </bean> <bean name="john-modern" class="com.example.Person" p:name="John Doe" p:spouse-ref="jane"/> <bean name="jane" class="com.example.Person"> <property name="name" value="Jane Doe"/> </bean> </beans>
正如你看到的,例子不仅使用p命名空间包含了一个属性值,而且使用了一个特殊的格式声明了一个属性的引用。在第一个bean
定义中使用了<property name="spouse" ref="jane"/>
创建一个john
bean 对jane
bean的引用,第二个bean的定义使用了p:spouse-ref="jane"
,它们做了同样一件事情。在这个例子中spouse
是属性名,而-ref
部分声明了这不是一个直接的值而是另一个bean的引用。
Note | |
---|---|
p命名空间没有标准XML格式那么灵活。举个例子,声明属性的引用是以 |
和p命名空间the section called “XML使用p命名空间简化”类似,Spring3.1 引入了c命名空间,使用内联的构造参数代替嵌套的constructor-arg
元素
让我们回顾一下the section called “构造器注入”使用c命名空间的例子:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:c="http://www.springframework.org/schema/c" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean id="bar" class="x.y.Bar"/> <bean id="baz" class="x.y.Baz"/> <!-- traditional declaration --> <bean id="foo" class="x.y.Foo"> <constructor-arg ref="bar"/> <constructor-arg ref="baz"/> <constructor-arg value="foo@bar.com"/> </bean> <!-- c-namespace declaration --> <bean id="foo" class="x.y.Foo" c:bar-ref="bar" c:baz-ref="baz" c:email="foo@bar.com"/> </beans>
和p命名空间约定的一样(bean的引用以-ref
结尾),c命名空间使用它们的名称作为构造器参数。同时它需要声明即使它没有
XSD schema中定义(但是它存在于Spring内核中)
极少数的情况下构造器参数的名称不可用(通常字节码没有经过调试信息编译),可以使用备份进行参数索引。
<!-- c-namespace index declaration --> <bean id="foo" class="x.y.Foo" c:_0-ref="bar" c:_1-ref="baz"/>
Note | |
---|---|
因为XML的语法,索引标记需要 |
在实践中,构造器解析机智机制在匹配参数上相当高效。除非你需要这么做,我们建议您在配置中使用符号名称。
如果一个bean是另外一个bean的依赖,这通常意味着这个bean可以设置成为另外一个bean的属性。在XML配置文件中你可以使用<<beans-ref-element, <ref/>`element>>
实现依赖。但是某些时候bean之间的依赖并不是那么直接。举个例子:类的静态块初始化,比如数据库驱动的注册。`depends-on
属性
可以同于当前bean初始化之前显式地强制一个或多个bean被初始化。下面的例子中使用了depends-on
属性来指定一个bean的依赖。
<bean id="beanOne" class="ExampleBean" depends-on="manager"/> <bean id="manager" class="ManagerBean" />
为了实现多个bean的依赖,你可以在depends-on
中将指定的多个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 | |
---|---|
|
ApplicationContext
实现的默认行为就是再启动时将所有singleton bean提前进行实例化。
通常这样的提前实例化方式是好事,因为配置中或者运行环境的错误就会被立刻发现,否则可能要花几个小时甚至几天。如果你不想
这样,你可以将单例bean定义为延迟加载防止它提前实例化。延迟初始化bean会告诉Ioc容器在第一次需要的时候才实例化而不是在容器启动时就实例化。
在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的依赖,但这个单例bean又不是 延迟加载,ApplicationContext
在启动时创建了延迟加载
的bean,因为它必须满足单例bean的依赖。因此延迟加载的bean会被注入单例bean,然而在其他地方它不会延迟加载。
你也可以使用<beans/>
元素上的default-lazy-init
属性在容器层次上控制延迟加载。比如:
<beans default-lazy-init="true"> <!-- no beans will be pre-instantiated... --> </beans>
Spring容器可以自动装配相互协作bean的关联关系。因此,如果可能的话,可以自动让Spring检测ApplicationContext
的内容自动
处理协作者(其他bean)。自动装配有以下好处:
当使用XML配置脚注:[See
Section 5.4.1, “依赖注入”],可以使用<bean/>
元素的autowire
属性
为定义的bean指定自动装配模式。你可以指定自动装配per bean,选择那种方式来自动装配。
Table 5.2. 自动装配模式
Mode | Explanation 模式解释 |
no | (默认)不自动装配。Bean的引用必须用 |
byName | 通过属性名称自动装配。Spring会寻找相同名称的bean并将其与属性自动装配。譬如,如果bean的定义设置了根据名称自动装配,
并且包含了一个master 属性(换句话说,它有setMaster(..)方法),Spring会寻找名为 |
byType | 如果容器中存在一个与指定属性类型相同的bean,那么将与该属性自动装配。如果存在多个该类型的bean,将会抛出异常,并指出 不能使用byType自动装配这个bean。如果没有找到相同类型的,什么也不会发生。属性不会被设置。 |
constructor | 和byType类似,不同之处在于它应用于构造器参数。如果在容器中没有找到与构造器参数类型一致的bean,就会抛出异常。 |
byType 或者 constructor 自动装配模式也可以应用于数组和指定类型的集合。在这种情况下容器中的所有匹配的自动装配对象将 被应用于满足各种依赖。对于key值类型为String的强类型Map也可以自动装配。一个自动装配的Map value值将由所匹配类型的bean所填充。
你可以结合自动装配和依赖检查,后者将会在自动装配完成之后进行。
在工程里一致使用自动装配,这将会工作得很好。如果自动装配并不常使用,只使用在一个或两个bean的定义上, 它可能会对开发者产生困扰。
考虑一下自动装配的局限性和缺点:
property
和 constructor-arg
显式的依赖设置总是会覆盖自动装配。你不能装配所谓的简单属性比如原始的Strings
, 和 Classes
(这样的简单属性数组也是)。这种缺陷是故意设计的。
针对于上述场景,你会有多个选项:
autowire-candidate
属性为false
避免该bean自动装配,这将会在下一节中详细描述。
元素上的`primary
属性为true
,将该bean设置为首选自动装配bean。
在提前实例化bean的基础上,你可以将bean排除在自动装配之外。在Spring XML格式中,将<bean/>
元素中的autowire-candidate
属性
设置为false
。容器会使特定的bean定义不可于自动装配(包括注解配置比如@Autowired
)
你也可以对使用bean名字进行模式匹配来对自动装配进行限制。顶层的<beans/>
元素在它的default-autowire-candidates
属性
接受一个或多个模式。譬如,为了限制
对于那些从来就不会被其他bean采用自动装配的方式注入的bean而言,这是有用的。不过这并不意味这被排除的bean自己就 不能使用自动装配来注入其他bean。更确切得说,该bean本身不会被考虑作为其他bean自动装配的候选者。
在大部分的应用场景中,容器中的大部分bean是singletons类型的。当一个单例bean需要和另外一个单例bean, 协作时,或者一个费单例bean要引用另外一个非单例bean时,通常情况下将一个bean定义为另外一个bean的属性值就行了。不过对于具有不同生命周期的bean 来说这样做就会有问题了,比如在调用一个单例类型bean A的某个方法,需要引用另一个非单例(prototype)类型bean B,对于bean A来说,容器只会创建一次,这样就没法 在需要的时候每次让容器为bean A提供一个新的bean B实例。
上面问题的一个解决方法是放弃控制反转,你可以实现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框架产生的耦合。方法注入,作为Spring Ioc容器的高级特性,可以以一种 干净的方法来处理这种情况。
Lookup方法具有使容器覆盖受容器管理的bean方法的能力,从而返回指定名字的bean实例。在上述场景中,Lookup方法注入适用于原型bean。 Lookup方法注入的内部机制是Spring利用了CGLIB库在运行时生成二进制代码的功能,通过动态创建Lookup方法bean的子类从而达到复写Lookup方法的目的。
Note | |
---|---|
为了使动态子类起作用,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);
如果方法是抽象
,动态生成的子类会实现该方法。沟则,动态生成的子类会覆盖类里的具体方法。譬如:
<!-- 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 bean实例时会调用createCommand()
方法。你必须将`command`bean部署为
原型(prototype)类型,如果这是实际需要的话。如果部署为singleton。那么每次将返回相同的
`command`bean。
Tip | |
---|---|
感兴趣的读者也许发现了 |
比起Lookup方法注入来,还有一种较少用到的方法注入形式,该注入能使用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 的定义,实际上是创建了一个产生真实实例的配方(recipe)。bean 定义是一个配方(recipe)这种概念是很重要的,它的意思是指,和class一样,你可以从一个配方(recipe)创建多个对象实例。
You can control not only the various dependencies and configuration values that are to
be plugged into an object that is created from a particular bean definition, but also
the scope of the objects created from a particular bean definition. This approach is
powerful and flexible in that you can choose the scope of the objects you create
through configuration instead of having to bake in the scope of an object at the Java
class level. Beans can be defined to be deployed in one of a number of scopes: out of
the box, the Spring Framework supports five scopes, three of which are available only if
you use a web-aware ApplicationContext
.
你不仅可以控制被插入到一个从特定 bean 定义创建的对象的各种依赖和配置值,还可以控制从这个特定 bean 定义创建的对象的作用域(scope)。这种方法是强大而灵活的,你可以选择(choose)通过配置创建的对象作用域而不必在 Java class level 的作用域(scope)预热。Bean 可以被多种作用域之一定义:Spring 框架支持五种开箱即用的作用域,其中三种仅在基于 web 的 ApplicationContext
中使用。
下面的作用域是开箱即用的。同时你也可以创建 自定义作用域.
Table 5.3. Bean作用域
作用域 | 描述 |
---|---|
(默认的) 每个 String IoC 容器作用域中一个 bean 定义只对应一个对象实例。 | |
一个 bean 定义对应多个对象实例。 | |
一个 bean 定义作用于 HTTP | |
一个 bean 定义作用于 HTTP | |
一个 bean 定义作用于全局的 HTTP | |
一个 bean 定义作用于整个 |
Note | |
---|---|
从 Spring 3.0 开始,新增了一个thread scope,但是默认是不被注册的。要获取更多相关信息,请看文档
|
仅管理一个单例 bean 的共享实例,并且所有通过 id 或者 ids 获得 bean 定义的请求,都会从 Spring 容器中得到同一个特定的 bean 实例。
换句话说,当你定义一个 bean 定义,并且它的作用域为单例的,Spring IoC 容器就会精确地(exactly)创建一个(one)对象实例。这个实例被存储在一个包含很多单例 bean 的缓存中。并且所有的后来的请求和引用(all subsequent requests and references)都会返回缓存的对象。
Spring 中单例 bean 的概念与四人帮(GoF)设计模式一书中的定义是不同的。GoF 中的单例是硬编码方式的对象的作用域以至于每个特定的类在每个类加载器(per ClassLoader)中有且仅有一个(and only one)实例。Spring 单例作用域更好的描述了每个容器和每个 bean(per container and per bean)。它的意思是说,如果你在一个 Spring 容器中定义了一个特定类的 bean,然后这个 Spring 容器就会创建一个且仅仅一个(and only one)通过指定 bean 定义的实例。单例作用域在 Spring 中是默认的作用域(The singleton scope is the default scope in Spring)_。在 XML 中定义一个单例的 bean,你需要写如下的配置,例如:
<bean id="accountService" class="com.foo.DefaultAccountService"/> <!-- 通过显示的冗余配置是等价的 (单例作用域是默认的) --> <bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
bean使用原型作用域而不是单例作用域的话,会在每次请求该bean,也就是bean被注入至另一个bean、
或通过调用Spring容器的 getBean()
方法时,创建一个新的bean实例 。
通常,对于有状态的bean使用原型作用域,无状态的bean则使用单例作用域。
下图展示了Spring的原型作用域。一般来说,数据访问对象(data access object, DAO )不会保存任何会话状态, 因此是无状态的,不该被配置为原型作用域。作者之所以在下图使用DAO,只是为了能重用上面单例的那张图,节约工作量。
这段样例代码展示了如何在xml配置文件中定义一个原型作用域的bean:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他作用域不同的是,Spring容器不会管理原型域bean的完整生命周期:Spring容器会初始化、 配置,亦或者组装原型域的bean对象,然后交给客户端,之后就再也不会管这个bean对象了。 因此,对于bean的生命周期方法来说,尽管所有作用域的 初始化方法 都会被调用, 但是原型域bean的 销毁方法 不会 被Spring容器调用。客户端代码要自己负责销毁原型域bean 以及和bean相关的资源(特别是开销大的资源)。如果想让Spring负责这些事(销毁bean、释放资源), 就得自定义bean的后处理器 bean post-processor ,它会持用原型域bean的引用。
从某种意义上说,对于原型域bean,Spring容器代替了Java 的 new
操作符。所有在 new
之后的生命周期管理任务,
都要由客户端自行处理。(想了解Spring容器具体如何管理bean的生命周期,请参考 Section 5.6.1, “生命周期回调”. )
如果你的单例bean依赖了原型bean,谨记这些依赖(的原型bean) 只在初始化时解析 。 因此,假如你将原型bean依赖注入至单例bean,在注入时会初始化一个新的原型bean实例, 这个被注入的原型bean实例是一个独立的实例。
不过,假如你希望单例bean在运行时能够反复获得一个新的原型bean实例,你就不能用依赖注入的方式, 因为这个注入只发生一次 ,即当Spring容器初始化单例bean并解析依赖时。 如果你需要在运行时多次获得新的原型bean实例,请参阅 Section 5.4.6, “方法注入”
仅当 你使用web相关的Spring ApplicationContext
(例如 XmlWebApplicationContext
)时,
请求 request
、会话 session
和全局会话 global session
作用域才会起作用。如果你在普通的Spring IoC 容器(例如 ClassPathXmlApplicationContext
)中使用这几个作用域,会抛出异常 IllegalStateException
,告知你这是一个未知的bean作用域
The request
, session
, and global session
scopes are only available if you use
a web-aware Spring ApplicationContext
implementation (such as
XmlWebApplicationContext
). If you use these scopes with regular Spring IoC containers
such as the ClassPathXmlApplicationContext
, you get an IllegalStateException
complaining about an unknown bean scope.
为了使用请求 request
、会话 session
和全局会话 global session
等作用域(web作用域),
在定义bean之前要做一些最基本的初始化配置。(如果使用单例和原型这类标准作用域,是 不需要 这些初始化配置的)。
具体如何组装这些初始化配置与你使用的特定的Servlet环境有关..
事实上,如果你使用Spring Web MVC ,在 DispatcherServlet
或 DispatcherPortlet
处理的请求内去访问这些作用域的bean,
那就不用做任何专门的配置:DispatcherServlet
和 DispatcherPortlet
已经帮你做了这些事情。
如果你使用的是 Servlet2.5 标准的web容器,而且请求不是由 Spring的 DispatcherServlet 处理
(例如使用了Struts或JSF),你就得注册 org.springframework.web.context.request.RequestContextListener
这一 ServletRequestListener
。
对于Servlet 3.0 以上的环境,可以通过 WebApplicationInitializer
接口以代码的方式去实现。
或者,对于比较老旧的web 容器,在 web.xml
配置文件中加入以下的声明:
<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
又或者,如果你在配置监听器时有问题,也可以使用 RequestContextFilter
。过滤器的映射与具体的web应用有关,所以你得自行修改映射的内容。
<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请求对象绑定到处理线程 Thread
上,
那么,请求、会话和全局会话作用域bean就可以沿着调用链继续往下走了。
考虑如下的bean定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
对于每个http请求,Spring容器会创建一个 LoginAction
bean 的新实例。也就是说,loginAction
bean 的作用域限于 HTTP 请求范围。
你可以在请求内随意修改这个bean实例的状态,因为其他 loginAction
bean实例看不到这些变化,bean实例是与特定的请求相关的。
当请求处理完毕,对应的bean实例也就销毁(被回收)了。
考虑如下的bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
在每个HTTP Session
的生命周期内,Spring容器会根据id为 userPreferences
的bean定义创建一个 UserPreferences
bean 的新实例。
也就是说,userPreferences
bean 的作用域限于 HTTP Session
范围。和请求作用域 request-scoped
bean 类似,
因为每个会话域 session-scoped
bean的范围限于特定的 HTTP Session
内部,所以一个 Session
内的 userPreferences
bean也是可以被随意修改,
而不会影响到其他 Session
中的 userPreferences
bean。当一个HTTP Session
最终用完被JVM回收时,相关的会话域 session-scoped
bean也被一起回收。
考虑如下的bean定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>
全局会话作用域 global session
与 上文 提到的标准HTTP Session
作用域类似,但是仅适用于基于门户(portlet)的web应用程序上下文。
Portlet规范定义了如何在组成一个门户的多个portlet组件之间共享全局 Session
。
因此,全局会话作用域 global session
bean的生命周期也就与全局门户 Session
紧密关联。
假如你开发了一个标准的、基于Servlet的web应用,并且定义了一个或多个全局会话作用域 global session
bean,
Spring容器会使用 HTTP Session
作用域(来代替),不会发生错误。
考虑如下的bean定义:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器依据 appPreferences
的bean 定义,在整个web应用启动时创建了一个 AppPreferences
bean 的实例。
也就是说, appPreferences
bean 的作用域限于 ServletContext
范围,作为一个普通的 ServletContext
属性进行存储。
这跟Spring的单例作用域有些相似,但有两个重大差异:1. 这是某个 ServletContext
的单例,
不是某个Spring ApplicationContext 的单例(任何给定的web应用都可能有多个 ApplicationContext )。
2. 这是作为一个 ServletContext
属性被暴露和访问的。
Spring IoC 容器不仅负责管理对象(beans)的创建,也负责有合作(依赖)关系对象之间的组装。 如果你想将一个HTTP 请求作用域的bean注入到另一个 bean,必须注入一个 AOP 代理来取代请求作用域的bean本身。 也就是说,你得有一个和作用域bean实现了相同接口、能在相应作用域(例如,HTTP 请求) 访问真正的请求域bean目标对象的代理对象,而且这个代理对象要能把方法调用委派给真正的请求域bean, 然后把代理对象注入到请求域bean该注入的地方。
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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!-- 将一个HTTP Session bean 暴露为一个代理bean --> <bean id="userPreferences" class="com.foo.UserPreferences" scope="session"> <!-- 通知Spring容器去代理这个bean --> <aop:scoped-proxy/> </bean> <!-- 将上述bean 的代理注入到一个单例bean --> <bean id="userService" class="com.foo.SimpleUserService"> <!-- 引用被代理的 userPreferences bean --> <property name="userPreferences" ref="userPreferences"/> </bean> </beans>
为了创建上文提到的代理对象,要在 request
请求、 session
会话、 globalSession
全局会话
和自定义作用域的bean声明中加入 <aop:scoped‐proxy/>
子元素。
(参考 the section called “选择要创建的代理类型” 和
Chapter 33, XML Schema-based configuration)
。为什么要这么做呢?让我们将这几个作用域的bean定义与单例作用域的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>
在上述例子中,将HTTP Session
会话域的 userPreferences
bean 注入进了单例域 userManager
。
代码的关键在于userManager 是个单例bean,它在每个Spring 容器中只会被初始化 一次,
它的依赖对象(在这个例子中是 userPreferences
bean)也只会被注入一次。这就意味着 userManager
每次操作的都是在最开始注入进来的同一个 userPreferences
对象。
当你把一个生命周期较短的bean注入至一个生命周期较长的bean时,例如把HTTP Session
bean注入到单例bean,
这种情况肯定 不是 你所期望的。相反,你希望有个唯一的 userManager
单例对象,在每个HTTP Session
的生命周期内,
都能有一个专门的 userPreferences
对象供 userManager
使用。因此,Spring容器会创建一个代理类,
使之与 UserPreferences
类实现相同的接口(理论上也是一个 UserPreferences
对象),并能根据作用域(HTTP 请求、 Session
,等等)去获得真正的 UserPreferences
对象。
Spring容器将这个代理类的对象注入到 userManager
, 但是 userManager
并不清楚它获得的 UserPreferences
其实是个代理。
在这个例子中,当UserManager 实例调用注入的 UserPreferences
对象的某个方法时,实际上调用的是代理对象的方法。
然后,代理对象从HTTP Session
中获取、并将方法调用委派给真正的 UserPreferences
对象。
所以,当你需要将 request-
, session-
, and globalSession-scoped
作用域的 bean 注入至其他合作者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>
默认情况下,对于使用了 <aop:scoped-proxy/>
元素的bean,Spring容器会创建一个 基于CGLIB类代理机制的代理类。
Note | |
---|---|
CGLIB 代理只能拦截 public方法!不要在代理类上调用 非 public方法,那样不会委派给真正的目标对象。 |
或者,你也可以指定 <aop:scoped-proxy/>
元素的 proxy-target-class
属性为 false
,
从而让Spring容器创建标准的、基于接口机制的JDK 代理类。使用 JDK 代理类意味着你不需要在应用程序的classpath 指定额外的类库。
但是,这也意味着你的 scoped-bean 至少得实现一个接口,而且 所有 合作者必须通过其中一个接口来引用被注入的scoped-bean。
<!-- DefaultUserPreferences 实现了 UserPreferences 接口 --> <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>
到底该选择基于类的还是基于接口的代理机制呢?可参考 aop代理 了解更多细节信息。
The bean scoping mechanism is extensible; You can define your own
scopes, or even redefine existing scopes, although the latter is considered bad practice
and you cannot override the built-in singleton
and prototype
scopes.
To integrate your custom scope(s) into the Spring container, you need to implement the
org.springframework.beans.factory.config.Scope
interface, which is described in this
section. For an idea of how to implement your own scopes, see the Scope
implementations that are supplied with the Spring Framework itself and the
Scope
javadocs,
which explains the methods you need to implement in more detail.
The Scope
interface has four methods to get objects from the scope, remove them from
the scope, and allow them to be destroyed.
The following method returns the object from the underlying scope. The session scope implementation, for example, returns the session-scoped bean (and if it does not exist, the method returns a new instance of the bean, after having bound it to the session for future reference).
Object get(String name, ObjectFactory objectFactory)
The following method removes the object from the underlying scope. The session scope implementation for example, removes the session-scoped bean from the underlying session. The object should be returned, but you can return null if the object with the specified name is not found.
Object remove(String name)
The following method registers the callbacks the scope should execute when it is destroyed or when the specified object in the scope is destroyed. Refer to the javadocs or a Spring scope implementation for more information on destruction callbacks.
void registerDestructionCallback(String name, Runnable destructionCallback)
The following method obtains the conversation identifier for the underlying scope. This identifier is different for each scope. For a session scoped implementation, this identifier can be the session identifier.
String getConversationId()
After you write and test one or more custom Scope
implementations, you need to make
the Spring container aware of your new scope(s). The following method is the central
method to register a new Scope
with the Spring container:
void registerScope(String scopeName, Scope scope);
This method is declared on the ConfigurableBeanFactory
interface, which is available
on most of the concrete ApplicationContext
implementations that ship with Spring via
the BeanFactory property.
The first argument to the registerScope(..)
method is the unique name associated with
a scope; examples of such names in the Spring container itself are singleton
and
prototype
. The second argument to the registerScope(..)
method is an actual instance
of the custom Scope
implementation that you wish to register and use.
Suppose that you write your custom Scope
implementation, and then register it as below.
Note | |
---|---|
The example below uses |
Scope threadScope = new SimpleThreadScope(); beanFactory.registerScope("thread", threadScope);
You then create bean definitions that adhere to the scoping rules of your custom Scope
:
<bean id="..." class="..." scope="thread">
With a custom Scope
implementation, you are not limited to programmatic registration
of the scope. You can also do the Scope
registration declaratively, using the
CustomScopeConfigurer
class:
<?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.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.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 | |
---|---|
When you place |
Spring提供了几个标志接口(marker interface),这些接口用来改变容器中bean的行为;它们包括InitializingBean和DisposableBean。 实现这两个接口的bean在初始化和析构时容器会调用前者的afterPropertiesSet()方法,以及后者的destroy()方法。
Tip | |
---|---|
在现代的Spring应用中,The JSR-250 如果你不想使用JSR-250 注解,但你还是寻找消除耦合,考虑使用对象的init方法和destroy方法定义元数据。 |
Spring在内部使用 BeanPostProcessor
实现来处理它能找到的任何回调接口并调用相应的方法。如果你需要自定义特性或者生命周期行为,你可以实现自己的
BeanPostProcessor
。更多信息,详情见Section 5.8, “容器拓展点”。
除了初始化和销毁回调之外,Spring管理对象可能还实现了Lifecycle
接口,这些对象可以参与由容器自身驱动的启动和关闭过程。
本节中描述了生命周期回调接口。
实现 org.springframework.beans.factory.InitializingBean
接口,允许容器在设置好bean的所有必要属性后,执行初始化事宜。
InitializingBean
接口仅指定了一个方法:
void afterPropertiesSet() throws Exception;
通常,要避免使用 InitializingBean
接口并且不鼓励使用该接口,因为这样会将代码和Spring耦合起来。
使用@PostConstruct
注解或者指定一个POJO的初始化方法。
在XML配置元数据的情况下,使用 init-method
属性去指定方法名,并且该方法无参数签名。
例如,下面的定义:
<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耦合。使用@PreDestroy
注解或者指定一个普通的方法,但能由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()
轻松的写一个类(不必想XML配置文件那样为每个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容器在bean创建和装配的时候会将 init
方法
作为实例化回调方法。如果类有这个方法,则会在适当的时候执行。
销毁回调方法配置是相同的 (XML配置),在顶级的<beans/>元素中使用 default-destroy-method
属性。
当已经存在的类的初始化方法的命名规则与惯例有差异的时候,你应该始终使用<bean/>元素中的init-method和destroy-method属性(在XML配置中)来覆盖默认的方式。
最后,请注意Spring容器保证在bean的所有依赖都满足后立即执行配置的初始化回调。这意味着初始化回调在原生bean上调用,这也意味着这个时候任何诸如AOP拦截器之类的将不能被应用。 一个目标bean是首先完全创建,然后才应用诸如AOP代理等拦截器链。注意,如果目标bean和代理是分开定义了,你的代码甚至可以绕开代理直接和原生bean通信。 因此,在初始化方法上使用拦截器将产生未知的结果,因为这将目标bean和它的代理/拦截器的生命周期绑定并且留下了和初始bean直接通信这样奇怪的方式。
截至 Spring 2.5,有三种选择控制bean生命周期行为:InitializingBean
和
DisposableBean
回调接口;自定义init()
和 destroy()
方法;
@PostConstruct
and `@PreDestroy`annotations。
你可以组合这些机制去控制给定的bean。
Note | |
---|---|
如果bean存在多种的生命周期机制配置并且每种机制都配置为不同的方法名, 那所有配置的方法将会按照上面的顺利执行。然而如果配置了相同的方法名 - 例如, init()初始化方法 - 采用多种机制配置后,只会执行一次。 |
为同一个bean配置多个生命周期机制,不同的初始化方法,调用如下:
@PostConstruct
元注释
InitializingBean
的 afterPropertiesSet()
定义
init()
方法
析构方法调用顺序是相同的:
@PreDestroy
元注释
DisposableBean
的 destroy()
定义
destroy()
方法
Lifecycle
接口 为任何有它自己生命周期要求的对象定义基本方法(例如 开始和停止一些后台处理):
public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何Spring管理的对象可能实现那个接口。然后,当 ApplicationContext
开始和停止的时候,它会将那些调用的所有生命周期的实现
定义在这样的上下文中。通过 LifecycleProcessor
:
public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
注意, LifecycleProcessor
本身扩展于 Lifecycle
接口。它还增加了两个其他的方法,用于对上下文进行刷新和关闭。
Notice that the LifecycleProcessor
is itself an extension of the Lifecycle
interface. It also adds two other methods for reacting to the context being refreshed
and closed.
启动和关闭的顺序调用也很重要。如果任何两个对象之间存在依赖关系,依赖方将会在依赖后开始,在依赖前停止。然而,有时候直接依赖关系是未知的。
你可能只知道某个类型的对象应该在另一种类型的对象之前开始。在这种情况下, SmartLifecycle
接口定义另一种选择,换句话说,
作为其超级接口,Phased
定义 getPhase()
方法。
public interface Phased { int getPhase(); }
public interface SmartLifecycle extends Lifecycle, Phased { boolean isAutoStartup(); void stop(Runnable callback); }
当开始的时候,最低阶段的对象首先开始,并且当停止的时候,是反向的顺序。因此,实现 SmartLifecycle
接口和返回值是 Integer.MIN_VALUE
的 getPhase()
方法 的对象将在第一个开始和最后一个停止。在另一方面,相位值Integer.MAX_VALUE
将表明对象应该第一个停止和最后开始(
可能是因为它依赖于其他进程的运行)。当考虑相位值的时候,同样重要的是要知道任何没有实现 SmartLifecycle
的 Lifecycle
对象默认的相位是0。
因此,任何负相值都会显示一个对象应该在那些标准组件前开始(并且在他们之后停止),反之为任何正相位值。
正如你所看到的, SmartLifecycle
接受回调,定义了停止方法。任何实现必须调用回调的 run()
方法,在实现的关闭进程完成之后。
那使得异步关闭,在默认实现的 lifecycleprocessor
接口开始,DefaultLifecycleProcessor
,将等待其在每个阶段中的对象组的超时值来调用这个回调。
默认的 per-phase 超时时间是30秒。你可以在上下文中定义一个名为"lifecycleProcessor"的bean来重写默认生命周期处理器实例。如果你打算修改超时时间,
然后定义以下的就足够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor"> <!-- timeout value in milliseconds --> <property name="timeoutPerShutdownPhase" value="10000"/> </bean>
如上所述, LifecycleProcessor
接口对于上下文的刷新和关闭定义了回调方法。如果` stop() 被显式调用,后者将简单的驱动关闭进程,
但当上下文关闭时会发生。 'refresh' 回调在另一方面来说是 `SmartLifecycle
bean的另一个特点。当上下文被刷新的时候(在所有对象被实例化和
初始化之后),回调将被调用,而在这一点上,默认生命周期处理器将检查通过每一个 SmartLifecycle
对象的 isAutoStartup()
方法返回的布尔值。
如果返回 "true",这个对象将在这一点上开始,而不是等待上下文的或者本身的 start()
方法的显示调用(与上下文刷新不同,上下文开始不会
为一个标准的上下文实现自动发生)。 "phase" 值以及 "depends-on" 关系将以相同的方式确定启动顺序,如上所述。
Note | |
---|---|
本节仅适用于非web应用程序。在基于web的ApplicationContext实现中已有相应的代码来处理关闭web应用时如何恰当地关闭Spring IoC容器。 This section applies only to non-web applications. Spring’s web-based
|
如果你正在一个非web应用的环境下使用Spring的IoC容器;例如在桌面富客户端环境下,你想让容器优雅的关闭,并调用singleton bean上的相应析构回调方法, 你需要在JVM里注册一个“关闭钩子”(shutdown hook)。这一点非常容易做到,并且将会确保你的Spring IoC容器被恰当关闭,以及所有由单例持有的资源都会 被释放(当然,为你的单例配置销毁回调,并正确实现销毁回调方法,依然是你的工作)。
为了注册“关闭钩子”,你只需要简单地调用在 AbstractApplicationContext
实现中的 registerShutdownHook()
方法即可。也就是:
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; }
When an ApplicationContext
creates an object instance that implements the
org.springframework.context.ApplicationContextAware
interface, the instance is provided
with a reference to that ApplicationContext
.
public interface ApplicationContextAware { void setApplicationContext(ApplicationContext applicationContext) throws BeansException; }
因此,bean可以通过编程方式操纵 ApplicationContext
来创建,通过 ApplicationContext
接口,或者通过向这个接口的一个已知的子类的引用,
如 ConfigurableApplicationContext
,公开附加功能。一个应用程序将是其他bean的方案检索。有时候这种性能是有用的,然而,一般来说,
你应该避免它,因为他会与Spirng耦合,并且不遵循反转控制方式,在合作者被提供给bean作为属性的地方。 ApplicationContext
的其他方法提供
获取文件资源,发布应用程序事件,和获取一个MessageSource
。这些附加功能在Section 5.15, “ApplicationContext 的附加功能”中描述。
截止Spring 2.5,自动装配是获取 ApplicationContext
索引的另一种选择。传统的 constructor
和 byType
自动模式(在
Section 5.4.5, “自动装配协作者”中描述)可以分别为 ApplicationContext
类型的构造函数参数或setter方法参数提供依赖。为了更灵活,
包括自动装配成员和多参数方法的能力,使用新的基于注释的自动功能,如果你这样做, ApplicationContext
被自动装配到成员,构造函数参数,或者
方法参数,希望` ApplicationContext 类型,如果成员,构造函数,方法在问题进行 `@Autowired
注释。
更多信息请看Section 5.9.2, “@Autowired”。
当 ApplicationContext
创建一个实现 org.springframework.beans.factory.BeanNameAware
接口的类,该类为
定义在其相关对象定义中的名称提供一个索引。
public interface BeanNameAware { void setBeanName(string name) throws BeansException; }
回调是在所有正常bean属性之后,但是在如 InitializingBean
afterPropertiesSet 或者一个自定义的初始化方法 的初始化回调之前,被调用。
除了上述的 ApplicationContextAware
和 BeanNameAware
,Spring提供一系列的 Aware
接口,允许bean表示他们需要一定基础设施依赖的容器。
最重要的 Aware
接口概括如下,作为一般规则,这个名称是依赖类型的一个很好的指示:
Table 5.4. Aware 接口
名称 | 注入依赖 | 解释… |
---|---|---|
| 声明 | |
| 封闭的事件发布者 | |
| 用于装载bean class 的装载器. | |
| 声明 | |
| 声明bean的名称 | |
| Resource adapter | |
| 在加载时定义weaver处理类的定义 | Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework” |
| 解决消息的配置策略 (用参数化和国际化支持) | |
| Spring JMX 通知发布者 | |
| 目前 | |
| 目前 | |
| 为低级别访问资源配置的加载程序 | |
| 目前 | |
| 目前 |
再次说明,这些接口的使用将您的代码联系到Spring API,并且不遵循反转控制方式。因此,它们被推荐用于要求对容器进行编程访问的基础bean。
在一个bean定义中,可以包含配置信息,包括构造器参数,属性值,以及容器的特定信息,比如初始化方法, 静态工厂方法名等等。子bean定义,从它的父bean定义继承配置数据;子bean定义可以覆盖一些值, 或者根据需要添加一些其他值。使用父子bean定义,可以避免很多配置填写。事实上,这是一种模板设计模式。
如果在工作中,你编程式地使用ApplicationContext
接口,子bean定义由ChildBeanDefinition
类来表示;
大多数的使用者,在工作中不在这个层面上使用它,而是声明式配置bean定义,比如使用ClassPathXmlApplicationContext
当使用基于XML的配置元数据,你可以指明一个子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"/> <!--age属性的值1,将会从父类继承--> </bean>
一个子bean定义如果没有指定class,他可以使用父bean定义的class,也可以覆盖它;在后一种情形中子bean定义, 必须兼容父类,也就是说,他要接受父类的属性值
子bean定义从父类继承构造器参数,属性值,和可以重写的方法,另外也可以增加新的值。你指定的任何初始化方法, 销毁方法,和/或者静态工厂方法设置,都会覆盖相应的父类设置。
其余的设置 总是 取自子bean定义:depends on, autowire mode, dependency check, singleton, scope, lazy init.
下面的例子,通过使用abstract
属性,明确地标明这个父类bean定义是抽象的。如果,父类bean定义
没有明确地指出所属的类,那么标记父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属性的值1,将会从父类继承--> </bean>
这个父bean不能自主实例化,因为它是不完整的,同时它也明确地被标注为abstract
;像这样,
一个bean定义为abstract
的,它只能作为一个纯粹的bean模板,为子bean定义,充当父bean定义。
尝试独立地使用这样一个abstract
的父bean,把他作为另一个bean 的引用,或者根据这个父bean的id显式调用getBean()
方法,
将会返回一个错误。类似地,容器内部的preInstantiateSingletons()
方法,也忽略定义为抽象的bean定义。
[注意]
ApplicationContext
默认地预实例化所有单例bean。因此,如果你打算把一个(父)bean定义仅仅作为模板来使用,
同时给它指定了类属性,你必须确保设置 abstract 属性为 true,否则,应用程序上下文,会(尝试)
预实例化这个abstract
bean。
通常,应用程序开发者,不需要继承ApplicationContext
的实现类。相反,Spring IoC容器可以通过插入特殊的集成接口的实现,进行拓展。新的一节中,描述了这些集成接口。
BeanPostProcessor
定义了回调方法,通过实现这个回调方法,你可以提供你自己的(或者重写容器默认的)
实例化逻辑,依赖分析逻辑等等。如果你想在Spring容器完成实例化配置,实例化一个bean之后,实现一些自定义逻辑
你可以插入一个或多个 BeanPostProcessor
的实现。
你可以配置多个BeanPostProcessor
实例,同时你也能通过设置 order
属性来控制这些BeanPostProcessor
s 的执行顺序。只有BeanPostProcessor
实现了Ordered
接口,你才可以设置 order
属性。如果,你编写了自己的BeanPostProcessor
也应当考虑实现 Ordered
接口。欲知详情,请参考BeanPostProcessor
和 Ordered
接口的javadoc。也可以看看下面的要点programmatic registration of BeanPostProcessors
[注意]
BeanPostProcessor
s 作用在一个bean(或者对象)的实例上;也就是说,Spring IoC实例化一个bean实例之后,
BeanPostProcessor
s,才开始进行处理。
BeanPostProcessor
s作用范围是每一个容器。这仅仅和你正在使用容器有关。如果你在一个容器中定义了一个BeanPostProcessor
,它将 仅仅 后置处理那个容器中的beans。换言之,一个容器中的beans不会被另一个,容器中的BeanPostProcessor
处理,即使这两个容器,具有相同的父类。
为了改变实际的bean定义(例如, blueprint 定义的bean),你反而需要使用BeanFactoryPostProcessor
,就像在Section 5.8.2, “Customizing configuration metadata with a BeanFactoryPostProcessor”中描述的那样。
org.springframework.beans.factory.config.BeanPostProcessor
接口,恰好有两个回调方法组成。
当这样的一个类注册为容器的一个后置处理器,由于每一个bean实例都是由容器创建的,这个后置处理器会从容器得到一个回调方法
在容器的初始化方法(比如InitializingBean的afterPropertiesSet()和任何生命的初始化方法)被调用之前和任何bean实例化回调之后。
后置处理器,可以对bean采取任何措施,包括忽略回到方法是否完成。一个bean后置处理器,通常会检查回调接口或者使用代理包装一个bean。一些
Spring AOP基础设施类,为了提供包装式的代理逻辑,被实现为bean后置处理器,
一个ApplicationContext
,自动地检测所有定义在配置元文件中,并实现了BeanPostProcessor
接口的bean。
该ApplicationContext
注册这些beans作为后置处理器,使他们可以在bean创建完成之后,被调用。
bean后置处理器可以像其他bean一样部署到容器中。
注意,当在一个配置类上,使用@Bean
工厂方法声明一个 BeanPostProcessor
,返回类型应该是实现类自身,
至少也要是 org.springframework.beans.factory.config.BeanPostProcessor
接口,要清楚地表明这个bean的后置处理器本质特点。
此外,在它完全创建之前,ApplicationContext
将不能通过类型自动探测它。由于一个BeanPostProcessor
,早期就需要被实例化,
以适应上下文中其他bean的实例化,因此这个早期的类型检查是至关重要的。
[注意]
编程式注册 BeanPostProcessors
虽然推荐使用ApplicationContext
的自动检测来注册BeanPostProcessor
,但是对于使用了addBeanPostProcessor
方法的ConfigurableBeanFactory
也可以编程式地注册他们。
在注册之前,或者是在继承层次的上下文之间复制bean后置处理器,需要对逻辑进行条件式的评估时,这是有用的。但是请注意,编程地添加的BeanPostProcessors
不需要考虑Ordered
接口 。
也就是注册的顺序决定了执行的顺序。也要注意,编程式注册的BeanPostProcessors
,总是预先被处理----早于通过自动检测方式注册的,同时忽略
任何明确的排序
[注意]
BeanPostProcessors 和 AOP 自动代理
实现了BeanPostProcessor
接口的类是特殊的,会被容器特殊处理。所有BeanPostProcessors`__和他们直接引用的
beans__都会在容器启动的时候被实例化,作为`ApplicationContext
特殊启动阶段的一部分。接着,所有的BeanPostProcessors
以一个有序的方式,进行注册,并应用于容器中的一切bean。因为AOP自动代理本身被实现为BeanPostProcessor
, 这个BeanPostProcessors
和它直接应用的beans都没有资格进行自动代理,这样就没有切面编织到他们里面。
对于所有这样的bean,你会看到一个info日志:"Bean foo 没有资格获取任何BeanPostProcessor接口的处理(例如,没有资格获取自动代理的)".
注意,如果你有beans使用自动装配或者@Resource
装配到了你的BeanPostProcessor
中,当根据候选依赖,搜索匹配类型时,Spring也许会访问意外类型的bean;
因此,使它们没有资格进行自动代理,或者其他类型的bean后置处理。例如,你使用@Resource
注解一个依赖,其中字段或者set方法名,不是和bean声明的名字直接对应
同时没有name属性被使用,然后,Spring将会根据类型,访问其他beans进行匹配。
下面的示例显示了如何编写,注册和在一个ApplicationContext
中使用BeanPostProcessor
。
This first example illustrates basic usage. The example shows a custom
BeanPostProcessor
implementation that invokes the toString()
method of each bean as
it is created by the container and prints the resulting string to the system console.
Find below the custom BeanPostProcessor
implementation class definition:
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.xsd http://www.springframework.org/schema/lang http://www.springframework.org/schema/lang/spring-lang.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>
Notice how the InstantiationTracingBeanPostProcessor
is simply defined. It does not
even have a name, and because it is a bean it can be dependency-injected just like any
other bean. (The preceding configuration also defines a bean that is backed by a Groovy
script. The Spring dynamic language support is detailed in the chapter entitled
Chapter 28, Dynamic language support.)
The following simple Java application executes the preceding code and configuration:
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); } }
The output of the preceding application resembles the following:
Bean messenger created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Using callback interfaces or annotations in conjunction with a custom
BeanPostProcessor
implementation is a common means of extending the Spring IoC
container. An example is Spring’s RequiredAnnotationBeanPostProcessor
- a
BeanPostProcessor
implementation that ships with the Spring distribution which ensures
that JavaBean properties on beans that are marked with an (arbitrary) annotation are
actually (configured to be) dependency-injected with a value.
The next extension point that we will look at is the
org.springframework.beans.factory.config.BeanFactoryPostProcessor
. The semantics of
this interface are similar to those of the BeanPostProcessor
, with one major
difference: BeanFactoryPostProcessor
operates on the bean configuration metadata;
that is, the Spring IoC container allows a BeanFactoryPostProcessor
to read the
configuration metadata and potentially change it before the container instantiates
any beans other than BeanFactoryPostProcessors
.
You can configure multiple BeanFactoryPostProcessors
, and you can control the order in
which these BeanFactoryPostProcessors
execute by setting the order
property.
However, you can only set this property if the BeanFactoryPostProcessor
implements the
Ordered
interface. If you write your own BeanFactoryPostProcessor
, you should
consider implementing the Ordered
interface too. Consult the javadocs of the
BeanFactoryPostProcessor
and Ordered
interfaces for more details.
Note | |
---|---|
If you want to change the actual bean instances (i.e., the objects that are created
from the configuration metadata), then you instead need to use a Also, |
A bean factory post-processor is executed automatically when it is declared inside an
ApplicationContext
, in order to apply changes to the configuration metadata that
define the container. Spring includes a number of predefined bean factory
post-processors, such as PropertyOverrideConfigurer
and
PropertyPlaceholderConfigurer
. A custom BeanFactoryPostProcessor
can also be used,
for example, to register custom property editors.
An ApplicationContext
automatically detects any beans that are deployed into it that
implement the BeanFactoryPostProcessor
interface. It uses these beans as bean factory
post-processors, at the appropriate time. You can deploy these post-processor beans as
you would any other bean.
Note | |
---|---|
As with |
You use the PropertyPlaceholderConfigurer
to externalize property values from a bean
definition in a separate file using the standard Java Properties
format. Doing so
enables the person deploying an application to customize environment-specific properties
such as database URLs and passwords, without the complexity or risk of modifying the
main XML definition file or files for the container.
Consider the following XML-based configuration metadata fragment, where a DataSource
with placeholder values is defined. The example shows properties configured from an
external Properties
file. At runtime, a PropertyPlaceholderConfigurer
is applied to
the metadata that will replace some properties of the DataSource. The values to replace
are specified as placeholders of the form ${property-name}
which follows the Ant /
log4j / JSP EL style.
<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>
The actual values come from another file in the standard Java Properties
format:
jdbc.driverClassName=org.hsqldb.jdbcDriver jdbc.url=jdbc:hsqldb:hsql://production:9002 jdbc.username=sa jdbc.password=root
Therefore, the string ${jdbc.username}
is replaced at runtime with the value sa, and
the same applies for other placeholder values that match keys in the properties file.
The PropertyPlaceholderConfigurer
checks for placeholders in most properties and
attributes of a bean definition. Furthermore, the placeholder prefix and suffix can be
customized.
With the context
namespace introduced in Spring 2.5, it is possible to configure
property placeholders with a dedicated configuration element. One or more locations can
be provided as a comma-separated list in the location
attribute.
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
The PropertyPlaceholderConfigurer
not only looks for properties in the Properties
file you specify. By default it also checks against the Java System
properties if it
cannot find a property in the specified properties files. You can customize this
behavior by setting the systemPropertiesMode
property of the configurer with one of
the following three supported integer values:
Consult the PropertyPlaceholderConfigurer
javadocs for more information.
Tip | |
---|---|
You can use the <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}"/> If the class cannot be resolved at runtime to a valid class, resolution of the bean
fails when it is about to be created, which is during the |
The PropertyOverrideConfigurer
, another bean factory post-processor, resembles the
PropertyPlaceholderConfigurer
, but unlike the latter, the original definitions can
have default values or no values at all for bean properties. If an overriding
Properties
file does not have an entry for a certain bean property, the default
context definition is used.
Note that the bean definition is not aware of being overridden, so it is not
immediately obvious from the XML definition file that the override configurer is being
used. In case of multiple PropertyOverrideConfigurer
instances that define different
values for the same bean property, the last one wins, due to the overriding mechanism.
Properties file configuration lines take this format:
beanName.property=value
For example:
dataSource.driverClassName=com.mysql.jdbc.Driver dataSource.url=jdbc:mysql:mydb
This example file can be used with a container definition that contains a bean called dataSource, which has driver and url properties.
Compound property names are also supported, as long as every component of the path except the final property being overridden is already non-null (presumably initialized by the constructors). In this example…
foo.fred.bob.sammy=123
sammy
property of the bob
property of the fred
property of the foo
bean
is set to the scalar value 123
.
Note | |
---|---|
Specified override values are always literal values; they are not translated into bean references. This convention also applies when the original value in the XML bean definition specifies a bean reference. |
With the context
namespace introduced in Spring 2.5, it is possible to configure
property overriding with a dedicated configuration element:
<context:property-override location="classpath:override.properties"/>
Implement the org.springframework.beans.factory.FactoryBean
interface for objects that
are themselves factories.
The FactoryBean
interface is a point of pluggability into the Spring IoC container’s
instantiation logic. If you have complex initialization code that is better expressed in
Java as opposed to a (potentially) verbose amount of XML, you can create your own
FactoryBean
, write the complex initialization inside that class, and then plug your
custom FactoryBean
into the container.
The FactoryBean
interface provides three methods:
Object getObject()
: returns an instance of the object this factory creates. The
instance can possibly be shared, depending on whether this factory returns singletons
or prototypes.
boolean isSingleton()
: returns true
if this FactoryBean
returns singletons,
false
otherwise.
Class getObjectType()
: returns the object type returned by the getObject()
method
or null
if the type is not known in advance.
The FactoryBean
concept and interface is used in a number of places within the Spring
Framework; more than 50 implementations of the FactoryBean
interface ship with Spring
itself.
When you need to ask a container for an actual FactoryBean
instance itself instead of
the bean it produces, preface the bean’s id with the ampersand symbol ( &
) when
calling the getBean()
method of the ApplicationContext
. So for a given FactoryBean
with an id of myBean
, invoking getBean("myBean")
on the container returns the
product of the FactoryBean
; whereas, invoking getBean("&myBean")
returns the
FactoryBean
instance itself.
An alternative to XML setups is provided by annotation-based configuration which rely on
the bytecode metadata for wiring up components instead of angle-bracket declarations.
Instead of using XML to describe a bean wiring, the developer moves the configuration
into the component class itself by using annotations on the relevant class, method, or
field declaration. As mentioned in the section called “Example: The RequiredAnnotationBeanPostProcessor”, using
a BeanPostProcessor
in conjunction with annotations is a common means of extending the
Spring IoC container. For example, Spring 2.0 introduced the possibility of enforcing
required properties with the @Required annotation. Spring
2.5 made it possible to follow that same general approach to drive Spring’s dependency
injection. Essentially, the @Autowired
annotation provides the same capabilities as
described in Section 5.4.5, “自动装配协作者” but with more fine-grained control and wider
applicability. Spring 2.5 also added support for JSR-250 annotations such as
@PostConstruct
, and @PreDestroy
. Spring 3.0 added support for JSR-330 (Dependency
Injection for Java) annotations contained in the javax.inject package such as @Inject
and @Named
. Details about those annotations can be found in the
relevant section.
Note | |
---|---|
Annotation injection is performed before XML injection, thus the latter configuration will override the former for properties wired through both approaches. |
As always, you can register them as individual bean definitions, but they can also be
implicitly registered by including the following tag in an XML-based Spring
configuration (notice the inclusion of the context
namespace):
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:annotation-config/> </beans>
(The implicitly registered post-processors include
AutowiredAnnotationBeanPostProcessor
,
CommonAnnotationBeanPostProcessor
,
PersistenceAnnotationBeanPostProcessor
,
as well as the aforementioned
RequiredAnnotationBeanPostProcessor
.)
Note | |
---|---|
|
The @Required
annotation applies to bean property setter methods, as in the following
example:
public class SimpleMovieLister { private MovieFinder movieFinder; @Required public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
This annotation simply indicates that the affected bean property must be populated at
configuration time, through an explicit property value in a bean definition or through
autowiring. The container throws an exception if the affected bean property has not been
populated; this allows for eager and explicit failure, avoiding NullPointerException
s
or the like later on. It is still recommended that you put assertions into the bean
class itself, for example, into an init method. Doing so enforces those required
references and values even when you use the class outside of a container.
As expected, you can apply the @Autowired
annotation to "traditional" setter methods:
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
Note | |
---|---|
JSR 330’s @Inject annotation can be used in place of Spring’s |
You can also apply the annotation to methods with arbitrary names and/or multiple arguments:
public class MovieRecommender { private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public void prepare(MovieCatalog movieCatalog, CustomerPreferenceDao customerPreferenceDao) { this.movieCatalog = movieCatalog; this.customerPreferenceDao = customerPreferenceDao; } // ... }
You can apply @Autowired
to constructors and fields:
public class MovieRecommender { @Autowired private MovieCatalog movieCatalog; private CustomerPreferenceDao customerPreferenceDao; @Autowired public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) { this.customerPreferenceDao = customerPreferenceDao; } // ... }
It is also possible to provide all beans of a particular type from the
ApplicationContext
by adding the annotation to a field or method that expects an array
of that type:
public class MovieRecommender { @Autowired private MovieCatalog[] movieCatalogs; // ... }
The same applies for typed collections:
public class MovieRecommender { private Set<MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
Tip | |
---|---|
Your beans can implement the |
Even typed Maps can be autowired as long as the expected key type is String
. The Map
values will contain all beans of the expected type, and the keys will contain the
corresponding bean names:
public class MovieRecommender { private Map<String, MovieCatalog> movieCatalogs; @Autowired public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) { this.movieCatalogs = movieCatalogs; } // ... }
By default, the autowiring fails whenever zero candidate beans are available; the default behavior is to treat annotated methods, constructors, and fields as indicating required dependencies. This behavior can be changed as demonstrated below.
public class SimpleMovieLister { private MovieFinder movieFinder; @Autowired(required=false) public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
Note | |
---|---|
Only one annotated constructor per-class can be marked as required, but multiple non-required constructors can be annotated. In that case, each is considered among the candidates and Spring uses the greediest constructor whose dependencies can be satisfied, that is the constructor that has the largest number of arguments.
|
You can also use @Autowired
for interfaces that are well-known resolvable
dependencies: BeanFactory
, ApplicationContext
, Environment
, ResourceLoader
,
ApplicationEventPublisher
, and MessageSource
. These interfaces and their extended
interfaces, such as ConfigurableApplicationContext
or ResourcePatternResolver
, are
automatically resolved, with no special setup necessary.
public class MovieRecommender { @Autowired private ApplicationContext context; public MovieRecommender() { } // ... }
Note | |
---|---|
|
Because autowiring by type may lead to multiple candidates, it is often necessary to
have more control over the selection process. One way to accomplish this is with
Spring’s @Qualifier
annotation. You can associate qualifier values with specific
arguments, narrowing the set of type matches so that a specific bean is chosen for each
argument. In the simplest case, this can be a plain descriptive value:
public class MovieRecommender { @Autowired @Qualifier("main") private MovieCatalog movieCatalog; // ... }
The @Qualifier
annotation can also be specified on individual constructor arguments or
method parameters:
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; } // ... }
The corresponding bean definitions appear as follows. The bean with qualifier value "main" is wired with the constructor argument that is qualified with the same value.
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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, the bean name is considered a default qualifier value. Thus you
can define the bean with an id "main" instead of the nested qualifier element, leading
to the same matching result. However, although you can use this convention to refer to
specific beans by name, @Autowired
is fundamentally about type-driven injection with
optional semantic qualifiers. This means that qualifier values, even with the bean name
fallback, always have narrowing semantics within the set of type matches; they do not
semantically express a reference to a unique bean id. Good qualifier values are "main"
or "EMEA" or "persistent", expressing characteristics of a specific component that are
independent from the bean id, which may be auto-generated in case of an anonymous bean
definition like the one in the preceding example.
Qualifiers also apply to typed collections, as discussed above, for example, to
Set<MovieCatalog>
. In this case, all matching beans according to the declared
qualifiers are injected as a collection. This implies that qualifiers do not have to be
unique; they rather simply constitute filtering criteria. For example, you can define
multiple MovieCatalog
beans with the same qualifier value "action"; all of which would
be injected into a Set<MovieCatalog>
annotated with @Qualifier("action")
.
Tip | |
---|---|
If you intend to express annotation-driven injection by name, do not primarily use
As a specific consequence of this semantic difference, beans that are themselves defined
as a collection or map type cannot be injected through
|
You can create your own custom qualifier annotations. Simply define an annotation and
provide the @Qualifier
annotation within your definition:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Genre { String value(); }
Then you can provide the custom qualifier on autowired fields and parameters:
public class MovieRecommender { @Autowired @Genre("Action") private MovieCatalog actionCatalog; private MovieCatalog comedyCatalog; @Autowired public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) { this.comedyCatalog = comedyCatalog; } // ... }
Next, provide the information for the candidate bean definitions. You can add
<qualifier/>
tags as sub-elements of the <bean/>
tag and then specify the type
and
value
to match your custom qualifier annotations. The type is matched against the
fully-qualified class name of the annotation. Or, as a convenience if no risk of
conflicting names exists, you can use the short class name. Both approaches are
demonstrated in the following example.
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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>
In Section 5.10, “Classpath scanning and managed components”, you will see an annotation-based alternative to providing the qualifier metadata in XML. Specifically, see Section 5.10.8, “Providing qualifier metadata with annotations”.
In some cases, it may be sufficient to use an annotation without a value. This may be useful when the annotation serves a more generic purpose and can be applied across several different types of dependencies. For example, you may provide an offline catalog that would be searched when no Internet connection is available. First define the simple annotation:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface Offline { }
Then add the annotation to the field or property to be autowired:
public class MovieRecommender { @Autowired @Offline private MovieCatalog offlineCatalog; // ... }
Now the bean definition only needs a qualifier type
:
<bean class="example.SimpleMovieCatalog"> <qualifier type="Offline"/> <!-- inject any dependencies required by this bean --> </bean>
You can also define custom qualifier annotations that accept named attributes in
addition to or instead of the simple value
attribute. If multiple attribute values are
then specified on a field or parameter to be autowired, a bean definition must match
all such attribute values to be considered an autowire candidate. As an example,
consider the following annotation definition:
@Target({ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) @Qualifier public @interface MovieQualifier { String genre(); Format format(); }
In this case Format
is an enum:
public enum Format {
VHS, DVD, BLURAY
}
The fields to be autowired are annotated with the custom qualifier and include values
for both attributes: genre
and 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; // ... }
Finally, the bean definitions should contain matching qualifier values. This example
also demonstrates that bean meta attributes may be used instead of the
<qualifier/>
sub-elements. If available, the <qualifier/>
and its attributes take
precedence, but the autowiring mechanism falls back on the values provided within the
<meta/>
tags if no such qualifier is present, as in the last two bean definitions in
the following example.
<?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.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.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>
In addition to the @Qualifier
annotation, it is also possible to use Java generic types
as an implicit form of qualification. For example, suppose you have the following
configuration:
@Configuration public class MyConfiguration { @Bean public StringStore stringStore() { return new StringStore(); } @Bean public IntegerStore integerStore() { return new IntegerStore(); } }
Assuming that beans above implement a generic interface, i.e. Store<String>
and
Store<Integer>
, you can @Autowire
the Store
interface and the generic will
be used as a qualifier:
@Autowired private Store<String> s1; // <String> qualifier, injects the stringStore bean @Autowired private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
Generic qualifiers also apply when autowiring Lists, Maps and Arrays:
// Inject all Store beans as long as they have an <Integer> generic // Store<String> beans will not appear in this list @Autowired private List<Store<Integer>> s;
The
CustomAutowireConfigurer
is a BeanFactoryPostProcessor
that enables you to register your own custom qualifier
annotation types even if they are not annotated with Spring’s @Qualifier
annotation.
<bean id="customAutowireConfigurer" class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer"> <property name="customQualifierTypes"> <set> <value>example.CustomQualifier</value> </set> </property> </bean>
The AutowireCandidateResolver
determines autowire candidates by:
autowire-candidate
value of each bean definition
default-autowire-candidates
pattern(s) available on the <beans/>
element
@Qualifier
annotations and any custom annotations registered
with the CustomAutowireConfigurer
When multiple beans qualify as autowire candidates, the determination of a "primary" is
the following: if exactly one bean definition among the candidates has a primary
attribute set to true
, it will be selected.
Spring also supports injection using the JSR-250 @Resource
annotation on fields or
bean property setter methods. This is a common pattern in Java EE 5 and 6, for example
in JSF 1.2 managed beans or JAX-WS 2.0 endpoints. Spring supports this pattern for
Spring-managed objects as well.
@Resource
takes a name attribute, and by default Spring interprets that value as the
bean name to be injected. In other words, it follows by-name semantics, as
demonstrated in this example:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource(name="myMovieFinder") public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
If no name is specified explicitly, the default name is derived from the field name or setter method. In case of a field, it takes the field name; in case of a setter method, it takes the bean property name. So the following example is going to have the bean with name "movieFinder" injected into its setter method:
public class SimpleMovieLister { private MovieFinder movieFinder; @Resource public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } }
Note | |
---|---|
The name provided with the annotation is resolved as a bean name by the
|
In the exclusive case of @Resource
usage with no explicit name specified, and similar
to @Autowired
, @Resource
finds a primary type match instead of a specific named bean
and resolves well-known resolvable dependencies: the BeanFactory
,
ApplicationContext
, ResourceLoader
, ApplicationEventPublisher
, and MessageSource
interfaces.
Thus in the following example, the customerPreferenceDao
field first looks for a bean
named customerPreferenceDao, then falls back to a primary type match for the type
CustomerPreferenceDao
. The "context" field is injected based on the known resolvable
dependency type ApplicationContext
.
public class MovieRecommender { @Resource private CustomerPreferenceDao customerPreferenceDao; @Resource private ApplicationContext context; public MovieRecommender() { } // ... }
The CommonAnnotationBeanPostProcessor
not only recognizes the @Resource
annotation
but also the JSR-250 lifecycle annotations. Introduced in Spring 2.5, the support
for these annotations offers yet another alternative to those described in
initialization callbacks and
destruction callbacks. Provided that the
CommonAnnotationBeanPostProcessor
is registered within the Spring
ApplicationContext
, a method carrying one of these annotations is invoked at the same
point in the lifecycle as the corresponding Spring lifecycle interface method or
explicitly declared callback method. In the example below, the cache will be
pre-populated upon initialization and cleared upon destruction.
public class CachingMovieLister { @PostConstruct public void populateMovieCache() { // populates the movie cache upon initialization... } @PreDestroy public void clearMovieCache() { // clears the movie cache upon destruction... } }
Note | |
---|---|
For details about the effects of combining various lifecycle mechanisms, see the section called “组合生命周期机制”. |
Most examples in this chapter use XML to specify the configuration metadata that
produces each BeanDefinition
within the Spring container. The previous section
(Section 5.9, “Annotation-based container configuration”) demonstrates how to provide a lot of the configuration
metadata through source-level annotations. Even in those examples, however, the "base"
bean definitions are explicitly defined in the XML file, while the annotations only
drive the dependency injection. This section describes an option for implicitly
detecting the candidate components by scanning the classpath. Candidate components
are classes that match against a filter criteria and have a corresponding bean
definition registered with the container. This removes the need to use XML to perform
bean registration, instead you can use annotations (for example @Component), AspectJ
type expressions, or your own custom filter criteria to select which classes will have
bean definitions registered with the container.
Note | |
---|---|
Starting with Spring 3.0, many features provided by the Spring JavaConfig project are
part of the core Spring Framework. This allows you to define beans using Java rather
than using the traditional XML files. Take a look at the |
The @Repository
annotation is a marker for any class that fulfills the role or
stereotype (also known as Data Access Object or DAO) of a repository. Among the uses
of this marker is the automatic translation of exceptions as described in
Section 14.2.2, “异常转化”.
Spring provides further stereotype annotations: @Component
, @Service
, and
@Controller
. @Component
is a generic stereotype for any Spring-managed component.
@Repository
, @Service
, and @Controller
are specializations of @Component
for
more specific use cases, for example, in the persistence, service, and presentation
layers, respectively. Therefore, you can annotate your component classes with
@Component
, but by annotating them with @Repository
, @Service
, or @Controller
instead, your classes are more properly suited for processing by tools or associating
with aspects. For example, these stereotype annotations make ideal targets for
pointcuts. It is also possible that @Repository
, @Service
, and @Controller
may
carry additional semantics in future releases of the Spring Framework. Thus, if you are
choosing between using @Component
or @Service
for your service layer, @Service
is
clearly the better choice. Similarly, as stated above, @Repository
is already
supported as a marker for automatic exception translation in your persistence layer.
Many of the annotations provided by Spring can be used as "meta-annotations" in
your own code. A meta-annotation is simply an annotation, that can be applied to another
annotation. For example, The @Service
annotation mentioned above is meta-annotated with
with @Component
:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Component // Spring will see this and treat @Service in the same way as @Component public @interface Service { // .... }
Meta-annotations can also be combined together to create composed annotations. For
example, the @RestController
annotation from Spring MVC is composed of
@Controller
and @ResponseBody
.
With the exception of value()
, meta-annotated types may redeclare attributes from the
source annotation to allow user customization. This can be particularly useful when you
want to only expose a subset of the source annotation attributes. For example, here is a
custom @Scope
annotation that defines session
scope, but still allows customization
of the proxyMode
.
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Scope("session") public @interface SessionScope { ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT }
Spring can automatically detect stereotyped classes and register corresponding
BeanDefinition
s with the ApplicationContext
. For example, the following two classes
are eligible for such autodetection:
@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 }
To autodetect these classes and register the corresponding beans, you need to add
@ComponentScan
to your @Configuration
class, where the basePackages
attribute
is a common parent package for the two classes. (Alternatively, you can specify a
comma-separated list that includes the parent package of each class.)
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { ... }
Note | |
---|---|
for concision, the above may have used the |
The following is an alternative using 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" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example"/> </beans>
Tip | |
---|---|
The use of |
Note | |
---|---|
The scanning of classpath packages requires the presence of corresponding directory entries in the classpath. When you build JARs with Ant, make sure that you do not activate the files-only switch of the JAR task. Also, classpath directories may not get exposed based on security policies in some environments, e.g. standalone apps on JDK 1.7.0_45 and higher (which requires Trusted-Library setup in your manifests; see http://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources). |
Furthermore, the AutowiredAnnotationBeanPostProcessor
and
CommonAnnotationBeanPostProcessor
are both included implicitly when you use the
component-scan element. That means that the two components are autodetected and
wired together - all without any bean configuration metadata provided in XML.
Note | |
---|---|
You can disable the registration of |
By default, classes annotated with @Component
, @Repository
, @Service
,
@Controller
, or a custom annotation that itself is annotated with @Component
are the
only detected candidate components. However, you can modify and extend this behavior
simply by applying custom filters. Add them as includeFilters or excludeFilters
parameters of the @ComponentScan
annotation (or as include-filter or exclude-filter
sub-elements of the component-scan
element). Each filter element requires the type
and expression
attributes. The following table describes the filtering options.
Table 5.5. Filter Types
Filter Type | Example Expression | Description |
---|---|---|
annotation (default) |
| An annotation to be present at the type level in target components. |
assignable |
| A class (or interface) that the target components are assignable to (extend/implement). |
aspectj |
| An AspectJ type expression to be matched by the target components. |
regex |
| A regex expression to be matched by the target components class names. |
custom |
| A custom implementation of the |
The following example shows the configuration ignoring all @Repository
annotations
and using "stub" repositories instead.
@Configuration @ComponentScan(basePackages = "org.example", includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"), excludeFilters = @Filter(Repository.class)) public class AppConfig { ... }
and the equivalent using XML
<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 | |
---|---|
You can also disable the default filters by setting |
Spring components can also contribute bean definition metadata to the container. You do
this with the same @Bean
annotation used to define bean metadata within
@Configuration
annotated classes. Here is a simple example:
@Component public class FactoryMethodComponent { @Bean @Qualifier("public") public TestBean publicInstance() { return new TestBean("publicInstance"); } public void doWork() { // Component method implementation omitted } }
This class is a Spring component that has application-specific code contained in its
doWork()
method. However, it also contributes a bean definition that has a factory
method referring to the method publicInstance()
. The @Bean
annotation identifies the
factory method and other bean definition properties, such as a qualifier value through
the @Qualifier
annotation. Other method level annotations that can be specified are
@Scope
, @Lazy
, and custom qualifier annotations.
Tip | |
---|---|
In addition to its role for component initialization, the |
Autowired fields and methods are supported as previously discussed, with additional
support for autowiring of @Bean
methods:
@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(spouse); 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); } }
The example autowires the String
method parameter country
to the value of the Age
property on another bean named privateInstance
. A Spring Expression Language element
defines the value of the property through the notation #{ <expression> }
. For @Value
annotations, an expression resolver is preconfigured to look for bean names when
resolving expression text.
The @Bean
methods in a Spring component are processed differently than their
counterparts inside a Spring @Configuration
class. The difference is that @Component
classes are not enhanced with CGLIB to intercept the invocation of methods and fields.
CGLIB proxying is the means by which invoking methods or fields within @Bean
methods
in @Configuration
classes creates bean metadata references to collaborating objects;
such methods are not invoked with normal Java semantics. In contrast, invoking a
method or field in an @Bean
method within a @Component
class has standard Java
semantics.
When a component is autodetected as part of the scanning process, its bean name is
generated by the BeanNameGenerator
strategy known to that scanner. By default, any
Spring stereotype annotation ( @Component
, @Repository
, @Service
, and
@Controller
) that contains a name
value will thereby provide that name to the
corresponding bean definition.
If such an annotation contains no name
value or for any other detected component (such
as those discovered by custom filters), the default bean name generator returns the
uncapitalized non-qualified class name. For example, if the following two components
were detected, the names would be myMovieLister and movieFinderImpl:
@Service("myMovieLister") public class SimpleMovieLister { // ... }
@Repository public class MovieFinderImpl implements MovieFinder { // ... }
Note | |
---|---|
If you do not want to rely on the default bean-naming strategy, you can provide a custom
bean-naming strategy. First, implement the
|
@Configuration @ComponentScan(basePackages = "org.example", nameGenerator = MyNameGenerator.class) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" name-generator="org.example.MyNameGenerator" /> </beans>
As a general rule, consider specifying the name with the annotation whenever other components may be making explicit references to it. On the other hand, the auto-generated names are adequate whenever the container is responsible for wiring.
As with Spring-managed components in general, the default and most common scope for
autodetected components is singleton. However, sometimes you need other scopes, which
Spring 2.5 provides with a new @Scope
annotation. Simply provide the name of the scope
within the annotation:
@Scope("prototype") @Repository public class MovieFinderImpl implements MovieFinder { // ... }
Note | |
---|---|
To provide a custom strategy for scope resolution rather than relying on the
annotation-based approach, implement the
|
@Configuration @ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver" /> </beans>
When using certain non-singleton scopes, it may be necessary to generate proxies for the scoped objects. The reasoning is described in the section called “将上述作用域的bean作为依赖”. For this purpose, a scoped-proxy attribute is available on the component-scan element. The three possible values are: no, interfaces, and targetClass. For example, the following configuration will result in standard JDK dynamic proxies:
@Configuration @ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES) public class AppConfig { ... }
<beans> <context:component-scan base-package="org.example" scoped-proxy="interfaces" /> </beans>
The @Qualifier
annotation is discussed in Section 5.9.3, “Fine-tuning annotation-based autowiring with qualifiers”.
The examples in that section demonstrate the use of the @Qualifier
annotation and
custom qualifier annotations to provide fine-grained control when you resolve autowire
candidates. Because those examples were based on XML bean definitions, the qualifier
metadata was provided on the candidate bean definitions using the qualifier
or meta
sub-elements of the bean
element in the XML. When relying upon classpath scanning for
autodetection of components, you provide the qualifier metadata with type-level
annotations on the candidate class. The following three examples demonstrate this
technique:
@Component @Qualifier("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component @Genre("Action") public class ActionMovieCatalog implements MovieCatalog { // ... }
@Component @Offline public class CachingMovieCatalog implements MovieCatalog { // ... }
Note | |
---|---|
As with most annotation-based alternatives, keep in mind that the annotation metadata is bound to the class definition itself, while the use of XML allows for multiple beans of the same type to provide variations in their qualifier metadata, because that metadata is provided per-instance rather than per-class. |
Starting with Spring 3.0, Spring offers support for JSR-330 standard annotations (Dependency Injection). Those annotations are scanned in the same way as the Spring annotations. You just need to have the relevant jars in your classpath.
Note | |
---|---|
If you are using Maven, the <dependency> <groupId>javax.inject</groupId> <artifactId>javax.inject</artifactId> <version>1</version> </dependency> |
Instead of @Autowired
, @javax.inject.Inject
may be used as follows:
import javax.inject.Inject; public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
As with @Autowired
, it is possible to use @Inject
at the class-level, field-level,
method-level and constructor-argument level. If you would like to use a qualified name
for the dependency that should be injected, you should use the @Named
annotation as
follows:
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; } // ... }
Instead of @Component
, @javax.inject.Named
may be used as follows:
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; } // ... }
It is very common to use @Component
without
specifying a name for the component. @Named
can be used in a similar fashion:
import javax.inject.Inject; import javax.inject.Named; @Named public class SimpleMovieLister { private MovieFinder movieFinder; @Inject public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; } // ... }
When using @Named
, it is possible to use
component-scanning in the exact same way as when using Spring annotations:
@Configuration @ComponentScan(basePackages = "org.example") public class AppConfig { ... }
When working with standard annotations, it is important to know that some significant features are not available as shown in the table below:
Table 5.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 | The JSR-330 default scope is like Spring’s
|
@Qualifier | @Named | - |
@Value | - | no equivalent |
@Required | - | no equivalent |
@Lazy | - | no equivalent |
Spring新的Java配置支持中核心构件是 @Configuration
注释类和 @Bean
注释方法。
@Bean
注释是用来表示一个方法实例化,配置和初始化一个由Spring IoC容器管理的新的对象。对于那些熟悉Spring的 <beans/>
XML 配置,
@Bean
注释和 <bean/>
一样起着同样的作用。你可以使用 @Bean
注释任何Spring @Component
的方法,但是,
最经常使用的是 @Configuration
注释bean。
用 @Configuration
注释一个类表明它的主要目的是作为bean定义的来源。此外, @Configuration
注释的类允许inter-bean依赖关系
在相同的类中,通过简单地调用其他 @Bean
方法被定义。最简单的 @Configuration
类定义如下:
@Configuration public class AppConfig { @Bean public MyService myService() { return new MyServiceImpl(); } }
上面的 AppConfig
类相当于下面的Spring <beans/>
XML:
<beans> <bean id="myService" class="com.acme.services.MyServiceImpl"/> </beans>
The @Bean
and @Configuration
annotations will be discussed in depth in the sections
below. First, however, we’ll cover the various ways of creating a spring container using
Java-based configuration.
The sections below document Spring’s AnnotationConfigApplicationContext
, new in Spring
3.0. This versatile ApplicationContext
implementation is capable of accepting not only
@Configuration
classes as input, but also plain @Component
classes and classes
annotated with JSR-330 metadata.
When @Configuration
classes are provided as input, the @Configuration
class itself
is registered as a bean definition, and all declared @Bean
methods within the class
are also registered as bean definitions.
When @Component
and JSR-330 classes are provided, they are registered as bean
definitions, and it is assumed that DI metadata such as @Autowired
or @Inject
are
used within those classes where necessary.
In much the same way that Spring XML files are used as input when instantiating a
ClassPathXmlApplicationContext
, @Configuration
classes may be used as input when
instantiating an AnnotationConfigApplicationContext
. This allows for completely
XML-free usage of the Spring container:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
As mentioned above, AnnotationConfigApplicationContext
is not limited to working only
with @Configuration
classes. Any @Component
or JSR-330 annotated class may be supplied
as input to the constructor. For example:
public static void main(String[] args) { ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class); MyService myService = ctx.getBean(MyService.class); myService.doStuff(); }
The above assumes that MyServiceImpl
, Dependency1
and Dependency2
use Spring
dependency injection annotations such as @Autowired
.
An AnnotationConfigApplicationContext
may be instantiated using a no-arg constructor
and then configured using the register()
method. This approach is particularly useful
when programmatically building an 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(); }
To enable component scanning, just annotate your @Configuration
class as follows:
@Configuration @ComponentScan(basePackages = "com.acme") public class AppConfig { ... }
Tip | |
---|---|
Experienced Spring users will be familiar with the XML declaration equivalent from
Spring’s <beans> <context:component-scan base-package="com.acme"/> </beans> |
In the example above, the com.acme
package will be scanned, looking for any
@Component
-annotated classes, and those classes will be registered as Spring bean
definitions within the container. AnnotationConfigApplicationContext
exposes the
scan(String...)
method to allow for the same component-scanning functionality:
public static void main(String[] args) { AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.scan("com.acme"); ctx.refresh(); MyService myService = ctx.getBean(MyService.class); }
Note | |
---|---|
Remember that |
AnnotationConfigWebApplicationContext
是一个AnnotationConfigApplicationContext
的WebApplicationContext
变种。这个实现可以用于当配置Spring ContextLoaderListener
servlet监听,Spring MVC的DispatcherServlet
等等。下面是一个web.xml
的片段,配置了一个典型的Spring MVC web应用,注意contextClass
的context-param和init-param:
<web-app> <!-- 配置ContextLoaderListener使用AnnotationConfigWebApplicationContext 替换默认的XmlWebApplicationContext --> <context-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </context-param> <!-- 配置多个路径必须使用逗号或空格分隔的全限定名称的@Configuration注解类, 也可以使用包名来自动扫描组件 --> <context-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.AppConfig</param-value> </context-param> <!-- 像平常一样使用ContextLoaderListener启动根应用上下文 --> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <!-- 像平常一样定义Spring MVC DispatcherServlet --> <servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <!-- 配置DispatcherServlet使用AnnotationConfigWebApplicationContext 替换默认的XmlWebApplicationContext --> <init-param> <param-name>contextClass</param-name> <param-value> org.springframework.web.context.support.AnnotationConfigWebApplicationContext </param-value> </init-param> <!-- 同上面一样配置路径 --> <init-param> <param-name>contextConfigLocation</param-name> <param-value>com.acme.web.MvcConfig</param-value> </init-param> </servlet> <!-- 将所有/app/* 的请求映射到dispatcher --> <servlet-mapping> <servlet-name>dispatcher</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
@Bean
是一个方法级别的注解,类似于XML`<bean/>元素。
这个注解支持一些由
<bean/>提供的属性,例如:
<<beans-factory-lifecycle-initializingbean,init-method>>,
<<beans-factory-lifecycle-disposablebean,destroy-method>>,
<<beans-factory-autowire,autowiring>> 和 `name
.
你可以在使用@Configuration
或@Component
的注解类中使用@Bean
注解。
To declare a bean, simply annotate a method with the @Bean
annotation. You use this
method to register a bean definition within an ApplicationContext
of the type
specified as the method’s return value. By default, the bean name will be the same as
the method name. The following is a simple example of a @Bean
method declaration:
@Configuration public class AppConfig { @Bean public TransferService transferService() { return new TransferServiceImpl(); } }
The preceding configuration is exactly equivalent to the following Spring XML:
<beans> <bean id="transferService" class="com.acme.TransferServiceImpl"/> </beans>
Both declarations make a bean named transferService
available in the
ApplicationContext
, bound to an object instance of type TransferServiceImpl
:
transferService -> com.acme.TransferServiceImpl
Any classes defined with the @Bean
annotation support the regular lifecycle callbacks
and can use the @PostConstruct
and @PreDestroy
annotations from JSR-250, see
JSR-250 annotations for further
details.
The regular Spring lifecycle callbacks are fully supported as
well. If a bean implements InitializingBean
, DisposableBean
, or Lifecycle
, their
respective methods are called by the container.
The standard set of *Aware
interfaces such as BeanFactoryAware,
BeanNameAware,
MessageSourceAware,
ApplicationContextAware, and so on are also fully supported.
The @Bean
annotation supports specifying arbitrary initialization and destruction
callback methods, much like Spring XML’s init-method
and destroy-method
attributes
on the bean
element:
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(); } }
Of course, in the case of Foo
above, it would be equally as valid to call the init()
method directly during construction:
@Configuration public class AppConfig { @Bean public Foo foo() { Foo foo = new Foo(); foo.init(); return foo; } // ... }
Tip | |
---|---|
When you work directly in Java, you can do anything you like with your objects and do not always need to rely on the container lifecycle! |
You can specify that your beans defined with the @Bean
annotation should have a
specific scope. You can use any of the standard scopes specified in the
Bean Scopes section.
The default scope is singleton
, but you can override this with the @Scope
annotation:
@Configuration public class MyConfiguration { @Bean @Scope("prototype") public Encryptor encryptor() { // ... } }
Spring offers a convenient way of working with scoped dependencies through
scoped proxies. The easiest way to create such
a proxy when using the XML configuration is the <aop:scoped-proxy/>
element.
Configuring your beans in Java with a @Scope annotation offers equivalent support with
the proxyMode attribute. The default is no proxy ( ScopedProxyMode.NO
), but you can
specify ScopedProxyMode.TARGET_CLASS
or ScopedProxyMode.INTERFACES
.
If you port the scoped proxy example from the XML reference documentation (see preceding
link) to our @Bean
using Java, it would look like the following:
// 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; }
By default, configuration classes use a @Bean
method’s name as the name of the
resulting bean. This functionality can be overridden, however, with the name
attribute.
@Configuration public class AppConfig { @Bean(name = "myFoo") public Foo foo() { return new Foo(); } }
As discussed in Section 5.3.1, “bean的命名”, it is sometimes desirable to give a single bean
multiple names, otherwise known asbean aliasing. The name
attribute of the @Bean
annotation accepts a String array for this purpose.
@Configuration public class AppConfig { @Bean(name = { "dataSource", "subsystemA-dataSource", "subsystemB-dataSource" }) public DataSource dataSource() { // instantiate, configure and return DataSource bean... } }
Sometimes it is helpful to provide a more detailed textual description of a bean. This can be particularly useful when beans are exposed (perhaps via JMX) for monitoring purposes.
To add a description to a @Bean
the
@Description
annotation can be used:
@Configuration public class AppConfig { @Bean @Description("Provides a basic example of a bean") public Foo foo() { return new Foo(); } }
@Configuration
is a class-level annotation indicating that an object is a source of
bean definitions. @Configuration
classes declare beans via public @Bean
annotated
methods. Calls to @Bean
methods on @Configuration
classes can also be used to define
inter-bean dependencies. See Section 5.12.1, “基本概念: @Bean and @Configuration” for a general introduction.
When @Bean
s have dependencies on one another, expressing that dependency is as simple
as having one bean method call another:
@Configuration public class AppConfig { @Bean public Foo foo() { return new Foo(bar()); } @Bean public Bar bar() { return new Bar(); } }
In the example above, the foo
bean receives a reference to bar
via constructor
injection.
Note | |
---|---|
This method of declaring inter-bean dependencies only works when the |
As noted earlier, lookup method injection is an advanced feature that you should use rarely. It is useful in cases where a singleton-scoped bean has a dependency on a prototype-scoped bean. Using Java for this type of configuration provides a natural means for implementing this pattern.
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(); }
Using Java-configuration support , you can create a subclass of CommandManager
where
the abstract createCommand()
method is overridden in such a way that it looks up a new
(prototype) command object:
@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(); } } }
The following example shows a @Bean
annotated method being called twice:
@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()
has been called once in clientService1()
and once in clientService2()
.
Since this method creates a new instance of ClientDaoImpl
and returns it, you would
normally expect having 2 instances (one for each service). That definitely would be
problematic: in Spring, instantiated beans have a singleton
scope by default. This is
where the magic comes in: All @Configuration
classes are subclassed at startup-time
with CGLIB
. In the subclass, the child method checks the container first for any
cached (scoped) beans before it calls the parent method and creates a new instance. Note
that as of Spring 3.2, it is no longer necessary to add CGLIB to your classpath because
CGLIB classes have been repackaged under org.springframework and included directly
within the spring-core JAR.
Note | |
---|---|
The behavior could be different according to the scope of your bean. We are talking about singletons here. |
Note | |
---|---|
There are a few restrictions due to the fact that CGLIB dynamically adds features at startup-time:
|
Much as the <import/>
element is used within Spring XML files to aid in modularizing
configurations, the @Import
annotation allows for loading @Bean
definitions from
another configuration class:
@Configuration public class ConfigA { @Bean public A a() { return new A(); } } @Configuration @Import(ConfigA.class) public class ConfigB { @Bean public B b() { return new B(); } }
Now, rather than needing to specify both ConfigA.class
and ConfigB.class
when
instantiating the context, only ConfigB
needs to be supplied explicitly:
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); }
This approach simplifies container instantiation, as only one class needs to be dealt
with, rather than requiring the developer to remember a potentially large number of
@Configuration
classes during construction.
The example above works, but is simplistic. In most practical scenarios, beans will have
dependencies on one another across configuration classes. When using XML, this is not an
issue, per se, because there is no compiler involved, and one can simply declare
ref="someBean"
and trust that Spring will work it out during container initialization.
Of course, when using @Configuration
classes, the Java compiler places constraints on
the configuration model, in that references to other beans must be valid Java syntax.
Fortunately, solving this problem is simple. Remember that @Configuration
classes are
ultimately just another bean in the container - this means that they can take advantage
of @Autowired
injection metadata just like any other bean!
Let’s consider a more real-world scenario with several @Configuration
classes, each
depending on beans declared in the others:
@Configuration public class ServiceConfig { @Autowired private AccountRepository accountRepository; @Bean public TransferService transferService() { return new TransferServiceImpl(accountRepository); } } @Configuration public class RepositoryConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } } @Configuration @Import({ServiceConfig.class, RepositoryConfig.class}) public class SystemTestConfig { @Bean public 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"); }
In the scenario above, using @Autowired
works well and provides the desired
modularity, but determining exactly where the autowired bean definitions are declared is
still somewhat ambiguous. For example, as a developer looking at ServiceConfig
, how do
you know exactly where the @Autowired AccountRepository
bean is declared? It’s not
explicit in the code, and this may be just fine. Remember that the
Spring Tool Suite provides tooling that
can render graphs showing how everything is wired up - that may be all you need. Also,
your Java IDE can easily find all declarations and uses of the AccountRepository
type,
and will quickly show you the location of @Bean
methods that return that type.
In cases where this ambiguity is not acceptable and you wish to have direct navigation
from within your IDE from one @Configuration
class to another, consider autowiring the
configuration classes themselves:
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { // navigate through the config class to the @Bean method! return new TransferServiceImpl(repositoryConfig.accountRepository()); } }
In the situation above, it is completely explicit where AccountRepository
is defined.
However, ServiceConfig
is now tightly coupled to RepositoryConfig
; that’s the
tradeoff. This tight coupling can be somewhat mitigated by using interface-based or
abstract class-based @Configuration
classes. Consider the following:
@Configuration public class ServiceConfig { @Autowired private RepositoryConfig repositoryConfig; @Bean public TransferService transferService() { return new TransferServiceImpl(repositoryConfig.accountRepository()); } } @Configuration public interface RepositoryConfig { @Bean AccountRepository accountRepository(); } @Configuration public class DefaultRepositoryConfig implements RepositoryConfig { @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(...); } } @Configuration @Import({ServiceConfig.class, DefaultRepositoryConfig.class}) // import the concrete config! public class SystemTestConfig { @Bean public 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"); }
Now ServiceConfig
is loosely coupled with respect to the concrete
DefaultRepositoryConfig
, and built-in IDE tooling is still useful: it will be easy for
the developer to get a type hierarchy of RepositoryConfig
implementations. In this
way, navigating @Configuration
classes and their dependencies becomes no different
than the usual process of navigating interface-based code.
It is often useful to conditionally enable to disable a complete @Configuration
class,
or even individual @Bean
methods, based on some arbitrary system state. One common
example of this it to use the @Profile
annotation to active beans only when a specific
profile has been enabled in the Spring Environment
(see Section 5.13.1, “Bean定义配置文件”
for details).
The @Profile
annotation is actually implemented using a much more flexible annotation
called @Conditional
.
The @Conditional
annotation indicates specific
org.springframework.context.annotation.Condition
implementations that should be
consulted before a @Bean
is registered.
Implementations of the Condition
interface simply provide a matches(...)
method that returns true
or false
. For example, here is the actual
Condition
implementation used for @Profile
:
@Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { if (context.getEnvironment() != null) { // Read the @Profile annotation attributes MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName()); if (attrs != null) { for (Object value : attrs.get("value")) { if (context.getEnvironment().acceptsProfiles(((String[]) value))) { return true; } } return false; } } return true; }
See the
@Conditional
javadocs for more detail.
Spring’s @Configuration
class support does not aim to be a 100% complete replacement
for Spring XML. Some facilities such as Spring XML namespaces remain an ideal way to
configure the container. In cases where XML is convenient or necessary, you have a
choice: either instantiate the container in an "XML-centric" way using, for example,
ClassPathXmlApplicationContext
, or in a "Java-centric" fashion using
AnnotationConfigApplicationContext
and the @ImportResource
annotation to import XML
as needed.
It may be preferable to bootstrap the Spring container from XML and include
@Configuration
classes in an ad-hoc fashion. For example, in a large existing codebase
that uses Spring XML, it will be easier to create @Configuration
classes on an
as-needed basis and include them from the existing XML files. Below you’ll find the
options for using @Configuration
classes in this kind of "XML-centric" situation.
Remember that @Configuration
classes are ultimately just bean definitions in the
container. In this example, we create a @Configuration
class named AppConfig
and
include it within system-test-config.xml
as a <bean/>
definition. Because
<context:annotation-config/>
is switched on, the container will recognize the
@Configuration
annotation, and process the @Bean
methods declared in AppConfig
properly.
@Configuration public class AppConfig { @Autowired private DataSource dataSource; @Bean public AccountRepository accountRepository() { return new JdbcAccountRepository(dataSource); } @Bean public 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 | |
---|---|
In |
Because @Configuration
is meta-annotated with @Component
, @Configuration
-annotated
classes are automatically candidates for component scanning. Using the same scenario as
above, we can redefine system-test-config.xml
to take advantage of component-scanning.
Note that in this case, we don’t need to explicitly declare
<context:annotation-config/>
, because <context:component-scan/>
enables all the same
functionality.
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>
In applications where @Configuration
classes are the primary mechanism for configuring
the container, it will still likely be necessary to use at least some XML. In these
scenarios, simply use @ImportResource
and define only as much XML as is needed. Doing
so achieves a "Java-centric" approach to configuring the container and keeps XML to a
bare minimum.
@Configuration @ImportResource("classpath:/com/acme/properties-config.xml") public class AppConfig { @Value("${jdbc.url}") private String url; @Value("${jdbc.username}") private String username; @Value("${jdbc.password}") private String password; @Bean public 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); // ... }
Environment
是集成在容器中的抽象,他包含了两个两个方面:profiles
和 properties.
profile是一个命名,是一组逻辑上bean定义的组,只有相应的profile被激活的情况下才会起作用。可以通过XML或者注解将bean分配给一个profile,Environment
对象在profile中的角色是判断哪一个profile应该在当前激活和哪一个profile应该在默认情况下激活。
属性在几乎所有应用中都扮演了非常重要的角色,并且可能来源于各种各样的资源:属性文件,JVM系统属性,系统环境变量,JNDI,servlet上下文参数,点对点的属性对象,映射等等。Environment
对象在属性中的角色是提供一个方便的服务接口来配置属性资源和解决它们的属性。
Beand定义配置文件是核心容器的一种机制,它允许为不同的bean在不同的环境中注册。 environment这个词可以意味着不同的事情不同的用户,而且这个功能可以帮助很多用例,包括:
我们考虑第一种情况,在一个需要DataSource
的实际应用中。在测试环境时,配置可能像下面这样:
@Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("my-schema.sql") .addScript("my-test-data.sql") .build(); }
现在我们考虑如何将这个应用部署到QA或者生产环境,假设应用程序的数据源将在生产应用服务器的JNDI目录中注册,我们的dataSource
bean现在看起来像这样:
@Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); }
现在的问题是如何基于当前环境在这两个变体之间切换。随着时间的推移,Spring用户设计了多种方法来完成这件事。通常依靠系统环境变量的组合和XML`<import/>语句包含
${placeholder}`标记来解决正确的配置文件路径。Bean定义配置文件是一个核心容器的功能,它提供了解决这个问题的方法。
如果我们归纳上面这个基于环境bean定义的例子,我们需要在某些情况下注册某些bean,而不是其他的。你可能想在A情况下注册一个bean定义的配置,在B情况下注册另一个配置。我们先看看如何更新配置来反映这一需求。
@Profile
注解用于当一个或多个配置文件激活的时候,用来指定组件是否有资格注册。使用上面的例子,我们可以按如下方式重写dataSource配置:
@Configuration @Profile("dev") public class StandaloneDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } }
@Configuration @Profile("production") public class JndiDataConfig { @Bean public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
@Profile
可以用作元注解,可以用来编写自定义的注解。下面的例子定义了一个自定义的@Production
注解,可以用来替代@Profile("production")
:
@Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Profile("production") public @interface Production { }
@Profile
可以用在方法级别,可以只包含配置类中的一个特殊的bean:
@Configuration public class AppConfig { @Bean @Profile("dev") public DataSource devDataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .addScript("classpath:com/bank/config/sql/test-data.sql") .build(); } @Bean @Profile("production") public DataSource productionDataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } }
[提示]
如果带@Configuration
的类被标记了@Profile
,那么只有当这个配置是激活状态的时候,这个类中标记@Bean
的方法和@Import
关联的类才有效,否则就会被忽略。
如果一个@Component
或@Configuration
类标记了@Profile({"p1", "p2"})
,这样的类只有当p1和(或)p2激活的时候才有效。如果一个配置使用了!
前缀,只有当这个配置不激活的时候才有效。例如@Profile({"p1", "!p2"})
,只有当p1激活,p2不激活的时候才有效。
XML对应的配置元素<beans>
新增了profile
属性,我们上面的配置示例可以重写为下面的两个XML文件:
<beans profile="dev" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xsi:schemaLocation="..."> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans>
<beans profile="production" xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans>
还可以避免分开写,将<beans/>
元素都写到同一个文件中:
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:jdbc="http://www.springframework.org/schema/jdbc" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="..."> <!-- 其他 bean 定义,beans只能写在最后面 --> <beans profile="dev"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/> </jdbc:embedded-database> </beans> <beans profile="production"> <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/> </beans> </beans>
spring-bean.xsd
强制允许将<beans>
元素定义在文件的最后面,这有助于在XML文件中提供灵活的方式而又不引起混乱。
现在我们已经更新了我们的配置,我们还需要指示那个配置文件处于激活状态。如果我们现在启动我们的示例程序,我们会看到抛出NoSuchBeanDefinitionException
异常,因为我们的容器找不到名为dataSource
的bean对象。
激活配置文件可以采取多种方式,但是最直接的方式就是以编程的方式使用ApplicationContext
API:
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(); ctx.getEnvironment().setActiveProfiles("dev"); ctx.register(SomeConfig.class, StandaloneDataConfig.class, JndiDataConfig.class); ctx.refresh();
此外,配置文件还可以以声明的方式通过spring.profiles.active
属性来激活,可以通过系统环境变量,JVM系统属性,web.xml
中的servlet上下文参数,甚至是JNDI中的一个条目来设置(Section 5.13.3, “PropertySource的抽象”)。
注意,配置文件不是“二选一”的;你可以一次激活多个配置文件。以编程方式,只需要在setActiveProfiles()
方法提供多个配置文件的名字即可,这里接收的String...
可变参数:
ctx.getEnvironment().setActiveProfiles("profile1", "profile2");
声明形式中,spring.profiles.active
可以接收逗号隔开的配置名字列表:
-Dspring.profiles.active="profile1,profile2"
default配置文件代表的是默认启用的配置文件。考虑以下:
@Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); } }
如果没有配置文件激活,上面的dataSource
就会被创建。这提供了一种默认的方式。如果有任何一个配置文件启用,default配置就不会生效。
默认配置文件的名字(default
)可以通过Environment
的setDefaultProfiles
方法或者spring.profiles.default
属性修改。
Spring的抽象环境提供了一个可配置的属性源的层次结构的搜索操作。为了充分说明,考虑下面的情况:
ApplicationContext ctx = new GenericApplicationContext(); Environment env = ctx.getEnvironment(); boolean containsFoo = env.containsProperty("foo"); System.out.println("Does my environment contain the 'foo' property? " + containsFoo);
在上面的代码片段中,我们看到了一种高层次的方式来询问Spring当前的环境中是否定义了foo
属性。
为了解答这个问题,Environment
对象执行了一组http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/env/PropertySource.html[PropertySource
]对象上的搜索。
PropertySource
是一个简单的抽象在任何源上的键值对,Spring的http://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/core/env/StandardEnvironment.html[StandardEnvironment
]
配置了两个属性源对象 — 一组是JVM系统属性(a la System.getProperties()
),另一组是系统环境变量(a la System.getenv()
)。
[注意]
These default property sources are present for StandardEnvironment
, for use in standalone
applications. StandardServletEnvironment
is populated with additional default property sources including servlet config and servlet
context parameters. StandardPortletEnvironment
similarly has access to portlet config and portlet context parameters as property sources.
Both can optionally enable a JndiPropertySource
.
See Javadoc for details.
Concretely, when using the StandardEnvironment
, the call to env.containsProperty("foo")
will return true if a foo
system property or foo
environment variable is present at
runtime.
Tip | |
---|---|
The search performed is hierarchical. By default, system properties have precedence over
environment variables, so if the |
Most importantly, the entire mechanism is configurable. Perhaps you have a custom source
of properties that you’d like to integrate into this search. No problem — simply implement
and instantiate your own PropertySource
and add it to the set of PropertySources
for the
current Environment
:
ConfigurableApplicationContext ctx = new GenericApplicationContext(); MutablePropertySources sources = ctx.getEnvironment().getPropertySources(); sources.addFirst(new MyPropertySource());
In the code above, MyPropertySource
has been added with highest precedence in the
search. If it contains a foo
property, it will be detected and returned ahead of
any foo
property in any other PropertySource
. The
MutablePropertySources
API exposes a number of methods that allow for precise manipulation of the set of
property sources.
The @PropertySource
annotation provides a convenient and declarative mechanism for adding a PropertySource
to Spring’s Environment
.
Given a file "app.properties" containing the key/value pair testbean.name=myTestBean
,
the following @Configuration
class uses @PropertySource
in such a way that
a call to testBean.getName()
will return "myTestBean".
@Configuration @PropertySource("classpath:/com/myco/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } }
Any ${...}
placeholders present in a @PropertySource
resource location will
be resolved against the set of property sources already registered against the
environment. For example:
@Configuration @PropertySource("classpath:/com/${my.placeholder:default/path}/app.properties") public class AppConfig { @Autowired Environment env; @Bean public TestBean testBean() { TestBean testBean = new TestBean(); testBean.setName(env.getProperty("testbean.name")); return testBean; } }
Assuming that "my.placeholder" is present in one of the property sources already
registered, e.g. system properties or environment variables, the placeholder will
be resolved to the corresponding value. If not, then "default/path" will be used
as a default. If no default is specified and a property cannot be resolved, an
IllegalArgumentException
will be thrown.
Historically, the value of placeholders in elements could be resolved only against JVM system properties or environment variables. No longer is this the case. Because the Environment abstraction is integrated throughout the container, it’s easy to route resolution of placeholders through it. This means that you may configure the resolution process in any way you like: change the precedence of searching through system properties and environment variables, or remove them entirely; add your own property sources to the mix as appropriate.
Concretely, the following statement works regardless of where the customer
property is defined, as long as it is available in the Environment
:
<beans> <import resource="com/bank/service/${customer}-config.xml"/> </beans>
Spring使用LoadTimeWeaver
动态转换已经加载到JVM虚拟机中的类。
你可以在@Configuration
类上增加@EnableLoadTimeWeaving
注解来启用该功能:
@Configuration @EnableLoadTimeWeaving public class AppConfig { }
或者在XML配置中使用context:load-time-weaver
元素:
<beans> <context:load-time-weaver/> </beans>
当ApplicationContext
启用该功能后,所有ApplicationContext
中的bean都可以实现LoadTimeWeaverAware
接口,从而获得对该加载时织入器实例的引用。
当结合Spring的JPA 支持的时候特别的有用,JPA类转换可能需要加载时的织入。查阅LocalContainerEntityManagerFactoryBean
帮助文档获得更多细节,更多AspectJ装载时织入,查看Section 9.8.4, “Load-time weaving with AspectJ in the Spring Framework”。
我们在本章的介绍中讨论过,org.springframework.beans.factory
包提供了管理和操作 bean 的基本功能,包括编程方式。org.springframework.context
包增加了 ApplicationContext
接口。它继承了 BeanFactory
接口,除了扩展其他接口外还提供了更为 application
framework-oriented style 的附加功能。很多人通过完全的声明方式来使用 ApplicationContext
,而非编程式地创建它,但是还可以依靠类似 ContextLoader
这样的的支持类来自动初始化 ApplicationContext
,并将它作为 Java EE web 应用程序的普通的启动过程的一部分。
在更为面向框架的风格上,为了增强 BeanFactory
的功能,context 包还提供了以下功能:
MessageSource
接口,访问 i18n 样式的信息。
ResourceLoader
接口,访问资源,比如 URL 和文件。
ApplicationListener
interface,
through the use of the ApplicationEventPublisher
interface.
HierarchicalBeanFactory
接口,加载多个(分层)上下文,这样使得每一个上下文可以关注特定的层,比如 web 层。
The ApplicationContext
interface extends an interface called MessageSource
, and
therefore provides internationalization (i18n) functionality. Spring also provides the
interface HierarchicalMessageSource
, which can resolve messages hierarchically.
Together these interfaces provide the foundation upon which Spring effects message
resolution. The methods defined on these interfaces include:
String getMessage(String code, Object[] args, String default, Locale loc)
: The basic
method used to retrieve a message from the MessageSource
. When no message is found
for the specified locale, the default message is used. Any arguments passed in become
replacement values, using the MessageFormat
functionality provided by the standard
library.
String getMessage(String code, Object[] args, Locale loc)
: Essentially the same as
the previous method, but with one difference: no default message can be specified; if
the message cannot be found, a NoSuchMessageException
is thrown.
String getMessage(MessageSourceResolvable resolvable, Locale locale)
: All properties
used in the preceding methods are also wrapped in a class named
MessageSourceResolvable
, which you can use with this method.
When an ApplicationContext
is loaded, it automatically searches for a MessageSource
bean defined in the context. The bean must have the name messageSource
. If such a bean
is found, all calls to the preceding methods are delegated to the message source. If no
message source is found, the ApplicationContext
attempts to find a parent containing a
bean with the same name. If it does, it uses that bean as the MessageSource
. If the
ApplicationContext
cannot find any source for messages, an empty
DelegatingMessageSource
is instantiated in order to be able to accept calls to the
methods defined above.
Spring provides two MessageSource
implementations, ResourceBundleMessageSource
and
StaticMessageSource
. Both implement HierarchicalMessageSource
in order to do nested
messaging. The StaticMessageSource
is rarely used but provides programmatic ways to
add messages to the source. The ResourceBundleMessageSource
is shown in the following
example:
<beans> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basenames"> <list> <value>format</value> <value>exceptions</value> <value>windows</value> </list> </property> </bean> </beans>
In the example it is assumed you have three resource bundles defined in your classpath
called format
, exceptions
and windows
. Any request to resolve a message will be
handled in the JDK standard way of resolving messages through ResourceBundles. For the
purposes of the example, assume the contents of two of the above resource bundle files
are…
# in format.properties message=Alligators rock!
# in exceptions.properties
argument.required=The {0} argument is required.
A program to execute the MessageSource
functionality is shown in the next example.
Remember that all ApplicationContext
implementations are also MessageSource
implementations and so can be cast to the MessageSource
interface.
public static void main(String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("message", null, "Default", null); System.out.println(message); }
The resulting output from the above program will be…
Alligators rock!
So to summarize, the MessageSource
is defined in a file called beans.xml
, which
exists at the root of your classpath. The messageSource
bean definition refers to a
number of resource bundles through its basenames
property. The three files that are
passed in the list to the basenames
property exist as files at the root of your
classpath and are called format.properties
, exceptions.properties
, and
windows.properties
respectively.
The next example shows arguments passed to the message lookup; these arguments will be converted into Strings and inserted into placeholders in the lookup message.
<beans> <!-- this MessageSource is being used in a web application --> <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource"> <property name="basename" value="exceptions"/> </bean> <!-- lets inject the above MessageSource into this POJO --> <bean id="example" class="com.foo.Example"> <property name="messages" ref="messageSource"/> </bean> </beans>
public class Example { private MessageSource messages; public void setMessages(MessageSource messages) { this.messages = messages; } public void execute() { String message = this.messages.getMessage("argument.required", new Object [] {"userDao"}, "Required", null); System.out.println(message); } }
The resulting output from the invocation of the execute()
method will be…
The userDao argument is required.
With regard to internationalization (i18n), Spring’s various MessageResource
implementations follow the same locale resolution and fallback rules as the standard JDK
ResourceBundle
. In short, and continuing with the example messageSource
defined
previously, if you want to resolve messages against the British (en-GB
) locale, you
would create files called format_en_GB.properties
, exceptions_en_GB.properties
, and
windows_en_GB.properties
respectively.
Typically, locale resolution is managed by the surrounding environment of the application. In this example, the locale against which (British) messages will be resolved is specified manually.
# in exceptions_en_GB.properties
argument.required=Ebagum lad, the {0} argument is required, I say, required.
public static void main(final String[] args) { MessageSource resources = new ClassPathXmlApplicationContext("beans.xml"); String message = resources.getMessage("argument.required", new Object [] {"userDao"}, "Required", Locale.UK); System.out.println(message); }
The resulting output from the running of the above program will be…
Ebagum lad, the userDao argument is required, I say, required.
You can also use the MessageSourceAware
interface to acquire a reference to any
MessageSource
that has been defined. Any bean that is defined in an
ApplicationContext
that implements the MessageSourceAware
interface is injected with
the application context’s MessageSource
when the bean is created and configured.
Note | |
---|---|
As an alternative to |
Event handling in the ApplicationContext
is provided through the ApplicationEvent
class and ApplicationListener
interface. If a bean that implements the
ApplicationListener
interface is deployed into the context, every time an
ApplicationEvent
gets published to the ApplicationContext
, that bean is notified.
Essentially, this is the standard Observer design pattern. Spring provides the
following standard events:
Table 5.7. Built-in Events
Event | Explanation |
---|---|
| Published when the |
| Published when the |
| Published when the |
| Published when the |
| A web-specific event telling all beans that an HTTP request has been serviced. This
event is published after the request is complete. This event is only applicable to
web applications using Spring’s |
You can also create and publish your own custom events. This example demonstrates a
simple class that extends Spring’s ApplicationEvent
base class:
public class BlackListEvent extends ApplicationEvent { private final String address; private final String test; public BlackListEvent(Object source, String address, String test) { super(source); this.address = address; this.test = test; } // accessor and other methods... }
To publish a custom ApplicationEvent
, call the publishEvent()
method on an
ApplicationEventPublisher
. Typically this is done by creating a class that implements
ApplicationEventPublisherAware
and registering it as a Spring bean. The following
example demonstrates such a class:
public class EmailService implements ApplicationEventPublisherAware { private List<String> blackList; private ApplicationEventPublisher publisher; public void setBlackList(List<String> blackList) { this.blackList = blackList; } public void setApplicationEventPublisher(ApplicationEventPublisher publisher) { this.publisher = publisher; } public void sendEmail(String address, String text) { if (blackList.contains(address)) { BlackListEvent event = new BlackListEvent(this, address, text); publisher.publishEvent(event); return; } // send email... } }
At configuration time, the Spring container will detect that EmailService
implements
ApplicationEventPublisherAware
and will automatically call
setApplicationEventPublisher()
. In reality, the parameter passed in will be the Spring
container itself; you’re simply interacting with the application context via its
ApplicationEventPublisher
interface.
To receive the custom ApplicationEvent
, create a class that implements
ApplicationListener
and register it as a Spring bean. The following example
demonstrates such a class:
public class BlackListNotifier implements ApplicationListener<BlackListEvent> { private String notificationAddress; public void setNotificationAddress(String notificationAddress) { this.notificationAddress = notificationAddress; } public void onApplicationEvent(BlackListEvent event) { // notify appropriate parties via notificationAddress... } }
Notice that ApplicationListener
is generically parameterized with the type of your
custom event, BlackListEvent
. This means that the onApplicationEvent()
method can
remain type-safe, avoiding any need for downcasting. You may register as many event
listeners as you wish, but note that by default event listeners receive events
synchronously. This means the publishEvent()
method blocks until all listeners have
finished processing the event. One advantage of this synchronous and single-threaded
approach is that when a listener receives an event, it operates inside the transaction
context of the publisher if a transaction context is available. If another strategy for
event publication becomes necessary, refer to the JavaDoc for Spring’s
ApplicationEventMulticaster
interface.
The following example shows the bean definitions used to register and configure each of the classes above:
<bean id="emailService" class="example.EmailService"> <property name="blackList"> <list> <value>known.spammer@example.org</value> <value>known.hacker@example.org</value> <value>john.doe@example.org</value> </list> </property> </bean> <bean id="blackListNotifier" class="example.BlackListNotifier"> <property name="notificationAddress" value="blacklist@example.org"/> </bean>
Putting it all together, when the sendEmail()
method of the emailService
bean is
called, if there are any emails that should be blacklisted, a custom event of type
BlackListEvent
is published. The blackListNotifier
bean is registered as an
ApplicationListener
and thus receives the BlackListEvent
, at which point it can
notify appropriate parties.
Note | |
---|---|
Spring’s eventing mechanism is designed for simple communication between Spring beans within the same application context. However, for more sophisticated enterprise integration needs, the separately-maintained Spring Integration project provides complete support for building lightweight, pattern-oriented, event-driven architectures that build upon the well-known Spring programming model. |
For optimal usage and understanding of application contexts, users should generally
familiarize themselves with Spring’s Resource
abstraction, as described in the chapter
Chapter 6, Resources.
An application context is a ResourceLoader
, which can be used to load Resource
s. A
Resource
is essentially a more feature rich version of the JDK class java.net.URL
,
in fact, the implementations of the Resource
wrap an instance of java.net.URL
where
appropriate. A Resource
can obtain low-level resources from almost any location in a
transparent fashion, including from the classpath, a filesystem location, anywhere
describable with a standard URL, and some other variations. If the resource location
string is a simple path without any special prefixes, where those resources come from is
specific and appropriate to the actual application context type.
You can configure a bean deployed into the application context to implement the special
callback interface, ResourceLoaderAware
, to be automatically called back at
initialization time with the application context itself passed in as the
ResourceLoader
. You can also expose properties of type Resource
, to be used to
access static resources; they will be injected into it like any other properties. You
can specify those Resource
properties as simple String paths, and rely on a special
JavaBean PropertyEditor
that is automatically registered by the context, to convert
those text strings to actual Resource
objects when the bean is deployed.
The location path or paths supplied to an ApplicationContext
constructor are actually
resource strings, and in simple form are treated appropriately to the specific context
implementation. ClassPathXmlApplicationContext
treats a simple location path as a
classpath location. You can also use location paths (resource strings) with special
prefixes to force loading of definitions from the classpath or a URL, regardless of the
actual context type.
你可以声明式地创建 ApplicationContext
实例,比如通过 ContextLoader
。当然你也可以编程式地创建 ApplicationContext
实例,只需要使用一个 ApplicationContext
实现。
你可以使用 ContextLoaderListener
来注册一个 ApplicationContext
,如下:
<context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/daoContext.xml /WEB-INF/applicationContext.xml</param-value> </context-param> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener>
该监听器会检查 contextConfigLocation
参数。如果该参数不存在,监听器会使用 /WEB-INF/applicationContext.xml
作为默认值。如果该参数存在,该监听器通过使用预置的分隔符(逗号,分号和空白符)来分隔字符串,并使用分隔后的值作为要查找的应用上下文的路径。同时支持 Ant 风格的路径模式。比如 /WEB-INF/*Context.xml
表示位于 "WEB-INF" 目录下且名称以 "Context.xml" 作为结尾的所有文件,/WEB-INF/**/*Context.xml
表示 "WEB-INF" 下的任意子目录下的所有这类文件。
It is possible to deploy a Spring ApplicationContext as a RAR file, encapsulating the context and all of its required bean classes and library JARs in a Java EE RAR deployment unit. This is the equivalent of bootstrapping a standalone ApplicationContext, just hosted in Java EE environment, being able to access the Java EE servers facilities. RAR deployment is more natural alternative to scenario of deploying a headless WAR file, in effect, a WAR file without any HTTP entry points that is used only for bootstrapping a Spring ApplicationContext in a Java EE environment.
RAR deployment is ideal for application contexts that do not need HTTP entry points but
rather consist only of message endpoints and scheduled jobs. Beans in such a context can
use application server resources such as the JTA transaction manager and JNDI-bound JDBC
DataSources and JMS ConnectionFactory instances, and may also register with the
platform’s JMX server - all through Spring’s standard transaction management and JNDI
and JMX support facilities. Application components can also interact with the
application server’s JCA WorkManager through Spring’s TaskExecutor
abstraction.
Check out the JavaDoc of the
SpringContextResourceAdapter
class for the configuration details involved in RAR deployment.
For a simple deployment of a Spring ApplicationContext as a Java EE RAR file: package
all application classes into a RAR file, which is a standard JAR file with a different
file extension. Add all required library JARs into the root of the RAR archive. Add a
"META-INF/ra.xml" deployment descriptor (as shown in SpringContextResourceAdapter
's
JavaDoc) and the corresponding Spring XML bean definition file(s) (typically
"META-INF/applicationContext.xml"), and drop the resulting RAR file into your
application server’s deployment directory.
Note | |
---|---|
Such RAR deployment units are usually self-contained; they do not expose components to the outside world, not even to other modules of the same application. Interaction with a RAR-based ApplicationContext usually occurs through JMS destinations that it shares with other modules. A RAR-based ApplicationContext may also, for example, schedule some jobs, reacting to new files in the file system (or the like). If it needs to allow synchronous access from the outside, it could for example export RMI endpoints, which of course may be used by other application modules on the same machine. |
The BeanFactory
provides the underlying basis for Spring’s IoC functionality but it is
only used directly in integration with other third-party frameworks and is now largely
historical in nature for most users of Spring. The BeanFactory
and related interfaces,
such as BeanFactoryAware
, InitializingBean
, DisposableBean
, are still present in
Spring for the purposes of backward compatibility with the large number of third-party
frameworks that integrate with Spring. Often third-party components that can not use
more modern equivalents such as @PostConstruct
or @PreDestroy
in order to remain
compatible with JDK 1.4 or to avoid a dependency on JSR-250.
This section provides additional background into the differences between the
BeanFactory
and ApplicationContext
and how one might access the IoC container
directly through a classic singleton lookup.
Use an ApplicationContext
unless you have a good reason for not doing so.
Because the ApplicationContext
includes all functionality of the BeanFactory
, it is
generally recommended over the BeanFactory
, except for a few situations such as in an
Applet
where memory consumption might be critical and a few extra kilobytes might make
a difference. However, for most typical enterprise applications and systems, the
ApplicationContext
is what you will want to use. Spring makes heavy
use of the BeanPostProcessor
extension point (to
effect proxying and so on). If you use only a plain BeanFactory
, a fair amount of
support such as transactions and AOP will not take effect, at least not without some
extra steps on your part. This situation could be confusing because nothing is actually
wrong with the configuration.
The following table lists features provided by the BeanFactory
and
ApplicationContext
interfaces and implementations.
Table 5.8. Feature Matrix
Feature | BeanFactory | ApplicationContext |
---|---|---|
Bean instantiation/wiring | Yes | Yes |
Automatic | No | Yes |
Automatic | No | Yes |
Convenient | No | Yes |
| No | Yes |
To explicitly register a bean post-processor with a BeanFactory
implementation, you
must write code like this:
ConfigurableBeanFactory factory = new XmlBeanFactory(...); // now register any needed BeanPostProcessor instances MyBeanPostProcessor postProcessor = new MyBeanPostProcessor(); factory.addBeanPostProcessor(postProcessor); // now start using the factory
To explicitly register a BeanFactoryPostProcessor
when using a BeanFactory
implementation, you must write code like this:
XmlBeanFactory factory = new XmlBeanFactory(new FileSystemResource("beans.xml")); // bring in some property values from a Properties file PropertyPlaceholderConfigurer cfg = new PropertyPlaceholderConfigurer(); cfg.setLocation(new FileSystemResource("jdbc.properties")); // now actually do the replacement cfg.postProcessBeanFactory(factory);
In both cases, the explicit registration step is inconvenient, which is one reason why
the various ApplicationContext
implementations are preferred above plain BeanFactory
implementations in the vast majority of Spring-backed applications, especially when
using BeanFactoryPostProcessors
and BeanPostProcessors
. These mechanisms implement
important functionality such as property placeholder replacement and AOP.
It is best to write most application code in a dependency-injection (DI) style, where
that code is served out of a Spring IoC container, has its own dependencies supplied by
the container when it is created, and is completely unaware of the container. However,
for the small glue layers of code that are sometimes needed to tie other code together,
you sometimes need a singleton (or quasi-singleton) style access to a Spring IoC
container. For example, third-party code may try to construct new objects directly (
Class.forName()
style), without the ability to get these objects out of a Spring IoC
container.If the object constructed by the third-party code is a small stub or proxy,
which then uses a singleton style access to a Spring IoC container to get a real object
to delegate to, then inversion of control has still been achieved for the majority of
the code (the object coming out of the container). Thus most code is still unaware of
the container or how it is accessed, and remains decoupled from other code, with all
ensuing benefits. EJBs may also use this stub/proxy approach to delegate to a plain Java
implementation object, retrieved from a Spring IoC container. While the Spring IoC
container itself ideally does not have to be a singleton, it may be unrealistic in terms
of memory usage or initialization times (when using beans in the Spring IoC container
such as a Hibernate SessionFactory
) for each bean to use its own, non-singleton Spring
IoC container.
Looking up the application context in a service locator style is sometimes the only
option for accessing shared Spring-managed components, such as in an EJB 2.1
environment, or when you want to share a single ApplicationContext as a parent to
WebApplicationContexts across WAR files. In this case you should look into using the
utility class
ContextSingletonBeanFactoryLocator
locator that is described in this
Spring
team blog entry.