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@PostConstructand@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")。