技术交流群:
1.5 bean的作用域
当你创建一个bean
的定义时候,你可以创建一个模版(recipe)通过bean
定义的类定义去创建一个真实的实例。bean
定义是模版(recipe)的概念很重要,因为这意味着,与使用类一样,你可以从一个模版(recipe)创建多个对象实例。
你不仅可以控制要插入到从特定bean
定义创建的对象中的各种依赖项和配置值,还可以控制从特定bean
定义创建的对象的作用域。这种方法是非常有用的和灵活的,因为你可以选择通过配置创建的对象的作用域,而不必在Java类级别上考虑对象的作用域。bean
能够定义部署到一个或多个作用域。Spring
框架支撑6种作用域,4种仅仅使用web
环境。你可以创建定制的作用域。
下面的表格描述了支撑的作用域:
Scope | Description |
---|---|
singleton | (默认)将每个Spring IoC容器的单个bean定义范围限定为单个对象实例。 |
prototype | 将单个bean定义的作用域限定为任意数量的对象实例 |
request | 将单个bean定义的范围限定为单个HTTP请求的生命周期。也就是,每个HTTP请拥有一个被创建的bean实例。仅在Spring ApplicationContext Web容器有效 |
session | 将单个bean定义的范围限制在HTTP Session生命周期。仅在Spring ApplicationContext Web容器有效 |
application | 将单个bean定义的范围限制在ServletContext生命周期。仅在Spring ApplicationContext Web容器有效 |
websocket | 将单个bean定义限制在WebSocket生命周期。仅在Spring ApplicationContext Web容器有效 |
从Spring3.0
后,线程安全作用域是有效的但默认没有注册。更多的信息,查看文档 SimpleThreadScope
。更多关于怎样去注册和自定义作用域,查看自定义作用域
1.5.1 单例bean作用域
单例bean
仅仅只有一个共享实例被容器管理,并且所有对具有与该bean
定义相匹配的ID
的bean
的请求都会导致该特定bean
实例被Spring
容器返回。换一种方式,当你定义一个bean
的定义并且它的作用域是单例的时候,Spring IoC
容器创建通过bean
定义的对象定义的实例。这个单例存储在缓存中,并且对命名bean
的所有请求和引用返回的是缓存对象。下面图片展示了单例bean
作用域是怎样工作的:
Spring
的单例bean
概念与在GoF
设计模式书中的单例模式不同。GoF
单例硬编码对应的作用域例如:只有一个特定类的对象实例对每一个ClassLoader
只创建一个对象实例。最好将Spring
单例的范围描述为每个容器和每个bean
(译者:GoF
设计模式中的单例bean
是针对不同ClassLoader
来说的,而Spring
的单例是针对不同容器级别的)。这意味着,如果在单个Spring
容器对指定类定义一个bean
,Spring
容器通过bean
定义的类创建一个实例。在Spring
中单例作用域是默认的。在XML中去定义一个bean
为单例,你可以定义一个bean
类似下面例子:
<bean id="accountService" class="com.something.DefaultAccountService"/>
<!-- 通过scope指定bean作用域 单例:singleton ,原型:prototype-->
<bean id="accountService" class="com.something.DefaultAccountService" scope="singleton"/>
1.5.2 原型作用域
非单例原型bean
的作用域部署结果是在每一次请求指定bean
的时候都会创建一个bean
实例。也就是,bean
被注入到其他bean
或在容器通过getBean()
方法调用都会创建一个新bean
。通常,为所有的无状态bean使用原型作用域并且有状态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
在运行时重复获取原型作用域bean
的一个新实例。你不能依赖注入一个原型bean
到一个单例bean
,因为注入只发生一次,当Spring
容器实例化单例bean
、解析和注入它的依赖时。如果在运行时不止一次需要原型bean
的新实例,查看方法注入
1.5.4 Request, Session, Application, and WebSocket Scopes
request
、session
、application
、和websocket
作用域仅仅在你使用Spring
的ApplicationContext
实现(例如:XmlWebApplicationContext
)时有效。如果你将这些作用域与常规的Spring IoC
容器(例如ClassPathXmlApplicationContext
)一起使用,则会抛出一个IllegalStateException
异常,该错抛出未知的bean
作用域。
-
初始化Web配置
为了支持这些bean的作用域在
request
、session
、application
、和websocket
级别(web作用域bean)。一些次要的初始化配置在你定义你的bean之前是需要的。(这个初始化安装对于标准的作用域是不需要的:singleton
、prototype
)。如何完成这个初始化安装依赖于你的特定
Servlet
环境。如果在
Spring Web MVC
中访问作用域bean
,实际上,在由Spring
DispatcherServlet
处理的请求中,不需要特殊的设置。DispatcherServlet
已经暴露了所有相关状态。如果你使用
Servlet 2.5
Web
容器,请求处理在Spring
的DispatcherServlet
外(例如:当使用JSF
或Structs
),你需要去注册org.springframework.web.context.request.RequestContextListener
、ServletRequestListener
。对于Servlet 3.0+
,这可以通过使用WebApplicationInitializer
接口以编程方式完成。或者,对于旧的容器,增加下面声明到你的web
应用程序web.xml
文件:<web-app> ... <listener> <listener-class> org.springframework.web.context.request.RequestContextListener </listener-class> </listener> ... </web-app>
或者,如果你的监听器设置有问题,考虑使用
Spring
的RequestContextFilter
。过滤器映射取决于周围的Web
应用程序配置。因此你必须适当的改变它。下面的清单显示web
应用程序filter
的部分配置:<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
请求对象绑定到为该请求提供服务的线程。这使得request
和session
范围的bean在调用链的更下方可用。 -
Request
作用域考虑下面的XML关于
bean
的定义:<!--请求作用域为request--> <bean id="loginAction" class="com.something.LoginAction" scope="request"/>
Spring
容器通过使用LoginAction
bean定义为每个HTTP
的请求创建一个LoginAction
新实例bean。也就是说,loginAction
bean的作用域在HTTP
请求级别。你可以根据需要更改所创建实例的内部状态。因为从同一loginAction
bean定义创建的其他实例看不到状态的这些变化。当这个请求处理完成,bean的作用域从request
丢弃。(译者:scope="request"
每个请求是线程级别隔离的、互不干扰)当使用注解驱动组件或
Java Config
时,@RequestScope
注解能够赋值一个组件到request
作用域。下面的例子展示怎样使用:@RequestScope//指定作用域访问为request @Component public class LoginAction { // ... }
-
Session作用域
考虑下面为
bean
定义的XML
配置:<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
Spring
容器通过使用userPreferences
的bean定义为单个HTTP
Session
的生命周期内的创建一个UserPreferences
的新实例。换句话说,userPreferences bean有效地作用在HTTP会话级别。与请求范围的Bean一样,您可以根据需要任意更改所创建实例的内部状态,因为知道其他也在使用从同一``userPreferencesBean定义创建的实例的HTTP Session实例也看不到这些状态变化,因为它们特定于单个HTTP会话。当
HTTP会话最终被丢弃时,作用于该特定
HTTP会话的
bean`也将被丢弃。当使用注解驱动组件或
Java Config
时,@SessionScope
注解能够赋值一个组件到session
作用域。下面的例子展示怎样使用:@SessionScope @Component public class UserPreferences { // ... }
-
Application作用域
考虑下面的XML关于
bean
的定义:<bean id="appPreferences" class="com.something.AppPreferences" scope="application"/>
Spring
容器通过使用appPreferences
的bean定义为整个Web
应用创建一个AppPreferences
的bean新实例。也就是说,appPreferences
的作用域在ServletContext
级别并且作为一个常规的ServletContext
属性被储存。这个和Spring
的单例bean类似,但有两个重要的区别:每个ServletContext
是一个单例,而不是每个Spring
的ApplicationContext
(在给定的Web
应用程序中可能有多个),并且它实际上是暴露的,因此作为ServletContext
属性可见。当使用注解驱动组件或
Java Config
时,@ApplicationScope
注解能够赋值一个组件到application
作用域。下面的例子展示怎样使用:@ApplicationScope @Component public class AppPreferences { // ... }
-
作用域bean作为依赖项
Spring IoC容器不仅管理对象(bean)的实例化,而且还管理协作者(或依赖项)的连接。(例如)如果要将HTTP请求范围的Bean注入另一个作用域更长的Bean,则可以选择注入AOP代理来代替已定义范围的Bean。也就是说,你需要注入一个代理对象,该对象暴露与范围对象相同的公共接口,但也可以从相关范围(例如HTTP请求)中检索实际目标对象,并将方法调用委托给实际对象。
在这些
bean
作用域是单例之间,你可以使用<aop:scoped-proxy/>
。然后通过一个可序列化的中间代理引用,从而能够在反序列化时重新获得目标单例bean
。当申明
<aop:scoped-proxy/>
原型作用域bean
,每个方法调用共享代理导致一个新目标实例被创建,然后将该调用转发到该目标实例。同样,作用域代理不是以生命周期安全的方式从较短的作用域访问
bean
的唯一方法。你也可以声明你的注入点(也就是,构造函数或者Setter
参数或自动注入字段)例如:ObjectFactory<MyTargetBean>
,允许getObject()
调用在每次需要时按需检索当前实例-不保留实例或单独存储实例。作为一个扩展的变体,你可以声明
ObjectProvider<MyTargetBean>
,提供了一些附加的获取方式,包括getIfAvailable
和getIfUnique
。JSR-330的这种变体称为
Provider
,并与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"> <!-- 一个HTTP Session作用域的bean暴露为一个代理 --> <bean id="userPreferences" class="com.something.UserPreferences" scope="session"> <!-- 指示容器代理周围的bean --> <aop:scoped-proxy/> //1. </bean> <!-- 一个单例作用域bean 被注入一个上面的代理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
定义中(查看选择代理类型去创建 和 基于Schema的XML配置)。为什么这些bean
的定义在request
、session
和自定义作用域需要<aop:scoped-proxy/>
元素?考虑以下单例bean
定义,并将其与需要为上述范围定义的内容进行对比(请注意,以下userPreferences
bean定义不完整):
<!--没有<aop:scoped-proxy/> 元素-->
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例bean (userManager
) 被注入一个引用到HTTP
Session
作用域的bean
(userPreferences
)。这个显著点是userManager
bean是一个单例bean:这个实例在每个容器值初始化一次,并且它的依赖(在这个例子仅仅一个,userPreferences
bean)仅仅被注入一次。这意味着userManager
bean运行仅仅在相同的userPreferences
对象上(也就是,最初注入的那个)。
当注入一个短生命周期作用域的bean
到一个长生命周期作用域bean
的时候这个不是我们期望的方式(例如:注入一个HTTP Session
作用域的协作者bean
作为一个依赖注入到单例bean
)。相反,你只需要一个userManager
对象,并且在HTTP
会话的生存期内,你需要一个特定于HTTP会话的userPreferences
对象。因此,容器创建一个对象,该对象公开与UserPreferences
类完全相同的公共接口(理想地,对象是UserPreferences
实例),可以从作用域机制(HTTP 请求,Session
,以此类推)获取真正的UserPreferences
对象。容器注入这个代理对象到userManager
bean,这并不知道此UserPreferences
引用是代理。在这个例子中,当UserManager
实例调用在依赖注入UserPreferences
对象上的方法时,它实际上是在代理上调用方法。然后代理从(在本例中)HTTP
会话中获取实际的UserPreferences
对象,并将方法调用委托给检索到的实际UserPreferences
对象。
因此,在将request-scoped
和session-scoped
的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
容器为bean
创建一个代理,这个bean
通过<aop:scoped-proxy/>
元素被标记,基于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>
更多详细信息冠以选择基于
class
或基于接口代理,参考代理机制参考代码:
com.liyong.ioccontainer.starter.XmlBeanScopeIocContainer
1.5.5 自定义作用域
bean
作用域机制是可扩展的。你可以定义你自己的作用域或者甚至重定义存在的作用域,尽管后者被认为是不好的做法,你不能覆盖内置的单例和原型范围。
-
创建一个自定义作用域
去集成你的自定义作用域到
Spring
容器中,你需要去实现org.springframework.beans.factory.config.Scope
接口,在这章中描述。有关如何实现自己的作用域的想法,查看Scope
实现提供关于Spring
框架自身和Scope
的文档,其中详细说明了你需要实现的方法。Scope
接口有四个方法从作用域获取对象,从作用域移除它们,并且让它们销毁。例如:
Sesson
的scope
实现返回Season
作用域bean
(如果它不存在,这个方法返回一个新的bean
实例,将其绑定到会话以供将来引用)。 下面的方法从底层作用域返回对象:Object get(String name, ObjectFactory<?> objectFactory)
例如:
Session
的scope
实现移除Season作用域bean
从底层的Session
。对象应该被返回,但是如果这个对象指定的名称不存在你也可以返回null
。下面的方法从底层作用域移除对象:Object remove(String name)
以下方法注册在销毁作用域或销毁作用域中的指定对象时应执行的回调:
void registerDestructionCallback(String name, Runnable destructionCallback)
查看javadoc或者
Spring
作用域实现关于更多销毁回掉的信息。以下方法获取基础作用域的会话标识符:
String getConversationId()
这个表示每个作用域是不同的。对于
Session
作用域的实现,此标识符可以是Session
标识符。 -
使用自定义作用域
在你写并且测试一个或更多自定义
Scope
实现,你需要去让Spring
容器知道你的新作用域。以下方法是在Spring
容器中注册新范围的主要方法:void registerScope(String scopeName, Scope scope);
这个方法在
ConfigurableBeanFactory
接口上被定义,该接口可通过Spring
附带的大多数具体ApplicationContext
实现上的BeanFactory
属性获得。registerScope(..)
方法第一个参数是唯一的名字关于作用域。Spring
容器本身中的此类名称示例包括单例和原型。registerScope(..)
方法第二个参数是自定义Scope
实现假设你写你的自定义
Scope
实现并且像下面的例子注册它。接下来例子使用
SimpleThreadScope
,它包括Spring
但是默认是不被注册的。对于你自己的自定义范围实现,是相同的。Scope threadScope = new SimpleThreadScope(); //注册自定义作用域 beanFactory.registerScope("thread", threadScope);
然后,你可以按照你的自定义范围的作用域规则创建bean定义,如下所示:
<bean id="..." class="..." scope="thread">
通过自定义
Scope
实现,你不仅限于以编程方式注册作用域。你可以声明式注册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
本身,而不是从getObject()
返回对象。参考代码:
com.liyong.ioccontainer.starter.XmlCustomScopeIocContainer
1.6 自定义bean
的性质
Spring
框架提供一些接口,你可以使用这些接口去自定义bean
的性质。这个章节包括下面内容:
-
生命周期回调
-
其他的
Aware
接口
1.6.1 生命周期回调
为了与容器的bean
的生命周期的管理交互,你可以实现Spring
提供的InitializingBean
和DisposableBean
接口。容器为前者调用afterPropertiesSet()
并为后者调用destroy()
,以使Bean
在初始化和销毁Bean
时执行某些操作。
在现代化的
Spring
应用中,JSR-250
的@PostConstruct
和@PreDestroy
注解是一般被考虑的最好实践去接收生命周期回调。使用这些注解意味着你的这些bean
不需要耦合Spring
规范接口。更多详情,查看@PostConstruct
和@PreDestroy
如果你不想去使用
JSR-250
注解,但是你仍然想移除耦合,考虑init-method和destroy-method
bean的元数据定义。
内部地,Spring
框架使用BeanPostProcessor
实现去处理任何的回调接口,它能找到和调用适合的方法。如果你需要定制特性或者其他的生命周期行为Spring
默认没有提供,你需要自己实现一个BeanPostProcessor
。关于更多的信息,查看容器扩展点。
除了初始化和销毁回掉外,Spring
管理的对象还可以实现Lifecycle
接口以便这些对象可以在容器自身的生命周期的驱动下参与启动和关闭过程。
生命周期回调接口在这章节中描述。
-
初始化回调
org.springframework.beans.factory.InitializingBean
接口允许在bean
的所有属性被设置之前执行初始化工作。InitializingBean
接口有一个简单方法:void afterPropertiesSet() throws Exception;
我们推荐你不要去使用
InitializingBean
接口,因为它不必要的耦合Spring
。或者,我们建议使用@PostConstruct
注解或者指定一个POJO
初始化方法。在基于XML
配置元数据实例中,你可以使用init-method
属性去指定方法的名称并且方法没有返回值和无参数签名。在JavaConfig
中,你可以使用@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
代码。参考代码:
com.liyong.ioccontainer.starter.XmlInitializationIocContainer
-
销毁回调
当包含
bean
的容器被销毁时,实现org.springframework.beans.factory.DisposableBean
接口的bean
获得回调。DisposableBean
接口有个简单方法:void destroy() throws Exception;
我们推荐你不要使用
DisposableBean
回掉接口,因为它不必要的耦合Spring
代码。或者,我们建议使用@PreDestroy
注解或者指定一个通用方法通过bean
的定义支持。基于XML
的配置元数据,你可以在上使用destroy-method
属性。通过JavaConfig
配置,你可以使用@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代码。
你可以赋值元素的
destroy-method
属性值,这指示Spring
自动的在bean
类上检测一个公共的close
或者shutdown
方法。(因此,任何实现java.lang.AutoCloseable
或java.io.Closeable
的类都将匹配)你可以在元素的default-destroy-method
属性设置这个指定的值去应用这个行为到整个bean
容器(查看默认初始化和销毁方法)。注意:这个一个默认行为在JavaConfig
中。参考代码:
com.liyong.ioccontainer.starter.XmlDestuctionIocContainer
-
默认初始化和销毁方法
当你写初始化和销毁方法回调的时候,不要使用
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>
顶层元素属性
default-init-method
中描述导致Spring IoC
容器在bean
的类上去识别一个叫做init
的方法作为初始化方法回调。当bean
被创建和组装好,如果bean
的类有一个方法,它在适当的时候被调用。你可以配置销毁方法回调类似(在
XML
)的通过使用在顶层元素上的default-destroy-method
方法。现有的
Bean
类已经具有回调方法的名称,这些方法的命名方式与约定不符,你可以通过元素属性init-method
和destroy-method
指定方法名称。Spring
容器保证配置初始化方法在bean
提供所有依赖后立即被调用。因此,在原始bean
引用上调用初始化回调,这意味着AOP
拦截器等还没有应用到bean
上。首先完全创建目标bean
,然后应用带有其拦截器链的AOP
代理。如果目标Bean
和代理分别定义,则你的代码甚至可以绕过代理与原始目标Bean
进行交互。因此,将拦截器应用于init
方法是不一致的,因为这样做会将目标bean
的生命周期耦合到它的代理或拦截器,并在代码直接与原始目标bean
交互时留下奇怪的语义。 -
组合生命周期机制
从Spring2.5后,你有三种可以选择的方式去控制生命周期行为:
InitializingBean
和DisposableBean
回调接口- 自定义
init()
和destroy()
@PostConstruct
和@PreDestroy
注解.。你可以组合这些机制去控制bean
。
如果为bean配置多个生命周期机制并且每个机制被配置不同的方法名称,每个配置的方法都按照此注释后列出的顺序执行。但是,如果为多个生命周期机制中的多个生命周期机制配置了相同的方法名称(例如,为初始化方法使用init()),则该方法将执行一次,如上一节所述。
为同一个
bean
配置的多种生命周期机制具有不同的初始化方法,如下所示:- 方法注解
@PostConstruct
- 通过
InitializingBean
回调接口定义afterPropertiesSet()
- 自定义配置
init()
方法
销毁方法的调用顺序相同:
- 方法注解
@PreDestroy
- 通过
DisposableBean
回掉接口定义destroy()
- 自定义配置
destroy()
方法
-
启动和关闭回调
Lifecycle
接口定义了必须要的方法为这些bean
生命周期所必须(例如:启动和停止一些后台处理)。public interface Lifecycle { void start(); void stop(); boolean isRunning(); }
任何
Spring
管理的对象可能实现Lifecycle
接口。然而,当ApplicationContext
接收到启动和停止信号(例如:在运行时停止和重启场景),它将这些调用级联到在该上下文中定义的所有生命周期实现。通过代理到LifecycleProcessor
处理,在下面清单显示:public interface LifecycleProcessor extends Lifecycle { void onRefresh(); void onClose(); }
注意:
LifecycleProcessor
是Lifecycle
接口的拓展。这个接口增加了两个额外的方法去接收上下文的刷新和关闭。注意:
常规的
org.springframework.context
。生命周期接口是显式启动和停止通知的普通契约,并不意味着在上下文刷新时自动启动。为了更细粒度控制bean
的自动启动(包括启动阶段),考虑使用org.springframework.context.SmartLifecycle
替换。停止通知不会被保证在销毁之前到来。在常规的关闭上,所有
Lifecycle
的bean
第一次接受停止通知在销毁回掉被传播之前。但是,在上下文生命周期中进行热更新或更新尝试失败时,仅调用destroy
方法。启动和关闭顺序调用是非常重要的。如果依赖关系存在任何对象之间,依赖侧开始在被依赖之后并且它的停止在它的依赖之前。然而,在运行时,这个直接依赖是未知的。你可能只知道某种类型的对象应该先于另一种类型的对象开始。在这种情况下,
SmartLifecycle
接口定义其他可选,即getPhase()
方法在它的父接口被定义,Phased
。下面的清单显示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
的“正常”生命周期对象的默认阶段为0
。因此,任何负的阶段值表示对象应该启动在这些标准的组件之前(停止在它们之后)。对于任何正阶段值,情况正好相反。停止方法通过
SmartLifecycle
接受一个回调被定义。任何实现必须在实现的关闭处理完成后调用回掉的run()
方法。这将在必要时启用异步关闭,因为LifecycleProcessor
接口的默认实现DefaultLifecycleProcessor
会等待其超时值,以等待每个阶段内的对象组调用该回调。每个阶段默认超时时间30秒。你可以通过定义一个bean
命名为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
的另一个特性。当这个上下文被刷新(所有的bean
被初始化和实例化),即回调被调用。在那个阶段,默认的生命周期处理器通过每个SmartLifecycle
对象的isAutoStartup()
方法检测返回boolean
值。如果true
,对象在那个阶段被启动而不是等待上下文或者自身的start()
方法显示调用(与上下文刷新不同,对于标准上下文实现,上下文启动不会自动发生)。像前面所描述的,phase
值和任何依赖关系确定了启动顺序。参考代码:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
-
在非Web应用程序中优雅关闭Spring IoC容器
这个部分仅仅适用非
web
应用程序。Spring
的基于Web
的ApplicationContext
实现提供优雅的关闭Spring IoC
容器,当相关的web
应用程序关闭的时候。如果你使用
Spring
的IoC
容器在非web
应用环境(例如,在一个富客户端桌面环境),在JVM
注册一个关闭钩子。这样做确保优雅的关闭和调用在单例bean
上的销毁方法,因此所有资源被释放。你必须仍然正确地配置和实现这些销毁回调。去注册一个关闭钩子,调用
registerShutdownHook()
方法是在ConfigurableApplicationContext
接口上声明,如下例子: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"); //增加一个Hook构造回调 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
创建它们,通过ApplicationContext
接口或者转换引用为已知的这个接口的子类(例如:ConfigurableApplicationContext
,它暴露了附加的功能)。一种用途是通过编程方式检索其他bean
。有时候这个能力非常有用。然而,一般的,你应该避免它,因为它耦合Spring
的代码并且不遵循控制反转格式,将协调者作为bean
的属性。ApplicationContext
其他方法提供获取资源文件、发布应用事件和访问MessageSource
。这些额外的特性在ApplicationContext的附加能力中描述。
自动装配是获得对ApplicationContext
的引用的另一种选择。传统的constructor
和byType
自动装配模式(在自动装配协作器中描述)能够为构造函数或者Setter
方法参数提供一个ApplicationContext
类型的依赖。为了获得更大的灵活性,包括自动装配字段和多个参数方法的能力,可以使用基于注解的自动装配特性。如果这样做,ApplicationContext
自动装配到字段、构造参数或方法参数,如果字段、构造函数或方法带有@Autowired
注解那么该参数期望ApplicationContext
类型。更多的信息,查看使用@Autowired。
当ApplicationContext
创建一个类并且实现org.springframework.beans.factory.BeanNameAware
接口时,该类将获得对其关联对象定义中定义的名称的引用。下面清单显示BeanNameAware
接口定义:
public interface BeanNameAware {
void setBeanName(String name) throws BeansException;
}
这个回调在bean
被填充之后被调用,但是在初始化回调之前例如InitializingBean
, afterPropertiesSet
或者自定义init-method
。
参考代码:
com.liyong.ioccontainer.starter.XmlAwareIocContainer
1.6.3 其他Aware接口
除了ApplicationContextAware
和BeanNameAware
之外,Spring
提供了广泛的Aware
回调接口,这些接口使Bean
向容器指示它们需要一些基础结构依赖性。作为基本规则,这个名字指示依赖类型。下面的表格总结最重要的Aware
接口:
Name | Injected Dependency | Explained in… |
---|---|---|
ApplicationContextAware | 注入 ApplicationContext . | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | 注入ApplicationEventPublisher . | Additional Capabilities of the ApplicationContext |
BeanClassLoaderAware | Class loader used to load the bean classes. | Instantiating Beans |
BeanFactoryAware | 注入 BeanFactory . | ApplicationContextAware and BeanNameAware |
BeanNameAware | 注入BeanName | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | 注入BootstrapContext | JCA CCI |
LoadTimeWeaverAware | 注入LoadTimeWeaver . | Load-time Weaving with AspectJ in the Spring Framework |
MessageSourceAware | 注入MessageSource | Additional Capabilities of the ApplicationContext |
NotificationPublisherAware | 注入NotificationPublisher . | Notifications |
ResourceLoaderAware | 注入ResourceLoader | Resources |
ServletConfigAware | 注入ServletConfig . | Spring MVC |
ServletContextAware | 注入ServletContext . | Spring MVC |
再次注意,使用这些接口关联到你的代码到Spring API
并且不遵循控制反转的风格。我们推荐使用基础设施bean
需要编程的去容器获取。
1.7 Bean Definition继承
bean
定义包含一些配置信息,包括构造函数参数、属性值、和容器特定信息,例如初始化方法、静态工厂方法名等等。子bean
定义继承父bean
定义配置数据。子bean
定义能够覆盖一些值或者增加其他需要。使用父bean
和子bean
定义能够保存一些类型。实际上,这是一种模版模式。
如果你编程式使用ApplicationContext
接口,子bean
定义通过ChildBeanDefinition
类描述。大多数用户不在这个级别使用(译者:应用开发人员一般不会接触)。相反,它们在例如ClassPathXmlApplicationContext
之类的类中声明性地配置bean
定义。当你使用基于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>
<!--parent指定继承父类-->
<bean id="inheritsWithDifferentClass"
class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBean" init-method="initialize">
<property name="name" value="override"/>
<!-- the age property value of 1 will be inherited from parent -->
</bean>
如果没有指定,子bean
定义将使用父定义中的bean
类,但也可以覆盖它。在后面这种情况下,子bean
类必须兼容父bean
定义(也就是说,必须接受父bean
的属性值)。
子bean
定义继承作用域、构造参数值、属性值、和覆盖父类方法,并可以添加新值。任何作用域、初始化方法、销毁方法或static
工厂方法设置都会覆盖对应的父bean
设置。
其余设置始终从子bean
定义中获取:依赖、自动装配、依赖检查、单例和懒加载。
前面的例子通过使用abstract
属性显示标记父bean
定义作用一个抽象,如果父bean
定义没有指定类,显示地标记父bean
定义为abstract
是需要的,像下面例子展示:
<bean id="inheritedTestBeanWithoutClass" abstract="true">
<property name="name" value="parent"/>
<property name="age" value="1"/>
</bean>
<bean id="inheritsWithClass" class="org.springframework.beans.DerivedTestBean"
parent="inheritedTestBeanWithoutClass" init-method="initialize">
<property name="name" value="override"/>
<!-- age will inherit the value of 1 from the parent bean definition-->
</bean>
父bean
不能被实例化,因为它是不完整的,并且它被显示的标记为abstract
。当一个定义是abstract
,它仅仅作为一个bean
定义的模版且父bean
定义为子bean
定义服务。尝试自己使用这样的抽象父bean
,通过将其引用为另一个bean
的ref
属性或使用父bean
ID
进行显式的getBean()
调用将返回错误。类似地,容器的内部preInstantiateSingletons()
方法将忽略定义为抽象的bean定义。
默认情况下,
ApplicationContext
会预先实例化所有单例。因此,重要的是(至少对于单例bean),如果有一个(父)bean
定义仅打算用作模板,并且此定义指定了一个类,则必须确保将abstract
属性设置为true
,否则应用程序上下文将实际上(试图)预先实例化抽象Bean
。
参考实例:
com.liyong.ioccontainer.starter.XmlBeanDefinitionInheritanceContainer
1.8 容器拓展点
典型地,应用程序开发者不需要实现ApplicationContext
类。相反,Spring IoC
容器通过插件实现指定的集成接口去扩展。下面的章节描述这些接口的集成。
1.8.1 通过使用BeanPostProcessor
自定义bean
BeanPostProcessor
接口定义回调方法,你可以实现这个接口提供你自己的(或者覆盖容器的默认设置)初始化逻辑、依赖解析逻辑等等。如果你想实现一些自定义逻辑,在Spring
容器完成实例化、配置、初始化bean
之后,你可以插入一个或多个自定义BeanPostProcessor
实现。
你可以配置多个BeanPostProcessor
实例并且你可以通过设置order
属性来控制这些BeanPostProcessor
实例的执行顺序。仅仅BeanPostProcessor
实现Ordered
接口是可以设置这个属性。如果自己实现BeanPostProcessor
,你应该考虑实现Ordered
接口。更多详情,查看 BeanPostProcessor
和Ordered
接口。参见有关以编程方式注册BeanPostProcessor实例的说明。
BeanPostProcessor
接口在bean
(对象)实例上操作。也就是说,Spring IoC
容器实例化一个bean
实例,然后BeanPostProcessor
实例执行它们的工作。(译者:在Spring容器初始化bean过程中执行相关的回调操作)
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
自动的检查所有bean
,这些bean
在配置元数据中实现了BeanPostProcessor
接口。ApplicationContext
注册这些bean
作为后置处理器,以便以后在创建bean
时可以调用它们。Bean
后处理器可以与其他bean
相同的方式部署在容器中。
注意,当通过在类上使用@Bean
工厂方法声明BeanPostProcessor
时,工厂方法返回类型应该是实现类本身或只是实现org.springframework.beans.factory.config.BeanPostProcessor
接口,清晰地表明该bean
的后处理器性质。否则,ApplicationContext
无法在完全创建之前按类型自动检测它。由于BeanPostProcessor
需要及早实例化才能应用于上下文中其他bean
的初始化,因此这种早期类型检测至关重要。
编程式地注册BeanPostProcessor实例
推荐的去注册
BeanPostProcessor
方式是通过ApplicationContext
自动检测(前面描述),可以使用addBeanPostProcessor
方法以编程方式针对ConfigurableBeanFactory
注册它们。当注册之前你需要评估条件逻辑甚至需要跨层次结构的上下文复制后置处理器时,这是非常有用的。注意,然而,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)
.如果你有通过使用自动装配或
@Resource
注入bean
到BeanPostProcessor
中(可能回退到自动装配),当搜索类型匹配的依赖项候选者时,Spring
可能会访问意外的Bean,因此使它们不适合进行自动代理或其他类型的Bean后处理。例如,如果你有一个用@Resource
标注的依赖项,其中字段或Setter
名称不直接与bean
的声明名称相对应,并且不使用name
属性,那么Spring
将访问其他bean
以按类型匹配它们。
下面的例子展示了在ApplicationContext
中怎样去编写、注册和使用BeanPostProcessor
接口。
例子:Hello World
第一个例子说明基础的使用。这个例子展示一个自定义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) {
//打印bean信息
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
例子:RequiredAnnotationBeanPostProcessor
将回调接口或与自定义BeanPostProcessor
实现结合使用是扩展Spring IoC
容器常用方法。Spring
的RequiredAnnotationBeanPostProcessor
是一个示例,它是Spring
发行版附带的BeanPostProcessor
实现,可以确保标有(任意)注解的bean
上的JavaBean
属性实际上(配置为)依赖注入了一个值。说明:就是常用的依赖注入。
参考代码:com.liyong.ioccontainer.starter.XmlBeanPostProcessorIocContainer
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,违反了标准容器生命周期。这会导致负面的影响,例如:绕过后置处理。
同样,BeanFactoryPostProcessor实例是按容器划分。(如果你使用分层的容器才会有意义) 如果你定义在容器中定义一个BeanFactoryPostProcessor,它仅适用于该容器中的bean定义。在一个容器中的bean定义不会被其他的容器中BeanFactoryPostProcessor后置处理,即使两个容器在同一个层级。
Bean工厂后处理器在ApplicationContext中声明时会自动执行,以便将更改应用于定义容器的配置元数据。Spring包括一些预定义的工厂后置处理器,例如PropertyOverrideConfigurer和
PropertySourcesPlaceholderConfigurer。你可以使用一个自定义的BeanFactoryPostProcessor-例如,注册一个自定义属性编辑器。
ApplicationContext自动地检测任何部署到容器中并实现BeanFactoryPostProcessor接口的实例bean。使用这些bean作为bean工厂后置处理器,在适当的时间。你可以像其他ban一样部署这些后置处理器。
与BeanPostProcessors一样,通常不希望配置BeanFactoryPostProcessors进行延迟初始。如果没有其他bean引用Bean(Factory)PostProcessor,这个后置处理器不会被初始化。因此,标记它为延迟初始化将被忽略并且Bean(Factory)PostProcessor将被提前初始化即使你的声明default-lazy-init属性为true。
参考代码:
com.liyong.ioccontainer.starter.XmlBeanFactoryPostProcessorIocContainer
例如:类名替换PropertySourcesPlaceholderConfigurer
可以使用PropertySourcesPlaceholderConfigurer通过使用标准Java属性格式将属性值从bean定义外部化到一个单独的文件中(意思是: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样式。
真实值来自于标准的Properties格式的其他文件:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,${jdbc.username}字符串在运行时被替换为sa
值,并且同样应用到其他的占位符在properties文件中匹配这些key。PropertySourcesPlaceholderConfigurer检查Bean定义的大多数属性和属性中的占位符。此外,你可以自定义占位符的前缀和后缀。
context命名空间在Spring2.5中被引入,你可以配置一个专用配置元素的占位符。在location属性中你可以提供一个或多个location通过逗号分隔,类似下面例子:
<context:property-placeholder location="classpath:com/something/jdbc.properties"/>
PropertySourcesPlaceholderConfigurer不仅仅在你指定的Properties中查找属性。默认情况,如果在指定的属性文件中没有找到属性,它会再次检查Spring Environment属性和常规的Java 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时,即在非lazy-init bean的ApplicationContext的preInstantiateSingletons()阶段,bean的解析将失败。
.参考代码:
com.liyong.ioccontainer.starter.XmlPropertySourcesPlaceholderConfigurerIocContainer
例子:PropertyOverrideConfigurer
另一个bean工厂后处理器PropertyOverrideConfigurer类似于PropertySourcesPlaceholderConfigurer,但与后者不同,原始定义可以具有bean属性的默认值,也可以完全没有值。如果覆盖的属性文件对于一些bean属性没有符合内容,默认的上下文定义被使用。
注意:bean定义没有意识到被覆盖,因此从XML定义文件中不能立即看出覆盖的配置正在被使用。由于存在覆盖机制,在多个PropertyOverrideConfigurer实例情况下对应相同bean属性不同的值,最后一个将被使用。
属性文件配置格式:
beanName.property=value
下面清单显示格式化例子:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
此示例文件可与容器定义一起使用,容器定义包含一个名为dataSource的bean,该bean具有driver和url属性。
复合属性名也是被支持的,只要路径的每个组件(被覆盖的最终属性除外)都是非空的(可能是由构造函数初始化的)。下面的例子,bean的tom属性fred的sammy属性设置只123:
tom.fred.bob.sammy=123
指定的重写值总是文本值。它们没有被转译为bean引用。当XML bean定义中的原始值指定bean引用时,这种约定也适用。
context命名空间在Spring2.5中被引入,可以使用专用配置元素配置属性覆盖,类似下面例子:
<context:property-override location="classpath:override.properties"/>
参考代码:
com.liyong.ioccontainer.starter.XmlPropertyOverrideConfigurerIocContainer
1.8.3 FactoryBean自定义初始化逻辑
可以为本身就是工厂的对象实现org.springframework.beans.factory.FactoryBean接口。
这个FactoryBean接口是Spring IoC容器的实例化逻辑可插拔点。如果你有一个复杂的初始化代码在Java中比冗余XML更好的表达,你可以创建你自己的FactoryBean,在实现类中写复杂的初始化逻辑,然后插入你的自定义FactoryBean到容器中。
FactoryBean接口提供三个方法:
- Object getObject():返回这个工厂创建的一个实例。这个实例可能被共享,依赖于这个工厂返回的是单例或原型。
- boolean isSingleton():如果FactoryBean返回一个单例返回true否则为false
- Class getObjectType():返回由getObject()方法返回的对象类型;如果类型未知,则返回null
FactoryBean概念和接口在Spring框架中一些地方被使用到。Spring有超过50个FactoryBean接口的实现。
当你需要向容器获取一个实际的FactoryBean实例本身而不是由它产生的bean时请在调用ApplicationContext的getBean()方法时在该bean的ID前面加上一个&
符号。因此,对于id为myBean的给定FactoryBean,在容器上调用getBean(“myBean”)将返回FactoryBean的产生的bean,而调用getBean(“&myBean”)将返回FactoryBean实例本身。
参考代码:
com.liyong.ioccontainer.service.CustomFactorBean
1.9 基于注解的容器配置
Spring配置注解比XML配置更好?
基于注解的配置介绍抛出一个问题,是否比XML方式更好。简单的回答是看场景。具体的描述是每种方式各有利弊,通常的,这个由开发者去决定更适合他们的策略。由于这种定义的方式,注解在声明中提供了大量的上下文,导致更短和更简洁的配置。XML擅长于连接组件,而不需要修改它们的源代码或重新编译它们。一些开发人员倾向于闭源,而另一些人则认为被注释的类不再是pojo,而且配置变得分散且更难控制。
无论怎么选择,Spring能够兼容两种风格甚至是混合使用。值得指出的是通过JavaConfig,Spring允许以无侵入式方式使用,不需要接触目标组件源代码。在工具方面,所有的配置风格通过Spring的Tools Eclipse工具支持。
基于注解的配置提供了XML设置的替代方法,它依靠字节码元数据来连接组件,而不是尖括号声明(不需要xml的格式配置)。替换使用XML去描述bean,开发者只需移动配置到类本身并通过在关联的类、方法、字段上使用注解。在RequiredAnnotationBeanPostProcessor
中提到,结合使用BeanPostProcessor和注释是扩展Spring IoC容器的常用方法。例如,Spring 2.0引入了使用@Required注释强制执行必需属性的可能性。Spring 2.5使遵循相同的通用方法来驱动Spring的依赖注入成为可能。实际上,@Autowired注解提供了相同的能力,在 Autowiring Collaborators中被描述,但是提供了更细粒度和更多的能力。Spring2.5增加对JSR250支持 ,例如:@PostConstruct、@PreDestroy。Spring3.0增加对JSR-330 (Java的依赖注入)注解包含在包javax.inject,例如@Inject和@Named。更多关于注解详情能在相关的文章中找到。
注解注入在XML注入之前被执行。因此,XML配置覆盖注解对属性的两种连接方式。
你可以注册他们作为独立的bean定义,但是他们也可以通过基于XML包含下面的标签被隐的注册(注意包含context命名空间)。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
隐式的注册后置处理器包括:
AutowiredAnnotationBeanPostProcessor
、CommonAnnotationBeanPostProcessor
, PersistenceAnnotationBeanPostProcessor
和前面提到的RequiredAnnotationBeanPostProcessor
<context:annotation-config />仅在定义它的相同应用程序上下文中查找关于bean的注解。意思是,如果你在WebApplicationContext中为DispatcherServlet配置< context:annotation-config/> ,它仅仅检查@Autowired在你的Control层并且不会在你的Service层。查看DispatcherServlet 更多信息。
1.9.1 @Required
@Required注解应用到bean属性的Setter方法,类似下面例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Required
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
这个注解表示bean属性在运行时必须被填充赋值,在一个bean定义中显示赋值或自动装配。如果受影响的bean属性没有被填充值容器抛出一个异常。这允许更早的显示异常,避免以后再出现NullPointerException实例等。我们仍然推荐你把断言放入到bean类本身中(例如,放入初始化方法)。这样做会强制执行那些必需的引用和值,即使你在容器外部使用该类也是如此。
@Required注解在Spring5.1中被正式的不推荐使用,更好的方式是,使用构造函数注入要求的设置(或InitializingBean.afterPropertiesSet()的自定义实现以及bean属性设置器方法)。
1.9.2 @Autowired
JSR 330的@Inject注解能够使用在Spring的@Autowired注解的地方。
可以在构造方法上使用@Autowired,类似下面例子:
public class MovieRecommender {
private final CustomerPreferenceDao customerPreferenceDao;
@Autowired
public MovieRecommender(CustomerPreferenceDao customerPreferenceDao) {
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
从Spring4.3开始,如果目标bean仅仅定义以一个构造函数开始,那么构造函数上的@Autowired注解是不必要的。然而,如果有一些构造函数是有效的并且没有主要默认的构造函数,则至少有一个构造函数必须被注解为@Autowired,目的是引导容器选择其中一个去使用。查看构造方法解析详情。
你也可以应用@Autowired注解到传统的Setter方法,类似下面例子:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Autowired
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
// ...
}
你可以应用@Autowired注解到任意方法名和多个参数,类似下面例子:
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注入点。否则,注入可能由于在运行时出现“找不到类型匹配”错误而失败。
对于基于XML定义的bean或者组件类通过类路径扫描,容器通常知道具体的类型。然而,对于@Bean工厂方法,你需要确保声明的返回类型足够的表达。对于实现多个接口的组件,或者对于实现类型可能引用的组件,请考虑在工厂方法中声明最具体的返回类型。至少与指向bean的注入点所要求的一样具体(译者:尽可能返回具体的实现类型)。
你还可以引导Spring从ApplicationContext中提供特定类型的所有bean,通过将@Autowired注解添加到需要该类型数组的字段或方法中,类似下面例子:
public class MovieRecommender {
@Autowired
private MovieCatalog[] movieCatalogs;
// ...
}
类型化集合也是如此,类似下面例子:
public class MovieRecommender {
private Set<MovieCatalog> movieCatalogs;
@Autowired
public void setMovieCatalogs(Set<MovieCatalog> movieCatalogs) {
this.movieCatalogs = movieCatalogs;
}
// ...
}
如果你想数组或集合中的元素在指定的顺序下被排序,那么你的目标bean可以实现org.springframework.core.Ordered接口、@Order或标准的@Priority注解。否则,它们的顺序遵循在容器中对应目标bean定义注册的顺序。
你可以在目标类级别和在@Bean方法上申明@Order注解,可能是针对单个bean定(多个定义情况下使用相同的bean类)。@Order值可能会影响注入点的优先级,不会影响单例bean的启动顺序,这是由依赖关系和@DependsOn声明确定。
注意:标准的javax.annotation.Priority注解在@Bean级别是无效的,因为它不能被声明在方法上。它的语义可以通过@Order值与@Primary结合在每种类型的单个bean上。
甚至类型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;
}
// ...
}
如果一个非必需的方法的依赖项(或者它的一个依赖项,在有多个参数的情况下)不可用,那么这个方法将不会被调用。在这种情况下,完全不需要填充非必需字段,将其默认值保留在适当的位置。注入构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能要处理多个构造函数,@Autowired中的required属性有一些不同的含义。
注入的构造函数和工厂方法参数是一种特殊情况,因为由于Spring的构造函数解析算法可能会处理多个构造函数,因此@Autowired中必填属性的含义有所不同。默认情况下,有效地需要构造函数和工厂方法参数,但是在单构造函数场景中有一些特殊规则,例如,如果没有匹配的Bean可用,则将多元素注入点(数组,集合,映射)解析为空实例。这允许一种通用的实现模式,其中所有依赖项都可以在唯一的多参数构造函数中声明-例如,申明一个简单的公共构造函数不需要@Autowired注解。
任何给定的bean类只有一个构造函数可以声明@Autowired并设置属性required为true,指示当使用Spring Bean时构造函数自动装配。因此,如果必填属性保留为默认值true,则只能使用@Autowired注释单个构造函数。如果有多个构造函数声明注解,那么它们都必须声明required=false,以便被认为是自动装配的候选对象(类似于XML中的autowire=constructor)。将选择通过匹配Spring容器中的bean可以满足的依赖关系数量最多的构造函数如果没有一个候选者满意,则将使用主要的/默认构造函数(如果存在)。同样,如果一个类声明了多个构造函数,但都没有使用@Autowired进行注解,则将使用主要的/默认构造函数(如果存在)。如果一个类仅声明一个单一的构造函数开始,即使没有注释,也将始终使用它。请注意,带注解的构造函数不必是公共的。
在setter方法上,建议使用@Autowired的required属性,而建议使用@Required注解。设置required属性为false则表明这个属性不是必须的对于自动装配意图,并且这个属性如果不能被自动装配将被忽略。换句话说,@Required更强大,因为它强制通过容器支持的任何方式设置属性,如果没有定义值,则会引发相应的异常。
另外,你可以通过Java 8的java.util.Optional来表达特定依赖项的非必需性质,类似以下示例所示:
public class SimpleMovieLister {
@Autowired
public void setMovieFinder(Optional<MovieFinder> movieFinder) {
...
}
}
从Spring5.0后,你可以使用@Nullable注解(任何包装中的任何种类-例如,JSR-305的javax.annotation.Nullable)或者只是利用Kotlin内置的null安全支持:
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注解是通过Spring的BeanPostProcessor实现处理的。这意味着你不能应用这些注解在你自己的BeanPostProcessor和BeanFactoryPostProcessor类中。这些类型必须被通过XML或者Spring @Bean方法连接。
参考代码:
com.liyong.ioccontainer.starter.XmlIocAnnotationContainerConfigrationntainer
1.9.3 使用@Primary
对基于注解的自动装配调整
因为通过类型的自动装配可能导致多个候选者,通常有必要需要更多的选择处理。一种可以完成的方式是使用Spring的@Primary注解。@Primary表示当多个bean自动装配到单值依赖时这个bean是我们更期望的值。如果主要的bean在候选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-->
<bean class="example.SimpleMovieCatalog" primary="true">
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
1.9.4 使用Qualifiers对基于注解的自动装配调整
@Primary
是一种根据类型使用自动装配的有效方法,当可以确定一个主要候选对象时,可以使用多个实例。当你需要更多的控制选择处理时,你可以使用Spring的@Qualifier
注解。你可以将限定符值与特定的参数相关联,从而缩小类型匹配的范围,以便每个参数选择特定的bean。在这个简单的例子中,这可以是简单的描述性值,如以下示例所示:
public class MovieRecommender {
@Autowired
@Qualifier("main")
private MovieCatalog movieCatalog;
// ...
}
你也可以指定@Qualifier
注解在各个构造函数参数或方法参数上,类似下面的例子:
public class MovieRecommender {
private MovieCatalog movieCatalog;
private CustomerPreferenceDao customerPreferenceDao;
@Autowired
public void prepare(@Qualifier("main") MovieCatalog movieCatalog,
CustomerPreferenceDao customerPreferenceDao) {
this.movieCatalog = movieCatalog;
this.customerPreferenceDao = customerPreferenceDao;
}
// ...
}
下面的例子显示对应的bean定义:
<?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"/> //1
<!-- inject any dependencies required by this bean -->
</bean>
<bean class="example.SimpleMovieCatalog">
<qualifier value="action"/> //2
<!-- inject any dependencies required by this bean -->
</bean>
<bean id="movieRecommender" class="example.MovieRecommender"/>
</beans>
- 具有
main
限定符值的Bean与构造函数参数连接,构造函数参数使用相同的值进行限定 - 带有
action
限定符值的bean与构造函数参数连接,构造函数参数使用相同的值进行限定。
为了回退匹配,bean的名字是默认的限定符。因此,你可以定义bean的id为main替代嵌入的限定符元素,导致相同的匹配结果。然而,即使你能使用这个约定通过名字指定bean,@Autowired基本上是关于带有可选语义限定符的类型驱动的注入(意思是@Autowired中带有require限定符)。这意味着限定符值总是有缩小类型匹配集合的语义,即使bean名为回退名称。他们从语义上不能表达引用一个唯一bean的id。好的限定符值是main
、EMEA
或persistent
,它们表示独立于bean id
的特定组件的特征,对于匿名bean定义(如前面示例中的定义),可以自动生成这些特征。
限定符也能应用到类型化的集合,像前面讨论的-例如,Set。在这种例子中,所有匹配的bean,根据声明的限定符,作为一个集合被注入。这暗示限定符没有必要是唯一的。相反,它们构成了过滤标准。例如,你可以定义具有相同限定符值action
的多个MovieCatalog Bean,所有这些都注入到以@Qualifier(“ action”)
注解的Set 中。
在类型匹配的候选者中,让限定符值选择目标bean名称,在注入点不需要@Qualifier注解。如果没有其他解析指示器(例如限定符或primary标记),对于非唯一依赖情况,Spring将注入点名(即字段名或参数名)与目标bean名相匹配,并选择同名的候选项(如果有的话)。
也就是说,如果你打算试图通过名称表示注解驱动注入,不要主要地使用@Autowired,即使它有通过名称在类型匹配的候选者间选择的能力。相反,使用JSR-250@Resource
注解,它是语义地通过使用唯一名称定义标识一个指定目标组件,声明的类型与匹配过程无关。@Autowired有不同的语义:按类型选择候选bean之后,指定的字符串限定符值仅在那些类型选择的候选中被考虑(例如,匹配一个account
限定符与bean被标记相同的限符标签)。
对于定义为集合、Map、数组类型的bean,@Resource
是一个好的解决方案,通过唯一的名称引用指定的集合或数组bean。也就是说,在Spring4.3以后你可以匹配一个Map和数组类型通过Spring的@Autowired
类型匹配算法,只要元素类型信息保留在@Bean返回类型签名或集合继承层次结构中。在这种场景中,你可以使用限符值去相同类型集合选择,如前一段所述。
在Spring4.3以后,@Autowired
还考虑了注入的自我引用(也就是说,引用回当前注入的bean)。注意自我注入是一种后备。对其他组件的常规依赖始终优先。从这个意义上说,自我推荐不参与常规的候选人选择,因此尤其是绝不是主要的。相反,它们总是以最低优先级结束。实际上,你应该仅将自我引用用作最后的手段(例如,在相同实例上通过bean的事物代理调用其他方法)。在这种情况下,考虑将受影响的方法分解为单独的委托Bean。或者,你可以使用@Resource
,它可以通过其唯一名称获取返回到当前bean的代理。
尝试将
@Bean
方法的结果注入相同的配置类也是一种有效的自引用方案。要么在实际需要的方法签名中懒惰地解析此类引用(与配置类中的自动装配字段相对),要么将受影响的@Bean
方法声明为静态,将其与包含的配置类实例及其生命周期脱钩。否则,仅在回退阶段考虑此类Bean,而将其他配置类上的匹配Bean选作主要候选对象(如果可用)。
@Autowired
应用到字段、构造函数和多参数方法,在参数级别允许使用限定符注解缩小选择范围。相反,@Resource
仅仅支持字段和bean属性Setter方法简单参数。因此,如果注入目标是构造函数或多参数方法,则应坚持使用限定符。
你可以创建你自己的自定义限符注解。为了这样做,定义一个注解和提供@Qualifier注解在你的定义中,类似下面例子:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Genre {
String value();
}
然后,你可以提供自定义限符在自动装配字段和参数上,类似下面例子:
public class MovieRecommender {
@Autowired
@Genre("Action")
private MovieCatalog actionCatalog;
private MovieCatalog comedyCatalog;
@Autowired
public void setComedyCatalog(@Genre("Comedy") MovieCatalog comedyCatalog) {
this.comedyCatalog = comedyCatalog;
}
// ...
}
接下来,你可以为候选者bean定义提供信息。你可以增加标签的子标签并且指定type
和value
为匹配你的自定义限定符注解。这个类型是注解的全限定类名的匹配。或者,为方便起见,如果不存在名称冲突的风险,则可以使用简短的类名。下面例子展示两种方式:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
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>
在Classpath扫描和组件管理中,你可以看到在XML中基于注解另类的提供限定符元数据,查看Providing Qualifier Metadata with Annotations。
在某些场景中,使用注解不需要值可能就满足。这是非常有用的,当注解应用更一般的用途和可以应用于几种不同类型的依赖项时。例如,你可以提供一个脱机目录,当没有Internet连接可用时可以进行搜索。首先,定义相同的注解,类似下面的例子:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface Offline {
}
然后,增加注解到字段或者属性去自动装配,类似下面例子:
public class MovieRecommender {
@Autowired
@Offline //1
private MovieCatalog offlineCatalog;
// ...
}
- 这一行添加
@Offline
注解
接下来,bean的定义仅需要一个限定符type
,类似下面例子:
<bean class="example.SimpleMovieCatalog">
<qualifier type="Offline"/> //1
<!-- inject any dependencies required by this bean -->
</bean>
- 这个元素指定了限定符
你还可以定义自定义限定符注解,这些注解除了接受简单value
属性之外,还可以接受命名属性。如果随后在要自动装配的字段或参数上指定了多个属性值,则Bean定义必须与所有此类属性值匹配才能被视为自动装配候选。例如,请考虑以下注释定义:
@Target({ElementType.FIELD, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Qualifier
public @interface MovieQualifier {
String genre();
Format format();
}
在这个例子中Format是一个枚举,定义如下:
public enum Format {
VHS, DVD, BLURAY
}
字段装配字段是被自定义限定符注解并且保护genre和format属性值,类似下面例子:
public class MovieRecommender {
@Autowired
@MovieQualifier(format=Format.VHS, genre="Action")
private MovieCatalog actionVhsCatalog;
@Autowired
@MovieQualifier(format=Format.VHS, genre="Comedy")
private MovieCatalog comedyVhsCatalog;
@Autowired
@MovieQualifier(format=Format.DVD, genre="Action")
private MovieCatalog actionDvdCatalog;
@Autowired
@MovieQualifier(format=Format.BLURAY, genre="Comedy")
private MovieCatalog comedyBluRayCatalog;
// ...
}
最后,bean定义应该包含匹配的限定符值。这个例子展示你可以使用bean元数据属性替换元素。如果可用,元素及其属性优先,但是如果不存在这样的限定符,则自动装配机制将退回到标记内提供的值,如下面示例中的最后两个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.5 使用泛型作为自定装配限定
除了@Qualifier注解,你也可以使用Java泛型类型作为隐士的限定。例如,假设你有下面的配置:
@Configuration
public class MyConfiguration {
@Bean
public StringStore stringStore() {
return new StringStore();
}
@Bean
public IntegerStore integerStore() {
return new IntegerStore();
}
}
假设前面的bean实现一个泛型接口,(也就是说,Store和Store),你可以使用@Autowire装配Store接口并且泛型作为一个限定符,类似下面的例子:
@Autowired
private Store<String> s1; // <String> qualifier, injects the stringStore bean String限定符
@Autowired
private Store<Integer> s2; // <Integer> qualifier, injects the integerStore bean Integer限定符
当自动装配集合、Map接口和数组的时泛型限定符也可以使用。下面的例子装配Store泛型为integer的List:
// 注入所有泛型为<Integer> 的Store的bean
// Store<String> 的bean不会出现在List中
@Autowired
private List<Store<Integer>> s;
参考代码:
com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer
1.9.6 使用CustomAutowireConfigurer
CustomAutowireConfigurer
是一个BeanFactoryPostProcessor
,它允许你注册你自己的自定义限定符注解类型,甚至他们不被注解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
值 - 在元素上任何
default-autowire-candidates
模式有效 @Qualifier
注解和任何自定义注解存在注册CustomAutowireConfigurer
当多个bean限定符作为自动装配候选者时,primary
的确定类似下面:如果候选者bean中有一个bean被定义primary
属性值被设置为true,它将被选择。
参考代码:
com.liyong.ioccontainer.starter.XmlQualifierIocContainer
1.9.7 @Resource
注入
Spring也支持通过使用JSR-250注解(javax.annotation.Resource
)在字段或bean属性Setter方法上注入。这是在JavaEE通用的模式,在JSF管理bean和JAX-WS端点。Spring提供这个模式去管理Spring的bean。
@Resource
采用一个名字属性。默认情况下,Spring将该值解释为要注入的Bean名称。换而言之,它遵循通过名字的语义,如下所示:
public class SimpleMovieLister {
private MovieFinder movieFinder;
@Resource(name="myMovieFinder") //1
public void setMovieFinder(MovieFinder movieFinder) {
this.movieFinder = movieFinder;
}
}
- 这行注入
@Resource
。
如果显示指定名称,这个默认名是从字段或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字段首先查找bean名字为customerPreferenceDao然后回退查找CustomerPreferenceDao主要匹配的类型:
public class MovieRecommender {
@Resource
private CustomerPreferenceDao customerPreferenceDao;
@Resource
private ApplicationContext context; //1
public MovieRecommender() {
}
// ...
}
- context字段是基于已知的可解析依赖项类型:ApplicationContext注入的。
1.9.8 使用@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;
//defaultCatalog为默认值
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;
}
}
参考代码:
com.liyong.ioccontainer.starter.XmlValueIocContainer
1.9.9 使用@PostConstruct
和@PreDestroy
CommonAnnotationBeanPostProcessor
不仅仅识别@Resource
注解也能识别JSR-250生命周期注解:javax.annotation.PostConstruct
和javax.annotation.PreDestroy
。在Spring 2.5中引入的对这些注解的支持为初始化回调和销毁回调中描述的生命周期回调机制提供了一种替代方法。CommonAnnotationBeanPostProcessor
在Spring的ApplicationContext
中被注入,在生命周期的同一点上(译者:同一类回调方法,比如:销毁方法),与相应的Spring生命周期接口方法或显式声明的回调方法调用带有其中一个注解的方法。在下面的例子中,在以下示例中,缓存在初始化时预先填充,并在销毁时清除。
public class CachingMovieLister {
@PostConstruct
public void populateMovieCache() {
// 填充缓存
}
@PreDestroy
public void clearMovieCache() {
// 清除缓存
}
}
有关组合各种生命周期机制的详细信息,查看组合生命周期机制。
类似
@Resource
一样,@PostConstruct
和@PreDestroy
注解类型是从JDK6到JDK8的标准Java库的一部分。然而,整个javax.annotation
包与JDK 9中的核心Java模块分离,最终在JDK 11中删除。如果需要,javax.annotation-api
可以通过Maven中央库获取,简单地增加到应用的类路径下面和其他库一样。
参考代码:
com.liyong.ioccontainer.starter.XmlLifecycleIocContainer
1.10 类路径扫描和管理组件
本章中的大多数示例都使用XML来指定在Spring容器中生成每个BeanDefinition
的配置元数据。前面的部(基于注解的容器配置)分展示怎样去提供一些配置元素数据通过源码级别的注解。然而,即使在这些示例中,基本bean定义也是在XML文件中显式定义的,而注解只驱动依赖项注入。本节介绍通过扫描类路径来隐式检测候选组件的选项。候选组件是符合过滤条件的类,并在容器中注册了相应的Bean定义。这消除了使用XML进行bean注册的需要。相反,你可以使用注释(例如@Component
)、AspectJ类型表达式或你自己的自定义筛选条件来选择哪些类具有在容器中注册的bean定义。
Spring3.0开始,通过Spring
JavaConfig
项目提供的许多Spring核心特性部分。这允许你使用Java定义bean而不是使用传统的XML文件。查看@Configuration
、@Bean
、@Import
和@DependsOn
注解例子怎样去使用这些新特性。
1.10.1 @Component和其他构造型注解
@Repository
注解是任何满足存储库角色或构造型(也称为数据访问对象或DAO)的类的标记。该标记的用途包括自动转译异常,如“异常转译”中所述。
Spring提供提供更进一步的构造型注解:@Component
、@Service
和@Controller
。@Component
是一个通用的构造型注解对于任何Spring管理的组件。@Repository
、@Service
和@Controller
是特殊化的@Component
为更多特定的使用场景(在持久化、服务、表现层中)。因此,你可以使用@Component
注解组件类,但是通过使用@ Repository
、@Service
或@Controller
注解组件类,你的类更适合通过工具进行处理或与aspects相关联。例如,这些构造型注释成为切入点的理想目标。@Repository
、@Service
和@Controller
在Spring框架未来的发布中可能增加额外的语义。因此,如果你在使用@Component
或@Service
之间选择为你的服务层,@Service
是更简洁的选择。类似地,如前所述,@Repository
已经被支持作为持久化层中自动异常转换的标记。
1.10.2 使用元注解和组合组件
通过Spring提供的许多注解在你自己代码中能够被作为元注解使用。元注解也是注解它能够被应用到其他注解上。例如,前面提到的@Service
注解使用@Component
进行元注解,类似下面例子:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component //1
public @interface Service {
// ...
}
@Component
导致@Service
的处理方式与@Component
相同。
你可以联合元注解去创建组合注解
。例如,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;
}
你可以使用@SessionScope
无需声明proxyMode
类似下面:
@Service
@SessionScope
public class SessionScopedService {
// ...
}
你可以覆盖proxyMode
值,类似下面例子:
@Service
@SessionScope(proxyMode = ScopedProxyMode.INTERFACES)
public class SessionScopedUserService implements UserService {
// ...
}
更多详情,查看Spring注解编程模型wiki页。
1.10.3 自动地检查类和注册BeanDefinition
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任务的文件专用开关。此外,在某些环境中,基于安全策略可能不会公开类路径目录-例如,在JDK 1.7.0_45及更高版本上的独立应用程序(这需要在清单中设置“信任的库” —请参见https://stackoverflow.com/questions/19394570/java-jre-7u45-breaks-classloader-getresources) 。
在JDK9的模块路径,Spring的类路径扫描通常按预期工作。然而,确保你的组件类在你的
module-info
描述被暴露。如果你期望Spring调用你的类非公共的成员,确保他们被打开
(也就是,他们在module-info
描述符中使用了一个opens
声明而不是exports
声明)。
此外,当你使用component-scan
元素时,AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
两者隐式地包含。也就是说两个组件被自动检查和连接在一起-所有这些都没有XML提供的任何bean配置元数据。
你可以通过设置
annotation-config
属性值为false禁止AutowiredAnnotationBeanPostProcessor
和CommonAnnotationBeanPostProcessor
注册。
参考代码:
com.liyong.ioccontainer.starter.AutoCheckAndRegisterBeanDefinitionIocContainer
1.10.4 使用Filer去自定义扫描
默认情况下,仅使用@Component
、@Repository
、@Service
、@Controller
、@Configuration
进行注解的类或使用@Component
进行注解的自定义注解是唯一被检测到的候选组件。然而,你可以修改和拓展这个行为通过使用自定义过滤器。增加@ComponentScan
注解(在XML配置中context:component-scan子元素 <context:include-filter />
或<context:exclude-filter />
)的includeFilters
或excludeFilters
属性。每个过滤器元素需要type
和expression
属性。下面表格描述过滤器可选项:
Filter Type | Example Expression | Description |
---|---|---|
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
、@Repository
、@Service
、@Controller
、@RestController
或@Configuration
注解或元注解的类的功能。
代码示例:
com.liyong.ioccontainer.starter.CustomizeScanByFilterIocContainer
1.10.5 在组件中定义bean元数据
Spring组件有助于bean定义元数据到容器。在类上,你可以使用@Configuration
定义同时在方法上使用@Bean
即可定义元数据。下面例子展示怎样使用:
@Component
public class FactoryMethodComponent {
@Bean
@Qualifier("public")
public TestBean publicInstance() {
return new TestBean("publicInstance");
}
public void doWork() {
// Component method implementation omitted
}
}
前面的类是一个Spring组件,它有一个在它的doWork()
方法特定于应用的代码。但是,它也提供了一个具有工厂方法的bean定义,该工厂方法引用了方法publicInstance()
。@Bean
注解标识工厂方法和其他bean定义的属性,例如,通过@Qualifier
注解指定限定符。其他方法级别注解能够被指定为@Scope
、@Lazy
和自定义限定符注解。
除了它的组件初始化角色外,你可以在注入点放置
@Lazy
注解标记@Autowired
或@Inject
。在这种情况下,它导致注入了惰性解析代理。
如前所述,自动装配字段和方法是被支持的同时附加的支持@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表达式语言元素定义属性值通过符号#{ }。对应@Value
注解,表达式解析器已经预先配置当解析表达式文本的时候查找bean的名称。
自Spring4.3以后,你可以声明一个类型InjectionPoint
(或其更具体的子类:DependencyDescriptor
)工厂方法参数来访问触发当前bean创建的请求注入点。请注意,这仅适用于实际创建bean实例,不会注入已经存在的实例。这个特性大多数用在单例bean作用域。对于其他作用域,factory方法只能看到在给定作用域中触发创建新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
类中的@Bean
方法不同。不同点是@Component
这些类不会被CGLIB增强去拦截方法和字段的调用。CGLIB代理是调用@Configuration类中的@Bean
方法中的方法或字段来创建对协作对象的bean元数据引用的方法。这些方法不会通过常规的Java语法调用,而是通过容器,去提供常规的生命周期管理和Spring bean的代理,即使通过对@Bean
方法的编程调用来引用其他bean。相反,在普通@Component
类中的@Bean
方法中调用方法或字段具有标准Java语义,没有其他特定的CGLIB处理或约束。说明:@Component
中调用@Bean
注解的方法和常规的方法调用一样,而在@Configuration
中的@Bean
调用则是通过容器去查找Bean和生成bean的元数据。
你可以声明
@Bean
方法为static的,这个配置类实例在容器中没有被创建时也允许调用这个方法(译者:脱离了Configuration
实例)。当定义后置处理器bean(例如,BeanFactoryPostProcessor
或BeanPostProcessor
类型)时这是特殊的场景,因为在容器生命周期中更早的获取bean实例并且应该在那时避免触发配置类中的其他部分。说明:把@Bean
方法标记为static后脱落了bean实例管理所以在需要提前触发场景可以使用,这样避免未被实例化的bean其他@Bean
方法被触发。静态
@Bean
方法的调用永远不会被容器拦截,即使在@Configuration
类中也是如此(在这个章节前面被描述),由于技术限制:CGLIB子类仅仅能够覆盖非静态类。因此,直接调用另一个@Bean
方法具有标准的Java语义,从而导致直接从工厂方法本身直接返回一个独立的实例。
@Bean
方法的Java语言可见性不会对Spring容器中的最终bean定义产生直接影响。你可以在非@Configuration
类中自由声明自己的工厂方法,也可以在任何地方声明静态方法。常规的在@Configuration
类中的@Bean
方法需要可覆盖的。也就是说,他们不能被声明未private
或final
。
@Bean
方法在给定的组件或配置类的基类中也是可以被发现的,以及在Java 8默认方法中由组件或配置类实现的接口中声明的方法。这为组合复杂的配置安排提供了很大的灵活性,从Spring 4.2开始,通过Java 8默认方法甚至可以进行多重继承。最后,一个类可以包含同一个bean的多个
@Bean
方法,根据运行时可用的依赖关系,可以安排使用多个工厂方法。这与在其他配置方案中选择“最贪婪”的构造函数或工厂方法的算法相同。在构造时会选择具有最大可满足依赖性的变量,这类似于容器在多个@Autowired
构造函数之间进行选择的方式。
参考代码:
com.liyong.ioccontainer.starter.XmlBeanDefinationWithinComponetIocContainer
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>
作为一般规则,当其他组件可能显式引用该名称时,请考虑使用注释指定该名称。另一方面,只要容器负责连接(连接其他组件),自动生成的名称就足够了。
代码示例:
com.liyong.ioccontainer.starter.BeanNameGeneratorIoCContainer
1.10.7 为自动检查组件提供作用域
一般情况下Spring管理的组件,对于自动检查组件默认情况下是singleton
作用域。然而,有时候你需要不同的作用域时可以通过@Scope
注解指定。你可以提供在注解中的作用域名称,类似下面的例子:
@Scope("prototype")
@Repository
public class MovieFinderImpl implements MovieFinder {
// ...
}
@Scope
注解仅在具体的bean类(用于带注解的组件)或工厂方法(对于@Bean
方法)上进行内省。与XML的bean定义对比,这里没有bean定义继承的概念,并且类级别的继承层次结构与元数据目的无关。
更多详情在Spring上下文中在特定的web作用域,例如request
或session
,查看Request、 Session、 Application和 WebSocket 作用域。与这些作用域的预构建注解一样,你可以通过使用Spring的元注解方法构建你自己的作用域注解:例如,自定义元注解@Scope("prototype")
,尽可能地声明一个自定义scoped-proxy
模式。
你可以实现
ScopeMetadataResolver
接口,去提供一个自定义策略为作用域解析,而不是依赖基于注解的方法。确保包含一个无参构造函数。当配置扫描器的时候你需要提供全限定类名,类似下面注解和bean定义例子:
@Configuration
@ComponentScan(basePackages = "org.example", scopeResolver = MyScopeResolver.class)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scope-resolver="org.example.MyScopeResolver"/>
</beans>
当使用一些非单例bean作用时,可能需要为作用域对象声明代理。这个原因在 Scoped Beans as Dependencies中描述。为了这个目的,在component-scan
元素上可以使用scoped-proxy
属性。有三种可以的值时:no
、interfaces
和targetClass
。例如,下面的配置是标准的JDK动态代理:
@Configuration
@ComponentScan(basePackages = "org.example", scopedProxy = ScopedProxyMode.INTERFACES)
public class AppConfig {
// ...
}
<beans>
<context:component-scan base-package="org.example" scoped-proxy="interfaces"/>
</beans>
1.10.8 为注解提供限定元数据
@Qualifie
注解在基于注解细粒度调整限定符中已经讨论。本节中的示例演示了在解析autowire
候选对象时使用@Qualifier
注解和自定义qualifier
注解来提供细粒度控制。因为这些例子是基于在XML定义的基础上的,因此限定符元数据是在XML中通过使用qualifier
或bean子元素meta在候选者bean定义上被提供的。当依靠类路径扫描为组件自动检测时,可以在候选者类中为限定符元数据提供类型级别的注解。下面三个例子演示了这个技术:
@Component
@Qualifier("Action")
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Genre("Action")//自定义限定符
public class ActionMovieCatalog implements MovieCatalog {
// ...
}
@Component
@Offline//自定义限定符
public class CachingMovieCatalog implements MovieCatalog {
// ...
}
与大多数基于注释的替代方法一样,请记住,注释元数据绑定到类定义本身,而XML的使用允许相同类型的多个bean在其限定符元数据中提供变体,因为元数据是针对每个实例而不是针对每个类提供的。
代码示例:
com.liyong.ioccontainer.starter.XmlGenericsQualifierIocContainer
1.10.9 生成候选者组件索引
虽然类扫描非常快,在编译的时候通过创建一个静态的候选者清单去改善大型项目的启动性能。在这种模式下,作为组件扫描目标的所有模块都必须使用此机制。
现有的
@ComponentScan
或<context:component-scan>
指令必须保持不变,以便请求上下文扫描某些包中的候选者。当ApplicationContext检测索引的时候,它自动地使用索引而不是去扫描类路径。
去生成这个索引,增加一个附加的依赖到每个模块,它包含组件是指令扫描的目标。下面的例子显示怎样使用Maven:
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-indexer</artifactId>
<version>5.2.6.RELEASE</version>
<optional>true</optional>
</dependency>
</dependencies>
在Gradle 4.5已经之前的版本,依赖需要在compileOnly配置中声明,类似下面的例子:
dependencies {
compileOnly "org.springframework:spring-context-indexer:5.2.6.RELEASE"
}
在Gradle 4.5已经之前的版本,在annotationProcessor配置中被声明,类似下面的例子:
dependencies { annotationProcessor "org.springframework:spring-context-indexer:{spring-version}" }
处理过程在jar文件中生成一个META-INF/spring.components文件。
在IDE中使用这种模式时,必须将
spring-context-indexer
注册为注解处理器,以确保在更新候选组件时索引是最新的。
当在类路径中找到
META-INF/spring.components
时索引被自动地激活。如果某些库(或用例)的索引部分可用,但无法为整个应用程序构建索引,你可以通过将spring.index.ignore
设置为true来回退到常规的类路径扫描(就像根本没有索引一样),要么在系统属性或者类路径的根目录下spring.properties
文件中配置。
作者
个人从事金融行业,就职过易极付、思建科技、某网约车平台等重庆一流技术团队,目前就职于某银行负责统一支付系统建设。自身对金融行业有强烈的爱好。同时也实践大数据、数据存储、自动化集成和部署、分布式微服务、响应式编程、人工智能等领域。同时也热衷于技术分享创立公众号和博客站点对知识体系进行分享。
博客地址:http://youngitman.tech
CSDN:https://blog.csdn.net/liyong1028826685
微信公众号: