1.5. Bean的作用域
bean
定义就是创建类实例的模板,也就是说通过这个 bean
定义(recipe)可以创建出很多对象。
通过配置 bean
定义不仅可以配置依赖,还可以设置作用域。这种方式非常强大和灵活,因为你可以通过配置设置 bean
的作用域,而不是在 java
类层面设置。Spring Framework
支持六种作用域,其中四个只能在 web-ware Application
中使用(request session application websocket
)。当然,你也可以自定义作用域。
下面的表格描述了(Spring Framework
)支持的作用域:
Table 3.Bean scopes
Scope | Description |
---|---|
singleton | 每个容器中只能有一个该 bean 的实例(作用域默认值) |
prototype | 该 bean 的实例可以有任意多个 |
request | 每个 http request 都会创建该 bean 的实例 |
session | 每个 http session 都会创建该 bean 的实例 |
application | 每个 ServletContext 都会创建该 bean 的实例 |
websocket | 每个 WebSocket 都会创建该 bean 的实例 |
Spring 3.0
开始,提供了另一个作用域thread scope
,但是默认没有注册,详情请看 SimpleThreadScope,关于如何注册作用域,请看 Using a Custom Scope.
1.5.1 Singleton Scope
容器中只有一个 single bean
的实例,所有需要这个 single bean
实例的地方,都会返回这个实例。
Spring 概念中的单例和设计模式(GOF)中不同,设计模式中的单例指的是每一个 ClassLoader
中只会创建一个单例类的实例,而 Spring 中的单例指的是每个容器中只会创建一个特定 bean
的实例。
你可以通过下面的XML定义一个单例 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. Prototype Scope
scope
为 prototype
的 bean
在每次注入到其他 bean
中或者通过 getBean
方法调用时都会创建一个新实例。通常,将有状态的 bean
的 scope
设置为 prototype
,将无状态的 bean
的 scope
设置为 singleton
。
下面的图阐明了 prototype scope
:
(
DAO
对象的 scope
并不适合设置为 prototype
,因为一般 DAO
都是无状态的,这里只是复用的前面的单例图)
下面通过XML 定义了 prototype bean
:
<bean id="accountService" class="com.something.DefaultAccountService" scope="prototype" />
与其他作用域不同的是,Spring
不会管理 prototype bean
的所有 lifecycle
。容器初始化、配置和装载 prototype bean
,然后将其交给客户端,之后不会再记录这个 prototype instance
。因此,虽然 initialization lifecycle
会在所有的对象上面调用不管 scope
是什么,但是 destruction lifecycle
不会在 prototype bean
上面调用,清理和释放资源的工作由客户端完成。如果想让容器释放 prototype bean
拥有的资源,可以使用 bean post-processor,这样容器会维护一个 prototype instance
的引用。
另一方面,可以认为容器在 prototype bean
的角色等同于 Java new operator
。所有生命周期的管理都由客户端完成。
1.5.3. Singleton Beans 依赖 Prototype-bean
如果 singleton bean
依赖于 prototype bean
时,其依赖关系是在实例化时解析的,也就是说 singleton bean
初始化完成以后就会有一个不变的 prototype bean
引用 ,为了在 singleton bean
上面每次获取新的 prototype bean
, 可以通过 Method Injection 实现。
1.5.4. Request,Session,Application,and WebSocket Scopes
request
,session
,application
和 websocket
作用域只能在实现了 ApplicationContext
的 web
环境中使用(比如:XmlWebApplicationContext
)。如果在常规的容器中使用,比如 ClassPathXmlApplicationContext
,会抛出 IllegalStateException
未知作用域异常。
初始化Web配置
为了支持 request
,session
,application
以及 webSocket
等 web
相关的作用域,在定义 bean
之前,需要做一些初始化的配置。
如何完成初始化设置依赖于所使用的 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.xml
文件中添加如下声明。
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
或者,如果使用监听器设置有问题,可以考虑使用 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
都做了相同的事情,即将请求对象绑定到服务该请求的线程。这使得作用域为 request
和 session
的 bean
可以在调用链的下游使用。
Request Scope
考虑下面的 bean
定义:
<bean id="loginAction" class="com.something.LoginAction" scope="request" />
对于每一个请求,容器都会通过 loginAction
bean
定义创建 loginAction
的实例,也就是说,loginAction bean
的作用域是 HTTP request
级别。你可以改变这个实例的内部状态,不会影响其他请求中创建的该实例。当请求完成以后,这些 bean
也会被销毁。
当使用 java annotation
配置时,@RequestScope
注解可以指定一个组件的作用域是 request
。下面的实例展示了如何使用:
@RequestScope
@Component
public class LoginAction{
//....
}
Session Scope
考虑下面的 bean
定义:
<bean id="userPreferences" class="com.something.UserPreferences" scope="session" />
对于每一个 HTTP Session
,容器都会创建该 bean
的 实例,你可以像 request scope
的 bean
一样,修改该实例的内部状态,这些修改对于其他 HTTP Session
是透明的。当 HTTP Session
销毁时,属于该它的作用域为 session
的实例也会被销毁。
当使用 java annoation
配置时,也可以使用 @SessionScope
注解指定一个组件的作用域为 session
。
@SessionScope
@Component
public class UserPreferences{
//....
}
Application Scope
考虑下面的 bean
定义:
<bean id="appPreferences" class="com.something.AppPreferences" scope="application " />
容器对整个 web
应用一次性使用 appPreferences
bean
定义创建 appPreferences
实例,也就是说 appPreferences
bean
的作用域是 ServletContext
级别,并且作为 ServletContext
的常规属性。这有些类似于单例作用域,但是有两个区别,首先,它是在每一个 ServletContext
中是单例的,其次,它是作为 ServletContext
属性公开可见的。
当使用 Java annoation
配置时,也可以使用 @ApplicationScope
注解指定一个组件的作用域是 application
。
@ApplicationScope
@Component
public class AppPreferences{
//...
}
Scoped Beans Dependencies
容器不仅管理 bean
的初始化,而且还会装载 bean
的协作者。如果你想把一个(比如)request-scoped bean
注入到一个作用域更长的 bean
,你可以选择将 AOP proxy
注入到作用域 bean
中。也就是说,你需要注入一个暴露了和作用域 bean
相同的公共接口的代理对象,但是可以从相关的作用域(比如:HTTP request
)中获取真实的目标对象,将方法调用委托到真实的目标对象上。
下面的配置只用一行设置了 AOP proxy
,但是理解为什么以及如何使用特别重要:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- an HTTP Session-scoped bean exposed as a proxy -->
<bean id="userPreferences" class="com.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>
给作用域 bean
中插入一个子元素 <aop:scoped-proxy />
来创建代理。为什么 request
,session
以及自定义作用域需要设置代理元素 <aop:scoped-proxy>
?考虑下面的单例 bean
定义来和上面的配置比较。
<bean id="userPreferences" class="com.something.UserPreferences" scope="session"/>
<bean id="userManager" class="com.something.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在上面的例子中, singleton bean
userManager
引用了一个 session bean
userPreferences
,但是 userManager
只会在每个容器中初始化一次,也就是它的依赖(userPreferences
)只会注入一次。
这应该不是你想要的,你需要的是在每个 HTTP Session
中,userManager
都引用属于这个 Session
的 userPreferences
,所以需要代理,对于 userManager
来说,别不知道它代用的是代理还是真实的目标对象,代理也会将方法调用委托到真实的目标对象上面。
因此,当需要将一个 request
或者 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>
选择创建代理方式
实现动态代理有两种方式:CGLIB
基于继承类的代理,JDK
基于接口的代理,CGLIB
只能代理公有方法且不能是 final
(final
方法不能被重写),JDK
只能代理实现了接口的类。
默认,<aop:scoped-proxy />
使用的是 CGLIB
代理。
如果想使用 JDK
代理,可以使用下面的配置,将元素 <aop:scope-proxy />
的属性 proxy-target-class
设置为 false
<!-- 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>
如果想了解更多关于基于类代理和基于接口代理,请查看 Proxying Mechanisms.
1.5.5. 自定义作用域
bean
的作用域机制是可扩展的,你也定义自己的作用域,也可以重新定义已经存在的作用域(这不是一种好的方式)。
创建自定义作用域
为了将自定义作用域整合到容器中,需要实现接口 org.springframework.beans.factory.config.Scope
。
Scope
接口有四个方法需要实现:
Object get(String name, ObjectFactory objectFactory)
Object remove(String name)
void registerDestructionCallback(String name, Runnable destructionCallback)
String getConversationId()
使用自定义作用域
定义了作用域以后,如果希望容器知道新的作用域,需要将其注册到容器中:
void registerScope(String scopeName, Scope scope);
这个方法是在 ConfigurableBeanFactory
接口中定义的,可以通过实现了 ApplicationContext
接口的实例上面的 BeanFatory
属性获取。
容器中还有一个作用域是
SimpleThreadScope
,但是默认没有注册。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("scope",threadScope);
现在就可以使用这个新注册的作用域了。
<bean id="..." class="..." scope="thread" >
也可以使用 CustomScopeConfigurer
类通过配置声明一个新的作用域,而不是编程的方式。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
<bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
<property name="scopes">
<map>
<entry key="thread">
<bean class="org.springframework.context.support.SimpleThreadScope"/>
</entry>
</map>
</property>
</bean>
<bean id="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>
1.6. 自定义Bean 的特性
Spring Framework
提供了一些列接口来自定义 bean
的特性
1.6.1. Lifecycle Callbacks
为了和 bean
的生命周期交互,需要实现 InitializingBean
和 DisposableBean
接口,容器会首先调用 afterPropertiesSet()
,然后调用 destory()
来执行 bean
的初始化和销毁操作。
JSR-250
中的@PostConstruct
和@PreDestroy
注解也可以实现相同的功能,更详细的信息可以查看:Using@PostConstruct
and@PreDestroy
.
如果不想使用这两个注解,还想与Spring Framework
的接口解耦,也可以使用init-method
和destroy-method
定义bean
。
在内部,Spring Framework
使用 BeanPostProcessor
接口的实现处理所有它能发现的回调接口,调用合适的方法。如果你需要自定义或者使用 Spring
不支持的生命周期行为,你也可以实现 BeanPostProcessor
接口,更多信息请看 Container Extension Points.
除了初始化和销毁回调之外,Spring
管理的对象也可以实现 Lifecycle
接口,使对象参与容器的启动和关闭的过程。
Initialization Callbacks
容器设置了 bean
所需属性之后,接口 org.springframework.beans.factory.InitializingBean
执行初始化操作。接口 InitializingBean
接口只有一个方法。
void afterPropertiesSet() throws Exception;
我们推荐使用 @PostConstruct
注解或者 <bean>
元素的属性 init-method
(或 @Bean
的属性 initMethod
) 来定义初始化,因为使用接口 InitizlizingBean
会与 Spring Framework
有耦合。更多信息请看 Receiving Lifecycle Callbacks。请看下面的例子:
<bean id="exampleInitBean" class="examples.ExampleBean" init-method="init" />
public class ExampleBean{
public void init(){
// do some initialization work
}
}
上面的例子和下面做了相同的事情
<bean id="exampleInitBean" class="examples.AnotherExampleBean" />
public class AnotherExampleBean implements InitializingBean{
public void afterPropertiesSet(){
// do some initialization work
}
}
Destruction Callbacks
如果 bean
实现了接口 org.springframework.beans.factory.DisposableBean
,当容器销毁时,这个 bean
或获得一个回调。DisposableBean
接口只有一个方法:
void destroy() throws Exception;
推荐使用 @PreDestroy
注解或者 <bean />
元素的 destroy-method
属性(或 @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 {
public void destroy() {
// do some destruction work (like releasing pooled connections)
}
}
然而,第一个定义并没有与 Spring
代码耦合。
Default Initialization and Destroy Methods
如果 bean
不实现 InitializingBean
和 DisposableBean
回调接口,一般我们会使用这些名称表示初始化和销毁的方法名称, init()
,initialize()
,dispose()
等等。在整个项目中应该保持这些名称一致。
也可以配置 <beans />
元素的 default-init-method
和 default-destroy-method
属性,来指定所有 bean
的默认初始化和销毁方法名称。如果个别 bean
的初始化和销毁方法不一致,可以在 <bean />
元素上面指定,会覆盖 <beans />
中的设定。
假设初始化回调叫 init()
,销毁回调叫 destroy()
,你的类应该和下面的类类似。
public class DefaultBlogService implements BlogService {
private BlogDao blogDao;
public void setBlogDao(BlogDao blogDao) {
this.blogDao = blogDao;
}
// this is (unsurprisingly) the initialization callback method
public void init() {
if (this.blogDao == null) {
throw new IllegalStateException("The [blogDao] property must be set.");
}
}
}
<beans default-init-method="init">
<bean id="blogService" class="com.foo.DefaultBlogService">
<property name="blogDao" ref="blogDao" />
</bean>
</beans>
当 bean
被创建和装载以后,如果它有这两个方法,方法将会在合适的时机调用。
容器能保证初始化回调方法将在 bean
的所有依赖提供以后调用。因此,初始化回调是在真实 bean
的引用上面调用,也是就是 AOP interceptors
之类还没有应用到 bean
上。一个目标 bean
第一次完全创建以后,AOP proxy
的拦截器链将被应用。如果目标 bean
和 代理对象独立定义,我们可以绕过代理直接访问目标 bean
。因此,将拦截器应用于初始化方法将会导致不一致,因为这样会将目标 bean
的生命周期和它的代理或拦截器耦合在一起,直接访问目标 bean
会有点奇怪。
Combining lifecycle mechanisms
Spring 2.5
之后,你有三种方式控制 bean
的生命周期行为:实现InitializingBean
和 DisposableBean
回调接口,自定义 init()
和 destroy()
方法以及使用 @PostConstruct
和 @PreDestroy
注解,你可以组合使用这三种方法。
如果有多种方式指定了
bean
的生命周期行为,并且每种配置的初始化或销毁的方法名称都不一样,则配置方法将会按下面的顺序都会执行,如果配置的方法名称有相同的,相同的方法只会执行一次。
同一个 bean
应用多种生命周期机制配置,使用不同的初始化方法:
@PostConstruct
-
InitializingBean
接口定义的afterPropertiesSet()
方法 - 自定义的
init()
方法
销毁方法调用顺序一样:
@PreDestroy
-
DisposableBean
接口定义的destroy()
方法 - 自定义的
destroy()
方法
Startup and shutdown callbacks
Lifecycle
接口给任意有它自己生命周期需求的对象定义了基本的方法(比如:启动和停止一些后台进程):
public interface Lifecycle {
void start();
void stop();
boolean isRunning();
}
任何 Spring
管理的对象都可以实现这个接口。当 ApplicationContext
收到启动和停止的信号时(比如:在运行时 stop/restart
场景),它就会委托 LifecycleProcessor
广播这些信号到所有实现了 Lifecycle
接口的 bean
。
public interface LifecycleProcessor extends Lifecycle {
void onRefresh();
void onClose();
}
请注意,LifecycleProcessor
也继承了 Lifecycle
接口,并且添加了两个方法。
请注意
org.springframework.context.Lifecycle
接口只是表示接受start/stop
(也就是在context
上面调用这两个方法)信号的契约,不适用于context refresh
时间的auto-startup
。可以使用org.springframework.context.SmartLifecycle
接口更细粒度控制特定bean
的auto-startup
。另外,请注意容器不能保证停止信号在销毁对象之前收到,多有实现了接口Lifecycle
的bean
将会在销毁对象的回调传播之前第一时间收到停止信号,但是,在context
的hot refresh
或者aborted refresh
期间,只有销毁回调会被调用(在context
上面首先调用start()
,然后调用refresh()
方法,收不到停止信号,只调用了销毁回调方法,如果不调用refresh()
,停止信号也会收到)。
启动的停止的调用顺序有时候也很重要。如果任意两个对象之间有依赖关系,依赖对象应该在被依赖对象之后启动,之前停止。然后,有时候依赖关系并不直接,可能我们只知道一种类型的对象优先于另一种类型的对象启动。在这种情况下,SmartLifecycle
接口提供了一种选择,它的父接口中有一个 getPhase()
方法,这个方法的返回值表示对象的优先级。
public interface Phased{
int getPhase();
}
public interface SmartLifecycle extends Lifecycle,Phased{
boolean isAutoStartup();
void stop(Runnable callback);
}
当启动的时候, phase
值最小的对象首先启动,当停止的时候,正好相反。因此,如果一个对象实现了 SmartLifecycle
接口,并且方法 getPhase()
返回了 Integer.MIN_VALUE
,那么这个对象是第一个启动,最后一个停止的。相反,如果一个对象实现了 SmartLifecycle
接口,并且方法 getPhase()
返回了 Integer.MAX_VALUE
,则表示这个对象是最后一个启动,第一个停止的。还有一点很重要,如果一个对象实现了 Lifecycle
接口而不是 SmartLifecycle
接口,则 phase
的值为 0
。
SmartLifecycle
接口的 stop(Runnable callback)
方法必须调用 callback
的 run()
方法,否则 DefaultLifecycleProcessor
接口认为停止没有完成,直到超时才会结束。也可以定义一个名叫 lifecycleProcessor
的 bean
来覆盖这个默认的生命周期处理器,。
<bean id="lifecycleProcessor" class="org.springframework.context.support.DefaultLifecycleProcessor">
<!-- timeout value in milliseconds -->
<property name="timeoutPerShutdownPhase" value="10000"/>
</bean>
当调用 context
的 start
方法时,如果对象实现了 Lifecycle
接口,并且 isRunning =false
, 则对象的 start()
方法被调用,当调用 stop
方法,只有 isRunning=true
,对象的 stop()
方法才会被调用。如果对象实现了 SmartLifecycle
接口,context refresh
(创建 context
时,会刷新)时,并且 isAutoStartup = true && isRunning=false
时,对象的 start()
方法被调用。
Shutting down the Spring IoC container gracefully in non-web applications
在基于 web
的 ApplicationContext
实现中,已有相应的实现来处理关闭 web
应用时恰当地关闭容器。 但是,如果你正在一个 no-web
应用的环境下使用 Spring IoC
容器,如 dubbo
服务,你想让容器优雅的关闭,并调用 singleton
的 bean
相应 destory
回调方法,你需要在 JVM
里注册一个“关闭钩子”(shutdown hook
)。这一点非常容易做到,并且将会确保你的容器被恰当关闭,以及所有由单例持有的资源都会被释放。
为了注册“关闭钩子”,你只需要简单地调用在org.springframework.context.support.AbstractApplicationContext
实现中的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 and BeanNameAware
当 ApplicationContext
创建一个实现了 org.springframework.context.ApplicationContextAware
接口的对象时,这个对象将会获得一个 ApplicationContext
的引用。
public interface ApplicationContextAware{
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
}
因此, bean
可以调用创建它的 ApplicationContext
对象上面的一些方法,也就可以转化为它的子类,调用子类上面的方法。比如:访问文件资源、发布事件以及访问 MessageSource
等等。一般不推荐这样做,因为违反的 Ioc
原则。
Spring 2.5
以后,我们也可以通过注解 @Autowire
获取创建对象的 ApplicationContext
引用。
@Autowired
private ApplicationContext context;
当 ApplicationContext
创建一个实现了 org.springframework.beans.factory.BeanNameAware
接口的对象时,这个对象可以获得它自己的 bean
名称
public interface BeanNameAware{
void setBeanName(String name) throws BeansException;
}
这些方法是在 bean
属性设置之前,初始化回调(@PostConstruct
,InitializingBean
和 initMethod
)之后调用。
1.6.3. Other Aware interfaces
除了上面讨论的 ApplicationContextAware
和 BeanNameAware
接口之外,Spring
还提供了一系列的 Aware
接口,使得 bean
可以获取它需要的基础依赖。下面列举了一些重要的 Aware
接口,一般接口的名称表明了基础依赖的类型。
Table4. Aware interfaces
Name | Injected Dependency | Explained in... |
---|---|---|
ApplicationContextAware | Declaring ApplicationContext | ApplicationContextAware and BeanNameAware |
ApplicationEventPublisherAware | Event publisher of the enclosing ApplicationContext | Additional capabilities of the ApplicationContext |
BeanClassLoaderAware | Class loader used to load the bean classes. | Instantiating beans |
BeanFactoryAware | Declaring BeanFactory | ApplicationContextAware and BeanNameAware |
BeanNameAware | Name of the declaring bean | ApplicationContextAware and BeanNameAware |
BootstrapContextAware | Resource adapter BootstrapContext the container runs in. Typically available only in JCA aware ApplicationContexts | JCA CCI |
LoadTimeWeaverAware | Defined weaver for processing class definition at load time | Load-time weaving with AspectJ in the Spring Framework |
MessageSourceAware | Configured strategy for resolving messages (with support for parametrization and internationalization) | Additional capabilities of the ApplicationContext |
NotificationPublisherAware | Spring JMX notification publisher | Notifications |
ResourceLoaderAware | Configured loader for low-level access to resources | Resources |
ServletConfigAware | Current ServletConfig the container runs in. Valid only in a web-aware Spring ApplicationContext | Spring MVC |
ServletContextAware | Current ServletContext the container runs in. Valid only in a web-aware Spring ApplicationContext | Spring MVC |
1.7. Bean definition inheritance
bean
定义包含许多配置信息,包括构造函数参数,属性值,以及初始化方法,静态工厂方法等等,child bean
定义可以继承 parent bean
定义。child bean
可以覆盖 parent bean
定义中的配置,也可新增配置。
child bean
定义是用 ChildBeanDefinition
类表示的。
请看下面配置:
<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>
inheritsWithDifferentClass
通过属性 parent
来引用 inheritedTestBean
。
inheritedTestBean
中,如果设置 abstract
为 true
,则这个 bean
只能作为创建其他 bean
的模板,不能作为其他 bean
的依赖,也不能通过 getBean()
方法获取(容器内部的方法 preInstantiateSingletons()
会忽略属性 abstract=true
的 bean
定义),如果 class
属性没有指定,那么必须将 bean
的属性 abstract
设置为 true
,并且 child bean
对应的类必须有这些属性。
<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>
1.8 Container Extension Points
一般,应用开发人员并不需要实现 ApplicationContext
来扩展容器,而是通过插入特定集成接口的实现来扩展。下面几节内容将会介绍这些集成接口。
1.8.1. Customizing Beans by Using a BeanPostProcessor
BeanPostProcessor
接口有两个回调方法,你可以通过实现它们来自定义初始化逻辑,依赖解析逻辑等等。如果你想在容器实例化、配置以及初始化一个 bean
之后,给 bean
实例自定义一些逻辑,你可以插入一个或多个 BeanPostProcessor
的实现。
你可以配置多个 BeanPostProcessor
实例,设置 order
属性来控制它们的执行顺序。只有 BeanPostProcessor
实例实现了 Ordered
接口才可以设置 order
属性。如果你想创建自己的 BeanPostProcessor
,你最好也实现一下 Ordered
接口。更详细的信息请看 BeanPostProcessor
和 Ordered
接口文档,也可以查看 programmatic registration of BeanPostProcessor
instances.
BeanPostProcessor
操作对象是bean
实例。也就是说,容器实例化一个bean
之后,BeanPostProcessor
在这些实例上面执行其他操作。
BeanPostProcessor
的作用域是每个容器。如果你使用了容器继承,应该关注这一点。如果在一个容器中定义了一个BeanPostProcessor
,它不会对其他容器中定义的bean
执行后置处理,即便是在同一个继承体系中的容器。
如果想改变bean
定义而不是bean
实例,可以通过BeanFactoryPostProcessor
实现,将会在下一节中介绍。
org.springframework.beans.factory.config.BeanPostProcessor
接口有两个回调方法,当这样的一个类注册为容器的后置处理器时,容器创建的 bean
实例在初始化前后都可以调用后置处理器。后置处理器可以对 bean
实例做任何操作,包括完全忽略回调。一个后置处理器典型的应用是检查回调接口或者创建 bean
的代理。Spring AOP
的一些基础类就是通过后置处理器实现的。
ApplicationContext
会自动检测实现了 BeanPostProcessor
接口的 bean
,将这些 bean
注册为后置处理器,这些后置处理器的 bean
定义和普通的 bean
定义没有什么区别。
如果 BeanPostProcessor bean
定义是通过配置类中的 @Bean
标记的工厂方法定义,则要求方法的返回值实现 org.springframework.beans.factory..config.BeanPostProcess
接口,否则 ApplicationContext
将不会检测到它是一个后置处理器。为了将后置处理器应用到容器中其他的 bean
,BeanPostProcessor
需要提前初始化,所以提前类型检测是关键。
编程的方式注册后置处理器
推荐使用ApplicationContext
自动检测的方式注册后置处理器,但是可以通过ConfigurableBeanFactory
的addBeanPostProcessor
方法注册后置处理器。如果注册后置处理器有条件,则编程的方式注册后置处理器就很有用。这些通过编程的方式注册的后置处理器不需要实现Ordered
接口,它们是根据注册的顺序来执行的。而且这种方法注册的后置处理器比自动检测到的后置处理器先执行。
后置处理器和
AOP
自动代理
容器会特殊对待实现BeanPostProcessor
接口的类,它们引用的其他BeanPostProcessor
和bean
将会在ApplicationContext
启动阶段实例化。因为AOP
自动代理也是通过BeanPostProcessor
实现的,因此BeanPostProcessor
以及它直接引用的其他Bean
都不适合自动代理,它们没有织入的概念。
对于这种bean
,你将会看到这个日志信息:"Bean foo is not eligible for getting processed by all BeanPostProcessor interfaces (for example: not eligible for auto-proxying)"
这一段不明白:"Note that if you have beans wired into your BeanPostProcessor using autowiring or @Resource (which may fall back to autowiring), Spring might access unexpected beans when searching for type-matching dependency candidates, and therefore make them ineligible for auto-proxying or other kinds of bean post-processing. For example, if you have a dependency annotated with @Resource where the field/setter name does not directly correspond to the declared name of a bean and no name attribute is used, then Spring will access other beans for matching them by type."
下面的例子展示了在 ApplicationContext
中如何编写、注册后置处理器。
Example:Hello World,BeanPostProcessor-style
下面的例子定义了一个调用容器中每个 bean
的 toString()
方法的 BeanPostProcessor
实现。
package scripting;
import org.springframework.beans.factory.config.BeanPostProcessor;
public class InstantiationTracingBeanPostProcessor implements BeanPostProcessor {
// simply return the instantiated bean as-is
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean; // we could potentially return any object reference here...
}
public Object postProcessAfterInitialization(Object bean, String beanName) {
System.out.println("Bean '" + beanName + "' created : " + bean.toString());
return bean;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:lang="http://www.springframework.org/schema/lang"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/lang
http://www.springframework.org/schema/lang/spring-lang.xsd">
<lang:groovy id="messenger"
script-source="classpath:org/springframework/scripting/groovy/Messenger.groovy">
<lang:property name="message" value="Fiona Apple Is Just So Dreamy."/>
</lang:groovy>
<!--
when the above bean (messenger) is instantiated, this custom
BeanPostProcessor implementation will output the fact to the system console
-->
<bean class="scripting.InstantiationTracingBeanPostProcessor"/>
</beans>
上面的配置中只是定义了 InstantiationTracingBeanPostProcessor
,并没有做额外的处理,甚至都没有设置 name
,这个 bean
也可以注入或依赖其他 bean
。
下面的的应用运行上面的代码和配置
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.scripting.Messenger;
public final class Boot {
public static void main(final String[] args) throws Exception {
ApplicationContext ctx = new ClassPathXmlApplicationContext("scripting/beans.xml");
Messenger messenger = (Messenger) ctx.getBean("messenger");
System.out.println(messenger);
}
}
输出结果类似于:
Bean 'messenger' created : org.springframework.scripting.groovy.GroovyMessenger@272961
org.springframework.scripting.groovy.GroovyMessenger@272961
Example:The RequiredAnnotationBeanPostProcessor
使用回调接口或者注解和 BeanPostProcessor
结合使用是容器扩展的常用手段。比如:RequiredAnnotationBeanPostProcessor
和 @Required
结合,可以确保依赖必须注入。
1.8.2. Customizing Configuration Metadata with a BeanFactoryPostProcessor
下一个我们要介绍的扩展点是 org.springframework.beans.factory.config.BeanFactoryPostProcessor
。这个接口的语义和 BeanPostProcessor
类似,主要的不同点是,BeanFactoryPostProcessor
的操作对象是配置元数据。因此,BeanFactoryPostProcessor
可以在容器实例化 bean
之前,除了它自己的配置元数据之外可以修改其他任何 bean
的配置元数据。
你可以配置多个 BeanFactoryPostProcessor
,通过 order
属性来控制它们的执行顺序。只有实现了 Ordered
接口的 BeanFactoryPostProcessor
才有 order
属性。如果你需要自定义 BeanFactoryPostProcessor
,你也应该实现 Ordered
接口。更详细的信息可以查看 BeanFactoryPostProcessor
and Ordered
的文档。
如果你想改变的是
bean
的实例,而不是bean
的配置元数据,应该使用BeanPostProcessor
,当然,从技术来说BeanFactoryPostProcessor
也可以通过BeanFactory.getBean()
修改bean
的实例,但是这样做会使bean
提前实例化,违背了标准的容器生命周期,也可能会带来其他的负面影响,比如:绕过了后置处理器。
BeanFactoryPostProcessor
的作用域和BeanPostProcessor
类似。
为了能修改容器中定义的配置元数据, 当在 ApplicationContext
声明了 bean factory
的后置处理器,它们会自动执行。Spring
中包含了许多预定义的 bean factory
后置处理器,比如:PropertyOverrideConfigurer
和 PropertyPlaceholderConfigurer
。你可以可以自定义 BeanFactoryPostProcessor
,比如注册一个自定义的属性编辑器。
ApplicationContext
会自动检测实现了 BeanFactoryPostProcessor
接口的 bean
,将它们作为 bean factory
的后置处理器,你可以像定义其他 bean
一样定义它们。
对于
Bean(Factory)PostProcessor
,你并不想把它们配置成懒初始化。如果没有其他bean
引用它们时,这些后置处理器将永远不会被初始化。因此,它们的lazy
属性将会被忽略,并且会提前实例化即便是将<beans />
元素的default-lazy-init
属性设置为true
。
Example: The Class Name Subsitution PropertyPlaceholderConfigurer
在单独的 Java Properties
格式的文件中定义属性值来,通过 PropertyPlaceholderConfigurer
来扩展 bean
的属性值。这样可以通过修改配置文件而不是 xml
文件来切换不同环境中特定的属性值。
下面的配置片段中定义了一个 DataSource
的 bean
,它的属性是通过占位符定义。在运行时, PropertyPlaceholderConfigurer
将配置元数据中格式为 ${property-name}
的占位符替换为外部 Properties
文件中对应的属性值。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations" value="classpath:com/foo/jdbc.properties"/>
</bean>
<bean id="dataSource" destroy-method="close"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="${jdbc.driverClassName}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
真实的值是通过 Java Properties
格式的外部配置文件中获取:
jdbc.driverClassName=org.hsqldb.jdbcDriver
jdbc.url=jdbc:hsqldb:hsql://production:9002
jdbc.username=sa
jdbc.password=root
因此,${jdbc.username}
在运行时被替换为 sa
,其他的属性替换方式一样。也可以给占位符自定义前缀和后缀。
从 Spring 2.5
开始引入了 context
命名空间,可以通过声明一个配置元素来配置属性占位符。可以给 location
属性指定多个路径使用逗号分隔。
<context:property-placeholder location="classpath:com/foo/jdbc.properties" />
PropertyPlaceholderConfigurer
不仅仅在你配置的属性文件中查找属性,默认情况下,如果在属性文件中没有找到对应的属性,也会在 java System Properties
中查找。你也可以将 systemPropertiesMode
设置为下面三个整形值中的一个,来自定义这个行为。
- nerver(0) :从不去
system properties
查找属性 - fallback(1) : 首先从指定的属性文件中查找,如果没有找到再去系统属性中查找,这是默认配置。
- override(2) : 首先去系统属性中查找,如果没有找到,再去指定的属性文件中查找。
查阅 PropertyPlaceholderConfigurer
文档查看更多信息。
你可以通过
PropertyPlaceholderConfigureer
来替换类名称,当在运行时指定一个特定的类。比如:
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:com/foo/strategy.properties</value>
</property>
<property name ="properties">
<value>custom.strategy.class=com.foo.DefaultStrategy</value>
</property>
</bean>
<bean id="serviceStrategy" class="${custom.strategy.class}"/>
在 ApplicationContext
的 preInstantiateSingletons()
,对于非懒实例化 bean
,如果指定的属性在运行时没有解析到有效的类,将会在创建时失败。
Example: the PropertyOverrideConfigurer
PropertyOverrideConfigurer
是另一个 bean factory
后置处理器,类似于 PropertyPlaceholderConfigurer
,但是不同于后者,原始的 bean
属性可以设置默认值,也可以不设置,如果覆盖属性文件中没有找到对应的属性值,则使用默认值。
请注意 bean
定义并不知道覆盖的存在,所以不能马上知道 XML
定义中使用的是那个覆盖配置。如果有多个 PropertyOverrideConfigurer
实例对同一属性定义了不同的值,根据覆盖机制,最后一个值将会被使用。
属性文件格式如下:
beanName.property =value
比如:
dataSource.driverClassName=com.mysql.jdbc.Driver
dataSource.url=jdbc:mysql:mydb
上面的配置文件可以应用到有一个 dataSource bean
,并且该 bean
有两个属性 driver
和 url
的容器中。
复合属性名称也是支持的。比如
foo.fred.bob.sammy=123
foo
对象的 fred
属性的 bod
属性的 sammy
属性被设置为 123
指定的覆盖值只会是字面量,不会被转化为
bean
的引用,这个约定也适用于原始的bean
定义中的属性值是引用。
自从 Spring 2.5
引入了 context
命名空间,也可以通过下面的声明配置元素来配置属性覆盖值:
<context:property-override location="classpath:override.properties" />
1.8.3. Customizing instantiation logic with a FactoryBean
实现接口 org.springframework.beans.factory.FactoryBean
,可以让 bean
变为工厂。
对于比较复杂的初始化逻辑,使用 XML
来配置可能比较麻烦,可以实现自己的 FactoryBean
来设置初始化逻辑。
FactoryBean
接口有三个方法:
-
Object getObject()
:返回工厂创建的实例,这个实例可以共享,具体取决于作用域。 -
boolean isSingleton()
: 如果返回的实例是单例返回true
,否则返回false
。 -
Class getObjectType()
: 返回getObject()
方法返回的实例的类型,如果不能预先知道可以返回null
。
FactoryBean
概念和接口在 Spring Framework
中很多地方在使用,Spring
自身实现了超过50 FactoryBean
接口。
如果想获取工厂创建的对象,通过 getBean("myBean")
,如果想获取工厂本身而不是工厂生成的实例,需要给 bean
名称前面添加一个 &
,getBean("&myBean")
。