1.Bean的作用域
当开发者定义Bean的时候,同时也会定义了该如何创建Bean实例。这些具体创建的过程是很重要的,因为只有通过对这些过程的配置,开发者才能创建实例对象。
开发者不仅可以控制注入不同的依赖到Bean之中,也可以配置Bean的作用域。这种方法是非常强大而且弹性也非常好的。开发者可以通过配置来指定对象的作用域,而不用在Java类层次上来配置。Bean可以配置多种作用域。
spring 框架提供了六种作用域支持,后面的四种是用于网络层面的
作用域 | 描述 |
---|---|
单例(singleton) | (默认)每一个Spring IoC容器都拥有唯一的一个实例对象 |
原型(prototype) | 一个Bean定义,任意多个对象 |
请求(request) | 一个HTTP请求会产生一个Bean对象,也就是说,每一个HTTP请求都有自己的Bean实例。只在基于web的Spring ApplicationContext中可用 |
会话(session) | 限定一个Bean的作用域为HTTPsession的生命周期。同样,只有基于web的Spring ApplicationContext才能使用 |
应用(application) | 限定一个Bean的作用域为ServletContext的生命周期。同样,只有基于web的Spring ApplicationContext可用 |
网络套接字(websocket) | 定义了websocket的一个生命周期,只有用于web的 ApplicationContext可用 |
2.单例bean
单例Bean全局只有一个共享的实例,所有将单例Bean作为依赖的情况下,容器返回将是同一个实例
。
换言之,当开发者定义一个Bean的作用域为单例时,Spring IoC容器只会根据Bean定义来创建该Bean的唯一实例。这些唯一的实例会缓存到容器中,后续针对单例Bean的请求和引用,都会从这个缓存中拿到这个唯一的实例
。
单例作用域是Spring的默认作用域
,下面的例子是在基于XML的配置中配置单例模式的Bean。
3.原型Bean
非单例的,原型的Bean指的就是每次请求Bean实例的时候,返回的都是新实例的Bean对象
。也就是说,每次注入到另外的Bean或者通过调用getBean()来获得的Bean都将是全新的实例。
其他的作用域相比,Spring是不会完全管理原型Bean的生命周期的
:Spring容器只会初始化,配置以及装载这些Bean,传递给Client
。但是之后就不会再去管原型Bean之后的动作了。
也就是说,初始化生命周期回调方法在所有作用域的Bean是都会调用的
,但是销毁生命周期回调方法在原型Bean是不会调用的
。所以,客户端代码必须注意清理原型Bean以及释放原型Bean所持有的一些资源。
可以通过使用自定义的bean post-processor来让Spring释放掉原型Bean所持有的资源
在某些方面来说,Spring容器的角色就是取代了Java的new操作符,所有的生命周期的控制需要由客户端来处理
4.请求,会话,全局会话的作用域
request,session以及global session
这三个作用域都是只有在基于web
的Spring的ApplicationContext实现的(比如XmlWebApplicationContext
)中才能使用。
如果开发者仅仅在常规的Spring IoC容器中比如ClassPathXmlApplicationContext中使用这些作用域,那么将会抛出一个IllegalStateException
来说明使用了未知的作用域。
-
初始化配置
`
为了能够使用request,session以及global session作用域(web范围的Bean),需要在配置Bean之前配置做一些基础的配置。(对于标准的作用域,比如singleton以及prototype,是无需这些基础的配置的)
具体如何配置取决于Servlet的环境。
比如如果开发者使用了 Spring Web MVC
框架的话,每一个请求会通过Spring的DispatcherServlet
或者DispatcherPortlet
来处理的,也就没有其他特殊的初始化配置
。DispatcherServlet和DispatcherPortlet已经包含了相关的状态。
如果使用Servlet 2.5的web容器,请求不是
通过Spring的DispatcherServlet(比如JSF或者Struts)来处理。那么开发者需要注册org.springframework.web.context.request.RequestContextListener或者ServletRequestListener
而在Servlet 3.0以后,这些都能够通过WebApplicationInitializer
接口来实现。或者,如果是一些旧版本的容器的话,可以在web.xml中增加如下的Listener声明:
...
org.springframework.web.context.request.RequestContextListener
...
如果是对Listener不甚熟悉,也可以考虑使用Spring的RequestContextFilter。Filter的映射取决于web应用的配置,开发者可以根据如下例子进行适当的修改。
...
requestContextFilter
org.springframework.web.filter.RequestContextFilter
requestContextFilter
/*
...
DispatcherServlet,RequestContextListener以及RequestContextFilter
做的本质上完全一致,都是绑定request对象到服务请求的Thread上。这才使得Bean在之后的调用链上在请求和会话范围上可见。
-
Request scope
参考如下的Bean定义
Spring容器会在每次用到loginAction来处理每个HTTP请求的时候都会创建一个新的LoginAction实例
。也就是说,loginActionBean的作用域是HTTPRequest级别的
。
开发者可以随意改变实例的状态,因为其他通过loginAction请求来创建的实例根本看不到开发者改变的实例状态,所有创建的Bean实例都是根据独立的请求来的。当请求处理完毕,这个Bean也会销毁
.
-
Session scope
参考的配置如下:
Spring容器会在每次调用到userPreferences在一个单独的HTTP会话周期
来创建一个新的UserPreferences实例。换言之,userPreferencesBean的作用域是HTTPSession级别的
。 在request-scoped作用域的Bean上,开发者可以随意的更改实例的状态,同样,其他的HTTPSession基本的实例在每个Session都会请求userPreferences来创建新的实例,所以开发者更改Bean的状态,对于其他的Bean仍然是不可见的。当HTTPSession销毁了,那么根据这个Session来创建的Bean也就销毁了。
-
Application scope
参考的配置如下:
Spring容器会在整个web应用
使用到appPreferences的时候创建一个新的AppPreferences的实例。也就是说,appPreferencesBean是在ServletContext级别的
,好似一个普通的ServletContext属性一样。这种作用域在一些程度上来说和Spring的单例作用域是极为相似的,但是也有如下不同之处:
(1)application作用域是每个ServletContext中包含一个
,而不是每个SpringApplicationContext之中包含一个(某些应用中可能包含不止一个ApplicationContext)。
(2)application作用域仅仅作为ServletContext的属性可见
,单例Bean是ApplicationContext可见。
作为依赖
Spring IoC容器不仅仅管理对象(Bean)的实例化,同时也负责装载依赖。如果开发者想装载一个Bean到一个作用域更广的Bean当中去
(比如HTTP请求返回的Bean),那么开发者选择注入一个AOP代理而不是短作用域的Bean
。也就是说,开发者需要注入一个代理对象,这个代理对象既可以找到实际的Bean,也能够创建一个全新的Bean。
当开发者希望能够正确的使用配置request,session或者globalSession级别
的Bean来作为依赖时,需要进行如下的类似配置:
选择代理的类型
默认情况下,Spring容器创建代理的时候标记为CGLIB
的代理。(CGLIB代理会拦截public方法调用!所以不要在非public方法上使用代理,这样将不会获取到指定的依赖。)
开发者可以通过指
。使用JDK基于接口的代理意味着开发者不需要在应用的路径引用额外的库来完成代理。当然,这也意味着短作用域的Bean需要额外实现一个接口
,而依赖是从这些接口来获取
的。
5.自定义作用域
为了能够使Spring可以管理开发者定义的作用域,开发者需要实现org.springframework.beans.factory.config.Scope接口。想知道如何实现开发者自己定义的作用域
可以参考源码org.springframework.web.context.request.SessionScope的具体实现
下面的例子使用了SimpleThreadScope
,这个例子Spring中是有实现的,但是没有默认注册。开发者自实现的Scope也可以通过如下方式来注册
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
之后,开发者可以通过如下类似的Bean定义来使用自定义的Scope:
在定制的Scope中,开发者也不限于仅仅通过编程方式来注册自己的Scope,开发者可以通过下面CustomScopeConfigurer
类来实现:
其实可以看看CustomScopeConfigurer源码的具体实现,
含有一个scopes属性和对应的setter方法
@Nullable private Map scopes;
public void setScopes(Map scopes) {
this.scopes = scopes;
}
实现了 BeanFactoryPostProcessor的接口,实际上本质也是通过ConfigurableListableBeanFactory进行registerScope的注入
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
if (this.scopes != null) {
for (Map.Entry entry : this.scopes.entrySet()) {
String scopeKey = entry.getKey();
Object value = entry.getValue();
if (value instanceof Scope) {
beanFactory.registerScope(scopeKey, (Scope) value);
}
else if (value instanceof Class) {
Class> scopeClass = (Class>) value;
Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class");
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else if (value instanceof String) {
Class> scopeClass = ClassUtils.resolveClassName((String) value, this.beanClassLoader);
Assert.isAssignable(Scope.class, scopeClass, "Invalid scope class");
beanFactory.registerScope(scopeKey, (Scope) BeanUtils.instantiateClass(scopeClass));
}
else {
throw new IllegalArgumentException("Mapped value [" + value + "] for scope key [" +
scopeKey + "] is not an instance of required type [" + Scope.class.getName() +
"] or a corresponding Class or String value indicating a Scope implementation");
}
}
}
}