3.5.4 Request, session, application, and WebSocket scopes
request,session,global session作用域,只有在spring web ApplicationContext的实现中(比如XmlWebApplicationContext)才会起作用,若在常规Spring IoC容器中使用,比如ClassPathXmlApplicationContext中,就会收到一个异常IllegalStateException来告诉你不能识别的bean作用域
初始化web配置
为了支持request
,session
,application
和websocket
级别(web-scoped bean)的bean的作用域,在定义bean之前需要一些小的初始配置。 (Spring标准作用域,包括singleton
和prototype
,这个初始设置不是必须要配置的。)
如何配置要根据具体的Servlet
环境
如果你在Spring Web MVC中访问作用域bean,实际上,在SpringDispatcherServlet
处理的请求中,则不需要特殊的设置:DispatcherServlet
已经暴露了所有相关的状态。
如果你使用一个Servlet 2.5 web容器,请求在Spring的dispatcherServlet
之外处理(例如,当使用JSF或Struts时),你需要注册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>
或者,如果你的listener设置有问题,请考虑使用Spring的RequestContextFilter
。 过滤器映射取决于周围的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
, and RequestContextFilter
all do exactly the same thing, namely bind the HTTP request object to the Thread
that is servicing that request. This makes beans that are request- and session-scoped available further down the call chain.
DispatcherServlet
,RequestContextListener
和RequestContextFilter
都做同样的事情,即将HTTP请求对象绑定到正在为该请求提供服务的Thread
线程中。 这使得相关bean可以共享一个相同请求和会话作用域,并在调用链中进一步可用。
Request scope
考虑下面这种bean定义:
<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>
Spring容器通过对每个HTTP请求使用loginAction
bean定义来创建一个LoginAction
bean的新实例。 也就是说,loginAction
bean的作用域是在HTTP请求级别。 您可以根据需要更改创建的实例的内部状态,因为根据此loginAciton
bean定义创建的其他bean实例并不会看到这些状态的改变;他们为各自的request拥有。 当reqeust完成处理,request作用的bean就被丢弃了。
当使用注解驱动组件或Java Config时,@RequestScope
注解可以用于将一个组件分配给request
范围。
@RequestScope
@Component
public class LoginAction {
// ...
}
Session scope
考虑下面这种bean的xml配置定义:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
Spring容器通过对单个HTTP会话的生命周期使用userPreferences
bean定义来创建UserPreferences
bean的新实例。 换句话说,userPreferences
bean在HTTPSession
级别有效地作用域。 和request-scoped
bean相类似,可以改变bean实例的内部状态,不管bean创建了多少实例都可以,要知道,使用相同的userPreferences
定义创建的其他的bean实例看不到这些状态的改变,因为他们都是为各自的HTTP Session服务的。 当HTTPSession
最终被丢弃时,被限定为该特定HTTPSession
的bean也被丢弃。
当使用注解驱动组件或Java Config时,@SessionScope
注解可用于将一个组件分配给session
范围。
@SessionScope
@Component
public class UserPreferences {
// ...
}
Application scope
考虑下面这种bean定义:
<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>
Spring容器通过对整个web应用程序使用appPreferences
bean定义来创建一个AppPreferences
bean的新实例。 也就是说,appPreferences
bean是在ServletContext
级别定义的,存储为一个常规的ServletContext
属性。 这有点类似于Spring单例bean,但在两个重要方面有所不同:1、他是每一个ServeltContext
一个实例,而不是SpringApplicationContext
范围。2、它是直接暴露的,作为ServletContext
属性,因此可见。
当使用注解驱动组件或Java Config时,@ApplicationScope
注解可用于将一个组件分配给application
作用域。
@ApplicationScope
@Component
public class AppPreferences {
// ...
}
不同级别作用域bean之间依赖
Spring IoC容器不仅管理对象(bean)的实例化,还管理协作者(或依赖关系)。 如果要将(例如)一个HTTP请求作用域bean注入到较长期作用域的另一个bean中,您可以选择注入一个AOP代理来代替该作用域bean。 也就是说,您需要注入一个代理对象,该对象暴露与作用域对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)查找实际目标对象,并将方法调用委托给真实对象。
你也可以在定义为“singleton”的bean之间使用<aop:scoped-proxy/> ,然后通过引用一个可序列化的中间代理,因此能够在反序列化时重新获得目标单例bean。当对 prototype scope的bean声明<aop:scoped-proxy/> 时,共享代理上的每个方法调用都将导致创建一个新的目标实例,然后将该调用转发给该目标实例。此外,scoped代理不是以生命周期安全的方式从较小作用域访问bean的唯一方法。您也可以简单地将注入点(即构造函数/ setter参数或自动注入字段)声明为 ObjectFactory<MyTargetBean> ,允许一个getObject() 调用在需要时根据所需查找当前实例 - 作为一个扩展的变体,你可以声明ObjectProvider ,它提供了几个附加的访问变量,包括getIfAvailable 和getIfUnique 。这个JSR-330变体被称为Provider , Provider 声明和对应的get() 调用每次检索尝试。有关JSR-330整体的更多详细信息,请参见 here . |
在下面的例子中的配置只有一行,但重要的是要了解背后的“为什么”以及“如何”.
NTOE:加入本人说明(当你访问userService可以不需要访问userPreferences,而你要访问userPreferences必须要先访问userService,使用了的AOP包装,在cglib生成userPreferences代理的时候先来一层AOP的包装)
<?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>
要创建这样的代理,您需要将一个子元素<aop:scoped-proxy/>
插入到一个有范围的bean定义中(参见选择要创建的代理类型和第38章,基于XML模式的配置部分) 为什么在request
,session
和自定义scope级别限定bean的定义需要<aop:scoped-proxy/>
元素? 让我们检查下面的单例bean定义,并将其与您需要为上述scope定义的内容进行对比(注意,下面的userPreferences
bean定义是不完整的)。
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
在前面的例子中,单例beanuserManager
被注入对HTTPSession
作用域的beanuserPreferences
的引用。这里的要点是userManager
bean是一个单例:它将被实例化每个容器一次,它的依赖(在这种情况下只有一个,userPreferences
bean)也只注入一次。这意味着userManager
bean将只对完全相同的userPreferences
对象操作,也就是说,它最初注入的对象。
这是不将较短寿命的作用域bean注入到较长寿命的作用域bean时所需的行为,例如将一个HTTPSession
作用域的协作bean作为依赖注入到单例bean中。相反,你需要一个单独的userManager
对象,并且对于HTTPSession
的生命周期,你需要一个特定于所述HTTP会话的userPreferences
对象。因此,容器创建一个对象,暴露与UserPreferences
类完全相同的公共接口(理想情况下,一个对象,是一个UserPreferences
实例),它可以从作用域机制(HTTP请求, Session
等)。容器将这个代理对象注入到userManager
bean中,它不知道这个UserPreferences
引用是一个代理。在这个例子中,当一个UserManager
实例调用一个依赖注入UserPreferences
对象的方法时,它实际上是在代理上调用一个方法。代理然后从(在这种情况下)HTTPSession
提取真正的UserPreferences
对象,并将方法调用委托给检索的真正的UserPreferences
对象。
因此,当将request-
和session-scoped
bean注入协作对象时,您需要以下,正确和完整的配置:
<bean id="userPreferences" class="com.foo.UserPreferences" scope="session">
<aop:scoped-proxy/>
</bean>
<bean id="userManager" class="com.foo.UserManager">
<property name="userPreferences" ref="userPreferences"/>
</bean>
选择创建代理类型
默认情况下,当Spring容器为标记了<aop:scoped-proxy/>
元素的bean创建一个代理时,将创建一个基于CGLIB的类代理。
CGLIB代理只拦截公共方法调用! 不要在这样的代理上调用非公共方法; 它们不会被委派给实际的作用域目标对象. |
或者,您可以将Spring容器配置成(分开念)为这些作用域bean创建标准的基于JDK接口的代理,通过为<aop:scoped-proxy/>
的“proxy-target-class”属性的值指定false
元素。 使用基于JDK接口的代理意味着在应用程序类路径中不需要其他库来实现此类代理。 然而,这也意味着作用域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>
有关选择基于类或基于接口的代理的更多详细信息,请参见第7.6节“代理机制”.