1.1 spring IoC容器和beans的简介
Spring 框架的最核心基础的功能是IoC(控制反转)容器,IoC也叫依赖注入(DI).不使用依赖注入时如果一个bean依赖其他bean处理过程是,通过构造函数或工厂方法的参数传入,或者生成对象(构造函数或对象工厂)后通过set方法设置.生成一个bean时,这个bean依赖的其他bean通过容器来注入,,这样依赖的处理就不是由bean来处理,而是容器,这样处理的过程就反转了(inverse),因此叫控制反转(IoC).
org.springframework.beans
和org.springframework.context
两个包是实现spring IoC功能最基础的两个包.BeanFactory
接口提供一种能够管理任何类型对象的高级配置机制.ApplicationContext
是BeanFactory
的子接口.ApplicationContext
增加与Spring AOP功能的集成;消息处理,事件发布;以及特定应用层相关的Context比如用于web应用中的WebApplicationContext
简单来说,BeanFactory
接口提供了配置框架和基本功能,ApplicationContext
增加了更多针对企业应用的功能.bean
的定义:在spring中,构成我们的应用程序且被spring IoC容器管理的对象(object)就叫做bean
.bean是一个实例化,组装并由Spring IoC容器管理的对象.bean只是应用程序中众多对象中的一个。 Bean和它们之间的依赖关系反映在容器使用的配置元数据中。
1.2容器概述
org.springframework.context.ApplicationContext
接口代表了Spring IoC 容器,负责实例化,配置,组装前面所说的beans.IoC容器通过解析配置元数据获取有关要实例化,配置和组装的对象的信息.配置元数据包 含在XML文件,java注解,或者java 代码.这些元数据能够表示组成我们应用程序的对象以及这些对象之间丰富的依赖关系.
Spring提供了ApplicationContext
接口的几个实现.在独立的应用程序中通常会创建一个
ClassPathXmlApplicationContext
实例或FileSystemXmlApplicationContext
实例.然而XML文件是传统配置元数据的方式,我们可以通过在XML文件中通过少许的配置,使得容器支持java 注解,java代码方式来配置元数据.
在大多数应用场景中,用户不需要显式的实例化一个或者多个Spring IoC容器.比如在,在一个web应用中,在web.xml文件只需8行配置就可以了.
下图从更抽象的角度来说明了Spring 是如何工作的.ApplicationContext
实例化后我们应用程序里的classes与配置元数据就组合在一起,我们就得到一个配置完整且可执行的系统或应用程序.
1.2.1配置元数据
配置元数据代表了应用开发者如何告诉spring 容器实例化,配置,组装应用里的对象.配置元数据可以通过传统的XML,也可以通过基于java注解(spring2.5开始),或基于java代码(spring3.0开始).
bean对应的实际对象组成了我们的应用程序.我们通常会定义业务逻辑层,dao,持久化对象等,但是不会定义更细粒度领域对象在容器中,因为定义加载领域对象是DAO和业务逻辑层的职责.然而,你可以使用Spring集成的 AspectJ去配置一个已经生成的但不受Spring IoC容器控制的对象.
1.2.2容器初始化
初始化一个容器很简单.只要给ApplicationContext
的构造函数传入元数据配置文件的路径即可.元数据配置文件可以是系统本地文件,也可以是java classpath的文件,还可以是其他的.
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
1.2.3使用容器
ApplicationContext
接口是一个能够管理注册在其上面的beans和这些beans依赖关系的高级factory.使用方法T getBean(String name, Class<T> requiredType)
就可以获取你想要的bean的实例.ApplicationContext
允许你通过下面的方式去获取bean和使用:
// create and configure beans
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
// retrieve configured instance
PetStoreService service = context.getBean("petStore", PetStoreService.class);
// use configured instance
List<String> userList = service.getUsernameList();
最灵活的使用的方式是:GenericApplicationContext
结合reader 代理. 比如结合XmlBeanDefinitionReader
读取xml:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
你可以使用getBean
去获取bean实例.ApplicationContext
还提供了其获取bean实例的方法,理想情况下你的应用程序就不该使用它们.事实上,你的应用程序代码根本不应该调用该 getBean()
方法,因此完全不依赖于Spring API.
1.3 Bean概述
一个Spring IoC容器管理一个或者多个Beans.这些bean通过你提供给容器的配置元数据(configuration metadata )创建.比如,以XML <bean/>
定义的形式.
在容器本身中,这些bean定义使用BeanDefinition
对象来表示,其中主要包含(还有其他信息)以下信息:
- 包限定类名称:通常是所定义的bean的实际实现类
- Bean的行为状态 ,说明它在容器的行为状态(作用域,生命周期回调等等)
- Bean的依赖关系
-
在新创建的对象中设置的其他配置设置,例如,用于管理连接池的Bean的连接数量或池的大小限制。
这些信息转换成类属性就就组成了bean定义.
下图是bean定义
1.3.1命名Beans
每个bean有一个或多个标识.这些标识在容器中必须是唯一的.一个bean通常只有一个标识,如果需要更多的标识,那么其他的标识就可以当作别名.
在xml配置文件中,可以使用id和name属性标识一个bean.id属性只能设置一个.如果想要给bean取其他别名,可以通过name属性,名字之间可以通过逗号,分号, 空格进行分割.
name 和id不是必须提供的.如果没有提供name或者id,容器会生成一个唯一标识给这个bean.如果想通过name来引用一个bean,你必须提供一个name.如果不提供name,可以通过使用inner bean和autowiring collaborators
别名
略
1.3.2实例化bean
bean定义本质上是创建一个或多个对象的配方.当请求获取bean时,容器就会查找bean的配方,然后用bean定义封装的元数据去创建一个实际对象.
如果你使用基于XML的元数据配置,你需要在<bean/>
中指定实例化对象对应的class
.class
属性是必须的,它是BeanDefinition
实例的一个类属性.class属性可以通过以下两种方式之一来使用:
- 容器通过反射的方式调用其构造函数创建bean,这与java代码使用
new
创建一个对象是一样的. - 通过指定创建class对象的静态工厂方法,容器调用这个类的静态工厂方法来创建bean.通过静态工厂返回的对象可能是同一个类也可能不是.
通过构造函数实例化
当通过构造函数创建一个bean的时候,所有普通的类都可以被Spring使用和兼容.这样正在开发的类不需要实现特定的接口或者使用特殊的方式编写.只需要指定bean对应的class即可.根据bean使用的IoC类型,需要提供一个默认的无参构造函数.
Spring IoC容器可以管理任何你想让容器管理的对象.不仅仅限于管理真正JavaBeans
.大多数Spring用户,仅使用只有默认构造函数(无参)和setter,getter属性方法的JavaBeans
.你可以把更多非JavaBeans
类型的类放到容器中去.
通过静态工厂方法实例化
当你定义一个使用静态工厂方法来创建的bean时,你在<bean/>
的class
属性指定这个带静态工厂方法的类,同时在factory-method
指定这个类的静态工厂方法名字.您应该能够调用此方法(使用后面介绍的可选参数)并返回一个活动对象,这个对象就像使用通过构造函数创建的的一样.这种bean实例化方法可以用于那些使用静态工厂创建对象的老代码中.
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
public class ClientService {
private static ClientService clientService = new ClientService();
private ClientService() {}
public static ClientService createInstance() {
return clientService;
}
}
通过'实例工厂方法'实例化
与通过静态工厂方法实例化相似,通过'实例工厂方法'实例化调用一个已存在bean的非静态方法创建一个新的bean.使用这种方式,class
属性需要留空,factory-bean
属性设置当前容器已存在的bean,factory-method
设置为factory-bean
中用来创建对象的方法.
<!-- the factory bean, which contains a method called createInstance() -->
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<!-- the bean to be created via the factory bean -->
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
}
一个工厂类可以包含多个工厂方法,如下所示:
<bean id="serviceLocator" class="examples.DefaultServiceLocator">
<!-- inject any dependencies required by this locator bean -->
</bean>
<bean id="clientService"
factory-bean="serviceLocator"
factory-method="createClientServiceInstance"/>
<bean id="accountService"
factory-bean="serviceLocator"
factory-method="createAccountServiceInstance"/>
public class DefaultServiceLocator {
private static ClientService clientService = new ClientServiceImpl();
private static AccountService accountService = new AccountServiceImpl();
public ClientService createClientServiceInstance() {
return clientService;
}
public AccountService createAccountServiceInstance() {
return accountService;
}
}
这种方法表明,factory bean本身可以通过依赖注入(DI)进行管理和配置.在Spring文档中,factory bean指的是在Spring容器中配置的bean,它将通过实例或 静态工厂方法创建对象 。相反,FactoryBean
(注意大写字母)是指特定于Spring的 FactoryBean
。
1.4依赖
一个典型的企业应用不会只有单个对象(或者spring所说的bean).即使最简单的应用程序也会有几个对象一起工作,给终端用户的是一个完整应用.下一节将介绍如何从定义许多独立的bean定义到这些bean对象相互协作从而实现一个完整的应用程序.
1.4.1依赖注入
依赖注入(DI)是对象获取它依赖的过程.也就说,其他和它一起工作的对象通过构造函数参数,工厂方法参数,工厂方法实例化或构造函数实例后设置属性这些方式注入.容器创建bean的时候会注入它的依赖,这个过程跟前面对象获取它依赖的方式相反,因此也叫控制反转(IoC),bean它本身控制依赖的实例或定位是通过这些类的直接构造或服务定位器模式.依赖注入(DI)与控制反转(IoC)表达的都是对象获取它依赖的方法.通过注入的方式获取依赖叫做依赖注入,通过注入的方式获取依赖与通常获取依赖的方式相反,所以叫控制反转.
通过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定义中定义的构造函数参数的顺序就是在实例化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时,类型是知道的,解析时候可以匹配上(像前面的例子一样).当使用基本数据类型时,比如true
,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>
构造函数参数名称
你还可以使用构造函数参数名称去标识value
以消除歧义.比如
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
基于Setter的依赖注入
基于Setter的注入实现方式是:容器通过调用无参的构造函数或者静态工厂方法实例化bean后,然后调用bean的setter方法把依赖注入进来.
下面的例子展示了一个类用纯setter方法实现依赖注入.这个类是传统的java类,不依赖容器任何接口,基类或者注解的POJO.
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on the MovieFinder
private MovieFinder movieFinder;
// a setter method so that the Spring container can inject a MovieFinder
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// business logic that actually uses the injected MovieFinder is omitted...
}
ApplicationContext
管理的bean支持基于构造函数的方式注入和基于setter方法的注入.它同样支持基于构造函数注入部分bean之后,再使用setter依赖注入其他依赖.你可以配置依赖通过BeanDefinition
的形式.然而,大多数Spring的用户都不会直接去使用这些类而是通过xml bean定义,组件注解(比如@Component
,@Controller
),使用@Configuration
注解的java类里的@Bean
方法.然后将这些源内部转换为实例BeanDefinition
并用于加载整个Spring IoC容器实例。
基于构造函数注入还是setter方法注入?
你可以混合使用构造函数注入和setter方法注入,一条比较好经验法则是:必须的依赖通过构造函数注入,可选的依赖通过setter方法注入.请注意,可以使用 setter方法上的@Required注释来使该属性成为必需的依赖项.
Spring团队主张使用构造函数的方式注入,因为它使得人们可以将应用程序组件实现为不可变对象,并确保所需的依赖关系不是null.此外通过构造函数注入的组件,它返回给调用方的是一个已经初始化好的组件.从另一个角度来说,包含大量参数的构造函数有一种烂代码的味道,说明了这个类承担了太多的责任应该拆分开来.
Setter注入主要只应用于可选的依赖关系,这些依赖关系可以在类中分配合理的默认值。否则,必须在代码使用依赖关系的任何地方执行非空检查。setter注入的一个好处是setter方法使得该类的对象可以重新配置或稍后重新注入.
依赖的处理过程
容器解决依赖的过程:
- 1
ApplicationContext
通过配置元数据去创建和初始化,配置元数据描述了所有的bean.配置元数据可以是XML,java 代码,注解. - 对于每个bean,它的依赖关系以属性,构造函数参数或静态工厂方法(如果不是用正常的构造函数)的参数的形式表示.当实际创建bean的时候,它的依赖被提供.
- 每个属性或者构造函数参数被设置一个实际值或引用bean容器里其他bean
- 每个属性的值或者构造函数参数的值根据描述信息转化成属性或者构造函数参数实际的类型.默认情况下,Spring可以把一个字符串格式的值转换成所有的内置的类型,如int,long,String,boolen等.
Spring容器在容器创建时验证每个bean的配置.但是,在实际创建 bean之前,bean属性本身不会被设置.当容器创建的时候单例的bean被预先实例化(默认情况下).作用域定义在 Bean 作用域.否则,只有在请求时才创建bean.创建一个bean可能会导致一系列的bean被创建,因为bean的依赖关系及其依赖关系的依赖关系(等等)被创建和分配.注意到依赖不匹配的处理晚点会说明.
循环依赖
如果你主要使用构造函数方式注入,你可能会碰到一个无法解析的循环依赖场景.
比如:类A需要类B的实例,通过构造函数注入.类B需要类A的实例,也是通过构造函数注入.如果将类A和B的Bean配置为相互注入,则Spring IoC容器会在运行时检测到此循环引用,并引发一个 BeanCurrentlyInCreationException
解决的办法就是修改代码,配置成setter方法注入而不是通过构造函数注入.或者避免构造函数注入而仅使用setter方法注入.换句话说,虽然不推荐.但是你可以通过setter方法注入配置一个循环依赖.
与典型情况(没有循环依赖)不同,bean A和bean B之间的循环依赖关系迫使其中一个bean在被完全初始化之前注入另一个bean(经典的鸡/鸡蛋场景)
你可以相信Spring能够正确的处理事情.它在容器加载的时候会检测配置问题,比如引用不存在的bean,循环依赖.Spring创建bean的时候,尽可能迟的设置属性值和解决依赖关系.这意味着,一个正确加载的spring容器在请求一个对象时也会产生异常,如果创建这个对象或它的依赖时出现问题.比如,当属性值缺失或非法,bean会抛出异常.某些配置问题被发现会延迟是因为ApplicationContext
的默认实现会预先实例化单例bean.在实际需要这些bean之前,为了创建这些bean需要一定的时间和内存,您会在ApplicationContext
创建时发现配置问题,而不是之后.你可以覆盖默认的实现,这样单例的bean会延迟实例化不是预先实例化
如果不存在循环依赖,一个或多个协作bean被注入到一个依赖bean之前都是完全配置.这意味着,假如有个Bean A它依赖Bean B,容器调用Bean A的setter方法之前已经完全配置好Bean B.换句话说,Bean被初始化,设置它的依赖,相关的生命周期方法会被调用.
依赖注入例子
下面是基于setter方法的依赖注入例子
<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;
}
}
前面例子是setters方法被声明为与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定义里描述的constructor-arg
会被当作ExampleBean
构造函数的参数.
现在思考一下这个例子的一个变体,替换使用构造函数,使用静态工厂方法返回一个实例对象.
<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属性之外),因此在此不讨论细节
1.4.2依赖和配置详解
如前一节所述,你可以定义bean属性和构造函数参数去引用其他beans(协作类),或者内联的值.Spring基于XML的元数据配置提供了<property/>
和<constructor-arg/>
去实现这个目的.
直接值(数字,字符串等)
用来描述类属性或构造函数参数的<property/>
里的value
用字符串来表示.Spring的 conversion service会把字符串转换成类属性或者构造函数参数的实际类型.
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<!-- results in a setDriverClassName(String) call -->
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mydb"/>
<property name="username" value="root"/>
<property name="password" value="masterkaoli"/>
</bean>
下面的例子使用p-namespace进行更简洁的配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close"
p:driverClassName="com.mysql.jdbc.Driver"
p:url="jdbc:mysql://localhost:3306/mydb"
p:username="root"
p:password="masterkaoli"/>
</beans>
前面的XML更加简洁;然而类型检查最好是运行时而不是设计时,除非你使用IDE,比如IntelliJ IDEA 或者Spring Tool Suite (STS),能够在bean定义的时候自动属性补全.推荐使用这样的IDEA.
你可以配置一个java.util.Properties
实例,如下:
<bean id="mappings"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring 容器使用JavaBean的PropertyEditor
机制把value
里面的text转换成java.util.Properties
实例.这是一个捷径,它是Spring团队赞成<value/>
在value属性样式上使用嵌套元素的少数几个地方之一
idref 元素
idref是一个简单的防错方式,将容器中另一个bean的id传给<constructor-arg/>
or<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,这个错误和异常只有在容器发布后才发现.
其中一个共同的地方(至少在早期比Spring 2.0版本)<idref/>
元素的值在ProxyFactoryBean
bean定义的AOP拦截器中配置.指定拦截器名称使用<idref/>
元素可以防止拼写错误一个拦截器的id.
引用其他beans(协作类)
略
内部beans
一个<bean/>
元素在<property/>
or<property/>
里面因此叫内部bean
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
内部bean不需要定义一个id或者name;假如有,容器也不使用这个值作为bean的标识.容器创建内部bean时同样忽略它的作用域(scope)属性:内部beans总是匿名的且它永远是由外部的bean创建.不可能把一个内部bean注入给一个协作bean或者独立访问他们.
集合
在<list/>,<set/>,<map/>,和<props/>元素中,你可以设置属性或参数为java的集合里List, Set, Map, and Properties
map的key或value,set的value,可以是以下任何元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
spring容器同样支持集合合并.开发者可以定义一个父级的<list/>, <map/>, <set/> or <props/>元素和定义子级的<list/>, <map/>, <set/> or <props/>元素去继承和覆盖父级集合的值.也就是说,子集合的值就是合并父结合的值和子集合的值.
这部分关于合并讨论了父子bean机制。不熟悉父代和子代bean定义的读者可能希望在继续之前阅读 相关章节
下面就是集合合并的例子
<beans>
<bean id="parent" abstract="true" class="example.ComplexObject">
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.com</prop>
<prop key="support">support@example.com</prop>
</props>
</property>
</bean>
<bean id="child" parent="parent">
<property name="adminEmails">
<!-- the merge is specified on the child collection definition -->
<props merge="true">
<prop key="sales">sales@example.com</prop>
<prop key="support">support@example.co.uk</prop>
</props>
</property>
</bean>
<beans>
注意在child bean的定义中,adminEmails属性中的<props/>
元素使用了merge=true
当child bean被容器解析并实例化时,生成的实例具有一个adminEmails Properties集合,该集合包含合并子集合 adminEmails与父adminEmails集合的结果.
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
childProperties的值继承了parent所有property元素的值,并且child的support属性值覆盖了父类的support属性值
这个合并行为同样适用于 <list/>, <map/>, and <set/>集合.在<list/>元素中,与List相关的语义(即ordered 值集合的概念)被保留.父类值的顺序全部排在子类值的前面.Map,Set,Properties没有顺序存在的.因此没有顺序语义关联到这些集合.
集合合并的限制
你不能合并不同的集合类型(比如一个Map和一个List),如果你试图这样做,就有Exception抛出.合并只能在更低的继承的子类中.在parent类的集合中定义合并是多余的,达不到你想要的合并
强类型集合
随着Java 5中引入泛型类型,您可以使用强类型集合.比如,你就可以定义一个只包含String类型的集合.如果您使用Spring将强类型集合依赖注入到bean中,你可以充分利用Spring的类型转换,这样强类型集合实例中的元素在添加到集合之前可以转换成合适的类型.
public class Foo {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="foo" class="x.y.Foo">
<property name="accounts">
<map>
<entry key="one" value="9.99"/>
<entry key="two" value="2.75"/>
<entry key="six" value="3.99"/>
</map>
</property>
</bean>
</beans>
当foo bean 的accounts属性准备注入时,有关强类型元素类型的泛型信息Map<String, Float>可通过反射获得.这样Spring的类型转换机制就会识别各个元素值的类型为Float,字符串值的 9.99, 2.75, 3.99 就会转换为实际的Float类型.
Null 和 空字符串值
Spring把properties的空值当作空字符串.下面基于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);
XML使用p-namespace快捷方式
略
XML使用c-namespace快捷方式
略
符合属性名称
设置bean属性的时候你可以使用复合的或者嵌套的属性名称,只要这个路径除了最终属性名称外的组件不为空.思考下面的bean定义:
<bean id="foo" class="foo.Bar">
<property name="fred.bob.sammy" value="123" />
</bean>
这个foo bean有个一个fred属性,fred属性有一个bob属性,bob属性有个sammy属性,最终sammy属性值被设置为123.foo bean创建完之后,foo的fred属性,fred的bob属性都不能为空,否则就会抛出NullPointerException.
1.4.3使用depends-on
如果一个bean是另一个bean的依赖,那通常意味着这个bean被设置为另一个bean的属性.通常,在基于XML的元数据配置中你可以使用<ref/>
元素实现.然而,有时候beans之间依赖并不是那么直接;比如,一个类中的一个静态初始化器需要被触发,比如数据库驱动注册.depends-on
属性明确要求一个或多个beans被初始化在使用这个元素的bean初始化之前.下面的示例使用depends-on
属性来表示对单个bean的依赖关系:
<bean id="beanOne" class="ExampleBean" depends-on="manager"/>
<bean id="manager" class="ManagerBean" />
要表示对多个bean的依赖关系,请提供一个bean名称列表作为depends-on属性的值,并使用逗号,空格和分号作为有效分隔符:
<bean id="beanOne" class="ExampleBean" depends-on="manager,accountDao">
<property name="manager" ref="manager" />
</bean>
<bean id="manager" class="ManagerBean" />
<bean id="accountDao" class="x.y.jdbc.JdbcAccountDao" />
1.4.4延迟初始化(lazy-init)bean
默认情况下,ApplicationContext
的实现会把单例bean的创建和配置当作它初始化过程的一部分.通常,预初始化是可取的,因为配置或环境的错误会被立即被发现,而不是几小时或几天后.当这种行为是不可取的时候,你可以阻止单例bean的预初始化通过标识这个bean定义是延迟初始化的.一个延迟初始化bean告诉IoC容器当第一次请求这个bean实例的时候才创建这个bean,而不是启动的时候.
在XML中,这个行为的控制通过<bean/>
元素的lazy-init
属性.比如:
<bean id="lazy" class="com.foo.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.foo.AnotherBean"/>
当前面的配置被ApplicationContext
处理的时候,lazy bean不会被马上预初始化在ApplicationContext
启动的时候,然而not.lazy bean会被马上预初始化.
然而,当一个延迟初始化bean是一个非延迟初始化单例bean的依赖时,ApplicationContext
会创建延迟初始化bean的实例在启动的时候,因为它必须满足单例bean的依赖.延迟初始化bean被注入单例bean中而不是延迟初始化的.
你还可以控制容器级别的延迟初始化,通过使用<beans/>
的default-lazy-init
属性.比如:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5自动注入协作类
Spring容器可以自动装配协作类之间的关系.你可以让Spring去自动解决你的bean的协作类通过检查ApplicationContext
的内容.自动装配有以下好处:
- 自动装配可以显著的减少所要描述的属性or构造函数参数
*自动装配可以更新配置随着对象的演变.
当使用基于XML的元数据配置的时候,你可以通过<bean/>
的autowire
属性设置成自动装配模式.
自动装配功能有四种模式.你可以设置每个bean的自动装配模式以及自动装配哪一个.
Table 2. 自动装配模式
模式 | 说明 |
---|---|
no | (默认)无自动装配.Bean的引用必须通过定义一个ref元素,大型的应用开发不建议修改默认配置,因为明确的指定协作者提供了更好的控制和清晰度.一定程度上它记录了系统的结构 |
byName (按属性名称) | 自动装配通过属性名称.Spring查找与需要注入的属性名称相同的bean .比如,如果一个bean定义设置成自动装配通过名称,并且它有个master属性(因此,它有一个setMaster()方法),Spring查找一个名称为master的bean且把它设置给这个属性. |
byType (按属性类型) | 允许属性自动装配如果容器中存在这个属性类型的bean.如果这个类型的bean超过一个,就会抛出错误异常,告诉你不可以使用byType 模式给这个bean自动装配.如果没有匹配的bean,什么事也不会发生,这个属性就没有设置 |
constructor | 与 byType相似,不过这是提供给构造函数参数的.如果容器中没有一个与构造函数参数类型一样的bean,一个严重的错误就会抛出. |
使用按属性类型或者构造函数自动装配模式,你可以使用数组和集合类型.在这种情况下,容器中的所有符合期望类型的自动装配候选都被提供来满足依赖关系.你可以自动装配一个强类型的Maps如果期望的key类型是一个String.一个自动装配Maps的值将会包含所有满足预期类型的bean实例并且Maps keys与bean的名称一致.
你可以将自动装配行为与依赖检查结合起来,依赖检查在自动装配完成后执行.
自动装配的局限和缺点
自动装配在一个项目中统一使用是最好的.如果通常不使用自动装配,开发人员可能会使用它来仅连接一个或两个bean定义。
思考一下自动装配的局限和缺点:
- 在属性和构造函数参数中设置显式依赖总是包含自动装配的功能.你不能自动装配所谓的简单属性比如数值,字符串,类.这个限制是通过设计的
- 自动装配没有显式设置精确.尽管像前面表格提到的,Spring小心避免在可能会有意想不到的结果的情况下进行猜测,Spring管理的对象之间的关系不像文档那样精确
- Spring容器提供的布线信息工具可能不能用来自动生成文档.
- 容器中的多个bean定义可能匹配setter方法或构造函数参数中自动装配的类型.对列表,集合,Maps来说不是个问题.然而对于期望依赖一个单例bean的,这种二异性是无法解决的.如果没有一个唯一的bean,就会抛出异常
接下来几个场景,你有几个选择: - 放弃自动装配,使用显式布线.
- 通过设置它的 autowire-candidate 属性为false避免自动装配一个bean,就像接下来的章节所讲的
- 通过设置
<bean/>
元素的primary
属性为true指定一个单例bean定义作为主候选 - 通过基于注解的配置实现更细粒度的控制,像 基于注解的容器配置.所说的
bean不自动装配
以每个bean为基础,你可以让一个bean不自动装配.在Spring的XML格式中,设置<bean/>
的autowire-candidate
属性为false就可以了.容器会使设置了这个属性为false的bean不能使用自动装配功能.(包括基于注解的配置比如@Autowired
.autowire-candidate
属性只对基于类型的自动装配有作用,对于按名称的自动装配不起作用.因此,如果名称匹配,通过名称自动装配将注入一个bean。
你同样可以限制自动装配候选类通过bean名称的模式匹配.顶级<beans/>
元素接受一个或多个模式在它的default-autowire-candidates
属性中.比如,限制bean名称后缀为Repository的候选类,通过提供一个值*Repository.如果定义多个模式,用空格或逗号或分号分割各个模式.显式设置bean定义的autowire-candidate
属性为true或false具有高优先级,这样模式匹配就不起作用.
这些技术会很有用对于那些不想要通过自动装配方式注入其他bean的beans来说.这并不意味着被排除的bean不能通过自动装配进来方式注入其他bean,而是说它自己不能被自动装配进其他bean
1.4.6方法注入
在大多数场景,在容器的大多数bean都是单例的.当一个单例bean需要和另一个单例bean协作或一个非单例bean需要和另一个非单例bean协作的时候,你可以处理这个依赖通过定义一个bean作为另一个的属性.一个问题出现当bean的生命周期不一样.假如一个单例bean需要使用一个非单例(原型)bean,也许在A的每个方法调用.容器只创建单例bean一次,这样只有一次机会设置这个值.容器不能给bean A提供一个新的bean B实例在bean A需要的时候.
一个解决的方法就是放弃一些控制反转.你可以让bean A 察觉容器通过实现ApplicationContextAware
接口 并且通过调用getBean("B")向容器请求一个bean B实例(通常是新的)在bean A每次需要的时候.下面的例子说明了这种方法:
// 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框架.方法注入(Method Injection)是Spring IoC容器的高级功能,能够低耦合的处理这样的场景
Lookup方法注入
Lookup方法注入能够让容器覆盖容器所管理的bean上的方法,以返回在容器中查找到的另一个名字的bean.lookup通常在调用一个原型bean上使用,就像前面章节描述的场景一样.Spring框架实现方法注入通过字节码生成库CGLIB动态生成覆盖父类方法的子类.
看之前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();
}
在客户端类包含要注入的方法,被注入的方法需要一个格式就像下面一样:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是抽象的,动态生成的子类就会实现这个方法.否则,动态生成的子类会覆盖之前父类的方法.比如:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="myCommand" 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="myCommand"/>
</bean>
commandManager bean调用它的createCommand()方法当它需要一个myCommand bean实例的时候.你必须小心的去开发myCommand bean把它设置成原型bean,这是我们实际需要的,如果是单例bean,每次都是返回同样的myCommand bean实例.
等价的,在基于注解的组件模型中,你可以定义一个lookup方法通过@Lookup
注解:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup("myCommand")
protected abstract Command createCommand();
}
或者,更常用的,你可以依靠lookup方法的返回类型找到目标的bean
public abstract class CommandManager {
public Object process(Object commandState) {
MyCommand command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract MyCommand createCommand();
}
任意方法替换
略
1.5 Bean的作用域
作用域 | 描述 |
---|---|
单例(singleton) | (默认)bean实例的作用范围是spring IoC容器创建后到spring IoC销毁.每次请求bean实例都是获取到同一个bean |
原型(prototype) | 每次请求bean的时候都返回一个新的bean实例.它的作用域是它所在的对象 |
请求(request) | bean实例的作用范围这个请求的生命周期,请求完成后这个bean就被销毁.因此每个请求都会创建自己的bean实例.只在web应用中有效 |
会话(session) | bean实例的作用范围是这个http session的生命周期.只在web应用中有效 |
应用程序(application) | bean实例的作用范围是ServletContext 的生命周期.只在web应用中有效 |
websocket(websocket) | bean实例的作用范围是webscoket的生命周期 |
当你创建一个bean定义的时候,你创建了一个创建实际类(在bean定义的class)实例的配方.一个bean定义是一个配方的概念很重要,因为这意味着,同样一个类,你可以创建很多实例对象用一个配方.
您不仅可以控制要插入到从特定的bean定义创建的对象中的各种依赖项和配置值,还可以控制从特定的bean定义创建的对象的范围.这种方法非常强大灵活,这样你可以通过元数据配置指定创建对象的作用域代替java类级别的作用域.Bean可以定义多种作用域中的一种:开箱即用的,Spring框架支持6种作用域,其中四种是在web应用中才能使用.
下面几种作用域是开箱即用的(ps:直接使用的意思).你可以创建自定义的作用域
表三 .Bean的作用域
作用域 | 描述 |
---|---|
单例(singleton) | (默认)bean实例的作用范围是spring IoC容器创建后到spring IoC销毁.每次请求bean实例都是获取到同一个bean |
原型(prototype) | 每次请求bean的时候都返回一个新的bean实例.它的作用域是它所在的对象 |
请求(request) | bean实例的作用范围这个请求的生命周期,请求完成后这个bean就被销毁.因此每个请求都会创建自己的bean实例.只在web应用中有效 |
会话(session) | bean实例的作用范围是这个http session的生命周期.只在web应用中有效 |
应用程序(application) | bean实例的作用范围是ServletContext 的生命周期.只在web应用中有效 |
websocket(websocket) | bean实例的作用范围是webscoket的生命周期 |
1.5.1单例作用域
单例bean只有一个共享实例被管理,对同一个bean id的所有bean请求容器都返回同一个bean实例.
换句话说,当你定义一个bean定义且设置它的作用域为singleton,Spring IoC只会给这个bean定义创建一个实例.这些单例bean的单个实例被存储在缓存中,后续所有请求和引用这个bean都返回缓存中的对象.
Spring 单例bean的概念与GoF设计模式所说的单例模式不同.GoF里的单例模式是通过硬编码的方式使得每个ClassLoader
有且只能创建一个类实例.Spring里的单例范围描述的是单个容器和单个bean.意思是说如果你在单个容器中给指定类定义了一个单例bean,然后Spring容器有且只给这个bean定义创建一个实例.单例作用域是Spring的默认作用域.在XML定义一个单例,你可以这样写,比如:
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
1.5.2原型作用域
非单例,原型bean的部署结果是每次请求这个bean的时候都会创建一个新的bean实例.这样,bean注入其他bean或请求bean都只能调用容器的getBean()方法.通常,原型bean用于所有有状态的beans而单例bean用于无状态的bean.
下图说明了Spring的原型作用域.DAO通常不会定义成原型,因为通常的DAO不保留任何会话状态,这使得开发者能够更容易重用单例bean.
下面的例子在XML定义了一个原型bean:
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不管理原型bean的完整生命周期:容器实例化,配置和以其他方式组装原型对象,并将其交给客户端,而不再记录该原型实例.这样,尽管所有对象都会调用初始化生命周期回调函数而不管作用域,但是在原型作用域中,不会调用配置的销毁生命周期回调函数.客户端代码必须负责清除原型对象和释放它所占有的宝贵资源.为了让Spring容器释放原型bean所占有的的资源,尝试使用 bean post-processor,它拥有需要清除的bean的引用.
在某些方面,Spring容器对于原型bean的作用就像是java new操作符的替换.全生命周期管理过去都是又客户端管理(Spring容器中bean的生命周期详细信息,请看 Lifecycle callbacks.).
依赖原型bean的单例bean
当你使用的单例bean依赖原型bean,需要知道的是依赖的解析在实例化的时候.这样如果你依赖注入一个原型bean到一个单例bean,一个新的原型bean被实例化然后注入到单例bean中.这个原型实例是唯一的实例提供给单例bean.
然而,假如你希望单例bean重复获取新的原型bean在运行时,你不能依赖注入一个原型bean给单例bean,因为依赖注入之发生一次,当容器实例化单例bean的时候会解析和注入它的依赖.如果你在运行时不止一次需要一个新的原型bean实例,请看方法注入
1.5.4请求(requset),会话(session),应用上下文(application),WebSocket作用域
请求(requset),会话(session),应用上下文(application),WebSocket作用域只在web应用(即ApplicationContext
实现是基于web的,比如XmlWebApplicaitonContext
)可用.如果你在通常的Spring IoC容器比如ClassPathXmlApplicaitonContext
使用这些作用域,一个IllegalStateException
就会抛出,说明这是一个未知的作用域
初始化一个web配置
为了支持bean的作用范围在请求(requset),会话(session),应用上下文(application),WebSocket,在定义你的bean之前需要一些小的初始化配置(这些初始化设置在标准的作用域单例或原型中不需要).
你如何完成初始化配置取决于你特定的Servlet环境.
如果你在Spring Web MVC访问作用域bean,实际上请求是由Spring DispatcherServlet
来处理的,你不需要特别的设置:因为DispatcherServlet
已经暴露了所有的相关状态.
如果你使用Servlet2.5 web容器,并且请求的处理不是Spring DispatcherServlet
(比如使用JSF或Struts),你需要注册org.springframework.web.context.request.RequestContextListener
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>
或者,如果您的监听器设置存在问题,请考虑使用Spring 的 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请求对象到处理请求的线程上.这使得请求和会话作用域的bean可以在调用链的更后面进行访问.
请求作用域
思考以下XML的bean定义
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器给每个http请求创建一个新的LoginAction bean 使用LoginAction bean定义.这样,loginAction bean作用于http请求这个级别.你可以改变这个实例的内部状态只要你想,因为其他从loginAction bean定义创建的bean实例看不到它的状态改变.它们只属于特定的请求.当请求处理完成后,作用于这个请求的bean就会被丢弃.
当使用注解驱动的components或java config,@RequestScope
注解可以用来标识一个compoment的作用域是请求.
@RequestScope
@Component
public class LoginAction {
// ...
}
会话作用域
思考以下XML的bean定义
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器使用UserPreferences 的bean定义创建一个UserPreferences bean给单个http会话生命周期.换句话说,UserPreferences bean作用于http会话这个级别.与请求作用域bean一样,你可以改变bean的内部状态只要你想,因为其他从UserPreferences bean定义创建的bean实例看不到它的状态改变.因为它只属于特定的会话.当http会话销毁,作用于会话的bean也会被销毁
当使用注解驱动的components 或Java config,@SessionScope
注解可以用来标识一个compoment的作用域是会话
@SessionScope
@Component
public class UserPreferences {
// ...
}
应用上下文作用域
思考以下XML的bean定义
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器在整个web应用中使用AppPreferences 的bean定义创建一个AppPreferences bean一次.因此,appPreferences作用于ServletContext
级别,它被当作ServletContext
的属性来保存.这有点像单例bean,但是与单例bean有两个很重要区别:(1)它对每个ServletContext
来说是单例的,不是每个ApplicationContext
(在任何给定的Web应用程序中可能有几个)(2)它实际上是公开的,因此是可见的作为ServletContext属性
当使用注解驱动的components 或Java config,@ApplicationScope
注解可以用来标识一个compoment的作用域是应用上下文.
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
作用域bean作为依赖
Spring IoC不仅管理你的bean的实例化,而且还管理协作类的连接(或者注入).如果你想注入(举个例子)一个http请求作用域的bean到另一个更长生命周期的bean中,你可以选择注入一个AOP代理类替代注入一个作用域bean.也就是说,你需要注入一个代理对象且暴露的pulic接口与作用域对象一样,但也可以从相关作用域(例如HTTP请求)中检索真实目标对象,并将方法调用委托给实际对象.
在下面的例子中只有一行配置,但是了解背后"为什么"与知道"怎么做"一样重要
<?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">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<!-- instructs the container to proxy the surrounding bean -->
<aop:scoped-proxy/>
</bean>
<!-- a singleton-scoped bean injected with a proxy to the above bean -->
<bean id="userService" class="com.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
去创建这样一个代理,你只需要在作用域bean定义(选择创建代理类型和基于XML的配置)中插入一个子元素<aop:scoped-proxy/>
即可.为什么bean定义作用域于请求,会话和自定义作用域级别需要<aop:scoped-proxy/>
元素?让我们看看下面的单例定义,对比我们之前的定义(注意下面的userPreferences bean定义是不完整的)
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例bean userManager注入一个http session作用域的bean.这有个关键点是userManager是单例:它只被每个容器实例化一次,因此它的依赖只能注入一次.这意味着userManager将操作同一个userPreferences对象,也就是它第一次注入的那个对象.
这不是你想要的行为当一个短生命周期的bean注入一个长生命周期的bean,比如注入一个http session作用域的协作类bean到一个单例bean中.相反,你需要一个userManager对象,并且在http session生命周期,你需要一个userPreferences对象来描述特定的http session.这样容器创建一个对象(理想情况下是一个 UserPreferences实例的对象),这对象所暴露的public 接口跟UserPreferences类完全一样,且可以获取到真实的UserPreferences对象从作用域机制中.容器注入一个代理对象到userManager bean中,userManager bean并不知道这个UserPreferences引用是一个代理对象.在这个例子中,UserManager实例调用依赖注入的UserPreferences对象的方法,它实际上调用的是代理对象的上的方法.这个代理对象然后从http session中获取真正的UserPreferences对象,并将方法调用委托给查找到的真正的UserPreferences对象.
因此,你需要以下准确和完整的配置当需要注入请求和会话作用域bean到协作对象时:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择创建的代理类型
默认情况下,当Spring容器给一个被<aop:scoped-proxy/>
元素标识的bean创建代理的时候,一个基于CGLIB的代理类被创建
CGLIB只接受public方法调用!不要在代理中调用非public方法,它们不会委托给实际作用域的目标象.
或者,你可以配置Spring容器创建基于标准JDK的接口代理给这些作用域bean,通过设置<aop:scoped-proxy/>
元素的proxy-target-class
属性为false.使用基于JDK的接口代理意味着你不需要在你的应用程序的的classpath添加其他的库生成代理.然而,这意味着这个作用域bean的class必须实现至少一个接口,并且所有注入这个作用域bean的协作bean必须通过它们的一个接口来引用.
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
更多关于选择基于类还是基于接口的代理的详细信息,看代理机制
1.5.5自定义作用域
bean作用域机制是可扩展的;你可以定义你自己的作用域,或者重新定义已存在的作用域,尽管这认为是不好的,你不能覆盖内置的单例和原型作用域.
创建一个自定义作用域
为了集成自定义作用域到Spring容器,你需要实现org.springframework.beans.factory.config.Scope
接口,这个接口在这个章节会有说明.有关实现自定义作用域的方法,建议查看Spring框架本身提供的Scope
实现和Scope
的开发文档javadocs,里面更详细的说明了你需要实现的方法.
Scope
接口包含有四个方法:从作用域中获取对象,从作用域中移除对象,允许它们销毁.
下面的方法从底层作用域返回一个对象.例如,session作用域实现,返回一个session作用域的bean(如果不存在,方法就返回一个bean的新实例,然后把它绑定到这个session给将来引用.)
Object get(String name, ObjectFactory objectFactory)
下面的方法从底层作用域删除一个对象.以实现session作用域为例,删除一个session作用域的bean从底层的session中.被删除的对象应该返回,但是你可以返回null如果指定name的对象不存在.
Object remove(String name)
下面的方法注册了一个回调,当作用域的bean被销毁或者指定作用域对象被销毁时需要回调.有关销毁回调的更多信息,请参阅javadocs或Spring Scope实现.
void registerDestructionCallback(String name, Runnable destructionCallback)
下面的方法返回底层作用域的一致性标识符.每个作用域的标识符是不一样的.对于session作用域来说,它的标识符可以是它的session标识符.
String getConversationId()
使用自定义作用域
当你写和测试自定义作用域实现的时候,你需要让Spring容器知道你的新作用域.下面的方法是注册一个新的作用域到Spring容器中的核心方法
void registerScope(String scopeName, Scope scope);
这个方法定义在ConfigurableBeanFactory
接口,在大多数的ApplicationContext
实现中都可以它的BeanFactory
属性来访问这个接口.
registerScope(..)方法中的第一个参数是与作用域关联的唯一名字;Spring容器本身的这些名字的例子是singleton和 prototype.registerScope(..)方法中的第二个参数实际是你想要注册使用的自定义作用域实现的实例.
假设你写了自定义作用域实现,然后像下面一样注册.
下面的SimpleThreadScope是Spring包含的,但是默认没有注册.跟你自定义实现作用域是一样的.
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后创建符合作用域规则的自定义作用域的bean定义.
<bean id="..." class="..." scope="thread">
自定义作用域实现中,不限制你只用编程的方式注册你的作用域.你还可以注册你的作用域,使用CustomScopeConfigurer
:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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>
1.6定制一个bean的本质
1.6.1生命周期回调
要与容器的bean生命周期管理交互,你可以实现Spring的InitializingBean
和DisposableBean
接口.容器调用InitializingBean
的afterPropertiesSet()
方法在bean完成初始化后,调用DisposableBean
的destroy()
在bean销毁前.
JSR-250的@PostConstruct和@PreDestroy注解被认为是Spring应用程序接收生命周期回调的最佳实践.使用这个注解意味着你的bean不用耦合Spring特殊的接口.详情请看@PostConstruct and @PreDestroy.
如果不想用JSR-250注解,但是仍然想要移除耦合,可以考虑在bean定义元数据中添加init-method和destroy-method.
在内部,Spring框架使用BeanPostProcessor
实现来处理找到的任何回调接口并且调用适当的方法.如果你需要自定义功能或者其它Spring不提供的生命周期行为,你可以自己实现BeanPostProcessor
.更多信息,请看Container Extension Points
除了初始化和销毁回调之外,Spring管理对象还可以实现Lifecycle
接口,以便这些对象可以参与由容器自身生命周期驱动的启动和关闭过程
本节讲解生命周期回调接口.
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许执行一些初始化工作在bean所有必须的属性都被容器设置后.InitializingBean
接口只定义了一个方法:
void afterPropertiesSet() throws Exception;
不建议你使用InitializingBean
接口,因为它的代码耦合了Spring.或者,你使用@PostConstruc
注解或者增加一个POJO初始化方法.在基于XML的元数据配置文件中,你使用init-method
属性去设置无参函数.在基于Java代码的配置中,你可以使用@Bean
的initMethod
属性,查看 Receiving lifecycle callbacks.例如,下面的例子:
<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
注解或者增加一个POJO初始化方法.在基于XML的元数据配置文件中,你使用destroy-method
属性去设置一个无参函数.在基于Java代码的配置中,你可以使用@Bean
的destroyMethod
属性,查看 Receiving lifecycle callbacks.例如,下面的例子:
<bean id="exampleInitBean" class="examples.ExampleBean" destroy-method="cleanup"/>
public class ExampleBean {
public void cleanup() {
// do some destruction work (like releasing pooled connections)
}
}
它等价于
<bean id="exampleInitBean" class="examples.AnotherExampleBean"/>
public class AnotherExampleBean implements DisposableBean {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是代码没有耦合Spring.
默认初始化和销毁方法
当你写一个初始化和销毁方法,但是不使用Spring提供的InitializingBean
和DisposableBean
接口方法时,你通常可以写这些方法的使用名字为init(),initialize(),dispose()等等.理想情况下,这些名字的生命周期回调方法是标准的在整个项目,这样所有的开发者使用同样的方法名和保证一致性.
你可以配置Spring去查找所有命名为初始化和销毁的回调方法在每个bean中.这意味着,作为一个应用开发者,可以在你的应用程序类中使用一个叫做 init()的初始化回调,而不需要在每个bean定义中配置init-method="init" 属性.Spring IoC容器调用这个方法当bean创建的时候.这个功能强制要求初始化和销毁回调方法命名要有一致性.
假如你的初始化回调方法命名为init() 和销毁回调方法命名为destroy(),你将会构建类向下面的例子一样:
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
顶级的<beans/>
元素的default-init-method
属性会使Spring IoC容器识别bean里的init方法作为初始化回调方法.当bean创建和组装,如果bean包含这个方法,它就会在适当的时间被调用.
配置销毁回调方法也类似,使用<beans/>
元素的default-destroy-method
方法.
当存在bean的已经有方法回调,但是名字与一致性要求的不同,你可以覆盖它通过<bean/>
的init-method 和 destroy-method 属性.
Spring容器保证bean的所有依赖都提供后立即调用初始化回调方法.这样初始化回调方法是原始bean引用调用的,这意味着AOP拦截器还未应用于bean.一个目标bean首先被完全创建,然后一个AOP代理(例如)的拦截器链应用于它.如果你的目标bean和代理是分开定义的,你的代码绕过代理直接与原始目标bean交互.因此,拦截器使用在初始化方法是不合适的,因为目标bean的生命周期和代理/拦截器耦合在一起并且当你的代码直接与原始bean交互的时候会留下奇怪的语义.
混合生命周期机制
从Spring2.5开始,你有三种方式控制bean的生命周期管理: InitializingBean
and DisposableBean
接口回调;自定义init() 和 destroy()方法;和@PostConstruct
和 @PreDestroy
.你可以混合使用这样注解
当多个生命周期机制配置在一个bean,并且每个机制配置配置不同方法名,这样配置方法按照一下顺序执行.然而当不同的生命周期机制配置相同的方法名,方法只会执行一次,就像前面章节所述.
在一个bean上多个生命周期配置不同初始化方法,调用顺序如下:
- 被@PostConstruct注解的方法
* InitializingBean
接口定义的 afterPropertiesSet()
- 自定义init()方法
销毁方法调用的顺序也一样: - 被@PreDestroy注解的方法
* DisposableBean
接口定义的 destroy()
- 自定义destroy()方法
启动和关闭回调
Lifecycle
接口给任何有自己生命周期需求的对象定义了基本的方法(比如开启和停止某些后台进程)
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
所有Spring管理的对象都可以实现这个接口.这样,当ApplicationContext
本身接收到启动和停止信号时,比如运行时的停止/重启场景,它将会级联调用上下文中所有实现Lifecycle
接口的bean的对应方法.它实现是通过委托LifecycleProcessor
:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意到LifecycleProcessor
本省继承了Lifecycle
接口.它还添加了两种其他方法来处理正在刷新和关闭的上下文.
注意常规的
org.springframework.context.Lifecycle
接口只是显式开始/停止通知的简单约定且意味着context刷新时间不会自动启动.可以考虑实现org.springframework.context.SmartLifecycle
提供更细粒度在自动启动的时候.同样,请注意停止通知不保证在销毁的之前到达:在常规的关闭中,所有Lifecycle
beans首先会收到停止同知在销毁回调被广播之前.然而,在上下文的生命周期中的热刷新或中止刷新尝试时,只会调用销毁方法。
启动和关闭的方法调用顺序很重要.如果一个"依赖关系"存在任何两个对象,依赖方将会在依赖之后开始,停止在依赖之前.然而,有时候直接依赖是不知道的.你只知道某些类型的对象应该开始在其他类型对象之前.在这些情况下,SmartLifecycle
接口定义了其他选项,命名了一个getPhase()方法在超类接口
Phased
中
public interface Phased {
int getPhase();
}
public interface SmartLifecycle extends Lifecycle, Phased {
boolean isAutoStartup();
void stop(Runnable callback);
}
当启动的时候,最低阶段值的对象先启动,当停止的时候,顺序相反.因此一个对象实现了SmartLifecycle
且它的getPhase()方法返回Integer.MIN_VALUE,它就是第一个启动和最后一个停止.在阶段的另外一端,如果一个阶段值是Integer.MAX_VALUE意味着这个对象是最后一个启动和最先停止的.当考虑阶段值的时候,知道没有实现SmartLifecycle
的Lifecycle
对象的默认阶段值是0很重要.因此,任何负数的阶段值意味着这个对象会比其他标准组件先启动,反之相反.
正如你所见SmartLifecycle
的stop方法接受一个回调.任何实现类必须调用callback的run()方法当实现类的的关闭进程完成后.这允许必要的时候启用异步调用,因为LifecycleProcessor
接口的默认实现DefaultLifecycleProcessor
将等待其每个阶段中的对象组的超时值来调用该回调.默认每个阶段的超时值是30妙.你可以覆盖默认的生命周期处理实例通过在上下文中调用一个名叫"lifecycleProcessor"的bean.如果你想要修改超时时间,下面的定义就够了:
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
如前所述,LifecycleProcessor接口定义了用于刷新和关闭上下文的回调方法.后者将简单的驱动关闭进程就好像stop()方法被调用一样,但是当上下文关闭的时候会发生.'刷新'回调启用了SmartLifecycle
bean的另外一个功能.当上下文刷新的时候(所有对象都实例化和初始化之后),回调将会被调用,到那个时候默认的生命周期处理器就会检查每个SmartLifecycle
对象的isAutoStartup()
方法返回的boolean值,如果是"true",然后对象就会启动而不是等待context或自身的start()方法的显式调用(不同于context刷新,context的启动在标准的context实现中不会发生).“阶段”值以及任何“依赖”关系将以与上述相同的方式确定启动顺序.
在非web应用中优雅的关闭Spring IoC容器
这个章节只针对非web应用.Spring 基于web的
ApplicationContext
实现已经有代码去优雅的关闭Spring IoC容器,当相关web应用关闭的时候
如果你在非web环境中使用Spring的IoC容器;比如,你在一个富客户端桌面环境.你使用JVM注册了一个关闭(shutdown)的钩子(hook).这样做可以确保优雅的关闭且调用单例bean相关的销毁方法释放bean它持有的所有资源.当然了,你必须正确配置和实现这些销毁方法.
去注册一个关闭钩子,你可以调用ConfigurableApplicationContext
接口的registerShutdownHook()
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public final class Boot {
public static void main(final String[] args) throws Exception {
ConfigurableApplicationContext ctx = new ClassPathXmlApplicationContext("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...
}
}
1.6.2ApplicationContextAware 和 BeanNameAware
当一个ApplicationContext
创建一个实现了org.springframework.context.ApplicationContextAware
接口的对象实例时,这个实例就会包含一个ApplicationContext
的引用.
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
这样bean就可以编程操作创建它们的ApplicationContext
,通过ApplicationContext
接口或者转型为这个接口的已知子类比如ConfigurableApplicationContext
,包含同样的功能.一个用途是检索其他beans.有时候这个功能是很有用的.然而,你通常你要避免使用它,因为这样你的代码会耦合Spring并且不符合控制反转的风格,在协作类作为bean的熟悉的时候.ApplicationContext
提供了访问文件资源,发布应用事件,访问MessageSource
的其他方法.这些附加功能在ApplicationContext的附加功能有描述
从Spring2.5开始,自动装配是获取ApplicationContext
的另外一种途径."传统" constructor
和byType
自动装配模式(如自动装配协作类中所述)可以分别为ApplicationContext
构造函数参数或setter方法参数提供类型的依赖关系.更灵活的做法,包括自动注入字段和多参数方法,使用新的基于注解的功能.你如果这样做,如果需要使用ApplicationContext
类型的字段,方法,构造函数参数或方法参数有@Autowired
注解,那么ApplicationContext
则自动装入到字段,构造函数参数或方法参数中.更多信息,查看 @Autowired.
当ApplicationContext
创建一个实现org.springframework.beans.factory.BeanNameAware
接口的类时,这个类提供一个关联这个对象定所义的名称的引用.
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个回调调用是在bean的正常属性注入之后,初始化方法回调比如InitializingBean
的afterPropertiesSet或自定义的初始化方法调用之前.
1.6.3其他Aware接口
名称 | 注入依赖 | 在..说明 |
---|---|---|
ApplicationContextAware | 定义ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | ApplicationContext关闭的事件发布者 | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | 用于加载Bean类的类加载器 | Instantiating beans |
BeanFactoryAware | 定义一个BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 定义bean的名称 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | BootstrapContext容器运行的资源适配器。通常仅在JCA中可用 | JCA CCI |
LoadTimeWeaverAware | 定义一个weaver处理类定义在加载的时候 | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | 配置处理消息的策略 | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX通知发布者 | Notifications |
ResourceLoaderAware | 配置一个加载器对资源的低级别访问 | Resources |
ServletConfigAware | 当前运行容器的ServletConfig.只在web应用中有效 | Spring MVC |
ServletContextAware | 当前运行容器的ServletContext.只在web应用中有效 | Spring MVC |
除了之前讨论的ApplicationContextAware
和BeanNameAware
,Spring还提供了一系列的Aware接口允许bean告诉容器它们需要一定的基础设施依赖.
最重要的Aware接口总结如下(上面一模一样的列表是简书上的bug) - 作为一般规则,名称是依赖类型的良好指示
表四.Aware接口
名称 | 注入依赖 | 在..说明 |
---|---|---|
ApplicationContextAware | 定义ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | ApplicationContext关闭的事件发布者 | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | 用于加载Bean类的类加载器 | Instantiating beans |
BeanFactoryAware | 定义一个BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | 定义bean的名称 | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | BootstrapContext容器运行的资源适配器。通常仅在JCA中可用 | JCA CCI |
LoadTimeWeaverAware | 定义一个weaver处理类定义在加载的时候 | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | 配置处理消息的策略 | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX通知发布者 | Notifications |
ResourceLoaderAware | 配置一个加载器对资源的低级别访问 | Resources |
ServletConfigAware | 当前运行容器的ServletConfig.只在web应用中有效 | Spring MVC |
ServletContextAware | 当前运行容器的ServletContext.只在web应用中有效 | Spring MVC |
再次注意,使用这些接口将您的代码绑定到Spring API,并且不遵循控制反转风格.因此,它们被推荐用于需要编程访问容器的基础beans中.
1.7Bean定义继承
一个bean定义可以包含许多配置信息,包括构造函数参数,属性值,和容器的描述信息比如初始化方法,静态工厂方法名字等等.一个子bean定义从一个父bean定义继承配置数据.这些子定义覆盖相同的值和增加其他值如果需要.使用父和子bean定义可以减少许多输入内容.实际上,这是一种模板形式.
如果你以编程的方式使用ApplicationContext
,子bean定义可以使用ChildBeanDefinition
表示.大多数用户不会使用他们在这个级别,而是用声明地配置bean定义就像ClassPathXmlApplicationContext一样.当使用基于XML的元数据配置时,你可以指定一个child bean定义通过使用parent属性,描述它的parent bean作为属性值:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
如果子bean定义不指定bean class就使用父bean定义的,但是可以覆盖.在后一种情况,子bean class必须兼容父bean,也就是说,它必须接受父bean的属性值.
一个子bean会继承作用域,构造函数参数,属性值,和覆写方法从父bean中,并且可以添加新的值.你设置任何作用域,初始化方法,销毁方法,和静态工厂方法都会覆盖父类的设置.
其余的设置来自子bean: 依赖关系,自动装配模式,依赖检查,单例,延迟加载.
之前的例子显式的标识父bean定义是abstract通过使用abstract
属性.如果父bean定义没有设置class,只是按需要显式的标识父bean是abstract,像下面一样:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean是不能实例化的,因为它是不完整的并且它也被显式标识为abstract.当一个bean定义像这样是abstract的,它只是纯粹的bean定义模板,作为子定义的父定义。试图单独使用这样一个abstract父bean
,将它作为另一个bean的ref属性或者显式调用getBean()使用父bean的id,将返回一个错误.类似地,容器内部的preInstantiateSingletons()方法将忽略定义成abstract的bean定义.
ApplicationContext
默认会预初始化所有单例beans.因此,这非常重要(至少对单例bean来说)如果你有一个(父)bean定义打算只作为一个模板,并且这个bean定义设置了一个class,你必须设置它的abstract属性为true,否则应用上下文会預实例化这个abstract bean.
1.8容器扩展点
通常,一个应用开发者不需要实现ApplicationContext
的子类.相反,Spring IoC容器可以通过插入特别集成接口实现来扩展.接下来的章节将会讲解这些集成接口.
1.8.1自定义beans使用BeanPostProcessor
BeanPostProcessor
接口定义了回调方法,你可以实现自己的实例化逻辑(或覆盖容器默认的),依赖处理逻辑等等.如果你想实现一些自定义逻辑在容器完成实例化,配置和初始化一个bean后,你可以增加一个或多个BeanPostProcessor
实现.
你可以配置多个BeanPostProcessor
实例,并且你可以控制这些BeanPostProcessors
执行顺序通过设置它的顺序(order)属性.只有你实现Ordered
接口时才能设置这个属性.如果你写自己的BeanPostProcessor
,你一样需要考虑实现Ordered
接口.更多详细信息,请参阅BeanPostProcessor
和 Ordered
接口的javadocs .请参阅以下关于编程式注册BeanPostProcessor
BeanPostProcessor
s对象一个bean(或对象)实例进行操作;意思是说,Spring IoC容器实例化一个bean实例然后BeanPostProcessor
s再进行处理.
BeanPostProcessor
s作用于每个容器.这只有在使用容器层次结构时才有意义.如果你定义一个BeanPostProcessor
在一个容器,那么它只处理这个容器里的bean.换句话说,beans定义在一个容器不会被定义在另一个容器的BeanPostProcessor
后处理,即使这两个容器都是同一层次结构的一部分
要更改实际的bean定义(即定义bean 的蓝图),您需要使用aBeanFactoryPostProcessor
,如 使用BeanFactoryPostProcessor定制配置元数据中所述。
org.springframework.beans.factory.config.BeanPostProcessor
接口包含两个回调方法.当这样的类在容器中注册为一个后处理器(post-processor),对于每个容器创建的bean实例,后处理器都会在容器初始化方法(如InitializingBean的afterPropertiesSet()之前和其他声明的init方法)以及任何bean初始化回调之后被调用.后处理器可以对bean实例执行任何操作,包括完全忽略回调.一个bean后处理器通常检查回调接口,或者可能用一个代理包装一个bean.一些Spring AOP基础类实现一个bean后处理器为了提供代理包装逻辑.
ApplicationContext
可以自动检测任何定义在配置元数据且实现了BeanPostProcessor
接口的beans.ApplicationContext
注册这些beans作为后处理器以便在后面创建bean的时候调用.Bean后处理器可以像任何其他bean一样部署在容器中.
注意到当定义一个BeanPostProcessor
使用@Bean
工厂方法在一个配置类,这个工厂方法的返回类型应该是实现类本身或者至少org.springframework.beans.factory.config.BeanPostProcessor
接口,清楚地表明该bean的后处理器特性.否则,ApplicationContext
在完全创建它之前, 将无法通过类型对其进行自动检测。由于BeanPostProcessor
需要尽早实例化以适用于上下文中其他bean的初始化,因此这种早期类型检测非常关键.
编程式注册BeanPostProcessors
推荐注册BeanPostProcessors
的方法是自定检测(想上面所说的一样),你也可以通过可编程的方式注册,调用ConfigurableBeanFactory
的addBeanPostProcessor
方法.当需要在注册之前评估条件逻辑,或者甚至跨层次结构中的上下文复制Bean后处理器时,这会非常有用.但请注意,BeanPostProcessor
s以编程方式添加忽略Ordered
接口。这是注册顺序决定执行顺序.还要注意,BeanPostProcessor
通过编程注册的总是在通过自动检测注册的之前进行处理,而不管任何明确的排序
BeanPostProcessors和AOP自动代理
实现BeanPostProcessor接口的类是特殊的,并且容器对其进行不同的处理.所有的BeanPostProcessor
s和beans的直接引用在启动的时候被实例化,作为ApplicationContext
特别启动阶段的一部分.接下来,所有BeanPostProcessor
s以已排序的方式注册并应用于容器中的所有其他bean.因为AOP自动代理是作为一个BeanPostProcessor
方式实现的,BeanPostProcessors
和它们直接引用的bean都不适合自动代理,因此没有编入它们的方面.
对于任何这样的bean,您应该看到一条信息性日志消息:“ Bean foo不适合通过所有BeanPostProcessor接口进行处理(例如:不适合自动代理) ”
请注意,如果您的bean已连接到BeanPostProcessor使用自动装配或 @Resource(可能会回退到自动装配),Spring在进行类型匹配查找时可能会访问到与预期不符的beans,,因此使得它们不适合作为自动代理或其它类型的bean 后处理器.比如,如果你有一个依赖使用@Resource
注解并且字段或者setter的名字与bean的名字不一致也不使用name属性,然后Spring将会访问其他beans通过类型匹配.
下面的例子展示了在ApplicationContext
如何编写,注册和使用BeanPostProcessor
s
例如:Hello World,BeanPostProcessor风格
第一个例子说明了基本用法.这个例子展示了一个自定义BeanPostProcessor
实现,调用容器创建的每个bean的toString()方法并把字符串结果打印到系统控制台.
在自定义BeanPostProcessor
实现类定义下面查找:
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
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>
注意InstantiationTracingBeanPostProcessor
如何简单定义。它甚至没有名称,因为它是一个bean,它可以像其他任何bean一样依赖注入。(前面的配置也定义了一个由Groovy脚本支持的bean。Spring动态语言支持在标题为动态语言支持的章节中有详细介绍 。)
下面简单的java应用执行前面的代码和配置:
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
前面的应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:RequiredAnnotationBeanPostProcessor
使用回调接口或者注解结合自定义的BeanPostProcessor
是实现扩展Spring IoC容器的常见方法.一个例子Spring的RequiredAnnotationBeanPostProcessor
是一个随Spring发布版一起发布的 BeanPostProcessor
实现,它确保Bean包含有注释(任意)的JavaBean属性(配置为)依赖注入一个值
1.8.2使用BeanFactoryPostProcessor自定义元数据配置
我们将看到下一个扩展点是org.springframework.beans.factory.config.BeanFactoryPostProcessor
.这个接口的语义与BeanPostProcessor
相似,一个主要的区别是:BeanFactoryPostProcessor
对bean配置元数据进行操作;因此,Spring IoC容器允许一个BeanFactoryPostProcessor
读取配置元数据,并可能在容器实例化除BeanFactoryPostProcessor
s任何beans之前修改它.
你可以配置多个BeanFactoryPostProcessor
s,并且可以控制这些BeanFactoryPostProcessor
s的执行顺序通过设置它的order属性.然而,只有你实现了Ordered
接口才能设置这个属性.如果你实现自己的BeanFactoryPostProcessor
,你需要考虑实现一个Ordered
接口.参考BeanFactoryPostProcessor
和Ordered
的javadocs获取更多详细信息.
如果您想更改实际的bean 实例(即从配置元数据创建的对象),则需要使用
BeanPostProcessor
(如上所述,使用BeanPostProcessor定制bean)。虽然技术上可以在一个BeanFactoryPostProcessor
(例如使用BeanFactory.getBean()
)bean中使用bean实例,但这样做会导致bean过早实例化,从而违反标准容器生命周期。这可能会导致负面影响,如绕过bean后处理.
同样,BeanFactoryPostProcessors
作用于每个容器.这只有在使用容器层次结构时才有意义.如果BeanFactoryPostProcessor
定义在一个容器,那么它只作用于定义于这个容器里的bean.bean定义在一个容器中不会被其他容器的BeanFactoryPostProcessors
处理,即使这两个容器是同样层次结构的一部分.
一个bean工厂后处理器定义在ApplicationContext
会被自动执行,以便运用于修改定义在容器中的元数据配置.Spring包含一系列预定义的bean工厂后处理器,比如PropertyOverrideConfigurer
和PropertyPlaceholderConfigurer
.一个自定义的BeanFactoryPostProcessor
一样可以使用,比如注册一个自定义的属性编辑器.
ApplicationContext
可以自动检测任何实现BeanFactoryPostProcessor
接口的bean.容器把这些bean当作bean工厂后处理器在适当的时候.您可以像任何其他bean一样部署这些后处理器bean.
就像
BeanPostProcessor
s一样,你不能配置BeanFactoryPostProcessors
延迟初始化.如果没有其他bean引用Bean(Factory)PostProcessor,那么后处理器根本就不会被实例化.因此,把它标记为延迟初始化就会被忽略,Bean(Factory)PostProcessor将会急切初始化即使你设置<beans />的default-lazy-init属性为为true.
示例:类名替换PropertyPlaceholderConfigurer
你可以使用PropertyPlaceholderConfigurer
去将bean定义属性值外部化到一个单独的标准java属性文件.这样做,允许部署应用程序的人员可以自定义特定于环境的属性,如数据库URL和密码,而无需修改容器的主XML定义文件或文件的复杂性或风险.
思考下面基于XML元数据配置片段,其中DataSource定义了一下占位值.下面的例子展示了属性配置从一个外部属性文件.运行时,PropertyPlaceholderConfigurer
被用于元数据替换DataSource的一些属性.要替换的值被指定为遵循Ant / log4j / JSP EL样式的表单的占位符${property-name}
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
实际值来自标准Java Properties格式的另一个文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,字符串 ${jdbc.username}在允许时就被替换成'sa',同样其他占位符值也会匹配属性文件的key.PropertyPlaceholderConfigurer
会检查占位符在大多数properties 和在bean定义中的属性.此外,占位符的前缀和后缀可以自定义.
使用Spring 2.5中引入的上下文命名空间,可以使用专用配置元素配置属性占位符.。一个或多个文件路径用逗号分隔作为location属性.
<context:property-placeholder location="classpath:com/foo/jdbc.properties"/>
PropertyPlaceholderConfigurer
不仅仅从你配置的配置文件中查找属性.默认情况下,它同样会查找Java System属性如果在描述的配置文件中找不到对应属性值的时候.你可以自定义这种行为通过设置systemPropertiesMode属性,这个属性支持以下三个整数值:
- never (0):从不检查系统属性
- fallback (1): 检查系统属性如果找不到指定属性值.这是默认的
-
override (2): 首先检查系统属性,在检查配置文件之前.这允许系统配置去覆盖其他配置源.
参考PropertyPlaceholderConfigurer
javadocs获取更多信息.
例如:PropertyOverrideConfigurer
略
1.8.3使用FactoryBean自定义实例化逻辑
为自己工厂的对象实现org.springframework.beans.factory.FactoryBean接口.
FactoryBean
接口是Spring IoC容器实例化逻辑的扩展点.如果你有复杂的初始化代码,使用java代码来实现更好,而不是冗长的XML,你可以实现自己的FactoryBean
,编写复杂的初始化代码在这个类里面,然后把你自定义的FactoryBean
插入到容器中.
FactoryBean
接口提供了三个方法:
- Object getObject(): 返回这个工厂创建的一个实例.这个实例是否共享,取决于工厂返回的是单例还是原型
- boolean isSingleton():如果
FactoryBean
返回的是单例,这个方法返回是true,否则返回false. - Class getObjectType(): 返回getObject()方法返回对象的类型或者null如果之前未知的话
这个FactoryBean
概念和接口在Spring框架的许多地方都有使用; Spring本身提供超过50多个FactoryBean
的接口实现.
当你向容器请求一个实际FactoryBean
实例而不是容器产生的bean,调用ApplicationContext
的getBean()方法时bean的id前面加个&.比如给定的FactoryBean
id是myBean,调用getBean("myBean")事容器返回的是FactoryBean产生的实例,然而调用getBean("&myBean"),返回的是FactoryBean实例本身.(ps:FactoryBean也是一个bean,只不过它是工厂bean,用来组装生成我们需要的bean,我们一般不直接使用FactoryBean)
1.9基于注解的容器配置
配置Spring使用注解比XML好?
引入基于注解的配置产生一个问题这种方式是否比XML更好.简单回答是看情况.详细点的话每个方法都有优缺点,通常由开发者决定使用那种方式.
由于他们定义的方式,注解提供大量的上下文在他们的声明中,从而使得配置更短,更简洁.但是,XML在连接组件时不需要涉及源码或者重新编译.
一些开发人员更喜欢连接源代码,而另一些开发人员则认为注释类不再是POJO,而且配置变得分散,难以控制
不管怎么选择,Spring可以支持两种风格甚至混合使用他们.值得指出的是通过JavaConfig选项,Spring允许注解通过非入侵的方式使用,
而不需要接触目标组件的源码,并且在工具方面,Spring Tool Suite支持所有配置样式
基于注释的配置提供了XML设置的替代方法,该配置依赖字节码元数据来连接组件而不是角括号声明.不同于使用XML去描述一个bean的连接,开发者通过在相关类,方法,字段上添加注解把配置移到组件类本身中.正如前面所提到的 例子: The RequiredAnnotationBeanPostProcessor,使用BeanPostProcessor
结合注解扩展Spring IoC 容器.例如,Spring 2.0引入了使用@Required注解标识属性是必须的。Spring 2.5使得遵循相同的通用方法来驱动Spring的依赖注入成为可能。本质上来说,@Autowired
注释提供了与Autowiring协作者中描述的功能相同的功能,但具有更细致的控制和更广泛的适用性。Spring 2.5还增加了对JSR-250注释的支持,例如 @PostConstruct
和@PreDestroy
。Spring 3.0增加了对javax.inject包(例如@Inject
和)中包含的JSR-330(依赖注入Java)注释的支持@Named
。有关这些注释的详细信息可以在相关部分找到
注解注入比XML注入先执行,这样后者的配置会覆盖前者的值
像往常一样,您可以将它们注册为单独的bean定义,但也可以隐式注册它们通过在基于XML的Spring配置中包含以下标记来(注意包含context名称空间):
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
(这些隐式注册的后处理器包括AutowiredAnnotationBeanPostProcessor
,CommonAnnotationBeanPostProcessor
,
PersistenceAnnotationBeanPostProcessor
,还有之前提到的RequiredAnnotationBeanPostProcessor
)
<context:annotation-config/> 只会查找它所定义的上下文里的bean的注解.意味着,如果你把<context:annotation-config/>放置在
DispatcherServlet
的WebApplicationContext
中,它只会检查controllers的@Autowired
bean,而不会检查services.更多信息查看DispatcherServlet
1.9.1@Required
略
1.9.2 @Autowired
JSR 330的
@Inject
注解可以用来替换Spring的@Autowired
注解在下面的例子中.查看这里获取更多信息.
你可以使用@Autowired
注解在构造函数上:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring4.3开始, 如果目标bean只定义了一个构造函数,
@Autowired
注解在这样的构造函数中不是必须的.
正如预期的那样,您还可以将@Autowired注释应用于“传统”setter方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你还可以把注解用于任意名字,包含多个参数的方法里:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
你还可以把@Autowired
放在字段上甚至混合构造函数一起使用:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
private MovieCatalog movieCatalog;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
略
可以从ApplicationContext
获取指定类型的所有bean,通过把@Autowired
注解加在指定类型的数组字段或者方法上:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
这同样适用于集合类型:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你希望数组中的bean按照指定的顺序排序,你的目标bean可以实现
org.springframework.core.Ordered
接口或者使用@Order
或者标准@Priority
注解.否则他们就按照目标bean定义注册到容器顺序排序.
@Order
注解可以定义在目标class上也可以定义在@Bean
方法上,对于每个bean定义都是非常独立的.@Order
值可能会影响注入点的优先级,但是需要注意的是它不影响单例bean的启动顺序,
这是由依赖关系和@DependsOn声明共同确定的.
甚至Map类型也可以自动注入,如果它的key是String类型.Map会保存所有预期类型的bean,它的key保存的是bean的名称.
public class MovieRecommender {
private Map<String, MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Map<String, MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
默认情况下,当没有匹配的bean时自动装配就会失败;默认的行为是被注解的方法,构造函数,字段认为是必须依赖.这个行为可以改变,如下所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
每个类只有一个带注释的构造函数可以标记为必需,但可以注释多个不需要的构造函数。在这种情况下,每个人都被认为是候选人,而Spring使用可以满足其依赖性的最贪婪的构造函数,即具有最多参数的构造函数。
建议在@Autowired
中使用required属性。required属性表示该属性不需要自动装配,如果它不能自动装配这个属性会被忽略。另一方面@Required
则更强大,因为它强制容器给这个属性设置一个值。如果没有值被注入,则会引发相应的异常
或者,您可以通过Java 8来表达特定依赖项的非必需性质java.util.Optional
:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring5.0开始,你可以使用@Nullablepublic class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你可以使用@Autowired
注入一些常见的接口依赖:BeanFactory
,ApplicationContext
,Environment
, ResourceLoader
, ApplicationEventPublisher
, 和 MessageSource
.这些接口和它们的扩展接口,比如ConfigurableApplicationContext
或 ResourcePatternResolver
会自动解析,不需要特别的设置.
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
,@Inject
,@Resource
, 和@Value
这些注解是被Spring的BeanPostProcessor
实现来处理的,反过来意味着你不能在你的BeanPostProcessor
或BeanFactoryPostProcessor
类(如果有的话)使用这些注解..这些类型显示的连接使用XML或@Bean
方法.
使用@Primary微调基于注解的自动装配
因为自动装配会导致多个候选匹配,这就需要更多的控制在选择的过程.一个实现的方式就是使用Spring的@Primary
注解.@Primary
表示当多个bean可以自动装配到单例依赖项时,被@Primary
注解的bean优先.如果在这些候选中确实存在一个'主要'bean,那么就注入这个值.
假设我们有下面的配置,其中定义了一个firstMovieCatalog作为主要的(primary)的MovieCatalog
@Configuration
public class MovieConfiguration {
@Bean
@Primary
public MovieCatalog firstMovieCatalog() { ... }
@Bean
public MovieCatalog secondMovieCatalog() { ... }
// ...
}
有了这样的配置,下面的MovieRecommender就会主动注入firstMovieCatalog.
public class MovieRecommender {
@Autowired
private MovieCatalog movieCatalog;
// ...
}
相应的bean定义如下所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4使用qualifiers微调基于注解的自动装配
按类型进行自动装配有多个实例需要确定一个主要候选的时候,@Primary
是个有效的方式.在选择过程需要更多控制的时候,可以使用Spring的@Qualifier
注解.您可以将限定符值与特定参数相关联,缩小匹配类型的集合,以便为每个参数选择特定的bean。在最简单的情况下,这可以是一个简单的描述性值:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
@Qualifier
注解同样适用在单个构造函数参数或者方法参数:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main")MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
相应的bean定义如下.限定符为main
的bean与有相同限定值构造函数参数关联.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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>
对于后备匹配,bean的名称被视为默认的限定符值.这样你可以定义一个bean id是"main",替代内部的限定符元素,得到的结果是一样的.然而,尽管你可以使用这种约定通过名称来引用指定的bean,@Autowired
结合可以选的限定符是最基本的类型驱动注入.这意味着限定符值(即使使用bean名称作为后备)也会在匹配类型集合内缩小语义;它们不会在语义上表达对唯一bean id的引用.好的限定符值如"main"或者"EMEA"或者"persistent",表示所描述的bean组件独立于bean id,id也许是自动生成对于匿名的bean定义.
限定符同样可以使用在集合类型上,就像前面讨论的一样,比如Set<MovieCatalog>.在这种情况下,根据声明的限定符所有匹配的Bean将作为集合注入.这意味着限定词不必是唯一的; 它们只是过滤条件.比如你可以定义多个限定符值为"action"的MovieCatalog beans,这些beans都会注入到被注解为@Qualifier("action")的Set<MovieCatalog>中
在类型匹配候选中,让限定符值针对目标bean名称进行选择 ,注入点并不需要@Qualifier注解。如果没有其他解析指示符(例如限定符或primary标记),那么对于非唯一的依赖性情况,Spring将匹配注入点名称(即字段名称或参数名称)与目标bean名称,并选择相同的名称的候选人,如果有的话。
也就是说,如果您打算按名称来进行注解驱动注入,不必主要使用@Autowired
,即使可以在类型匹配的候选中使用bean名称进行选择。相反,使用JSR-250@Resource
注解,该注解在语义上定义标识特定目标组件通过其唯一名称,并且声明的类型与匹配过程无关。@Autowired
具有相当不同的语义:在按类型选择候选bean之后,指定的字符串限定符值将仅在这些类型选择的候选者中被考虑。
对于本身被定义为集合/map或数组类型的bean,@Resource
是一个很好的解决方案,通过唯一名称引用特定集合或数组bean。也就是说,@Autowired只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中,就可以通过Spring的类型匹配算法来匹配集合/映射和数组类型 。在这种情况下,可以使用限定符值在相同类型的集合中进行选择,如前一段所述。
@Autowired适用于字段,构造函数和多参数方法,允许通过参数级别的限定符注释进行缩小。相比之下,@Resource 仅支持具有单个参数的字段和bean属性设置方法。因此,如果注入目标是构造函数或多参数方法,则坚持使用限定符
你可以创建自定义的限定符注解.你定义一个简单的注解并在注解上添加@Qualifier
注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你就可以使用自定义的限定符注解在主动注入的字段和参数上:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,提供候选bean定义的信息.你可以在<bean/>
增加一个<qualifier/>
元素,在里面描述类型和值来匹配你自定义的限定符注解.类型匹配自定义注解的全限定类名.或者,如果不存在冲突风险,为了方便可以用短类名.下面的例子演示这两种方法:
<?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>
在Classpath扫描和管理组件中,您将看到一种基于注释的替代方法来提供XML中的限定符元数据。具体来说,请参阅提供限定符元数据和注释。
在某些情况下,使用注解不需要值就够了.当注解提供更通用的用途并且可以应用于多种不同类型的依赖关系时,这可能很有用。比如,你可以提供一个离线类别去搜索当没有网络连接的时候.首先定义一个简单的注解:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后添加注解到将要自动装配的字段或属性中:
public class MovieRecommender {
@Autowired
@Offline
private MovieCatalog offlineCatalog;
// ...
}
现在bean定义只需要一个限定符类型:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/>
<!-- inject any dependencies required by this bean -->
</bean>
你可以同样自定义限定符接受一个名称属性而不是简单的value属性.如果多个属性值应用于自动装配的字段或者参数,一个bean定义必须匹配所有属性值才被考虑为自动装配候选者.举个例子,思考下面注解定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
例子中Format是个枚举:
public enum Format {
VHS, DVD, BLURAY
}
要自动装配的字段使用自定义限定符进行注解,并包含两个属性的值:genre和format。
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最终,bean定义需要包含匹配限定符值.这个例子同时也演示了meta属性可以替代子元素<qualifier/>
.如果可用,<qualifier/>
和它的属性优先,但<meta/>如果不存在此类限定符,则自动装配机制会回退标签中提供的值,如以下示例中的最后两个bean定义所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.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>
1.9.5使用泛型作为自动装配限定符
处理@Qualifier
注解外,还可以使用java的泛型作为隐式的限定符.如下例所示,假如你有如下配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
加入上述bean实现一个通用接口,比如Store<String> 和 Store<Integer>,你可以@Autowire
Store接口和它的泛型作为限定符:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
泛型限定符同样适用于列表,Map,数组:
// 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;
1.9.6CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
允许你注册自定义的限定符注解类型,即使他们不使用@Qualifier
注解.
<bean id="customAutowireConfigurer"
class="org.springframework.beans.factory.annotation.CustomAutowireConfigurer">
<property name="customQualifierTypes">
<set>
<value>example.CustomQualifier</value>
</set>
</property>
</bean>
AutowireCandidateResolver
通过以下方式觉得自动注入的候选人:
- 每个bean定义的autowire-candidate值
- <beans/>元素中任何可用的default-autowire-candidates模式值
- 包含
@Qualifier
的注解和任何自定义注册到CustomAutowireConfigurer
的注解
当多个bean被认定为自动装配候选者时,“primary”的确定如下:如果候选者中恰好有一个bean定义primary 属性设置为true,则它将被选中。
1.9.7@Resource
Spring 注入同样支持使用JSR-250@Resource
注解在字段或者bean属性的setter方法.这是一个通用的模式在Java EE 5 and 6.例如在JSF 1.2托管bean或JAX-WS 2.0端点中。Spring也支持这种Spring管理对象的模式.
@Resource
需要一个name属性,Spring将该值解释为要注入的bean名称.换句话说,它的语意是按名称注入,下面的例子所说的:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder")
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有明确指定名称,则默认名称是从字段名称或setter方法派生的.在字段名称上,它使用字段名称.
在方法上,它使用bean属性名称.所以下面的例子将把名为“movieFinder”的bean注入到它的setter方法中:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
name解析成bean的名称是由
ApplicationContext
的CommonAnnotationBeanPostProcessor
来完成.名称解析可以通过JNDI如果你配置了Spring的SimpleJndiBeanFactory
.然而,推荐你使用默认的行为并简单地使用Spring的JNDI查找功能来保留间接级别。
在没有指定明确名称的情况下使用@Resource,这有点类似于@Autowired
,@Resource
查找主类型匹配而不是一个具体的bean并解决众所周知的解析依存关系:BeanFactory,
ApplicationContext
, ResourceLoader
, ApplicationEventPublisher
, 和 MessageSource
接口.
这样,在下面的例子中,customerPreferenceDao字段首先查找一个bean名字为customerPreferenceDao,然后退而求其次主类型匹配查找类型CustomerPreferenceDao. "context" 字段被注入ApplicationContext
.
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
1.9.8@PostConstruct and @PreDestroy
CommonAnnotationBeanPostProcessor
不仅能识别@Resource
注解还能识别JSR-250生命周期注解.在Spring 2.5中引入的对这些注释的为初始化回调函数和 销毁回调函数提供了另一种替代方案.实现识别这些生命周期注解的CommonAnnotationBeanPostProcessor
注册在Spring的ApplicationContext
,带有这些注解其中一个的方法会被调用,与Spring生命周期接口方法或显式定义的回调方法对应.在下面的示例中,缓存将在初始化时预填充,并在销毁时清除。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
1.10类路径扫描和组件管理
在这个章节中大多数例子使用XML去描述Spring容器生成每个BeanDefinition
的配置元数据.在之前的章节 (基于注解的容器配置)演示了如何通过源代码级注解提供大量配置元数据.但是,即使在这些示例中,“基本”bean定义在XML文件中显式定义,而注解用于依赖注入.本节介绍一种可选方法用于隐式检测候选组件通过扫描类路径.候选组件是与过滤条件相匹配的类,并在容器中注册相应的bean定义。这就消除了使用XML来执行bean的注册;相反你使用注解(比如@Component
),AspectJ类型表达式,或者你自定义的过滤条件去选择在容器中注册bean定义的类.
从Spring3.0开始,Spring JavaConfig项目提供的许多功能都是Spring核心框架的一部分.这允许你定义bean使用java而不是传统的XML文件.看看
@Configuration
,@Bean
,@Import
,和@DependsOn
注解有关的例子,它们是如何使用这些新功能.
1.10.1 @Component和更原型的注解
@Repository
注解通常标识一个类的作用是repository(也被称为数据访问对象或DAO).这个标记的用途是自动翻译异常就像异常翻译所描述的一样
Spring提供了更原型的注解:@Component
, @Service
, 和 @Controller
.@Component
是任何Spring管理组件的通用原型.@Repository
, @Service
, 和 @Controller
是定制化的@Component
,用于更具体的场景.比如,分别在持久化层,业务层,和表示层.因此你可以注解你的组件类使用@Component
,但如果使用@Repository
,@Service
或者@Controller
注解它们,你的类能更好地被工具处理,或与切面进行关联.比如,这些原型注解是切入点的理想目标.@Repository
, @Service
, 和 @Controller
在未来的Spring版本中可能会增加额外的语义.这样,如果你在服务层中选择@Component
或@Service
,很明显@Service
是明智的选择.同样,如上所述,@Repository已经被支持作为持久层中自动异常转换的标记.
1.10.2元注解
Spring提供的很多注解可以作为你代码的元注解.元注解是一个可以用于其他注解的简单的注解.比如,之前提到的@Service
被@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 {
// ....
}
元注释也可以组合起来创建组合注释.比如,Spring MVC的@RestController
注解就组合了@Controller
和@ResponseBody
.
此外,组合注解可以重定义元注解的属性允许用户自定义.当您只想暴露元注解属性的子集时,这可能特别有用.比如Spring的@SessionScope
硬编码作用域名字是session但仍然允许自定义proxyMode.
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Scope(WebApplicationContext.SCOPE_SESSION)
public @interface SessionScope {
/**
* Alias for {@link Scope#proxyMode}.
* <p>Defaults to {@link ScopedProxyMode#TARGET_CLASS}.
*/
@AliasFor(annotation = Scope.class)
ScopedProxyMode proxyMode() default ScopedProxyMode.TARGET_CLASS;
}
然后@SessionScope
直接使用而不需要定义proxyMode,如下所示:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
或者覆盖proxyMode值,如下所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
更多详细信息,查看 Spring注解编程模型
1.10.3自动检测类并注册bean定义
Spring可以自动检测原型类并注册对应的BeanDefinition
s到ApplicationContext.比如,以下两个类都是可以自动检测的:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public SimpleMovieLister(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
@Repository
public class JpaMovieFinder implements MovieFinder {
// implementation elided for clarity
}
为自动检测这些类并注册对应的bean,你需要添加@ComponentScan
到你的@Configuration
类,其中该basePackages属性是这两个类的共同父级包(或者,你可以使用逗号/分号/空格分隔各个父包)
@Configuration
@ComponentScan(basePackages = "org.example")
public class AppConfig {
...
}
为了简洁,上面你可以使用注解的value属性,比如@ComponentScan("org.example")
与下面使用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>
使用<context:component-scan>隐式的包含了<context:annotation-config>的功能,所以使用<context:component-scan>的时候就没有必要使用<context:annotation-config> .
类路径包的扫描要求类路径中存在相应的目录条目。在使用Ant构建JAR时,请确保不要 激活JAR任务的仅文件开关。此外,在某些环境中,类路径目录可能不会基于安全策略公开,例如JDK 1.7.0_45和更高版本上的独立应用程序(这需要在您的清单中设置“受信任的库”;请参阅 http://stackoverflow.com/questions/ 19394570 / java-jre-7u45-breaks-classloader-getresources)。
在JDK 9的模块路径(Jigsaw)中,Spring的类路径扫描一般按预期工作。但是,请确保您的组件类在您的module-info
描述符中导出; 如果你希望Spring调用你的类的非公共成员,确保它们是'打开'的(即使用opens
声明而不是描述符中的exports
声明module-info
)。
此外,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
都会隐式包含当使用组件扫描(component-scan)元素的时候.这意味着这两个组件会自动检测和连接在一起而不需要在XML中有任何的bean元数据配置.
1.10.4使用过滤器自定义扫描
默认情况下,被@Component
, @Repository
, @Service
, @Controller
或自身被@Component
注解的自定义注解注解的类才会被作为自动检测的候选组件.然而,你可以很容易修改和扩展这些行为通过提供自定义过滤器.添加它们作为@ComponentScan的 includeFilters或 excludeFilters 参数(或者作为组件扫描元素的子元素 include-filter 或 exclude-filter 的值).每个过滤器元素需要类型和表达式属性.下表描述了过滤器可选项.
表5.过滤器类型
过滤器类型 | 表达式例子 | 描述 |
---|---|---|
注解(annotation)默认类型 | org.example.SomeAnnotation | 注解存在目标组件类级别上 |
assignable | org.example.SomeClass | 目标组件分配的一个类(或接口(扩展或实现)) |
aspectj | org.example..*Service+ | 一个AspectJ类型表达式匹配目标组件 |
正则表达式(regex) | org.example.Default.* | 一个正则表达式目标组件的类名 |
自定义 | org.example.MyTypeFilter | 自定义实现org.springframework.core.type .TypeFilter接口 |
下面的例子展示了配置忽略所有@Repository
注解和使用"sub"repositories替代
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
等价的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>
你可以禁用默认过滤器通过设置useDefaultFilters=false在注解上或者设置<component-scan/> 的属性use-default-filters="false".这将会关闭被@Component, @Repository, @Service, @Controller, or @Configuration注解的类的自动检测
1.10.5使用component定义bean元数据.
Spring的component同样可以将bean元数据定义提供给容器.你可以使用@Bean注解去定义bean元数据与@Configuration
注解类一样.如下例子所示:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
这个类是一个Spring组件,它的doWork()方法中包含了特定于应用程序的代码.然而,它同样提供了bean定义,该定义涉及到一个工厂方法publicInstance().@Bean
注解标识工厂方法和其他bean定义属性,比如通过@Qualifier
设置限定符值.其他可用的方法级注解是@Scope
,@Lazy
和自定义限定符注解.
略
就像前面所说的一样,自动装配字段和方法同样支持,还支持自动注入@Bean
方法:
@Component
public class FactoryMethodComponent {
private static int i;
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
// use of a custom qualifier and autowiring of method parameters
@Bean
protected TestBean protectedInstance(
@Qualifier("public") TestBean spouse,
@Value("#{privateInstance.age}") String country) {
TestBean tb = new TestBean("protectedInstance", 1);
tb.setSpouse(spouse);
tb.setCountry(country);
return tb;
}
@Bean
private TestBean privateInstance() {
return new TestBean("privateInstance", i++);
}
@Bean
@RequestScope
public TestBean requestScopedInstance() {
return new TestBean("requestScopedInstance", 3);
}
}
这个例子中字符串方法参数country的值注入的是另一个名为privateInstance bean的age 属性.Spring表达式语言元素通过符号#{ <expression> }定义属性的值.对于@Value
注解,一个表达式解析器被预先配置查找bean的名称在解析表达式文本的时候(ps:@Value(${})注入的是配置文件的内容).
从Spring框架4.3开始,你可以定义一个工厂方法,参数类型是InjectionPoint
(或者更具体的子类DependencyDescriptor
)以便访问触发创建当前bean的请求注入点.注意到这个只运用于实际创建bean的实例,而不是注入已存在的实例.因此,这个功能大多数的使用场景是原型bean.对于其它作用域,工厂方法只会看到触发创建新bean实例的注入点在给定的作用域:比如,依赖触发创建延迟加载的单例bean.在这种情况下使用提供的注入点元数据和语义保护:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}