目录
3.Request、session、application以及WebSocket范围
Bean 范围
当创建了一个bean定义时,就相当于创建了一个用来创建bean中定义的类的实例对象的菜单。
通过bean定义,不仅可以控制有指定类创建出的对象的不同的依赖与配置值,还可以控制对象范围。
通过配置来选择创建的对象的范围,比通过java代码来实现,要强大灵活许多。
Spring框架内置了六种范围,其中四种用于web应用。
singleton:对于每一个容器,一个bean定义对应一个对象。(默认)
prototype:一个bean定义对应任意数量的对象。
request:一个bean定义对应一个http请求。
session:将bean定义的范围界定在一个HTTP session的生命周期以内。
application:将bean定义的范围界定在一个ServletContext的生命周期以内。
websocket:将bean定义的范围界定在一个WebSocket的生命周期以内。
Spring3.0之后,thread范围可以被使用,但不是默认被注册。
SimpleThreadScope
1.Singleton范围
单例bean只有一个共享实例是被管理的,所有使用id对beans的请求匹配到的bean定义的结果都会从Spring容器中得到一个特定的单例对象。
Spring的单例bean与GoF单例模式不同,GoF单例通过硬编码使得每一个ClassLoader只有一个特定类的对象。
Spring的单例是指每一个容器与每一个bean只有一个对象。
<bean id="accountService" class="com.foo.DefaultAccountService"/>
<!-- the following is equivalent, though redundant (singleton scope is the default) -->
<bean id="accountService" class="com.foo.DefaultAccountService" scope="singleton"/>
2.Prototype范围
bean配置为原型范围结果将导致每一次针对特定bean的请求都会引发一个新的实例被创建。此处,对bean的请求意为bean被用于注入到其他bean之中或通过getBean方法调用容器中的bean。
一般情况下,将原型范围应用于有状态的的bean中,将单例范围应用于无状态的bean中。
<bean id="accountService" class="com.foo.DefaultAccountService" scope="prototype"/>
Spring不会管理原型bean的完整生命周期,所以,虽然初始化生命周期将会回掉所有范围的方法bean的对象的方法,但是原型bean设置的destruction生命周期不会被回调。
客户端必须清理圆形范围对象并且释放被持有的多余的原型对象资源。
通过使用定制的bean post-proccessor,令Spring container释放原型范围bean资源。
在某些方法,关于原型bean的Spring容器角色取代了java new操作。
3.Request、session、application以及WebSocket范围
只有当使用web-aware Spring ApplicationContext(eg:XmlWebApplicationContext)时,request、session、application以及websocket范围才是可用的。
如果在ClassPathXmlApplicationContext使用这些范围,将会抛出IllegalStateException。
初始化web配置
为了支持request、session、application以及websocket级别的bean,在定义beans之前,一些初始化配置是必需的。(如果使用singleton与prototype则不需要。)
如果完成这个初始化的启动过程,取决于具体的Servlet环境。
如果通过DespatcherServlet处理的请求访问SpringWebMVC中的限定范围的bean,没有特定的启动过程是必须的。DespatcherServlet已经公开了所有的相关状态。
如果使用Servlet 2.5web容器,使用没有经过DispatcherServlet处理的请求,则需要注册:
org.springframework.web.context.request.RequestContextListener
ServletReuqestListener
对于Servlet3.0+,可以通过WebApplicationInitializer接口,以编程方式初始化。
web.xml进行配置
<web-app>
...
<listener>
<listener-class>
org.springframework.web.context.request.RequestContextListener
</listener-class>
</listener>
...
</web-app>
如果监听器的启动存在问题,可以考虑使用Spring的RequestContextFilter。
由于过滤器映射取决于应用环境配置,所以必须根据需要进行改设置
<web-app>
...
<filter>
<filter-name>requestContextFilter</filter-name>
<filter-class>org.springframework.web.filter.RequestContextFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>requestContextFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
...
</web-app>
DispatcherServlet、RequestContextListener、RequestContextFilter做了相同的工作,即将HTTP请求对象绑定在服务于请求的Thread上。
这使得beans可以沿着调用链变为request、session范围可用的。
request范围
<bean id="example" class="examples.Example" scope="request" />
note:对于每一次的HTTP请求通过Example的bean定义创建一个实例,并且在请求完成后,将这个实例丢弃。
ps:可以改变实例的内部状态,因为由此bean定义创建出的其他实例不会意识到这些变化。
使用注解驱动组件或Java配置时,使用@RequestScope
@RequestScope
@Component
public class LoginAction {
// ...
}
session范围
<bean id="example" class="examples.Example" scope="session" />
note:对于单独的HTTP session使用期,通过Example的bean定义创建一个实例。当HTTP session被丢弃时,对应这个session的session范围的bean将会被丢弃。
使用注解驱动组件或Java配置时,使用@SessionScope
@SessionScope
@Component
public class UserPreferences {
// ...
}
application范围
<bean id="example" class="examples.Example" scope="application" />
note:对于一次完整的应用周期,通过Example的bean定义创建一个实例。
ps:application与singleton的区别在于,application是每ServletContext一个实例,singleton是每ApplicationContext一个实例,而一个web应用可以有多个ApplicationContext。
使用注解驱动组件或java配置
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
作为依赖的范围约束bean
当尝试将request范围的bean注入拥有更长生存范围的其他bean中时,可以选择注入一个代替范围约束bean的AOP代理。即,需要注入一个代理对象,这个代理对象暴露的公共接口与范围限定对象相同,不过代理对象可以检索到限定范围内的目标对象然后将方法调用委托给真正的对象。
在范围限定在singleton的beans之间使用<aop:scoped-proxy/>,通过可以序列化的中间代理提供引用,然后可通过还原序列化重新获得对象。
当为范围限定在prototype的beans声明<aop:scoped-proxy/>,每一次方法调用共享的代理都会导致新的实例的创建,然后方法的调用会被转发到这个实例。
范围限定的代理并不是以生命周期安全风格访问更短范围beans的唯一方法。
可以简单地声明一个与ObjectFactory<MyTargetBean>相同的诸如点(eg:构造器参数、setter参数、自动装配域),每一次需要实例时,通过一个getObject()调用检索即时的当前实例,而不是持有一个实例或分开存储。
作为一个扩展变体,ObjectProvider<MyTargetBean>实现了一些额外访问变体,即getIfAvailable与getIfUnique。
JSR-330的变体为Provider,针对每一次的检索意图,使用一个Provider<MyTargetBean>声明与一个get()调用。
<?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.foo.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.foo.SimpleUserService">
<!-- a reference to the proxied userPreferences bean -->
<property name="userPreferences" ref="userPreferences"/>
</bean>
</beans>
note:如果不使用代理,userPreferences只会被注入一次,而不会在session被丢弃时被丢弃,从而导致至始至终只有一个userPreferences。
ps:将<aop:scoped-proxy/>插入进范围限定的bean定义中,用于创建代理。
选择创建的代理类型
默认情况下,当容器为使用了<aop:scoped-proxy/>元素的bean创建一个代理时,一个基于CGLIB的类代理将会被创建。
CGLIB代理只会拦截公共方法调用,不要以代理方式调用非公共方法。
通过将<aop:scoped-proxy/>标签的proxy-target-class属性设置为false,可以配置容器为范围限定bean创建基于标准JDK接口的代理。
使用基于标准JDK接口的代理意味着不再需要应用classpath下的额外库来运作。
然而,这样同样意味着范围限定的bean的类需要实现至少一个接口,而被注入bean的协作者在引用这个bean时需要通过这个bean的接口。
<!-- DefaultUserPreferences implements the UserPreferences interface -->
<bean id="userPreferences" class="com.foo.DefaultUserPreferences" scope="session">
<aop:scoped-proxy proxy-target-class="false"/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
4.定制范围
bean的范围机制是可扩展的,即可以定义新的范围或者重定义已存在的范围,不过后者并不提倡,而且内置的singleton与protype范围不能被重载。
创建一个定制范围
将定制的范围集成进Spring容器,需要实现org.springframework.beans.factory.config.Scope接口。
Scope接口通过四个方法来从范围中获取对象、将对象从范围中移除,并且允许它们被销毁。
Object get(String name, ObjectFactory objectFactory)
ps:从设定的scope中返回对象,如果存储机制之中不存在此对象,则创建一个新的对象。
Object remove(String name)
ps:将对象从scope之中移除,并且将对象返回,如果对象没有找到,返回null。
void registerDestructionCallback(String name, Runnable destructionCallback)
ps:注册当对象被销毁时或scope被销毁时调用的回掉方法。
String getConversationId()
ps:获取scope的会话标识符,每一个scope的标识符都不相同。
使用定制范围
void registerScope(String scopeName, Scope scope);
ps:此方法将新Scope注册进Spring容器。
ps:此方法在ConfigurableBeanFactory接口中声明。大部分的ApplicationContext都可以调用此方法。
ps:第一个参数为scope的名称,例如singleton。第二个参数为实现了定制Scope的实例。
Scope threadScope = new SimpleThreadScope();
beanFactory.registerScope("thread", threadScope);
note:将SimpleThreadScope注册。
ps:Spring中内置了SimpleThreadScope,但是默认没有注册。
<bean id="..." class="..." scope="thread">
note:bean的定义中使用thread。
除了使用编程方式注册Scope,还可以使用CustomScopeConfigure类进行注册。
<?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="bar" class="x.y.Bar" scope="thread">
<property name="name" value="Rick"/>
<aop:scoped-proxy/>
</bean>
<bean id="foo" class="x.y.Foo">
<property name="bar" ref="bar"/>
</bean>
</beans>