核心技术
版本6.0.0-SNAPSHOT
此部分参考文档涵盖了Spring Boot绝对不可或缺的所有技术。
其中最主要的是Spring Framework的控制反转(Inversion of Control:IOC)容器。在对Spring Framework的ICO容器进行彻底处理之后,紧接着全面介绍Spring的面向切面编程(Aspect-Oriented Programming:AOP)技术。Spring Frmework有它自己的AOP框架,它在概念上容易理解的,并成功地解决了在企业编程中80%的AOP需求的最佳点。
还介绍了Spring与AspectJ的集成(就特性而言,目前AspectJ是最成熟的,当然在Java企业领域是最成熟的AOP实现)。
AOT处理可以用于提早优化你的应用程序。它通常用于使用GraalVM本机镜像部署。
1. IoC容器
这章节涵盖Spring的控制反转(Inversion of Control:IoC)容器。
1.1. Spring IoC容器和Bean的介绍
这章节涵盖Srping Framework的控制反转(Inversion of Control:IoC)原理的实现。IoC也被称为依赖注入(DI)。在此过程中,对象仅通过构造器参数,工厂方法的属性或者在对象实例构造或者从工厂方法返回后在对象实例上设置的属性定义他们的依赖关系(及他们工作的其他对象)。然后容器在创建bean时,将注入这些依赖项。这个过程从根本上说bean本身的逆过程(因此得名,Inversion of Control),它通过使用类的直接构造或者服务定位模式等机制来控制依赖项的实例化或者位置。
org.springframework.beans
和org.springframework.context
包是Spring Framework的IoC容器的基本。BeanFactory
接口提供一个高级的配置机制有管理任何对象类型的能力。ApplicationContext
是BeanFactory
的一个子接口。他添加了以下特性:
- 更容易与Spring的AOP特性集成。
- 信息资源处理(对于在国际化上的使用)。
- 事件发布。
- 分层应用程序特性的上下文,例如在web应用程序中使用的
WebApplicationContext
。
总之,BeanFactory
提供自动配置框架和基本的功能,ApplicationContext
添加更多特定于企业的功能。ApplicationContext
是的BeanFactory
的完整超集,在本章Spring的IoC容器描述中被专门使用。要了解关于使用BeanFactory
代替ApplicationContext
的更多信息,请查看涵盖BeanFactory
API的部分。
在Spring中,构成应用程序骨干和被Spring IoC容器管理的对象被称为bean。一个bean是被Spring IoC容器实例化,组装和管理的对象。除此之外,bean只是在你应用程序中的许多对象的一个。bean及其之间的依赖关系反映在容器使用的配置元数据中。
1.2. 容器概述
org.springframework.context.ApplicationContext
接口表示Spring IoC容器并且主要负责实例化,配置和组装bean。容器通过读取配置元数据获取关于实例化,配置和组装哪些对象的指令。配置元数据用XML,Java注解或者Java代码表示。它允许你表达组成应用程序的对象和这些对象之间丰富的相互依赖关系。
Spring提供了ApplicationContext
接口的几个实现。在独立的应用程序中,创建一个ClassPathXmlApplication
或者FileSystemXmlApplicationContext
实例是比较常见的。尽管XML已经是用于定义配置元数据的传统格式,你可以通过提供少量的XML配置以声明的方式支持这些额外的元数据格式,指示容器来使用Java注解或者代码作为元数据格式。
在大多数应用程序场景中,显示的用户代码是不要求实例化一个或者多个Spring IoC容器的实例的。例如,在web应用程序场景,在应用程序的web.xml
文件中简单的8行(大约)样本web描述符XML文件通常足够了(请查看方便的ApplicationContext实例化Web应用程序)。如果你使用Spring Tools for Eclipse(一个有力的Eclipse开发环境),你可以使用几下鼠标点击或者键盘就容易地创建这个样板配置。
以下图标展示了一个Spring如何工作的高级别视图。你的应用程序类与配置元数据组合,因此,在ApplicationContext
创建和初始化之后,你有一个完整的配置的也可执行的系统或者应用程序。
1.2.1. 配置元数据
正如上面的图标展示,Spring IoC容器使用了一种形式的配置元数据。这个配置元数据表示作为一个应用程序开发者,如何告诉Spring容器实例化,配置和组装你应用程序的对象。
配置元数据通常使用一个简单的,直观的XML格式提供,本章的大部分内容都是使用这种格式来传达Spring IoC容器的关键概念和特性。
配置元数据的方式不仅仅允许基于XML的元数据。Spring IoC容器本身与实际编写此配置元数据的格式完全解耦。最近,许多开发者针对他们的Spring应用程序选择基于Java配置。
关于使用其他方式的Spring容器元数据,请查看:
- 基于注解的配置:Spring 2.5介绍对基于注解配置元数据的支持。
- 基于java配置:开始于Spring 3.0,Spring JavaConfig项目提供的许多特性变为核心Spring Framework的一部分。这样,你可以使用Java定义应用程序之外的bean,而不用XML文件。要使用这些特性,请查看
@Configuration
,@Bean
,@Import
和@DependOn
注解。
Spring配置由容器必须管理的至少一个(通常超过一个)bean定义组成。基于XML配置元数据将这些bean配置为在顶级<beans/>
元素内的<bean/>
元素。Java配置通常使用在@Configuration
类内的@Bean
注解的方法。
这些bean定义对应真实的对象来组成你的应用程序。通常,你定义服务层对象,数据访问对象(DAOs),表示层对象例如Struts的Action
实例,基础设施对象例如Hibernate的SessioinFactories
,JMS的Queues
,等等。通常,不需要在容器中配置细粒度的域对象,因为创建和加载域对象通常是DAO和业务逻辑的职责。然而,你可以使用Spring与AspectJ的集成来配置在IoC控制之外已经创建的对象。请查看使用AspectJ来用Spring依赖注入域对象。
以下示例展示基本的基于XML配置元数据的结构:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="..." class="...">①②
<!-- collaborators and configuration for this bean go here -->
</bean>
<bean id="..." class="...">
<!-- collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions go here -->
</beans>
①id
属性是一个字符串,表示单独的bean定义。
②class
属性定义bean的类型并使用全量限定类名。
id
属性的值指定是协作的对象。这个XML对于协作的对象没有在这个示例中展示。请查看依赖项了解更多细节。
1.2.2. 实例化一个容器
提供给ApplicationContext
构造器位置路径或者多个路径是资源字符串并让容器从各种各样的外部资源加载配置元数据,例如本地文件系统,Java的CLASSPATH
等等。
ApplicationContext context = new ClassPathXmlApplicationContext("services.xml", "daos.xml");
在你学习Spring的IoC容器之后,你可能想要知道更多关于Spring的
Resource
抽象(正在在Resources
中描述的),它提供了一个方便的机制用于从URI语法中定义的位置读取一个InputStream。特别是,Resource
路径用于建造应用程序上下文,正如在Application Context和Resource Paths所描述的。
以下示例展示了服务层对象(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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- services -->
<bean id="petStore" class="org.springframework.samples.jpetstore.services.PetStoreServiceImpl">
<property name="accountDao" ref="accountDao"/>
<property name="itemDao" ref="itemDao"/>
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for services go here -->
</beans>
以下示例展示了数据访问对象daos.xml
文件:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="accountDao"
class="org.springframework.samples.jpetstore.dao.jpa.JpaAccountDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<bean id="itemDao" class="org.springframework.samples.jpetstore.dao.jpa.JpaItemDao">
<!-- additional collaborators and configuration for this bean go here -->
</bean>
<!-- more bean definitions for data access objects go here -->
</beans>
在前面的实例中,服务层由PetStoreServiceImpl
类和两个类型为JpaAccountDao
和JpaItemDao
(基于标准的JPA关系型对象映射)数据访问对象组成。property name
元素引用JavaBean属性的名称,并且ref
元素引用另一个bean定义的名称。id
和ref
元素之间的联系表达合作对象之间的依赖。要了解配置对象的依赖详情,请查看Dependenies。
构成基于XML配置元数据
让bean定义跨越多个XML文件是非常有用的。通常每一个单个的配置文件展示的是一个逻辑层或者你的架构中的模块。
你可以使用应用程序上下文构造器来从所有这些XML片段中加载bean定义。这个构造器使用多个Resource
位置,正如以前章节中展示的。或者,使用一个或者多个<import />
元素的资源指引从另外文件中加载bean定义。以下示例展示如何这样做:
<beans>
<import resource="services.xml"/>
<import resource="resources/messageSource.xml"/>
<import resource="/resources/themeSource.xml"/>
<bean id="bean1" class="..."/>
<bean id="bean2" class="..."/>
</beans>
在前面的例子中,从三个文件:services.xml
,messageSource.xml
和themeSource.xml
加载外部的bean定义。所有位置路径是相对于执行导入操作的定义文件,所以services.xml
必须与执行导入操作的文件相同的目录或者类路径位置,而messageSource.xml
和themeSource.xml
必须在导入文件位置之下的resources
位置。正如你看到的,前导斜线是被忽略了。然而,考虑到这些路径是相对的,最好完全不适用斜线。被引入文件的内容,包含顶级元素,必须是有小的XML bean定义,根据Spring Schema。
可以使用,但不推荐在父目录中使用相对的
../
路径引用文件。这样做创建一个在当前应用程序之外的文件依赖。特别是,依赖不推荐classpath:
URL(例如,classpaths:../services.xml
),其中运行时解决过程选择“最近的”类路径根然后查找他的父目录。类路径配置变化可能导致选择不同的,错误的目录。你可以一直使用全量限定资源位置代替相对路径:例如,
file:C:.config/services.xml
或者classpath:/config/services/xml
。然而,要注意的是你正在将你的应用程序配置藕合到特定的绝对位置上。通常更可取的方式是对这样的绝对位置使用间接的方式,通过在运行时根据JVM系统属性解析"${…}"占位符。
命名空间本身提供导入指令特性。在普通的bean定义之外,Spring提供了一系列XML命名空间还提供了更多的配置特性,例如context
和util
命名空间。
Groovy Bean定义DSL
作为外部化配置元数据进一步示例,bean定义也可以在Spring的Groovy Bean定义DSL中表示,这在Grails框架中是众所周知的。通常,这样的配置在以下示例展示的结构的.groovy
文件中:
beans {
dataSource(BasicDataSource) {
driverClassName = "org.hsqldb.jdbcDriver"
url = "jdbc:hsqldb:mem:grailsDB"
username = "sa"
password = ""
settings = [mynew:"setting"]
}
sessionFactory(SessionFactory) {
dataSource = dataSource
}
myService(MyService) {
nestedBean = { AnotherBean bean ->
dataSource = dataSource
}
}
}
这个配置风格很大程度等价于XMLbean定义,设置支持Spring的XML配置命名空间。它也允许通过importBeans
指令引入XMLbean定义文件。
1.2.3. 使用容器
ApplicationContext
是有维护不同的bean和他们依赖注册表的能力的高级的工厂的接口。通过使用方法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();
使用Groovy配置,引导指令非常类似。它有不同的上下文实现类,实现类是Groovy-aware(也可以理解XMLbean定义)。以下示例展示了Groovy配置:
ApplicationContext context = new GenericGroovyApplicationContext("services.groovy", "daos.groovy");
最灵活的变体是GenericApplicationContext
与阅读器委托的组合-例如,使用对XML文件的XmlBeanDefinitionReader
,如下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new XmlBeanDefinitionReader(context).loadBeanDefinitions("services.xml", "daos.xml");
context.refresh();
你也可以对Groovy文件使用GroovyBeanDefinitionReader
,如下示例所示:
GenericApplicationContext context = new GenericApplicationContext();
new GroovyBeanDefinitionReader(context).loadBeanDefinitions("services.groovy", "daos.groovy");
context.refresh();
你可以在相同的ApplicationContext
上混合并匹配这样的阅读器委托,从不同的配置源读取bean定义。
然后你可以使用getBean
来检索你的bean实例。ApplicationContext
接口有一些其他方法用于检索bean,但是,理想上你的应用程序代码绝不应该使用他们。实际上,你的应用程序代码应该根本不会调用getBean
方法,由此根本不需依赖Spring API。例如,Spring与web框架的集成提供了对各种web框架组件的依赖注入,例如controller和管理的JSFbean,允许你通过元数据(例如一个自动注入注解)声明一个特定的bean的依赖。
1.3 bean概述
Spring IoC容器管理一个或者更多bean。这些bean是用你提供给容器的配置元数据创建(例如,使用XML <bean />
格式的定义)。
容器本身内,这些bean定义是作为BeanDefinition
对象存在的,其包含(以及其他信息)以下元数据:
- 包限定类名:通常,bean定义的真正实现类。
- bean行为配置元素,它说明bean在容器内应该如何行为(作用范围,生命周期回调等等)。
- 此bean要完成他的工作所需要的其他bean的引用。这些引用被称为合作者或者依赖。
- 在最近创建的对象设置的其他配置设置-例如,在管理一个连接池的bean中池的限制大小或者要使用的链接的数量。
这个源数据转译为一个组成每一个bean定义的属性集合。以下表描述这些属性:
属性 | 解释 |
---|---|
Class | 初始化bean |
Name | 命名bean |
Scope | bean作用范围 |
Constructor arguments | 依赖注入 |
Properties | 依赖注入 |
Autowiring mode | 自动注入合作者 |
Lazy initialization mode | 延迟初始化bean |
Initialization method | 初始化回调 |
Destruction method | 销毁回调 |
除了包含关于如何创建一个特定的bean信息的bean定义,ApplicationContext
实现也允许容器之外创建的现有的对象(用户创建的)的注册。是通过getBeanFactory
方法访问ApplicationContext的BeanFactory
来完成的,它返回了DefaultListableBeanFactory
实现。DefaultListableBeanFactory
通过registerSingleton(..)
和registerBeanDefinition(..)
方法支持此注册。但是,典型的应用程序只使用通过常规的bean定义元数据的bean定义工作。
bean元数据和手工提供的单例实例需要尽早注册,以便容器在自动装配和其他自省步骤期间正确地对他们进行推导。虽然在某种程度上支持覆盖现有元数据和现在单例实例,在运行时(并行实时访问工厂)新bean的注册不正式地支持并且可能导致在bean容器中并行访问异常,不一致状态,或者都出现。
1.3.1. 命名bean
每一个bean有一个或者更多标志符。这些标志符在持有这些bean的容器内必须是唯一的。一个bean通常只有一个标志符。但是,如果它要求超过一个,额外的一个可以考虑别名。
在基于XML配置的元数据中,你使用id
属性,name
属性,或者都来指定bean标志符。id
属性允许你指定精确的一个id。按照惯例,这些名称是字母数字的(‘myBean’,'someService’等等),但是他们也可以包含特殊的字符串。如果你想为bean引入其他别名,你也可以在name
属性指定他们,通过逗号(,
)分号(;
)或者空格分隔。作为一个历史上的注意事项,在Spring 3.1之前的版本,id属性是作为xsd:ID
类型定义的,它限制了可能的字符串。从3.1之后,它作为xsd:string
类型定义的。注意,容器是一直强制bean id
唯一性的,尽管不在通过XML解析器强制。
你不必须为一个bean提供name
或者id
。如果你不显性地提供name
或者id
,容器为此bean生成一个唯一名称。然而,如果你想要通过名称引用该名称,通过ref
元素的使用或者一个Service Locater(定位器)风格查找,你必须提供一个名称。不提供名称的动机与使用内部bean和自动装配合作者有关。
Bean命名规范
当命名bean时,这个规范是使用对于实例字段名称的标准的Java规范。也就是,bean名称以小写字母开头并从那里开始使用驼峰大小写。这样的名称的示例包括accountManager
,accountService
,userDao
,loginController
,等等。一致性的命名bean使你的配置更容易阅读和理解。而且,如果你使用Spring AOP,当提供一组与名称相关的的bean的增强(advice)时,它提供很多帮助。
在类路径中使用组件扫描时,Spring为未命名的组件生成bean名称,遵循早前所描述的规则:大体上,使用简单类名称并且转换他的初始字符为小写。但是,在一些特殊的(不常用)的情况,当存在超过一个字符时,第一个和第二个字符都是大写,原始情况保留下来。这些规则与
java.beans.Introspector.decapitalize
定义的规则相同(这里Spring使用的它)。
在Bean Definition外为一个Bean使用别名
在一个bean定义本身,你可以为此bean提供多个名称,可以使用id
属性指定的最多一个名称和在name
属性中任意数量的其他名称的组合。这些名称可以是同一个的bean的等价别名,在一些场景是非常有用的,例如通过使用特定于该组件本身的bean的名称,让在一个应用程序中每一个组件引用一个公共的依赖项。
但是,指定bean实际定义的所有别名并不总是足够的。有时,为一个在其他地方定义的bean引入一个别名是可取的。在大型系统中,这个是一个常见的情况,在大型系统中配置被分到每一个子系统当中,每一个子系统有它自己的对象定义集合。在基于XML配置元数据中,你可以使用<alias/>
元素来实现它。以下示例展示了如何做到:
<alias name="fromName" alias="toName"/>
这种情况,一个命名为fromName
的bean(在相同的容器中)在别名定义使用之后也可以作为toName
被引用。
例如,子系统A的配置元数据可能通过subsystemA-dataSource
的名称引用一个DataSource。子系统B的配置元数据可能通过subsystemB-dataSource
的名称引用一个DataSource。当使用全部这些子系统组成一个主要的应用程序,这个主应用程序通过myApp-dataSource
名称引用一个DataSource。要存在所有三个名称引用相同的对象,你可以添加以下别名定义到配置元数据中:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
现在每一个组件和主要的应用程序可以通过名称引用dataSource,这个名称是唯一的并保障不会与任何其他定义冲突(有效地创建命名空间),到目前为止他们引用相同的bean。
Java-配置
如果你使用java配置,@Bean
注解可以用于提供别名。请查看使用@Bean
注解了解详情。
1.3.2. 实例化bean
bean定义本质上是创建一个或者多个对象的处方。当请求或者使用通过bean定义创建一个真正的对象封装的配置元数据,这个容器为已命名的bean查看这个处方。
如果你使用基于XML配置元数据,则需要指定对象的类型(或者类),此对象将是在<bean />
元素的class
属性中实例化。class
属性(在BeanDefinition
实例内部,它是Class
属性)通常是强制的。(例外情况,请查看使用实例工厂方法实例化和Bean Definition Inheritance(bean定义继承))。你可以两种方式的其中一个使用Class
属性:
- 通常,指定要构造的bean类,这种情况,容器本身通过反射调用它的构造器直接创建bean,某种程度上等价于使用
new
操作符的Java代码。 - 要指定实际的类,包含
static
工厂方法,此方法被调用用于创建对象,在不太常见的情况下,容器调用一个类上的static
工厂方法来创建bean。从static
工厂方法的调用返回的这个对象类型可以是同一个类或者也可以完全是另外一个类。
内部类名
如果你想要配置一个内部类的bean定义,你需要使用二进制名称或者内部类的源名称。
例如,如果你有一个名称SomeThing
在com.example
包,并且这个SomeThing
类有一个static
内部类称为OtherThing
,他们可以通过叨乐符($
)或者逗号(.
)分隔。所以在bean定义中class
属性的值将是com.example.SomeThing$OtherThing
或者com.example.SomeThing.OtherThing
。
使用构造器实例化
当你使用构造器的方式创建一个bean时,Spring可以使用并兼容所有普通的类。也就是说,正在开发的类无需实现任何特定的指定的接口或者使用指定的风格编码。简单地指定bean类就足够了。但是,对于特定的bean取决于你使用的IoC的类型是什么,你可能需要默认的(空的)构造器。
Spring IoC容器实际上可以管理你想让它管理的任何类。它不限于管理真实的JavaBean。大多数Spring用户更喜欢实际的JavaBean,它只有一个默认(没有参数)构造器,并根据容器中的属性建模适当的setter和getter。容器内你也可以有非bean风格比较奇异的类。例如,如果你需要使用古老的绝对没有遵守JavaBean规范的链接池,Spring也可以管理它。
使用基于XML配置元数据,你可以如下指定你的bean类:
<bean id="exampleBean" class="examples.ExampleBean"/>
<bean name="anotherExample" class="examples.ExampleBeanTwo"/>
关于提供参数到构造器(如果需要的话)和在构造对象之后设置对象实例属性机制的更多细节,请查看依赖注入。
使用静态工厂方法实例化
当定义一个bean,你可以使用静态工厂方法创建,使用class
属性来指定包含static
工厂方法类,并且名称factory-method
属性来指定工厂方法本身的名称。你应该可以调用这个方法(使用可选的参数,正如稍后所描述的)并返回一个活跃对象,该对象随后将视为通过构造器创建的对象。这样的bean定义一种用途是在遗留代码中调用static
工厂。
以下bean定义指定将通过调用工厂方法创建bean。此定义不会指定返回对象的类型(class),而是此类包含工厂方法。在这个示例中,createInstance()
方法必须是一个static
方法。以下示例展示如何指定一个工厂方法:
<bean id="clientService"
class="examples.ClientService"
factory-method="createInstance"/>
以下示例展示与前面的bean定义工作的类:
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
属性设置工厂方法本身的名称。以下示例展示如何配置这样的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;
}
}
这种方式展示工厂bean本身可以通过依赖注入(DI)管理和配置。请查看详细的依赖和配置
在Spring文档,"factory bean"引用一个在Spring容器中配置的bean并且它通过一个实例或者静态工厂方法创建对象。相比之下,
FactoryBean
(注意是大写)引用一个特定的SpringFactoryBean
实现类。
确定一个Bean的运行时类型
特定的bean运行时类型的确定并不简单。在bean元数据定义中指定的类仅仅是一个最初的类的引用,可能与声明的工厂方法组合在一起,或者成为一个FactorBean
类,其可能导致不同的bean的运行时类型,或者在一个实例级别工厂方法情况根本就不设置(通过指定的factory-bean
名称代替解析)。或者,AOP代理可能使用基于接口的代理封装bean实例,该代理有限地暴露目标bean的实际类型(只是他的实现的接口)。
找出特别的bean实际运行时类型推荐的方式是对指定的bean名称的BeanFactory.getType
调用。这将考虑上述所有情况,并返回BeanFactory.getBean
调用将为相同的bean名称返回的对象的类型。
1.4. 依赖
一个典型的企业级应用程序不是由单个对象(或者在Spring语法的bean)组成。及时最简单的应用程序有几个一起工作的对象来呈现最终用户眼中的一致的应用程序。这下一部分解释从定义多个独立的bean定义到一个完全已实现的应用程序(其中对象协作来实现目标)你如果处理。
1.4.1. 依赖注入
依赖注入(DI)是一个过程:借此对象仅通过构造器参数,工厂方法参数,或者对象实例在实例化之后或者从工厂方法返回之后在对象实例上设置的属性定义他们的依赖项(也就是,他们工作的其他对象)。然后当容器创建bean时,该容器注入这些依赖项。这个过程根本上是bean本身的逆过程(因此得名控制反转(Inversion of Control)),它通过直接构造类或者服务定位器模式控制依赖项实例化或者位置。
使用DI原则的代码更简洁,并且当对象通过他们的依赖提供,去藕合更加有效。对象不查找它的依赖项并且不知道依赖项位置或者类。因此,你的类变得更容易测试,特别当依赖是接口或者抽象基类上时,它允许在单元测试中使用存根和模拟实现。
DI有两个主要的变体:基于构造器的依赖注入和基于setter的依赖注入。
基于构造器的依赖注入
基于构造器的DI是由容器调用多个参数的构造器完成的,每一个表示一个依赖项。使用特定的参数调用static
工厂方法来构建bean几乎是等价的,本文将以类似的方式处理构造函数和static
工厂方法。以下示例展示只能使用构造器注入依赖注入一个类:
public class SimpleMovieLister {
// the SimpleMovieLister has a dependency on a MovieFinder
private final 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...
}
注意,关于这个类没有任何特殊的地方。它是一个不依赖容器的特定接口,基类或者注解的POJO。
构造器参数解析
构造器参数解析匹配通过使用参数类型发生。如果在bean定义的构造器参数中不存在潜在的歧义,在一个bean定义中定义构造器参数的顺序就是当bean被实例化时,这些参数被提供给适当的构造器的顺序。考虑以下类:
package x.y;
public class ThingOne {
public ThingOne(ThingTwo thingTwo, ThingThree thingThree) {
// ...
}
}
假设,ThingTwo
和ThingThree
类没有通过继承关联,没有潜在的歧义存在。这样的话,以下配置工作的很好,并且你不需要在<constructo-arg />
元素中显式地指定构造器参数索引或者类型。
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
当引用另外一个bean时,类型是已知的,并且可以匹配(正如使用前面示例的情况)。当使用一个简单的类型,例如<value>true</value>
,Spring不能确定值的类型,所以在没有帮助的情况下,不能根据类型匹配。考虑以下类:
package examples;
public class ExampleBean {
// Number of years to calculate the Ultimate Answer
private final int years;
// The Answer to Life, the Universe, and Everything
private final String ultimateAnswer;
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
构造器参数类型匹配
在前面的场景中,如果你使用type
属性明确指定构造器参数的类型,容器可以使用类型匹配简单类型。如下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
构造器参数索引
你可以使用index
属性来显示地指定构造器参数的索引,如下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
除了解决多个简单参数的歧义之外,指定一个索引解决了构造器有两个相同类型的参数的歧义。
索引从0开始。
构造器参数名称
对于值歧义消除,你也可以使用构造器参数名称,正如以下示例所示:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg name="years" value="7500000"/>
<constructor-arg name="ultimateAnswer" value="42"/>
</bean>
记住,要使此功能开箱即用,你的代码必须启用debug标志来编译,以便Spring可以从构造器查找参数名称。如果你不能或者不想使用debug标志来编译你的代码,你可以使用@ConstructorProperties JDK注解来显式地命名你的构造器参数。示例类必须如下所示:
package examples;
public class ExampleBean {
// Fields omitted
@ConstructorProperties({"years", "ultimateAnswer"})
public ExampleBean(int years, String ultimateAnswer) {
this.years = years;
this.ultimateAnswer = ultimateAnswer;
}
}
基于Setter依赖注入
在调用无参构造器或者无参static
工厂方法实例化你的bean之后,通过容器调用bean上的setter方法完成基于Setter DI。
以下示例展示了只能通过使用纯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的DI。它也支持一些依赖已经通过构造器方式注入之后基于setter的DI。你使用BeanDefinition
的形式配置依赖,你可以将其与PropertyEditor
实例结合使用来将属性从一个格式转换为另一个格式。但是,大多数Spring用户并不直接使用(即程序化)这些类,而是使用XML bean
定义,注解的组件(即,使用@Component
,@Controller
等等注解的类)或者使用基于Java的Configuration
类的@Bean
方法。这些源也是被内部转换为BeanDefinition
实例并用于载入一个整个Spring IoC容器实例。
基于构造器还是基于Setter DI? 因为你可以混合基于构造器和基于Setter DI,根据经验,对于强制依赖使用构造器注入,对于可选依赖使用setter方法或者配置方法。注意,在setter方法上的@Autowired注解的使用可以用于使属性称为一个必须的依赖;但是,使用参数的程序化验证的构造器注入是优选的。
Spring 团队通常提倡构造器注入,因为它让你实现应用程序组件作为不可变的对象并确保必须的依赖项不为null
。而且,注入的构造器组件总是以完全初始化的状态返回给客户端(调用)代码。顺便说一句,大量的构造器参数是一个坏代码味道,这意味着类可能有太多责任,应该重构以更好的解决适当的关注点分割。
setter注入应该主要地用于可选的依赖,在此类内可以分配合理的默认值。除此之外,在代码使用依赖的地方必须执行非空检查。setter注入的一个好处是setter方法使该类的对象能够在以后重新配置或者重新注入。因此通过JMX MBean进行管理是setter注入的一个引人注目的用例。
使用对特定类具有意义的DI样式。有时,当处理你没有源的第三方类库时,可以为你作出选择。例如,如果第三方类没有暴露任何setter方法,然后构造器注入可能是唯一可用的DI形式。
依赖解析过程
容器执行如下bean依赖解析:
- 使用描述所有的bean的配置元数据创建和初始化的
ApplicationContext
。配置元数据可以使用XML,Java代码或者注解指定。 - 对于每一个bean,它的依赖关系以属性,构造器参数,或者静态工厂方法参数形式表示(如果你使用Spring,而不是平常的构造器)。当bean被实际被创建时,这些依赖被提供给该bean。
- 每一个属性或者构造参数都是要设置的值的实际定义,或者是对容器中另外一个bean的引用。
- 每一个属性或者构造器参数是一个值,此值从它的指定的格式被转换为实际该属性或者构造参数类型。默认情况下,Spring可以将一个字符串格式提供的值转换为所有内建类型,例如
int
,long
,String
,boolean
等等。
Spring容器在创建容器时验证每一个bean的配置。但是,直到bean被实际创建时才会设置bean属性本身。当创建容器时,创建单例范围的bean和设置成为预实例化(默认情况)。作用范围被定义在bean的作用范围(Bean Scopes)。除此之外,只有当请求bean时才会创建它。bean的创建可能会导致创建bean的图表,因为bean的依赖和它的依赖的依赖(等等)被创建和分配。注意在这些依赖中解析不匹配可能会显示延迟–即,在第一次创建受影响的bean时出现。
循环依赖 如果你主要使用构造器注入,它可以创建一个无法解决的循环依赖场景。
例如:类A通过构造器注入需要一个类B实例,类B通过构造器需要一个类A实例。如果配置了类A和类B相互注入的bean,在运行时Spring IoC容器检测该循环依赖,并抛出BeanCurrentlyInCreationException
。
一种可能的解决方案是修改一些类的源码为通过setter配置而不是构造器。或者,禁止构造器注入并只能使用setter注入。换句话说,尽管你可以通过setter注入配置循环依赖,这是不推荐的。
与典型情况的情况不同(没有循环依赖),在bean A和bean B之间的一个循环依赖强制在完全初始化自己之前将bean的其中一个被注入到另一个(典型的先有鸡还是先有蛋场景)。
你通常可以相信Spring会做正确的事情。它检测配置问题,例如在容器加载时,引用不存在的bean和循环依赖。当这个bean被正真创建时,Spring尽可能晚地设置属性和解析依赖。这表示,如果创建该对象或者他的依赖存在问题,已经正确的加载的Spring容器稍后可以在请求对象时生成一个异常-例如,bean由于缺少或者无效属性抛出一个异常。某些配置问题的可见性可能延迟,这就是ApplicationContext
实现默认预单例化单例bean的原因。在实际创建这些bean之前,创建这些bean需要花费一些前期时间和内存,你会在ApplicationContext
创建时发现问题,而不是稍后。你仍然可以覆盖该默认行为,以便单例bean延迟初始化,而不是提前初始化。
如果没有循环依赖存在,当多个合作bean已经被注入到依赖的bean,每一个合作的bean总是在被注入到依赖的bean之前被配置。这表示,如果bean A依赖bean B,Spring IoC容器完全配置bean B先于调用beanA的setter方法。换句话说,bean被实例化(如果它不是预实例化的单例),设置它的依赖和调用相关的生命周期方法(例如初始化方法配置或者初始化bean回调方法)。
依赖注入的示例
以下示例使用基于XML配置源数据进行基于setter的DI。Spring XML配置文件的小部分指定以下一些bean定义:
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例展示相应的Example
类:
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
在上面的示例中,声明了与XML文件中的指定的属性匹配的setter。以下示例使用基于构造器的DI:
<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"/>
以下示例展示了响应的ExampleBean
类:
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定义中指定的构造器参数作为Example
的构造器的参数使用。
现在考虑该示例的一个变体,告知Spring使用static
工厂方法来返回一个对象的实例,而不是使用一个构造器:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
以下示例展示相应的ExampleBean
类:
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;
}
}
static
工厂方法的参数通过<constructor-arg/>
元素提供,就像实际使用了构造器一样。通过工厂方法返回的类型不必与和包含static
工厂方法的类的类型一样(尽管,在此示例中,它一致)。可以以基本相同的方式使用实例(非静态的)工厂方法(除了使用factory-bean
属性而不是class
属性),所以这里我们不讨论这些细节。
1.4.2. 详细的依赖和配置
正如在前面章节提及的,你可以将bean属性和构造器参数定义为对其他管理的bean(合作者)的引用或者内联定义的值。为达到该目的,Spring的基于XML配置元数据在它的<property/>
和<constructor-arg/>
内支持子元素类型。
直接注入值(原始类型,String等等)
<property/>
元素的value
属性将属性或者构造器指定为人类可读的字符串表示表示形式。Spring的转换服务用于将这些值从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="misterkaoli"/>
</bean>
以下示例使用p-namespace用于更加简洁的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
https://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="misterkaoli"/>
</beans>
前面的XML是更简洁的。但是,拼接错误在运行时发现而不是设计时,除非当你创建bean定义时,你使用支持自动属性补全IDE(例如 IntelliJ IDEA或者Spring Tools for Eclipse)。强烈推荐这种IDE帮助。
你也可以配置java.util.Properties
实例,如下:
<bean id="mappings"
class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<!-- typed as a java.util.Properties -->
<property name="properties">
<value>
jdbc.driver.className=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mydb
</value>
</property>
</bean>
Spring容器通过使用JavaBean PropertyEditor
机制将<value />
元素内的文本转换到java.util.Properties
实例。这个是一个非常好的快捷方式,也是Spring团队喜欢使用的内嵌的<value/>
元素而不是value
属性样式的少数地方之一。
idref
元素
idref
元素是仅仅是一个误差校验方式来将容器中另外一个bean的id
(字符串值 - 没有引用)传入到<constructor-arg/>
或者<property/>
元素。以下示例展示如何使用它:
<bean id="theTargetBean" class="..."/>
<bean id="theClientBean" class="...">
<property name="targetName">
<idref bean="theTargetBean"/>
</property>
</bean>
前面的bean定义片段是完全等价于以下片段:
<bean id="theTargetBean" class="..." />
<bean id="client" class="...">
<property name="targetName" value="theTargetBean"/>
</bean>
第一种形式比第二种形式更合适,因为使用idref
标签让容器在部署时验证被引用的命名的bean是否真实存在。在第二种变化中,没有对此传入到clent
bean的targetName
属性的值执行验证。拼写错误只有当client
bean真正实例化时才会发现(极有可能发生致命的结果)。如果client
bean是一个原型bean,这个拼写错误和导致的异常可能只有容器被部署很久后发现。
在4.0 bean XSD中不在支持
idref
元素中的local
属性,因为它不在提供常规的bean
引用之上的值。当升级到4.0模式时,将你现有的idref local
引用修改为idref bean
。
一个常见的地方(至少早于Spring 2.0版本),其中<idref />
元素带来的价值是在ProxyFactoryBean
bean定义中在AOP 拦截器的配置。当你指定拦截器名称时,使用<idref />
元素防止你拦截器ID拼写错误。
引用其他bean(合作者)
ref
元素是<constructor-arg/>
或者<property/>
定义元素内的最后一个元素。这里,你设置bean的指定属性的值为引用另外一个容器管理的bean(合作者)。被引用的bean是要设置其属性的bean的一个依赖项,并且在设置属性之前根据需要进行初始化。(如果合作者是单例bean,它可能已经被容器初始化。)所有引用最终是其他对象的一个引用。作用范围和验证取决于你是否通过bean
或者parent
属性指定其他对象的ID或者名称。
通过<ref/>
标签的bean
属性指定目标bean是最常用的形式,并允许在同一个容器或者父容器中对任何bean的引用的创建,无论它是否在同一个XML文件中。bean
属性的值可能与目标bean的id
属性一样或者与目标bean的name
属性中一个相同。以下示例展示了如何使用ref
元素:
<ref bean="someBean"/>
通过parent
属性创建一个对bean的引用来指定目标bean,此bean是在当前容器的父容器中。parent
属性的值可能与目标bean的id
属性相同或者与目标bean的name
属性中一个相同。目标bean必须是在当前容器的父容器中。当存在一个容器的继承并且你想要使用与父bean相同名称的代理封装一个在父容器中现有的bean,你应该主要使用此bean的引用变体。以下一对列表展示了如何使用parent
属性:
<!-- in the parent context -->
<bean id="accountService" class="com.something.SimpleAccountService">
<!-- insert dependencies as required here -->
</bean>
<!-- in the child (descendant) context -->
<bean id="accountService" <!-- bean name is the same as the parent bean -->
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref parent="accountService"/> <!-- notice how we refer to the parent bean -->
</property>
<!-- insert other configuration and dependencies as required here -->
</bean>
在
ref
元素上的local
属性不在4.0 bean XSD支持,因为它不在提供常规bean
引用之上的值。当升级到4.0模式时,将已有的ref local
引用修改为ref bean
。
内部bean
在<property/>
或者<constructor-arg/>
元素内的<bean/>
元素定义了一个内部bean,如下示例所示:
<bean id="outer" class="...">
<!-- instead of using a reference to a target bean, simply define the target bean inline -->
<property name="target">
<bean class="com.example.Person"> <!-- this is the inner bean -->
<property name="name" value="Fiona Apple"/>
<property name="age" value="25"/>
</bean>
</property>
</bean>
一个内部bean定义不需要一个定义的ID或者名称。如果已经指定,容器不使用这样的值作为一个标识符。容器在创建时也忽略scope
标志,因为内部bean一直是匿名的并且通过外部bean创建的。它不可能独立访问内部bean或者将他们注入到合作bean,除了注入到封闭bean中。
作为一种极端情况,他可能从一个自定义的作用域接收销毁回调—例如,对于单例bean内包含的一个请求作用域的内部bean。内部bean示例的创建受到它的容器bean的限制,但是销毁回调让它参与到请求作用域的生命周期。这个不是常用的场景。内部bean通常只是共享他们的包含bean的作用域。
集合
<list/>
,<set/>
,<map/>
和<props/>
元素依次设置Java Collection
类型List
,Set
,Map
和Properties
属性和参数。以下示例展示如何使用他们:
<bean id="moreComplexObject" class="example.ComplexObject">
<!-- results in a setAdminEmails(java.util.Properties) call -->
<property name="adminEmails">
<props>
<prop key="administrator">administrator@example.org</prop>
<prop key="support">support@example.org</prop>
<prop key="development">development@example.org</prop>
</props>
</property>
<!-- results in a setSomeList(java.util.List) call -->
<property name="someList">
<list>
<value>a list element followed by a reference</value>
<ref bean="myDataSource" />
</list>
</property>
<!-- results in a setSomeMap(java.util.Map) call -->
<property name="someMap">
<map>
<entry key="an entry" value="just some string"/>
<entry key="a ref" value-ref="myDataSource"/>
</map>
</property>
<!-- results in a setSomeSet(java.util.Set) call -->
<property name="someSet">
<set>
<value>just some string</value>
<ref bean="myDataSource" />
</set>
</property>
</bean>
map 键或者值的值,或者set的值,也可以是任意以下元素:
bean | ref | idref | list | set | map | props | value | null
集合合并
Spring容器也支持合并集合。应用程序开发者可以定义一个父<List/>
,<map/>
,<set/>
或者<props/>
元素和有子<List/>
,<map/>
,<set/>
或者<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
包含子adminEmails
集合与父的adminEmails
集合合并的Properties
集合。以下列表展示结果:
administrator=administrator@example.com
sales=sales@example.com
support=support@example.co.uk
合并行为类似地应用到<list/>
,<map/>
和<set/>
集合类型。在<list/>
元素特定的情况,与List
集合类型(也就是说,值的ordered
集合的概念)关联的语义被保留。父值优先所有的子集合的值。在Map
,Set
和Properties
集合类型的情况,没有顺序存在。因此,对于容器内部使用关联的Map
,Set
和Properties
实现类型的集合类型,没有有效的排序语义。
集合合并的限制
你不能合并不同的集合类型(例如Map
和List
)。如果你尝试这样做,抛出一个适当的Exception
。merge
属性必须在较低的,继承的,子定义上指定。在一个父集合定义上指定merge
属性是多余的,不会导致所需的合并。
强类型集合
感谢java对泛型的支持,你可以使用强类型集合。也就是说,它可以声明一个Collection
类型,使其只能包含(例如)String
元素。如果你使用Spring来将一个强类型集合的Collection
依赖注入到一个bean,你可以利用Spring的类型转换在强类型Collection
实例元素在被添加到Collection
之前,将其转换为合理的类型。以下Java类和bean定义展示如何这样做:
public class SomeClass {
private Map<String, Float> accounts;
public void setAccounts(Map<String, Float> accounts) {
this.accounts = accounts;
}
}
<beans>
<bean id="something" class="x.y.SomeClass">
<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>
当something
bean的accounts
属性准备好用于注入时,关于强类型的Map<String,Float>
元素类型的这个泛化的信息通过反射获得。因此,Spring的类型转换基础设施将各种值元素识别为Float
类型,并且将字符串值(9.99
,2.75
,3.99
)被转换为实际的Float
类型。
Null和空字符串值
Spring将属性的空参数视为空字符串。以下基于XML配置元数据片段设置email
属性为空string
value(“”)
<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-namespace的XML 快捷方式
p-namespace让你使用bean
元素的属性(代替内嵌的<propert/y>
元素)来描述你的合作bean属性值,或者两者兼有。
Spring使用命名空间支持可扩展的配置格式,它是基于XML Schema定义。在本章节讨论的beans
配置格式在XML Schema文档中定义。但是,p-namespace没有在XSD文件中定义,它只存在Spring核心中。
以下示例展示两个XML片段(第一个使用标准的XML格式,第二个使用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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean name="classic" class="com.example.ExampleBean">
<property name="email" value="someone@somewhere.com"/>
</bean>
<bean name="p-namespace" class="com.example.ExampleBean"
p:email="someone@somewhere.com"/>
</beans>
示例展示了在bean定义中称为email
的p-namespace中的属性。告诉Spring包含了一个属性定义。正如前面提及的,p-namespace没有scheam定义,所以你可以设置将参数设置为属性的名字。
下面的示例包含两个多的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
https://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-namespace包含一个属性值,而且使用特殊的格式来声明属性依赖。但是,到一个bean定义使用<property name="spouse" ref="jane"/>
来创建beanjohn
引用bean jane
,第二个bean定义使用p:spouse-ref="jane"
作为一个参数来做完全相同的事。在这种情况中,spouse
是这个属性名称,因为-ref
部分表示这个不是一个直接值,而是对另一个bean的引用。
p-namespace不像标准的XML格式那样灵活。例如,声明属性引用的格式与
Ref
结尾的属性的冲突,但是标准的XML格式就不会。我们推荐你小心地选择你的方式并将此推荐传达到你的团队成员来防止产生同时使用所有三种方式的XML文档。
使用c-namespace的XML 快捷方式
类似于使用p-namespace的XML快捷方式,在Spring 3.1引入的c-namespace,允许用于配置构造器参数的内联参数,而不是内嵌的constructor-arg
元素。
以下示例使用c:
命名空间来做相同的事情,就像基于构造器依赖注入:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<!-- traditional declaration with optional argument names -->
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<!-- c-namespace declaration with argument names -->
<bean id="beanOne" class="x.y.ThingOne" c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree" c:email="something@somewhere.com"/>
</beans>
c:
命名空间使用与p:
相同的规范(用于bean引用的一个后缀-ref
)用于通过他们的名称设置构造器参数。类似地,它需要在XML文件中声明尽管它没有在XSD模式中定义(它存在于Spring核心内部)。
对于少见的情况,构造器参数名称是不可用的(通常,如果字节码没有使用调试信息编译),你可以回退到参数索引,如下:
<!-- c-namespace index declaration -->
<bean id="beanOne" class="x.y.ThingOne" c:_0-ref="beanTwo" c:_1-ref="beanThree"
c:_2="something@somewhere.com"/>
由于XML语法,索引表示法需要前面的
_
存在,因为XML参数名称不能以数字开头(尽管一些IDE允许)。一个相应的索引表示法对于<constructor-arg>
元素也是可用的,但并不常用,因为在那里声明的纯顺序通常就足够了。
在实践中,构造器解析机制在匹配参数中是非常高效的,所以除非你真正需要,我们推荐使用名称表示法贯穿你的配置。
混合属性名称
当你设置bean属性时,你可以使用混合或者内嵌的属性名称,只要所有的路径组件(除最终的属性名称外)不为null
.考虑以下bean定义:
<bean id="something" class="things.ThingOne">
<property name="fred.bob.sammy" value="123" />
</bean>
something
bean存在fred
属性,它有一个bob
属性,它有一个sammy
属性,然后该最终的sammy
属性被设置为123
的值。为了这个可以工作,在bean构造之后,something
的fred
属性和fred
的bob
属性必须不能为null
。否则,抛出NullPointerException
。
1.4.3. 使用depends-on
如果一个bean是另外一个bean的依赖,通常意味着一个bean被设置为另一个的属性。通常你使用在基于XML配置元属性中使用<ref />
元素完成。但是,有时bean之间的依赖关系不那么直接。一个示例是,当类中的静态初始化器需要被触发时,例如对数据库驱动注册。depends-on
参数可以显性地强制一个或者多个bean被初始化,在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" />
depends-on
参数可以指定初始化时的依赖和只在单例bean情况下相应的销毁时的依赖。在给定的bean本身被销毁之前,优先销毁定义了一个depends-on
与给定的bean关系的依赖bean。因此,depends-on
也可以控制停机顺序。
1.4.4. 延迟初始化bean
默认情况下,ApplicationContext
实现及早创建和配置所有的单例bean作为初始化过程一部分。通常,这个预实例化是合适的,在配置中或者周围的环境的错误会被立即发现,而不是在几小时甚至几天之后发现。当此行为不合理时,你可以通过标记bean定义为延迟初始化来预防预一个单例bean的预实例化。延迟初始化bean告诉IoC容器当首次请求时创建一个bean实例,而不是在启动时。
在XML中,此行为通过在<bean/>
元素上的lazy-init
属性控制,如下示例所示:
<bean id="lazy" class="com.something.ExpensiveToCreateBean" lazy-init="true"/>
<bean name="not.lazy" class="com.something.AnotherBean"/>
当前面的配置通过ApplicationContext
使用,当应用程序启动时,lazy
bean不会及早域实例化,但是not.lazy
bean是及早预实例化的。
但是,当一个延迟初始化的bean是一个单例非延迟初始化bean的依赖,ApplicationContext
在启动时创建延迟初始化bean,因为它必须满足单例的依赖。延迟初始化bean被注入到其他没有延迟初始化的单例bean中。
你也可以通过使用在<bean/>元素上的``default-lazy-init
属性控制延迟初始化在容器的级别,如下示例所示:
<beans default-lazy-init="true">
<!-- no beans will be pre-instantiated... -->
</beans>
1.4.5. 自动装配合作者
Spring容器可以自动注入合作bean的关系。你可以让Spring通过检查ApplicationContext
的内容为你的bean自动解析合作者(其他bean)。自动装配存在以下优势:
- 自动装配可以显著地减少指定属性或者构造器参数的需要。(其他机制,例如在本章节其他地方讨论的bean模板在这方面也很有价值)
- 自动装配可以随着对象的发展而更新配置。例如,如果你需要将一个依赖添加到一个类,无需你修改配置就可自动化满足此依赖。因此自动装配在开发期间是特别有用的,而不用在代码库变得更加稳定时切换到显示连接。
当使用基于XML配置元数据(请查看依赖注入),你可以使用<bean/>
元素的autowire
参数为bean定义指定自动装配的模式。自动装配功能有四个模式。你为每个bean指定自动装配这样就可以选择要自动装配的bean。以下表格描述四种自动装配模式:
模式 | 说明 |
---|---|
no | 默认,不自动装配。必须通过ref 元素定义bean引用。对于大型部署,修改默认设置是不推荐的,因为显性地指定合作者会提供更大的控制和清晰度。在某种程度上,它记录系统的结构。 |
byName | 通过属性名称自动装配。Spring使用与需要被注入的属性相同的名字查找一个bean。例如,如果一个bean定义被设置为通过名称自动注入并且它包含一个master 属性(即,他有一个setMaster(..) 方法),Spring查找一个命名为master 的bean定义并使用它来设置这个属性 |
byType | 如果在容器中明确存在属性类型的一个bean则自动装配属性。如果存在多个,抛出致命的异常,这表明你不能使用byType 自动装配该bean。如果没有匹配bean,什么也不会发生(不设置此属性) |
constructor | 类似于byType ,但是提供到构造器参数。如果在容器中不存在明确的一个构造器参数类型,会引起致命错误。 |
使用byType
或者constructor
自动装配模式,你可以装配数组和类型化集合。在这种情况下,将提供容器内所有匹配预期类型的自动装配候选者,以满足依赖关系。如果预期key类型是String
,你可以自动装配强类型的Map
实例。一个自动装配的Map
实例的值由所有匹配预期类型的bean实例组成,并且Map
实例的key包含相应的bean名称。
自动装配的限制和劣势
自动装配在项目中一致使用时效果最好。如果自动装配一般不使用,如果只使用它装配一个或者两个bean定义,可能会让开发者感到困惑。
考虑自动装配的限制和劣势:
- 在
property
和constructor-arg
设置中的显示依赖总会覆盖自动装配。你不能自动装配简单属性,例如原生类型,Strings
和Classes
(和这样简单属性的数组)。这个限制是设计出来的。 - 自动装配不如显示装配精确。尽管,正如在前面表格所指出的,Spring小心地避免在可能产生意外结果的模棱两可的情况下进行猜测。Spring管理的对象之间的关系不在显示地记录。
- 封装信息可能无法用于从Spring容器中生成文档的工具。
- 容器内的多个bean定义可以匹配由要自动装配的setter方法或者构造器参数指定的类型。对于数组,集合或者
Map
实例,这不一定是一个问题。但是,对于期望单个值的依赖项,这个不确定性不是任意解决的。如果没有唯一的bean定义可用,将抛出异常。
在后者场景中,你有几种选项:
- 放弃自动装配,支持显性装配。
- 通过将bean定义的
autowire-candidate
属性设置为false
来避免自动装配,正如在下一部分描述的。 - 通过设置bean定义的
<bean/>
元素的primary
属性为true
标示一个单独的bean定义为主要的候选。 - 使用基于注解的配置实现更加细粒度的有效控制,正如在基于注解的容器配置所描述的。
将bean从自动装配中排除
在每个bean的基础上,你可以将bean从自动装配中排除。在Spring的XML格式中,设置<bean/>
元素的autowire-candidate
属性为false
。容器使特定的bean定义自动装配基础设施不可用(包括注解格式配置例如@Autowired
)。
autowire-candidate
属性被设计为只影响基于类型的自动装配。它不影响通过名称的显式地引用,及时指定的bean没有标记为自动装配候选,也会得到解析。因此,如果名称匹配,通过名称的自动装配仍然会注入一个bean。
你也可以基于针对bean名称的格式匹配限制自动装配候选。顶级<bean/>
元素在它的default-autowire-candidates
参数内接收一个或者多个格式。例如,要限制自动装配候选状态到名称以Repository
结尾的任意bean,提供一个*Repository
的值。要提供多个格式,以逗号分隔列表定义他们。对于一个bean定义的autowire-candidate
参数显性的true
或者false
的值总是优先级高。对于这样的bean,格式匹配规则不生效。
对于你不想通过自动装配被注入到另外一个bean的那些bean这些技术是有用的。它不意味着一个排除的bean本身不能通过自动装配被配置。相反,bean本身并不是自动装配其他bean的候选者。
1.4.6. 方法注入
在大多数应用程序场景中,在容器中的大多数bean是单例的。当一个单例的bean需要与另一个单例bean合作或者一个非单例的bean需要与另外一个非单例的bean合作,你通常通过定义一个bean作为另外一个bean的属性来处理依赖。当bean的生命周期不同时则会出现一个问题。假设单例bean A需要使用使用非单例(原型类型)bean B,可能在每一个方法调用A。容器只创建一次单例bean A,但是这样只能获取一次机会设置属性。容器不能在每次需要Bean B的新实例时为bean A提供。
一种解决方案是放弃控制反转。你可以通过实现接口ApplicationContextAware
使bean A 感知容器,并每次bean A需要Bean B时通过使getBean("B")
调用容器请求bean B实例。以下示例展示这个方法:
// a class that uses a stateful Command-style class to perform some processing
package fiona.apple;
// Spring-API imports
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
public class CommandManager implements ApplicationContextAware {
private ApplicationContext applicationContext;
public Object process(Map commandState) {
// grab a new instance of the appropriate Command
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
protected Command createCommand() {
// notice the Spring API dependency!
return this.applicationContext.getBean("command", Command.class);
}
public void setApplicationContext(
ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
前面的是不可取的,因为业务代码感知和与Spring Framework藕合。方法注入(Method Injection),Spring IoC容器的一个稍微高级的特性,让你干净地处理此用例:
你可以在博客目录中阅读更多关于方法注入的动机。
查找方法注入
查找方法注入是容器覆盖容器管理的bean上的方法并返回容器中另一个命名的bean的查找结果的能力。此查找涉及一个原型类型的bean,正如在前面部分所描述的场景一样。Spring Framework通过使用从CGLIB类库的字节码生成来动态生成一个重写此方法的子类实现此方法注入。
- 为了此动态子类可以工作,Spring bean容器子类不能为
final
,并且要被重写的方法也不能是final
。- 单元测试一个有
abstract
方法的类需要你自己对此类进行子类化并且提供abstract
方法的stub实现。- 组件扫描也需要具体的方法,这需要具体的类来选取。
- 进一步关键的限制是查找方法不能与工厂方法一起工作,特别是不能和配置类中的
@Bean
方法一起工作,因为,在该情况下,容器不负责创建实例,因此不能动态地创建一个运行时生成的子类。
在前面代码片段中的CommandManager
类的情况中,Spring容器动态地重写了createCommand()
方法的实现。CommandManager
类不会有任何Spring依赖,正如重写的示例所示:
package fiona.apple;
// no more Spring imports!
public abstract class CommandManager {
public Object process(Object commandState) {
// grab a new instance of the appropriate Command interface
Command command = createCommand();
// set the state on the (hopefully brand new) Command instance
command.setState(commandState);
return command.execute();
}
// okay... but where is the implementation of this method?
protected abstract Command createCommand();
}
在包含要注入的方法的客户端类(这种情况的CommandManager
)中,要注入的方法需要以下格式的标记:
<public|protected> [abstract] <return-type> theMethodName(no-arguments);
如果方法是abstract
的,动态生成的子类实现此方法。否则,动态生成的子类重写在原始类中的具体的方法。考虑以下示例:
<!-- a stateful bean deployed as a prototype (non-singleton) -->
<bean id="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只要当它需要myCommand
bean的一个新实例,则会调用它自己的createCommand()
方法。如果实际需要的话,你必须小心的部署一个原生类型的myCommand
bean。如果它是一个单例的,每次返回myCommand
bean相同的实例。
或者,在基于注解的组件模型内,你可以通过@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();
}
或者,更常用的,你可以依赖目标bean针对查找方法声明的返回类型来解析:
public abstract class CommandManager {
public Object process(Object commandState) {
Command command = createCommand();
command.setState(commandState);
return command.execute();
}
@Lookup
protected abstract Command createCommand();
}
注意,你通常应该使用具体的stub实现声明这样注解的查找方法,为了他们可以与Spring的组件扫描规则兼容,其中默认情况下抽象类进行忽略。这个限制不能应用到显式地注册的或者显式引入的bean类。
访问不同的作用范围的目标bean的另一个方式是
ObjectFactory
/Provider
注入点。请查看将bean限定范围为依赖项
你也可以发现ServiceLocatorFactoryBean
(在org.springframework.beans.factory.config
包)也很有用。
任意方法替换
比查找方法注入稍微不好用的方法注入的形式是使用另外一个方法实现替换管理的bean中的任意方法的能力。你可以安全的调过本章节的剩余内容,直接你真正需要这个功能的时候。
使用基于XML配置的元数据,对于一个已经部署的bean,你可以使用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
因为参数的数量通常足够区分每一个可能的选择,这个快捷方式可以通过只允许输入匹配参数类型的最短字符串来节省大量输入。
1.5 bean作用范围
当你创建一个bean定义,你创建了一个用于创建该bean定义所定义的类的真正的实例的处方。一个bean定义是一个处方的想法是重要的,因为它意味着,与使用类一样,你可以从一个处方创建许多对象。
你不仅可以控制插入到一个从特别的bean定义创建的对象中的各种依赖项和配置值,而且还控制从特别的bean定义创建的对象的作用域。这种方式是强大的和灵活的,因为你可以选择通过配置你所创建的对象的作用域,而不必在Java类级别固定对象的作用域。bean可以被定义为已部署在多个作用域中的一个。Spring Framework支持六个作用域,其中的四个只有在你使用web-aware ApplicationContext
时可用。你也可以创建一个自定义作用域。
以下表格描述了支持的作用域:
作用域 | 描述 |
---|---|
singleton | (默认)将每一个Spring IoC容器的一个单例的bean定义作用域定义为单个对象实例。 |
prototype | 将单个bean定义的作用域定义为对象实例的任意数量 |
request | 将单个bean定义的作用域定义为单个HTTP请求的生命周期。也就是说,每一个HTTP请求有它的自己基于单个bean定义创建的bean的实例。只有在一个web-aware Spring ApplicationContext 内有效。 |
session | 将一个单个bean定义定义为HTTP Session 的生命周期的作用域。只有在一个web-aware Spring ApplicationContext 内有效。 |
application | 将一个单个bean定义定义为HTTP ServletContext 的生命周期的作用域。只有在一个web-aware Spring ApplicationContext 内有效。 |
websocket | 将一个单个bean定义定义为HTTP WebSocket 的生命周期的作用域。只有在一个web-aware Spring ApplicationContext 内有效。 |
从Spring 3.0之后,线程的作用域是可用的,但是默认情况下没有注册。要了解更多信息,请查看
SimpleThreadScope
的文档。对于关于如何注册这个或者任何其他的自定义作用域的说明,请查看使用自定义作用域。
1.5.1. 单例作用范围
只管理一个单例bean的共享的实例,并且所有对具有一个ID或者多个ID的bean的请求都将导致Spring容器返回一个特定的bean实例。
从另一个角度说,当你定义一个bean定义并且将它的作用范围定义为单例,Spring IoC容器精确创建通过bean定义所定义对象的一个实例。这个单例实例被存储在一个这样单例bean的缓存中,并且所后续对该命名的bean的请求和引用返回缓存的对象。以下图片展示单例作用域如何工作:
Spring的单例bean的概念与正如在Gang of Four(GoF)模式书定义的单例模式的概念不同。GoF代理硬编码一个对象的作用域这样每个ClassLoader仅有一个特定的类的对象被创建。Spring单例的作用域最好描述为每个容器和每个bean。这意味着,如果在单个Spring容器中你对一个特定的类定义了一个bean,Spring容器只创建一个通过该bean定义所定义的类的实例。在Spring中单例作用域是默认的作用域。在XML中要定义一个bean为单例的,你可以按照以下示例所示定义一个bean:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2. 原生类型作用范围
bean部署的非单例的原型作用域导致每次对特定的bean发出的请求时创建一个新的bean实例。也就是说,此bean被注入到另外一个bean或者你通过在容器中的getBean()
方法调用请求它。一般来说,对于有状态的bean你应该使用原型作用域,对于无状态bean你应该使用单例作用域。
以下图表举例说明了Spring原型作用域
(一个数据访问对象(DAO)通常不会配置为原型类型,因为典型的DAO不会持有任何会话状态。对于我们来说更容易重用单例图表的核心。)
以下示例使用XML定义了一个原型类型的bean:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype"/>
与其他作用域相比,Spring不会管理原型类型bean的完整的生命周期。容器实例化,配置和其他方式组装一个原型类型对象并将其交给客户端,而不需要该原型实例的进一步记录。这样,尽管初始化生命周期回调方法在所有对象都会被调用和作用域无关,在原型类型的情况中,配置的销毁生命周期回调不会被调用。客户端代码必须清除原型类型作用域的对象,并释放原型类型bean持有的代价高的资源。要让Spring容器释放原型作用域bean所持有的资源,尝试使用一个自定义的bean post-processor,它持有需要被清理bean的依赖。
有些方面,Spring容器对于一个原生类型作用域的bean的作用是对java new
操作符的替代品。超过该点的所有生命周期管理都必须由客户端管理(关于Spring容器的bean的生命周期的细节,请查看生命周期回调)。
1.5.3. 使用原生类型bean依赖的单例bean
当你使用具有依赖原生类型bean的单例作用域的bean,请注意依赖关系在实例化时被解析。这样,如果你将一个原生类型作用域的bean依赖注入到一个单例作用域的bean,一个新的原生类型bean被实例化,然后被依赖注入到单例bean。原生类型实例是单独的实例,一直被提供到单例作用域bean。
但是,假设你想要运行时单例作用域bean重复地获得一个原生类型作用域bean的新的示例。你不能将一个原生类型作用域的bean依赖注入到你的单例bean,因为当Spring容器实例化单例bean并解析和注入它的依赖时,该注入只发生一次。如果在运行时,你需要多个原生类型bean的新的实例,请查看方法注入。
1.5.4. Request, Session, Application, 和WebSocket Scopes
request
,session
,application
,websocket
范围只有你使用web相关的Spring ApplicationContext
(例如XmlWebApplicationContext
)实现时可用。如果你与常规的Spring IoC容器使用这些作用域,例如ClassPathXmlApplicationContext
,将抛出诉说关于未知bean作用域的IllegalStateException
。
初始的Web配置
为支持bean的request
,session
,application
和websocket
级别(web作用域bean)的作用域,在你定义你的bean之前需要一些次要的初始配置。(标准的作用域不需要初始设置:singleton
和prototype
)。
如何完成初始设置取决于你的特别的Servlet环境。
实际上,如果在Spring Web MVC内你访问作用域的bean,在Spring DispatcherServlet
处理的请求内,没有特殊的设置是必须的。DispatcherServlet
已经暴露所有相关的状态。
如果你使用Servlet web容器,使用Spring的DispatcherServlet
外处理的请求(例如,当使用JSF或者Struts),你需要注册org.springframework.web.context.request.RequestContextListener ServletRequestListener
。这个可以通过使用WebApplicationInitializer
接口程序化的方式完成。或者,将以下声明添加到你的web应用程序的web.xml
文件:
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果你的监听器设置有问题,考虑使用Spring的RequestContextFilter
。这个过滤器映射取决于web应用程序配置周围环境,所以你必须合理地修改它。以下列表展示了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
。这使得request和session作用域的bean在调用链更下游可用。
Request作用范围
考虑以下bean定义的XML配置:
<bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring容器对于每一次和每一个HTTP请求使用loginAction
bean定义创建一个LoginAction
bean的新实例。也就是说,loginAction
bean的作用域在HTTP请求级别。你可以根据需要任意修改所创建的实例的内部状态,因为从同一个loginAction
bean定义创建的其他实例不会看到这些状态变化。他们是特定于个别请求。当请求完成处理,request作用域的bean将废弃。
当使用驱动注解组件或者Java配置,RequestScope
注解可以用来分配request
作用域的组件。以下示例展示如何这样做:
@RequestScope
@Component
public class LoginAction {
// ...
}
Session作用范围
考虑以下对于bean定义的XML配置:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring容器通过在单个HTTP session
的生命周期内使用userPreferences
bean定义创建一个的UserPreferences
bean的新实例。换一句话说,userPreferences
bean有效地作用域在HTTP Session
级别。与使用request作用域bean一样,你可以改变你想要创建的实例的内部状态,已知其他HTTP Session
实例,正在使用从同一个userPreferences
bean定义创建的实例不会看到这些状态变化,因为他们是特定于一个单个的HTTP Session
。当HTTP Session
被最终废弃,作用域是HTTP Session
的bean也将被废弃。
当使用注解驱动组件或者Java配置,你可以使用@SessionScope
注解来分配seesion
作用域组件:
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application作用范围
考虑以下bean定义的XML配置:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring容器通过在整个web应用程序内使用appPreferences
bean定义创建一个AppPreference
bean的新的实例。也就是说,appPreferences
bean的作用域在ServletContext
级别并且以一个常规的ServletContext
属性存储。这有些类似于Spring单例bean但是有两个重要方式的不同:它是每个ServletContext
的一个单例,不是每个Spring ApplicationContext
(对此在任何给定的web应用程序中可能有几个),它实际是公开的,因为作为ServletContext
属性可见。
当使用注解驱动组件或者Java配置,你可以使用@ApplicationScope
注解来分配application
作用域组件。以下示例展示如何这样做:
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
WebSocket作用域
WebSocket作用域与WebSocket会话的生命周期关联,并在WebSocket应用程序上应用到STOMP,请查看WebSocket作用域了解详情。
将bean限定范围为依赖项
Spring IoC容器不仅管理你对象的实例,而且管理合作者的连接(或者依赖)。如果你想要(例如)将一个HTTP请求作用域bean注入到另外一个较长生命时长作用域的bean,你可以选择在作用域bean的位置注入一个AOP代理。也就是说,你需要注入一个代理对象来暴露与作用域对象相同的公共接口,但也可以从相关的作用域(例如HTTP请求)检索实际目标对象并将方法调用委托到实际对象。
你也可以在作用域为
singleton
的bean之间使用<aop:scoped-proxy/>
,然后引用通过一个可序列化的中间代理,从而能够在反序列化时重新获得目标单例bean。
当对作用域为prototype
的bean声明<aop:scoped-proxy/>
,在共享的代理上的每一个方法调用导致一个新的实例创建,然后将调用转发到该实例。
另外,作用域代理不是以生命周期安全的方式从较短作用域访问bean的唯一方法。你也可以声明你的注入点(也就是说,构造器或者setter参数或者自动装配的字段)作为ObjectFactory<MyTargetBean>
,允许getObject()
调用在每次需要时根据需求来检索当前实例–而不是保留实例或单独存储它。
作为一个扩展的变体,你可以声明ObjectProvider<MyTargetBean>
,它提供了几个额外的访问变体,包括getIfAvailable
和getIfUnique
。
这个被称为Provider
的JSR-330变体与Provider<MyTargetBean>
声明使用,并且相应的get()
调用用于每次检索尝试。请查看这里了解更多关于全面的JSR-330细节。
在以下示例中的配置只有一行,但是理解其背后的“为什么”以及“如何”是非常重要的:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.something.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.something.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
①这行定义了代理
要创建这样的一个代理,你插入一个子<aop:scoped-proxy/>
元素到作用域的bean定义(请查看选择创建的代理类型和基于XML模式配置)。为什么作用域在request
,session
和自定义作用域级别的bean定义需要<aop:scoped-proxy/>
元素?考虑以下单例bean定义和将它与你需要为上述提到的作用域定义的内容做对比(注意以下userPreferences
bean定义是不完整的):
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的示例中,HTTP Session-
作用域bean(userPreferences
)的引用注入到单例bean(userManager
)。这里的突出点是userManager
bean是单例的:每个容器只实例化一次,并且它的依赖(在这种情况中只有一个userPreferences
bean)也只被注入一次。这意味着userManager
bean只操作完全相同的userPreferences
对象(也就是说,即最初注入它的那个对象)。
当将一个较短生命周期作用域的bean注入到一个较长生命周期作用域的bean时,这个不是你想要的行为(例如将HTTP Session-
作用域的合作bean作为依赖注入到单例bean中)。相反,你需要一个单个的userManager
对象,并且对于HTTP Session
的生命周期,你需要一个特定的HTTP Session
的userPreferences
对象。这样,此容器创建一个对象,其暴露与UserPreferences
类完全相同的公共接口(理想情况下,对象是UserPreferences
实例),此对象可以从作用域机制(HTTP请求,Session
等等)获取真实的UserPreferences
对象。容器将这个代理对象注入到userManager
bean,它不会感知到UserPreferences
引用是一个代理。在这个示例中,当一个UserManager
实例调用依赖注入的UserPreferences
对象的方法,它实际调用了代理上的一个方法。然后此代理从HTTP Session
(在此情况下)拿到真实的UserPreferences
对象,并将方法调用委托给检索的真实的UserPreferences
对象。
因此,当将request-
和session
的作用域的bean注入到合作对象,你需要以下配置(正确和完整的),如下示例所示:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择要创建的代理的类型
默认情况下,当Spring容器创建使用<aop:scoped-proxy/>
元素标记的bean的代理时,创建基于CGLIB类代理。
CGLIB代理只拦截公共方法调用!在这样的代理上不会调用非公共方法。他们不被被委托给实际的作用域的目标对象。
或者,你可以为这样的作用域的bean配置Spring容器来创建标准的JDK基于接口的代理,通过指定<aop:scoped-proxy/>
元素的proxy-target-class
属性值为false
。使用JDK基于接口的代理意味着在你的应用程序类路径你不需要额外的类库来影响这样的代理。但是,它也意味着作用域bean的类必须实现至少一个接口,并且被注入到作用域bean的所有合作者必须通过它的一个接口引用此bean。以下示例展示基于接口的代理:
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.stuff.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.stuff.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
要了解关于选择基于类或者基于接口的代理的更多详细信息,请查看代理机制。
1.5.5. 自定义Scopes
bean作用域机制是可扩展的。你可以定义你自己的作用域或者甚至重新定义现有的作用域,尽管后者被认为是不好的实践,并且你不能重写内建的singleton
和prototype
作用域。
创建自定义Scopes
要将你的自定义作用域融入到Spring容器,你需要实现org.springframework.beans.factory.config.Scope
接口,在本章节描述它。对于如何实现你自己的作用域的想法,查看Spring Framework本身提供的Scope
实现和Scope
javadoc,它在更多细节上解释了你需要实现的方法。
Scope
接口有四个方法来从作用域获取对象,从作用域移除对象并让他们销毁。
例如,session作用域实现,返回seesion作用域的bean(如果它不存在,该方法将其绑定到会话以供将来参考之后,方法返回一个新的bean实例)。以下方法从底层作用域返回对象:
Object get(String name, ObjectFactory<?> objectFactory)
例如session作用域实现,从底层session中移除session作用域的bean。应该返回此对象,但是,如果使用特定名称的对象没有发现你可以返回null
。以下方法从底层作用域移除对象:
Object remove(String name)
以下方法注册了一个回调,当作用域销毁或作用域中特定的对象被销毁时,作用域应该调用此回调方法:
void registerDestructionCallback(String name, Runnable destructionCallback)
请查看javadoc或者Spring作用域实现了解更多关于销毁回调的信息。
以下方法获得底层作用域的对话标识符:
String getConversationId()
这个标志符在每一个作用域中是不同的。对于session作用域实现,这个标志符是session标志符。
使用自定义Scopes
在你写或者测试多个自定义Scope
实现之后,你需要是Spring容器感知到你的新的作用域。以下方法是中心方法使用Spring容器注册新的Scope
。
void registerScope(String scopeName, Scope scope);
这个方法注册在ConfigurableBeanFactory
接口上,该接口可以通过Spring附带大多数具体的ApplicationContext
实现的BeanFactory
属性使用。
registerScope(..)
方法的第一个参数是与作用域关联的唯一名称。在Spring容器本身中这样名称的示例是singleton
和prototype
。registerScope(..)
方法的第二个参数是一个你想要注册并使用的自定义Scope
实现的实际实例。
假设,你写了自定义Scope
实现,然后按照下一个示例注册它。
下一个示例使用
SimpleThreadScope
,Spring包含它,但是默认情况下,并没有注册它。对于你自己的自定义Scope
实现,指令是相同的。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
然后你可以创建一个bean定义并带有你自定义Scope
的作用域规则,如下:
<bean id="..." class="..." scope="thread">
使用自定义的Scope
实现,你就不局限于程序化作用域的注册。你也可以使用CustomScopeConfigurer
类声明式地完成注册,如下示例所示:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://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="thing2" class="x.y.Thing2" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="thing1" class="x.y.Thing1">
<property name="thing2" ref="thing2"/>
</bean>
</beans>
当为一个
FactoryBean
实现将<aop:scoped-proxy/>
放入到bean
定义内,它是被作用域的factory bean本身,而不是从getObject
返回的对象。
1.6. 自定义bean的特性
Spring Framework提供多个你可以用来定制bean性质的接口。本章节按照如下分组:
1.6.1. 生命周期回调
要与容器的bean的生命周期的管理交互,你可以实现Spring InitializingBean
和DisposableBean
接口。容器调用前者的afterPropertiesSet()
和后者的destroy()
来让bean执行你的bean初始化和销毁上的某些行为。
JSR-250
@PostConstruct
和@PreDestroy
注解通常被认为是在现代Spring应用程序中接收生命周期回调的最佳实践。使用这些注解意味着你的bean没有解耦到特性于Spring接口。要了解更多细节,请查看使用@PostConstruct
和@PreDestroy
。
如果你不想要使用JSR-250注解,你仍想要移除藕合,考虑init-method
和destroy-method
bean定义元数据。
在内部,Spring Framework使用BeanPostProcessor
实现来处理你发现的任意回调接口并调用合理的方法。如果你需要自定义特性或者其他Spring默认没有提供的生命周期行为,你可以实现BeanPostProcessor
。要了解更多信息,请查看容器扩展点。
除了初始化和销毁回调,Spring管理的对象也可以实现Lifecycle
接口,以便这些对象可以在启动和停机时参与,和被容器自身的生命周期驱动一样。
在本章节将描述生命周期回调接口。
初始化回调
当容器已经设置完成所有bean上的属性工作之后,org.springframework.beans.factory.InitializingBean
接口允许一个bean执行初始化工作。InitializingBean
接口指定一个单独的方法:
void afterPropertiesSet() throws Exception;
我们推荐不要使用InitializingBean
接口,因为它不必要的将代码与Spring藕合。或者,我们建议使用@PostConstruct
注解或者指定一个POJO初始化方法。在基于XML配置元数据情况,你可以使用init-method
属性来指定方法的名称,它有一个无返回值,无参数的签名。使用java配置,你可以使用@Bean
的initMethod
属性,请查看接收生命周期回调。
考虑以下示例:
<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 {
@Override
public void afterPropertiesSet() {
// do some initialization work
}
}
但是,前面两个示例的第一个没有将代码与Spring藕合。
销毁回调
当包含实现org.springframework.beans.factory.DisposableBean
接口的bean的容器被销毁时,允许此bean获得一个回调。DisposableBean
接口指定一个单独的方法:
void destroy() throws Exception;
我们推荐不要使用DisposableBean
回调接口,因为它不必要的将代码与Spring藕合。或者,我们建议使用@PreDestroy
注解或者指定一个bean定义支持的通用方法。使用基于XML的配置元数据,你可以使用</bean>
上的destroy-method
属性。使用java配置,你可以使用@Bean
的destroyMethod
属性。请查看接受生命周期回调。考虑以下定义:
<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 {
@Override
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
但是,前面两个定义的第一个不会将代码和Spring藕合。
你可以分配
<bean/>
元素的destroy-method
属性一个特殊的(interfaced)
的值,它指示Spring自动化检测在特定的bean类上的一个公共的close
或者shutdown
方法。(任何实现java.lang.AutoCloseable
或者java.io.Closeable
类都会匹配。)你也可以在<bean/>
元素上的default-destroy-method
属性设置特殊的(interfaced)
值来将该行为应用到bean的完整设置(请查看默认初始化和销毁方法)。注意这是java配置的默认行为。
默认初始化和销毁方法
当你写初始化和销毁方法回调,但没有使用Spring特定的InitializingBean
和DisposableBean
回调接口时,你通常写名称为init()
,initialize()
,dispose()
等等方法。理想情况下,这些生命周期回调方法的名称在整个项目是标准化的,以便所有开发者使用相同的方法名称并确保一致性。
你可以将配置Spring容器为在每一个bean上"查找"命名的初始化和销毁回调方法名称。这意味着,作为一个应用程序开发者的你,可以写你的应用程序类和使用一个命名为init()
的初始化回调,不必在每一个bean定义中配置一个init-method="init"
属性。当bean被创建时Spring IoC容器调用该方法(并且与前面描述的标准的生命周期回调合约一致)。此特性对于初始化和销毁方法回调也强制执行一致的命名规范。
假设你初始化回调方法被命名为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.");
}
}
}
你可以在一个bean中类似以下方式使用该类:
<beans default-init-method="init">
<bean id="blogService" class="com.something.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
在顶级<bean/>
元素属性上存在的default-init-method
属性导致Spring IoC容器识别出在bean类上称为init
方法作为初始化方法回调。当bean被创建和装配时,如果bean类有这样的方法,它会在合适的时机被调用。
你可以通过使用在顶级<bean/>
元素上的default-destroy-method
属性配置类似地销毁方法回调(即,使用XML)。
已有bean类已经有与约定不同的命名的回调方法,你可以通过使用<bean/>
本身的init-method
和destroy-method
属性指定方法名称覆盖默认值。
Spring容器保证一个已配置的初始化回调在bean与依赖一起被提供之后立即被调用。因此,初始化回调是在未处理的bean引用上被调用的,这意味着AOP拦截器等还不会应用到bean。首先一个目标bean被完全创建,然后应用一个AOP代理(例如)与它的拦截器链。如果目标bean和代理被分开定义的,你的代码甚至可以绕过代理,与未处理的bean交互。因此,它将拦截器应用到init
方法是不一致的,因为这样做将目标bean的生命周期与它的代理或者拦截器藕合,并且当你的代码直接与未处理的目标bean交互时留下奇怪的语义。
结合生命周期机制
从Spring 2.5以来,你有三个选项控制bean生命周期行为:
InitializingBean
和DisposableBean
回调接口- 自定义
init()
和destroy()
方法 @PostConstruct
和@PreDestroy
注解。你可以结合这些机制来控制一个给定的bean。
如果对一个bean配置了多个生命周期机制,每一个机制使用不同的方法名称配置,然后每一个配置的方法以本说明后的列表的顺序运行。但是,如果相同的方法名称被配置-例如,初始化方法的
init()
,用在了多个生命周期机制,该方法只运行一次,正如在前面章节所解释的一样。
相同的bean配置的多个生命周期机制,使用了不同的初始化方法,如下被调用:
@PostConstruct
注解的方法- 通过
InitializingBean
回调接口定义的afterPropertiesSet()
- 自定义配置的
init()
方法
以相同的顺序调用销毁方法:
PreDestroy
注解的方法- 通过
DisposableBean
回调接口定义的destroy()
方法 - 一个自定义配置的
destroy()
方法
启动和停机回调
Lifecycle
接口为有他们自己生命周期要求的任何对象定义了必不可少的方法(例如启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何Spring管理的对象可以实现Lifecycle
接口。然后,当ApplicationContext
本身接收到开始和停止信号(例如,对于在运行时停止和重启场景),它将这些调用关联到所有在该上下文定义的Lifecycle
实现。它通过委托给LifecycleProcessor
做到这些,在以下列表展示:
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
注意,LifecycleProcessor
是本身的Lifecycle
接口的继承。它也添加了两个其他方法用于响应上下文刷新和关闭。
注意,常规的
org.springframework.context.Lifecycle
接口是一个显性的启动和停止通知的普通约定,并不意味着在上下文刷新时自动启动。对于在一个特定的bean(包含启动阶段)的启动上细粒度的控制,考虑实现org.springframework.context.SmartLifecycle
代替。
而且,请注意停止通知并不保证销毁之前触发。在常规的停机时,所有的Leftcycle
bean在传播常规销毁回调之前,首先接收一个停止通知。但是,在上下文的生命时间期间的热刷新或者在停止刷新尝试时,只有destroy方法被调用。
启动和停机调用顺序可能很重要的。如果一个“depend-on”关系存在任意两个对象之间,依赖方在它的依赖之后启动,并且它在它的依赖之前关闭。但是,有时候,直接依赖关系是未知的。你可能只知道某个类型的对象应该在类外一个类型的对象之前启动。在这些情况下,SmartLifecycle
接口定义另外一个选项,即在它的超级接口Phased
上定义的getPhase()
方法。以下列表展示了Phased
接口的定义:
public interface Phased {
int getPhase();
}
以下列表展示SmartLifecycle
接口的定义:
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方法接收一个回调。在该实现的停机过程完成后,任何实现都必须调用该回调的run()
方法。这样就可以在需要的地方启用了异步停机,因为LifecycleProcessor
接口的默认实现DefaultLifecycleProcessor
会等待每一个阶段内的调用该回调对象组的超时时间值。默认每个阶段超时时间是30秒。你可以通过在上下文中定义名称为lifecycleProcessor
来覆盖默认值过程实例。如果你只想要修改超时时间,定义以下将满足:
<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()
方法的返回boolean值。如果为true
,在此时启动该对象而不是等待一个上下文或者它本身start()
方法显性调用(不像上下文刷新,对于一个标准的上下文实现,上下文启动不会自动发生)。phase
值和任何“depend-on”关系决定启动顺序,正如上面所描述的一样。
在非web应用程序中优雅关闭Spring IoC容器
本章节只适用于非web应用程序。Spring的基于web的
ApplicationContext
实现已经有了适当的代码,当相关的web应用程序关闭时优雅关闭Spring IoC容器。
如果你在非web应用程序环境中使用Spring的IoC容器(例如,在丰富的客户端桌面环境),使用JVM注册一个关闭钩子。这样做确保优雅停机并调用在你的单例bean上相关的destroy方法,以便所有的资源都被释放。你仍然必须正确地配置和实现这些destroy回调。
为注册一个关闭钩子,调用在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.2. ApplicationContextAware
和BeanNameAware
当ApplicationContext
创建一个实现了org.springframework.context.ApplicationContextAware
接口的对象实例,此实例被提供一个ApplicationContext
的一个引用。以下列表展示了ApplicationContextAware
接口的定义:
public interface ApplicationContextAware {
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
这样,bean可以通过ApplicationContext
接口或者通过将引用指向已知的此接口子类(例如ConfigurableApplicationContext
,其暴露了额外的功能)程序化地操作创建他们的ApplicationContext
。一种用途是对其他bean进行程序化检索。有时,这个功能是有用的。但是,通常情况下,你应该禁用它,因为它将代码和Spring藕合,并且没有遵守控制反转风格,这里合作者应该作为属性被提供。ApplicationContext
的其他方法提供文件资源的访问,推送应用程序事件和访问MessageSource
。在额外ApplicationContext
功能描述这些额外的特性。
自动装配是另外一种获取ApplicationContext
的引用的替代方案。传统的contructor
和byType
注解模式(正如在自动装配合作者描述那样)可以分别为构造器参数或setter方法参数提供ApplicationContext
类型的依赖。为了更加灵活,可以使用基于注解的自动装配特性,包含自动装配字段和多个参数方法的能力。如果你这样做了,ApplicationContext
自动装配到字段,构造器参数,或者方法属性,如果有问题的字段,构造器和方法携带@Autowired
注解,则他们期望ApplicationContext
类型.
当ApplicationContext
创建了一个实现org.springframework.beans.factory.BeanNameAware
接口的类,此类被提供了在他的关联的对象定义的名称的引用。以下列表展示了BeanNameAware接口的定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
回调被调用在普通bean属性填充之后,但是在初始化回调之前,例如,InitializingBean.afterPropertiesSet()
或者自定义init方法。
1.6.3. 其他Aware
接口
除了ApplicationContextAware
和BeanNameAware
(前面所讨论的)之外,Spring也提供了广泛的Aware
回调接口,这样让bean向容器表明他们需要一个特定的基础设施依赖项。一般地,名称表明依赖类型。以下表格汇总了大多数重要的Aware
接口:
名称 | 依赖注入 | 解释 |
---|---|---|
ApplicationContextAware | Declaring ApplicationContext | ApplicationContextAware 和BeanNameAware |
ApplicationEventPublisherAware | 封闭的 ApplicationContext 事件推送器 | 额外的ApplicationContextAware 功能 |
BeanClassLoaderAware | 用于加载bean类的Class加载器 | 实例化bean |
BeanFactoryAware | 声明BeanFactory | BeanFactory API |
BeanNameAware | 声明bean的名称 | ApplicationContextAware 和BeanNameAware |
LoadTimeWeaverAware | 在载入时处理类定义的定义的Weaver | [使用Spring框架中AspectJ载入时织入](#Load-time_Weaving_with_AspectJ in_the_Spring_Framework) |
MessageSourceAware | 为解决消息传输配置的策略(支持参数化和国际化) | 额外的ApplicationContextAware 功能 |
NotificationPublisherAware | Spring JMX通知推送器 | Notifinations |
ResourceLoaderAware | 用于低级别访问资源配置的加载器 | Resources |
ServletConfigAware | 容器当前运行的ServletConfig ,只有在web-aware的Spring ApplicationContext 中有效 | Spring MVC |
ServletContextAware | 容器当前运行的ServletContext ,只有在web-aware的Spring ApplicationContext 中有效 | Spring MVC |
再次注意,使用这些接口将你的代码与Spring API绑定并没有遵循控制反转风格。因此,我们推荐将他们用于需要程序化访问容器的基础设施bean。
1.7. bean定义继承
bean定义可以包含很多配置信息,包括构造参数,属性值和特定的容器信息,例如初始化方法,静态工厂方法名称等等。一个子bean定义继承来自父定义配置数据。子定义可以根据需要重写一些值或者添加其他值。使用父子bean定义可以节省大量的输入。实际上,这是一个模板的形式。
如果你以编程的方式使用ApplicationContext
接口,通过使用ChildBeanDefinition
类表示子bean定义。大多数用户不会在这个级别上使用他们。相反,他们在一个类内声明式地配置bean定义,例如,ClassPathXmlApplicationContext
.当你使用基于XML配置元数据,你可以使用parent
属性表明一个子bean定义,指定parent bean作为这个属性的值。以下示例展示如何这样做:
<bean id="inheritedTestBean" abstract="true"
class="org.springframework.beans.TestBean">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize"> ①
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
①注意:parent
属性。
如果没有指定,子bean定义使用来自父定义的bean类也可以覆盖它。在后者情况,子bean类必须与父类兼容(即,它必须接收父的属性值)。
子bean定义从父类继承作用范围,构造器参数值,属性值和方法重写,并且可以添加新值。你指定的任何作用域,初始化方法,销毁方法,或者static
工厂方法设置重写相应的父设置。
其余的设置总是从子定义中获取:depends on,自动装配模式,依赖检查,单例和延迟初始化。
前面的示例中通过使用abstract
属性显式地将父bean定义标记为abstract。如果父定义没有指定一个类,显式地将父bean定义标记为abstratct
是有必须的,如下示例所示:
<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
。当一个定义是abstract
时,它只能用于作为一个为子定义的父定义服务的纯净的模板bean定义使用。尝试单独使用这样的abstract
父bean,将其做为另一个bean的ref属性的引用或者使用父bean ID做一个明显的getBean
调用,将返回一个错误。类似地,容器的内部preInstantiateSingletons()
方法忽略作为抽象所定义的bean定义。
默认情况下,
ApplicationContext
预实例化所有单例。因此,如果你有一个(父)bean定义,你打算只是将它作为一个模版使用是非常重要的(至少对于单例bean),并且此定义指定一个类,你必须确保设置abstract
属性为true
,否则应用程序上下文将实际(尝试)预初始化此abstract
bean。
1.8. 容器扩展点
通常情况下,一个应用程序开发者不需要子类ApplicationContext
实现类。相反,Spring IoC容器可以通过可插入特殊的集成接口实现来扩展。下面几个章节描述这些集成接口。
1.8.1. 使用BeanPostProcessor
自定义bean
BeanPostProcessor
接口定义回调方法,你可以实现他们提供你自己的(或者覆盖容器默认的)实例化逻辑,依赖解决方案逻辑等等。如果你想要在Spring容器完成实例化,配置和初始化一个bean之后,实现一些自定义逻辑,你可以插入一个或者多个自定义BeanPostProcessor
实现。
你可以配置多个BeanPostProcessor
实例,并且你可以通过设置order
属性控制在这些BeanPostProcessor
实例运行中的顺序。只有当BeanPostProcessor
实现Ordered
接口时,你才可以设置此属性。如果你写你自己的BeanPostProcessor
,你也应该考虑实现Ordered
接口。要了解更多明细,请查看BeanPostProcessor
和Ordered
接口javadoc。也请查看在BeanPostProcessor
实例编程化的方式注册。
BeanPostProcessor
实例对bean(或者对象)实例进行操作。即,Spring IoC容器实例化一个bean实例,然后BeanPostProcessor
实例执行他们的工作。
BeanPostProcessor
实例是作用域的每个容器。只有你使用容器分层才有意义。如果你在一个容器中定义一个BeanPostProcessor
,它将只对该容器中的bean做后置处理。换句话说,在一个容器中被定义的bean不会通过在另外一个容器中定义的BeanPostProcessor
被后置处理,即使两个容器都属于相同的层次结构。
要修改实际的bean定义(即,定义bean的设计图),你需要使用一个BeanFactoryPostProcessor
,正如在使用BeanFactoryPostProcessor
自定义配置元数据所描述的那样。
org.springframework.beans.factory.config.BeanPostProcessor
接口恰好包含两个回调方法。当这样的一个类被注册为容器的后置处理器,对于每一个通过容器创建的bean实例,在容器初始化方法(例如InitializingBean.afterPropertiesSet()
或者任何声明的init
方法)被调用之前和任何bean初始化回调之后,后置处理器从容器中获取一个回调。后置处理器可以处理bean实例 的任何行为,包括完全忽略回调。bean后置处理器通常检查回调接口,或者它可能使用代理封装一个bean。一些Spring AOP底层设施类作为bean后置处理器实现,为了提供代理封装逻辑。
ApplicationContext
自动化检测实现BeanPostProcessor
接口的在配置元数据中定义的任何bean。ApplicationContext
将这些bean注册为后置处理以便稍后在创建bean时可以调用他们。bean后置处理可以以与其他bean相同的形式部署到容器中。
注意,当在一个配置类上通过使用@Bean
工厂方法声明一个BeanPostProcessor
,工厂方法的返回类型应该是实现类本身或者至少org.springframework.beans.factory.config.BeanPostProcessor
接口,清晰地表明该bean的后置处理器本质。否则,在完全创建它之前,ApplicationContext
不能自动检测它。因为BeanPostProcessor
需要较早被实例化,为了应用于上下文中的其他bean的初始化,此这种早期类型检测是极其重要的。
程序化注册
BeanPostProcessor
尽管
BeanPostProcessor
注册推荐的方法是通过ApplicationContext
自动检测(正如早前描述的那样),你可以通过使用addBeanPostProcessor
方法程序化地针对ConfigurableBeanFactory
注册他们。当你需要在注册之前评估条件逻辑或者甚至在分层跨上下文之间复制bean后置处理器时,这个可能有用。但是,请注意,程序化地添加的BeanPostProcessor
实例不遵守Ordered
接口。这里,它是注册的顺序及决定了执行的顺序。也要注意程序化注册的BeanPostProcessor
实例一直在这些通过自动检测注册之前被处理,不管任何显式地顺序。
BeanPostProcessor
实例和AOP自动代理实现
BeanPostProcessor
接口的类是特殊的并且容器对他们进行不同的处理。所有的BeanPostProcessor
实例和他们直接应用的bean在启动时被实例化,作为ApplicationContext
的特殊启动阶段的一部分。接下来,所有的BeanPostProcessor
实例按照排序的方式被注册,并应用到容器中的所有其他bean。因为AOP自动代理本身作为BeanPostProcessor
的一个实现,所以无论BeanPostProcessor
实例或者他们直接引用的bean都不符合自动代理的条件,因此,不会有切面织入他们。
对于任何这样的bean,你应该看有信息性的日志信息:Bean someBean is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)
。
如果你有bean通过使用自动装配或@Resource
(它可能会回退到自动装配)封装到你的BeanPostProcessor
),当查找类型匹配的依赖候选人时,Spring可能访问未预期的bean,因此使他们有资格自动代理,或者其他种类的bean后置处理器。例如,如果你有一个使用@Resource
注解的依赖,其注解的位置的字段或者setter名称没有直接与bean声明的名称相对应,并且没有使用名称属性,Spring根据类型访问其他bean来匹配他们。
以下示例展示在Application
中如何写,注册和使用BeanPostProcessor
实例。
示例: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;
}
}
以下beans
元素使用InstantiationTracingBeanPostProcessor
:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
https://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 = ctx.getBean("messenger", Messenger.class);
System.out.println(messenger);
}
}
上面应用程序的输出类似于以下内容:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
示例:AutowiredAnnotationBeanPostProcessor
使用回调接口或者注解与自定义的BeanPostProcessor
实现结合是一种常用的扩展Spring IoC容器的手段。示例为Spring的AutowiredAnnotationBeanPostProcessor
-一个Spring发行版附带的BeanPostProcessor
实现,可以自动装配注解的字段,setter方法和任意的配置方法。
1.8.2. 使用BeanFactoryPostProcessor
自定义配置元数据
下一个扩展点是我们看下org.springframework.beans.factory.config.BeanFactoryPostProcessor
。此接口的含义类似于BeanPostProcessor
,有一个主要的不同:BeanFactoryPostProcessor
操作bean配置元数据。即,Spring IoC容器允许BeanFactoryPostProcessor
读取配置元数据并在容器实例化任何bean之前隐式地修改它,除了BeanFactoryPostProcessor
实例。
你可以配置多个BeanFactoryPostProcessor
实例,并且你可以通过设置order
属性控制这些BeanFactoryPostProcessor
实例运行的顺序。但是,只有当BeanFactoryPostProcessor
实现Ordered
接口时,你才能设置此属性。如果你写你自己的BeanFactoryPostProcessor
,你应该考虑实现Ordered
接口。请查看BeanFactoryPostProcessor
和Ordered
接口了解更多详情。
如果你想要修改实际的bean实例(即,从配置元数据创建的对象),然后你需要使用
BeanPostProcessor
(在使用BeanPostProcessor
定义bean中前面的描述)。虽然在技术上在BeanFactoryPostProcessor
内可以使用bean实例(例如,使用BeanFactory.getBean()
),这样做导致过早的bean实例化,违反了标准容器生命周期。这可能导致副作用,例如绕过bean后置处理。
而且,BeanFactoryPostProcessor
实例作用域是针对每个容器。只有你使用容器分层结构这样才有意义。如果你在一个容器中定义一个BeanFactoryPostProcessor
,它只应用于该容器中的bean定义。在一个容器中的bean定义不会被另外一个容器中的BeanFactoryPostProcessor
实例后置处理,尽管两个容器是相同的分层结构的一部分。
一个bean工厂后置处理器在ApplicationContext
内声明,则它是自动执行的,为了将变化应用到定义容器的配置元数据。Spring包含多个预定义的bean工厂后置处理器,例如PropertyOverrideConfigurer
和PropertySourcePlaceholderConfigurer
。你也可以使用自定义的BeanFactoryPostProcessor
–例如,注册自定义属性修改器。
ApplicationContext
自动检测部署到其中实现BeanFactoryPostProcessor
接口的任何bean。在合理的时间,它将这些bean作为bean工厂后置处理器使用。你可以像部署其他bean一样部署这些后置处理器bean。
正如
BeanPostProcessor
一样,你通常不希望将BeanFactoryPostProcessor
配置为延迟初始化。如果没有其他bean引用一个Bean(Factory)PostProcessor
,该后置处理器将根本不会实例化化。因此,标记它为延迟初始化将被忽略,并且Bean(Factory)PostProcessor
将及早实例化化,尽管你在你的<beans />
元素的声明上将default-lazy-init
属性设置为true
。
示例:类名称替换PropertySourcesPlaceholderConfigurer
你可以使用PropertySourcesPlaceholderConfigurer
通过使用标准的Java Properties
格式,将bean定义中的属性值外部化到单独的文件中。这样做,部署应用程序的人员就可以定制与环境相关的属性,例如数据库URL和密码,就没有修改容器的核心XML定义文件的复杂性和风险。
考虑以下基于XML配置元数据片段,定义了使用占位符值的DataSource
:
<bean class="org.springframework.context.support.PropertySourcesPlaceholderConfigurer">
<property name="locations" value="classpath:com/something/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>
这个示例展示了来自外部Properties
文件配置的属性。在运行时,PropertySourcesPlaceholderConfigurer
被用于替换DataSource的一些属性的元数据。要替换的值被指定为格式${property-name}
的占位符,它遵循Ant和Log4j和JSP EL风格。
实际的值来自使用标准的Java Properties
格式的另外一个文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,运行时,${jdbc.username}
使用值’sa’替换,并且相同地应用到其他匹配属性文件中的key的占位符的值。PropertySourcesPlaceholderConfigurer
检查在bean定义中大多数属性和参数的占位符。此外,你可以定制占位符的前缀和后缀。
使用在Spring 2.5中介绍的context
命名空间,你可以使用专业的配置元素配置属性占位符。你可以在location
属性中以逗号分隔的列表提供一个或者多个位置列表,如下示例所示:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer
不仅查找你指定的Properties
文件的属性。默认情况下,如果它不能在特定的属性文件中查找到属性,它就检查Spring Environment
属性和常规的System
属性。
你也可以使用
PropertySourcesPlaceholderConfigurer
来替代类名称,当在运行时,你必须选择一个特别的实现类,这种用法有时是有用处的。以下示例展示了如何这样做:<bean class="org.springframework.beans.factory.config.PropertySourcesPlaceholderConfigurer"> <property name="locations"> <value>classpath:com/something/strategy.properties</value> </property> <property name="properties"> <value>custom.strategy.class=com.something.DefaultStrategy</value> </property> </bean> <bean id="serviceStrategy" class="${custom.strategy.class}"/>
如果在运行时此类不能被解析为有效的类。bean即将被创建时解析失败,对于一个非延迟初始化的bean发生在
ApplicationContext
的preInstantiateSingletons()
阶段。
示例:PropertyOverrideConfigurer
PropertyOverrideConfigurer
,另外一个bean factory后置处理器,类似于PropertySourcesPlaceholderConfigurer
,但是和后者不同的是,bean属性的最初的定义可以有默认值或者根本没有值。如果一个覆盖的Properties
文件没有某个bean属性的条目,则使用默认的上下文定义。
注意此bean定义不知道已经被覆盖,所以从XML定义文件中不会立即看出正在使用覆盖配置器。在相同的bean属性定义了不同值的多个PropertyOverrideConfigurer
实例的情况,由于覆盖机制,会取用最后一个。
属性文件配置行使用以下格式:
beanName.property=value
以下列表展示了一个格式的示例:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可以与包含一个有driver
和url
属性名为dataSource
bean的容器定义一起使用。
也支持混合的属性名称,只要路径的每一个组件(除了被覆盖的最终属性)都是非空的(可能由构造器初始化)。在以下示例中,tom
bean的fred
属性的bob
属性的sammy
属性设置为标量值123
:
tom.fred.bob.sammy=123
指定的覆盖值总是字面值。他们不会被转换成bean引用。当在XML bean定义中的原始值指定一个bean引用也应用此约定。
使用Spring 2.5介绍的context
命名空间,它可以使用专业的配置元素配置属性覆盖,正如以下实例所示:
<context:property-override location="classpath:override.properties"/>
1.8.3. 使用FactoryBean定制实例化逻辑
你可以为本身是工厂的对象实现org.springframework.beans.factory.FactoryBean
接口。
FactoryBean
接口是可插入到Spring IoC容器的实例化逻辑的点。如果你有复杂的初始化代码,使用java更好的表达,而不是(可能)冗长的XML,你可以创建你自己的FactoryBean
,将复杂的初始化写入该类,然后将你的自定义FactoryBean
插入到容器。
FactoryBean<T>
接口提供三个方法:
T getObject()
:返回此工厂创建对象的实例。此实例可能被共享,取决于此工厂是否返回单例或者原型类型。boolean isSingleton()
:如果此FactoryBean
返回单例返回true
,否则返回false
。此方法的默认实现返回true
。Class<?> getObjectType()
:返回getObject()
方法返回的对象类型或者如果此类型提前未知则返回null
。
FactoryBean
概念和接口在Spring Framework内许多位置使用。Spring本身带有超过50多个FactoryBean
接口的实现。
当你需要请求容器一个实际的FactoryBean
实例本身而不是它产生的bean时,当调用ApplicationContext
的getBean
方法时,bean的id
加上引用符号(&
)。对于一个给定的id
为myBean
的BeanFactory
,在容器上调用getBean("myBean")
返回FactoryBean
的制品,因此,调用getBean("&myBean")
返回FactoryBean
实例本身。
1.9. 基于注解的容器配置
配置Spring注解比XML更好吗? 基于注解配置的引入引发了这种方法是否“好于”XML?的问题。简短的答案是”视情况而定“。长一点的回答是每一个方式都有支持者和反对者,通常情况下,这个都是由开发者决定哪一个策略更适合他们。由于他们定义的方式,注解在他们声明中提供了大量的上下文,导致更短和更简洁的配置。但是XML擅长于在不修改源代码和重新编译他们的情况下连接组件。有一些开发者更喜欢让连接靠近源代码,而另外一些人认为注解类不在是POJO,并且,此外,配置变得更分散和难以控制。
无论那种选择,Spring可以适应两者风格,甚至将他们混合到一起。值的指出的是,通过它的JavaConfig选项,Spring允许以非侵入的方式使用注解,无需接触目标组件源代码,并且对于工具而言,所有的配置风格由Spring Tools for Eclipse支持。
基于注解配置提供了XML设置的一个可替代方案,它依赖字节码源数据来连接组件而不是尖括号声明。代替使用XML来描述bean连接,开发者通过使用在相关类、方法或者字段声明上进行注解移动配置到组件类本身。正如在前面示例AutowiredAnnotationBeanPostProcessor
中所提到的,使用BeanPostProcessor
与注解结合是扩展Spring容器常用的想法。例如,Spring 2.5引入基于注解的方法来驱动Spring的依赖注入。本质上,@Autowired
注解提供与在Autowiring Collaborators相同的功能,但是具有更细粒度的控制和广泛的适用性。Spring 2.5也添加了对JRS-250的支持,例如@PostConstrust
和@PreDestroy
。Spring 3.0也提供了对在jakarta.inject
包中包含的JRS-330注解的支持(Java的依赖注入),例如@Inject
和@Name
。关于这些注解的细节可以在相关章节找到。
注解注入在XML注入之前执行。这样,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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
<context:annotation-config/>
元素隐式地注册了以下后置处理器:
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
PersistenceAnnotationBeanPostProcessor
EventListenerMethodProcessor
<context:annotation-config/>
只在定义它的相同的应用程序上下文中查找bean上的注解。这意味着,如果你将<context:annotation-config/>
放入到DispatcherServlet
的WebApplicationContext
,它只在的控制器(controller)中查找@Autowired
bean,并不在你的服务中查找。请查看DispatcherServlet
了解更多细节。
1.9.1. 使用@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
注解不在是必须的。但是,如果多个构造器可用并没有primary/default构造器,至少一个构造器使用@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;
}
// ...
}
确保你的目标组件(例如,
MovieCatalog
和CustomerPreferenceDao
)通过类型被一致性声明,此类型是你用于你的@Autowired
注解的注入点。否则,注入可能由于运行时”no type match found“错误而失败。
对于通过类路径扫描定义的XML的或者发现的组件类,容器通常预先知道具体的类型。但是,对于@Bean
工厂方法,你需要确保声明的返回类型充分表达。对于实现几个接口的组件或者可能由他们的实现类型引用的组件,考虑在你的工厂方法上声明尽可能特定的返回类型(至少与引用bean的注入点所需的特定类型相同)。
你也可以通过将@Autowired
注解添加到期望为该类型的数组的字段或者方法来指示Spring从ApplicationContext
提供特别类型的所有bean,如下示例所示:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
同样适用于类型化的集合,如下示例所示:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你想要在数组或者列表中的项目以特定的顺序排序,你的目标bean可以实现
org.springframework.core.Ordered
接口或者使用@Order
或者标准的@Priority
注解。否则,他们的顺序按照容器中相应的目标bean定义注册的顺序。
你可以在目标类基本和@bean
方法上声明@Order
注解,可能用于单个bean定义(在多个bean定义使用相同的bean类的情况下)。@Order
值可能影响在注入点的优先级,但是要注意他们不会影响单例的启动顺序,启动顺序是由依赖关系和@DependsOn
声明确定的正交关系。
注意标准的jakarta.annotation.Priority
注解在@Bean
级别是不可用的,因为他不能在方法上声明。它的语义可以通过每个类型的单个bean上@Order
值结合@Primary
建模。
即使是类型话的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可用则自动装配失败。在声明的数组,集合或map的情况中,最少需要一个匹配元素。
默认的形式是将注解的方法和字段视为表明了需要的依赖。你可以按照以下示例来修改此行为,通过标记注入点为非必须来启用框架调跳过不满足的注入点(即,通过将@Autowired
中required
属性设置为false
)。
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired(required = false)
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
如果非必须的方法的依赖(在多个参数的情况下,或者是它依赖的其中一个)不可用,此方法将根本不会被调用。一个非必须的字段在这样的情况下不会得到填充,保留了他的默认值。
换句话说,将required
属性设置为false
表明相应的属性对于自动装配而言是可选的,并且如果属性不能自动装配,属性将被忽略。这允许属性被分配默认值,此默认值可以通过依赖注入选择性地覆盖。
注入的构造器和工厂方法参数是一个特殊的情况,因为在@Autowired
中的required
属性有一些不同含义,这是由于Spring的构造器解析算法可能会处理多个构造器。默认情况下,构造器和工厂方法参数实际上是必须的,但在单个构造器场景中有一些特定的规则,例如如果没有匹配的bean可用,多元素注入点(数组,集合,map)解析为空实例。这就形成了一种常见的实现模式,其中所有依赖可以在唯一的多参数构造器中声明—例如,声明为单个公共的构造器,而不需要@Autowired
注解。
任何给定bean类的只有一个构造器可以声明
required
属性为true
的@Autowired
,表明当用于作为一个Spring bean时自动装配此构造器。因此,如果required
属性保持默认属性为true
,只有一个单独的构造器可以使用@Autowired
注解。如果多个构造器声明注解,他们所有将必须声明required=false
为了考虑为当做自动装配的候选(类似于XML中的autowire=constructor
)。通过匹配Spring容器中的bean,将选择具有最多依赖项数量的构造器。如果没有候选可以满足,然后primary/default构造器(如果存在)将被使用。类似地,如果一个类声明了多个构造器,但是他们中没有使用@Autowired
注解,然后primary/default构造器(如果存在)将被使用。如果一个类在开始时只声明了一个单独的构造器,它将一直被使用,即使没有被注解。注意,一个注解的构造器不必须是public。
或者,你可以通过Java8的java.util.Optional
表示特定的依赖项非必须的性质,如下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring Framework 5.0开始,你也可以使用@Nullable
注解(在任意包中的任意类型,例如,来自JSR-305的javax.annotation.Nullable
)或者只利用Kotlin内建的空安全支持:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(@Nullable MovieFinder movieFinder) {
...
}
}
你也可以将@Autowired
用于已知的可解析的依赖:BeanFactory
,ApplicationContext
,Environment
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
。这些接口和他们的扩展接口,例如ConfigurableApplicationContext
或者ResourcePatternResolver
,是自动解析的,无需必要的特殊设置。以下示例自动装配一个ApplicationContext
对象:
public class MovieRecommender {
@Autowired
private ApplicationContext context;
public MovieRecommender() {
}
// ...
}
@Autowired
,@Inject
,@Value
和@Resource
注解通过SpringBeanPostProcessor
实现处理的。这意味着你不能在你自己的BeanPostProcessor
或者BeanFactoryPostProcessor
类型(若有的话)中应用这些注解。这些类型必须通过使用XML或者Spring@Bean
方法显式’连接’。
1.9.2. 使用@Primary
微调基于注解的自动装配
因为通过类型的自动装配可能导致多候选人,通常需要进一步控制选择过程是有必要的。一种完成的方式是使用Spring的@Primary
注解。@Primary
表明当多个bean是自动装配到单值依赖项的候选者的时候,特定的bean应该被优先考虑。如果恰好一个primary bean存在候选人之中,它变为自动装配的值。
考虑以下配置,其定义了firstMovieCatalog
为主要的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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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.3. 使用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定义:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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>
- ①带有
main
限定符值的bean与使用相同的值限定构造器参数连接。 - ②带有
action
限定符值的bean与使用相同的值限定构造器参数连接。
为了后补匹配,bean名称被考虑为默认的qualifier值。这样,你可以定义id
为main
的bean代替内嵌的限定值元素,导致相同的匹配结果。但是,尽管你可以使用此规范通过名称来引用特定的bean,@Autowired
从根本上是关于带有可选择语义限定符的类型驱动注入的。这意味着限定符值(即使使用bean名称后补),在类型匹配集合中始终具有缩小语义。他们没有在语义上表示对唯一bean id的引用。好的限定符值是main
或者EMEA
或者persistenet
,表示与bean id
独立的特定的组件的特征,在匿名的bean定义(例如,在前面例子中的一个)的情况下,这些特征可能是自动生成。
限定符也可以应用于类型化集合,正如前面所讨论的–例如,应用到Set<MovieCatalog>
.在这种情况下,所有匹配的bean,通过声明的限定符,被注册为集合。这意味着限定符不是必须唯一的。相反,他们构造了过滤标准。例如,你可以定义多个使用相同的限定符值action
的MovieCatalog
bean,所有被注入到使用@Qualifier("action")
注解的Set<MovieCatalog>
。
让限定符值在类型匹配的候选中针对目标bean名称进行选择,在注入点不需要
@Qualifier
注解。如果没有其他解析指示器(例如,一个限定符或者primary标记),对于一个非唯一的依赖情景,Spring根据目标bean名称匹配注入点名称(即,字段名称和参数名称)并选择相同名称的候选者(如果有的话)。
也就是说,如果你打算通过名称表示的注解驱动的注入,不要主要地使用@Autowired
,尽管它能够通过类型匹配候选中bean名称进行选择。相反,使用JSR-250 @Resource
注解,它是在语义上被定义为通过它的唯一名称识别一个特定目标组件,声明的类型与匹配过程无关。@Autowired
有相当不同的语义:通过类型选择候选bean之后,指定的String
限定符值只在这些选择的类型候选者考虑(例如,将一个account
限定符与使用相同的限定符标签标记的bean进行匹配)。
对于本身被定义为集合,Map
或者数组类型的bean,@Resource
是一个好的解决方案,通过唯一名称引用特定的集合或者数组bean。也就是说,从4.3开始,你也可以通过Spring的@Autowired
类型匹配算法匹配集合,Map
和数组类型。只要元素类型信息在@Bean
返回类型签名或者集合继承分层结构中保存。在这种情况下,你可以使用限定符值在相同类型的集合中进行选择,正如在前面段落中所概述的。
从4.3开始,@Autowired
也考虑注入的自引用(即,对当前被注入的bean的引用)。注意本身注入是一个应急方案。在其他组件的常规依赖项总是优先的。在该意义上说,自引用不会参与常规的候选者选择因此从不是特别重要。相反,他们总是以最低优先级结束。在实践中,你应该只在最后使用自引用(例如,通过bean事务代理调用相同实例的其他方法)。在这种场景中将受影响的方法分解到一个单独的委托bean。或者,你可以使用@Resource
,它可以通过它的唯一名称获取返回到当前的bean的一个代理。
尝试将来自
@Bean
方法的结果注入到相同的配置类也是有效自引用场景。要么在实际需要的地方,延迟地在方法签名中解析这样的引用(与配置类中的一个自动装配字段截然相反)要么声明受影响的@Bean
方法为static
方法,将他们从包含配置类实例和它的声明周期解耦。否则,这样的bean只会在回退阶段被考虑,而其他配置类上的匹配bean会被选择为主要候选者(如果有的话)。
@Autowired
应用于字段,构造器和多参数方法,允许通过qualifier注解在参数级别缩小范围。相比之下,@Resource
只支持字段和bean属性单个参数的setter方法。因此,如果你的注入目标是一个构造器或者多参数方法,你应该坚持qualifier。
你可以创建你自己的自定义qualifier注解。为此,定义一个注解并在你的定义内提供@Qualifier
注解,如下示例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后你可以提供此自定义qualifier在自动装配的字段和参数上,如下示例所示:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,你可以提供候选bean定义信息。你可以添加<qualifier />
标签作为<bean />
标签的子元素然后指定type
和value
来匹配你自定义qualifier注解。通过此注解的全量限定类名匹配此类型。或者,如果没有名称冲突存在,为方便起见,你可以使用短的类名称。以下示例示范了两种方式:
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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>
在类路径扫描和管理组件中,你可以查看XML提供qualifier元数据的基于注解的替代方案。特别是,请查看使用注解提供Qualifier元数据。
在一些情况中,使用一个不带值的注解可以满足要求。当注解服务于更通用的用途并且可以在跨几个不同类型的依赖应用时,这样是比较有用的。例如,你可能提供了一个离线的catalog,其可以在没有网络连接可用时能够被搜索。首先,定义简单的注解,如下例所示:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后添加注解到字段或者自动装配的属性,如下示例所示:
public class MovieRecommender {
@Autowired
@Offline // 此行添加`@Offline`注解
private MovieCatalog offlineCatalog;
// ...
}
现在,bean定义只需要一个qualifier type
,如下示例所示:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> //此元素指定qualifier
<!-- inject any dependencies required by this bean -->
</bean>
你也可以定义自定义qualifier注解,它可以接收命名的属性,除了或者代替简单的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
}
使用自定义的qualifier注解的自动装配的字段,包含了两个属性的值:genre
和format
,如下示例所示:
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最后,bean定义应该包含匹配qualifier值。此示例也展示了你可以使用元属性代替<qualifier/>
元素。如果可以的话,<qualifier/>
元素和他的属性优先考虑,但是如果没有这样的qualifier存在,自动装配机制回退到<meta/>
标签内提供的值,正如在以下示例中最后两个bean定义:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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.4. 使用泛型作为自动装配Qualifiers
除了@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
接口并此泛型被当做qualifier使用,如下示例所示:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean
当自动装配list时,Map
实例和数组,泛型的qualifier也是有效的。以下示例将自动装配一个泛型List
:
// Inject all Store beans as long as they have an <Integer> generic
// Store<String> beans 将不会出现在此列表中
@Autowired
private List<Store<Integer>> s;
1.9.5. 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,允许你注册你自己的自定义qualifier注解类型,即使他们没有使用Spring的@Qualifier
注解进行注解。以下示例展示了如何使用CustomAutowireConfigurer
:
<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
值。 - 在
<bean/>
元素上任何可用的default-autowire-candidates
模式。 @Qualifier
注解的存在和任何使用CustomAutowireConfigurer
注册的自定义注解。
当多个bean限定作为自动装配候选者,一个”主要的候选者“确定按照以下方式:如果在候选者之中有一个精确的bean定义的primary
参数设置为true
,那么选择它。
1.9.6. 使用@Resource
注入
Spring也支持使用在字段或者bean属性的setter方法上的JSR-250 @Resource
注解(jakarta.annotation.Resource
)进行注入。在Jakarta EE中是常见的模式:例如,在JSF管理的bean和JAX-WS端点。Spring也支持此模式用于Spring管理的对象。
@Resource
使用一个name属性。Spring将此值理解为将注入的bean名称。换句话说,它遵循按照名称语义,正如以下示例所展示的:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") //此行注入了一个`@Resource`
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
如果没有名称被显式指定,默认的名称来源于字段名称或者setter方法。在字段的情况中,它使用字段名称。在setter方法的情况下,它使用bean属性名称。以下示例将命名为movieFinder
的bean注入到它的setter方法:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
注解所提供的名称通过
CommonAnnotationBeanPostProcessor
感知的ApplicationContext
解析为bean名称。如果你显式地配置了Spring的SimpleJndiBeanFactory
,此名称可以通过JNDI解析。但是,我们建议你依赖默认的行为并使用Spring的JNDI查找功能来保持间接的级别。
在没有显性地指定名称的@Resource
用法的独有情况中,类似于@Autowired
,@Resource
查找一个主要的类型匹配,而不是一个特定的命名的bean,并解析众所周知可解析的依赖项:BeanFactory
,ApplicationContext
,ResourceLoader
,ApplicationEventPublisher
和MessageSource
接口。
这样,在以下示例中,customerPreferenceDao
字段优先查找名称为customerPreferenceDao
的bean,然后回退到主要类型匹配类型CustomerPreferenceDao
:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; //`context`字段注入是基于已知可解析的依赖项类型:`ApplicationContext`。
public MovieRecommender() {
}
// ...
}
1.9.7. 使用@Value
@Value
通常用于注入外部的属性:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name}") String catalog) {
this.catalog = catalog;
}
}
使用以下配置:
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig { }
以及以下application.properties
文件:
catalog.name=MovieCatalog
在该情况下,catalog
参数和字段将等于MovieCatalog
值。
Spring提供了默认的宽松内嵌值解析器。它将尝试解析属性值,如果它不能被解析,属性名(例如,${catalog.name}
)将作为值被注入。如果你想要保持严格控制不存在的值,你应该声明PropertySourcesPlaceholderConfigurer
bean,如下示例所示:
@Configuration
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new PropertySourcesPlaceholderConfigurer();
}
}
当使用JavaConfig配置了一个
PropertySourcesPlaceholderConfigurer
,@Bean
方法必须为static
。
如果任何${}
占位符不能被解析,使用以上配置确保Spring初始化失败。它也可以使用方法,像setPlaceholderPrefix
,setPlaceholderSuffix
或者setValueSeparator
来定制占位符。
默认情况下,Spring Boot配置了一个
PropertySourcesPlaceholderConfigurer
bean,它会从application.properties
和application.yml
文件获取属性。
Spring提供的内建的转换器支持允许简单的类型转换(例如转换为Integer
或者int
)自动处理。多个逗号分隔的值可以顺便被自动转换为String
数组。
它可以提供一个默认的值,如下:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("${catalog.name:defaultCatalog}") String catalog) {
this.catalog = catalog;
}
}
Spring BeanPostProcessor
在幕后使用一个ConversionService
来处理在@Value
中的String
值转换为目标类型的处理。如果你想要提供一个对你自己的自定义类型的转换支持,你可以提供你自己的ConversionService
bean实例,正如以下示例所示:
@Configuration
public class AppConfig {
@Bean
public ConversionService conversionService() {
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(new MyCustomConverter());
return conversionService;
}
}
当@Value
包含一个SpEL
表达式,在运行时,此值将动态计算,如下示例所示:
@Component
public class MovieRecommender {
private final String catalog;
public MovieRecommender(@Value("#{systemProperties['user.catalog'] + 'Catalog' }") String catalog) {
this.catalog = catalog;
}
}
SpEL也能够用于更加复杂的数据接口:
@Component
public class MovieRecommender {
private final Map<String, Integer> countOfMoviesPerCatalog;
public MovieRecommender(
@Value("#{{'Thriller': 100, 'Comedy': 300}}") Map<String, Integer> countOfMoviesPerCatalog) {
this.countOfMoviesPerCatalog = countOfMoviesPerCatalog;
}
}
1.9.8. 使用@PostConstruct和@PreDestroy
CommonAnnotationBeanPostProcessor
不仅识别出@Resource
注解,而且还有JSR-250生命周期注解:jakarta.annotation.PostConstruct
和jakarta.annotation.PreDestroy
。正如在Spring2.5介绍的,这些注解提供了正如在初始化回调和销毁回调中描述的生命周期回调机制的替代方案的支持。CommonAnnotationBeanPostProcessor
所提供的被注册到Spring上下文中,带有这些注解其中一个的方法在与相应的Spring生命周期接口方法或者明确的声明的回调方法声明周期相同的点被调用。在以下示例中,此缓存在初始化时预先填充的并且在销毁时清空:
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// populates the movie cache upon initialization...
}
@PreDestroy
public void clearMovieCache() {
// clears the movie cache upon destruction...
}
}
关于组合多个生命周期的影响的更多细节,请查看组合生命周期机制。
像
@Resource
,@PostConstruct
,@PreDestroy
注解类型是标准java类库从JDK6到8的一部分.但是,但是完整的javax.annotation
包从JDK9核心Java模块单独出来,最终在JDK11移除。从Jakarta EE 9开始,现在,此包存活在jakarta.annotation
。如果需要,jakarta.annotation-api
artifact需要通过Maven Central获取,无非就是像其他类库一样被添加到应用程序类路径。
1.10. 类路径扫描和管理组件
在此章节大多数示例使用XML来指定配置元数据来生成Spring容器内的每一个BeanDefinition
。在前面的章节(基于注解的容器配置)示范了如何通过源级别注解提供大量配置元数据。但是,即使在这些示例中,”基本的“bean定义在XML文件中显示地定义,而注解只驱动依赖注入。此章节描述一个通过类路径扫描隐式地检测候选者组件的选项。候选组件是与过滤器条件相匹配的类,并且有一个相对应的容器注册的bean定义。这样就移除了需要使用XML来执行bean注册。相反,你可以使用注解(例如,@Component
),AspectJ类型表达式,或者你自己自定义的过滤器条件来选择哪个类已经在容器中注册了bean定义。
从Spring3.0开始,Spring JavaConfig项目提供的许多特性是核心Spring Framework的一部分。这样允许你使用java定义bean而不是使用传统的XML文件。可以看一看
@Configuration
,@Bean
,@Import
,@DependsOn
注解来了解如何使用这些新特性的示例。
1.10.1. @Component
并进一步原生类型注解
@Repository
注解是对任何类的一个标注器,并履行一个仓库的角色或者原生类型(也被称为Data Access Object或者DAO)。在此标注器的使用中是自动化异常转换,正如在异常转换中所描述的。
Spring进一步提供原生注解:@Component
,@Service
,@Controller
。@Component
是所有Spring管理的组件的通用原型。@Repository
,@Service
,@Controller
是组件的特殊化用于更特殊的用例(依次在持久化层,服务层和展示层)。因此,你可以使用@Component
注解你的组件类,但是,可以使用@Repository
,@Service
,@Controller
代替来注解他们,你的类更适合通过工具进行处理或者与切面关联。例如,这些原生注解成为切入点理想的目标。@Repository
,@Service
,@Controller
在Spring Framework未来的版本也可以带有额外的语义。这样,如果对于服务层,你正在使用@Component
或@Service
之间选择,@Service
毫无疑问是更好的选择。类似地,如前所述,@Repository
已经被支持作为你的持久化层自动异常转换标记。
1.10.2. 使用元注解和组合注解
许多Spring提供的注解在你自己的代码中当做元注解使用。一个元注解是一个应用于另一个注解的注解。例如在前面提到的@Service
注解是使用@Component
元注解的,如下示例所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component // `@Component`导致`@Service`按照与`@Component`相同的方式处理
public @interface Service {
// ...
}
你也可以组合元注解来创建"组合注解"。例如,来自Spring MVC的@RestController
注解是由@Controller
和@ResponseBody
组成。
除此之外,组合的注解可以选择性地重新声明元注解的属性来允许自定义。当你只想暴露元注解属性的子集时,这是特别有用的。例如,Spring的@SessionScope
注解硬编码将作用域名称为session
,但是仍允许proxyMode
的自定义。以下列表展示SessionScope
注解的定义:
@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;
}
你仍可以使用没有声明proxyMode
的@SessionScope
,如下:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
你也可以覆盖proxyMode
的值,如下示例所示:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
要进一步了解细节,请查看Spring注解编程模型 wiki页面。
1.10.3. 自动化检测类和注册bean定义
Spring可以自动化检测原型的类并在ApplicationContext
中注册相应的BeanDefinition
实例。例如,对于这样的自动检测,以下两个类符合条件的:
@Service
public class SimpleMovieLister {
private MovieFinder movieFinder;
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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://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任务的files-only的开关。而且,在相同的环境中基于安全策略可能不会暴露类路径目录–例如,JDK 1.7.0_45或者更高版本的独立APP。(在你的清单中其需要’授信的类库’设置–请查看https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources)
在JDK 9的模块路径(Jigsaw),Spring的类路径扫描通常会正如期望的那样工作。但是,确保你的组件类在你的module-info
描述符中导出。如果你希望Spring调用类的非public成员,确保他们是’打开的’。(即,他们使用一个opens
声明,而不是在module-info
描述符的exports
声明。)
此外,当你使用组件扫描元素时,隐式地包含AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
。这意味着自动检测这两个组件并绑定到一起–无需任何XML提供bean配置元数据。
你可以通过包含值为
false
的annotation-config
属性禁用AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
。
1.10.4. 使用过滤器来定制扫描
默认情况下,带有@Component
,@Repository
,@Service
,@Controller
,@Configuration
注解的类或者本身使用@Component
注解的自定义注解的类是唯一检测的候选者组件。但是你可以通过应用自定义过滤器修改和扩展此行为。将他们添加为@ComponentScan
注解的includeFilters
或者excludeFilters
属性。(或者作为XML配置中<context:component-scan>
的<context:include-filter />
或者<context:exclude-filter />
子元素)。每一个过滤器元素需要type
和expression
属性。以下表格描述了过滤器选项:
过滤器类型 | 示例表达式 | 描述 |
---|---|---|
annotation (default) | org.example.SomeAnnotation | 在目标组件类型级别存在或者元存在的注解 |
assignable | org.example.SomeClass | 目标组件可分配的(继承或者实现)类或者接口 |
aspectj | org.example..*Service+ | 通过目标组件可匹配的AspectJ表达式 |
regex | org\.example\.Default.* | 目标组件的类名称匹配的正则表达式 |
custom | org.example.MyTypeFilter | org.springframework.core.type.TypeFilter 接口的自定义实现 |
以下示例展示了配置忽略了所有@Repository
注解和使用"stub"仓库代替:
@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
或者提供use-default-filters="false"
作为<component-scan/>
元素的属性来禁用默认的过滤器。这样有效地禁用了带有@Component
,@Repository
,@Service
,@Controller
,@RestController
或者@Configuration
注解或者元注解的类的自动检测。
1.10.5. 在组件内定义bean元数据
Spring组件也可以将bean定义元数据贡献到容器。你可以使用与在@Configuration
注解的类内用于定义bean元数据相同的@Bean
注解做到这些。以下示例展示了如何做到:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
前面的类是一个Spring组件,在doWork()
方法中它有特定的应用程序代码。但是,它也贡献了一个有一个引用方法publicInstance()
的工厂方法的bean定义。@Bean
注解识别工厂方法和其他bean定义属性,例如,通过@Qualifier
注解识别的一个限定符值。其他可以被指定的方法级别注解有@Scope
,@Lazy
和自定义限定符注解。
除了它的组件初始化的角色,你还可以将
@Lazy
注解放到@Autowired
或者@Inject
标记的注入点。在此上下文中,它造成延迟解析代理的注入。但是,这样的代理方式是相当有限。对于复杂的延迟交互,特别是在与可选依赖项组合时,我们推荐ObjectProvider<MyTargetBean>
代替。
正如前面所讨论的,自动装配字段和方法是支持的,并且额外支持@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);
}
}
此示例将String
方法参数country
自动装配为在另外一个名称为privateInstance
bean age
属性的值。Spring表达式语音(Expression Language)元素通过符号#{<expression>}
定义属性的值。对于@Value
注解,当解析表达式文本时,预配置一个表达式解析器来查找bean名称。
从Spring Framework 4.3开始,你还可以声明一个类型InjectionPoint
的工厂方法参数(或者它的更专业的子类:DependencyDescriptor
)来访问请求触发当前bean创建的注入点。注意,只适用于bean实例的真实创建,不适用于现有的实例的注入。因此,此特性对于大多数原始作用域bean是有意义的。对于其他作用域,工厂方法只能看到在给定的作用域(例如,触发延迟单例bean的创建)中触发新bean实例的创建注入点。在这种情况下,你可以使用提供的注入点元数据并注意语义。以下示例展示了如何使用InjectionPoint
:
@Component
public class FactoryMethodComponent {
@Bean @Scope("prototype")
public TestBean prototypeInstance(InjectionPoint injectionPoint) {
return new TestBean("prototypeInstance for " + injectionPoint.getMember());
}
}
在常规的Spring组件的@Bean
方法与Spring @Configuration
类中的相对应的处理方式不同。这个不同点是@Component
类不会使用CGLIB增强来防止方法和字段的调用。CGLIB代理是通过由调用在@Configuration
类@Bean
方法内的方法或者字段来创建协作者对象的bean元数据的引用的方法。这样的方法不会使用普通的Java语义调用,而是通过容器调用,以便提供Spring bean的通常生命周期管理和代理,甚至当通过编程式调用@Bean
方法引用其他bean时也是如此。与此相反,调用在普通的@Component
类内的@Bean
方法内的方法或者字段有标准的Java语义,不应用特殊的CGLIB处理或者其他约束。
你可以声明
@Bean
方法为static
,允许他们在没有创建包含他们的配置类作为实例的情况下调用他们。当定义post-processors bean时这样特别有意义(例如,类型BeanFactoryPostProcessor
或者BeanPostProcessor
),因为这样的bean在容器声明周期中较早初始化,并应该避免在此时触发配置其他部分。
通过容器调用静态的@Bean
方法不会被拦截,甚至在@Configuration
类也不会(正如在本章节前面做描述的),由于技术限制:CGLIB子类只可以重写非静态的方法。因此,直接调用另外一个@Bean
方法有标准的Java语义,导致一个独立的实例直接从工厂方法本身被返回。
@Bean
方法的Java语言可见性不会对Spring的容器中生成的bean定义产生直接的影响。你可以自由地在非@Configuration
类中声明你认为合适的工厂方法,也可以在任何地方的静态方法。但是,常规的在@Configuraiton
类中的@Bean
方法需要是可重写的–即,他们不能被声明为private
或者final
。
@Bean
方法在给定的组件或者Configuration类的基类上被发现,而且在Java 8通过组件和配置类实现了在接口中声明的默认方法也会被发现。这为在组合复杂的配置安排提供了很大的灵活性,甚至可以通过Spring 4.2的Java8的默认方法实现多重继承。
最后,单例类可能持有相同的bean的多个@Bean
方法,作为在运行时根据可用的依赖来使用的多个工厂合法的安排。这个和在其他配置场景中选择“最贪心”的构造器或者工厂方法一样的算法:在构造时,选择最大数量可满足的依赖的变体,类似于容器在多个@Autowired
构造器之间如何选择。
1.10.6 命名自动检测的组件
当组件作为扫描过程的一部分被自动检测时,它的bean名称通过该扫描器已知的BeanNameGenerator
策略生成。默认情况下,任何Spring原生类型注解(@Component
,@Repository
,@Service
和@Controller
)包含一个名称value
从而提供相对应bean定义的名称。
如果这样的注解包含没有名称value
或者任何其他检测的组件(例如通过自定义过滤器检测的那些),默认的bean名称生成器返回驼峰非全量限定类名。例如,如果以下组件类被检测,名称将是myMovieLister
和movieFinderImpl
:
@Service("myMovieLister")
public class SimpleMovieLister {
// ...
}
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
如果你不想依赖默认的bean名称策略,你可以提供自定义bean名称策略。首先,实现BeanNameGenerator
接口,然后确保包含默认的无餐构造器。然后,在配置扫描器时,提供全量限定类名,正如以下示例中注解和bean定义所示。
如果由于多个自动检测组件存在相同的非全限定类名,遭遇命名冲突(即:属于不同的包但是完全名称一样的类),你可能需要配置一个
BeanNameGenerator
,来默认全量限定类名用于生成bean名称。从Spring Framework 5.2.3以来,位于包org.springframework.context.annotation
中的FullyQualifiedAnnotationBeanNameGenerator
可以用于这样的功能。
@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>
一般来说,当其他组件可能显性引用此名称时,考虑使用注解指定名称。从另一方面来说,容器负责绑定,自动生成名称是足够好的。
1.10.7 为自动检测的组件提供作用域
与Spring管理的组件一样,自动检测的组件的默认的和最常用的作用域是singleton
。但是,有时候你需要一个不同的作用域,可以通过@Scope
注解指定。你可以在注解内提供作用域的名称,正如以下示例所示:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
注解只有在具体的bean类(对应注解的组件)或者工厂方法(对应@Bean
方法)才会自省。相比于XML