Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.5.4 请求(request)会话(session)和全局会话(global session)作用域

Posted tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.5.4 请求(request)会话(session)和全局会话(global session)作用域相关的知识,希望对你有一定的参考价值。

6.5.4 请求(request)、会话(session)和全局会话(global session)作用域

请求,会话和全局会话作用域仅在您使用Web的Spring ApplicationContext实现(例如XmlWebApplicationContext)时可用。如果将这些作用域与常规的Spring IoC容器(如ClassPathXmlApplicationContext)一起使用,则会出现IllegalStateException来说明使用了未知的bean作用域。

初始化的web配置

为了在请求,会话和全局会话级别(Web范围的bean)支持bean的作用域,在定义bean之前需要一些小的初始配置。(单例和原型作用域不需要这种初始设置。)
如何完成此初始设置取决于您的特定Servlet环境。
如果您在Spring Web MVC中访问具有作用域的bean,实际上请求是由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应用程序的web.xml文件中:

<web-app>
    ...
    <listener>
        <listener-class>
            org.springframework.web.context.request.RequestContextListener
        </listener-class>
    </listener>
    ...
</web-app>

或者,如果您的监听器设置存在问题,请考虑使用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和RequestContextFilter都是做同样的事,将HTTP请求对象绑定到为该请求提供服务的线程。这使得请求和会话范围的bean可以在调用链中进一步使用。

请求(request)作用域

请思考以下Bean的定义:

<bean id="loginAction" class="com.foo.LoginAction" scope="request"/>

Spring容器通过对每个HTTP请求使用loginAction bean定义来创建LoginAction bean的新实例。也就是说,loginAction bean的作用域是HTTP请求级别。您可以根据需要更改创建的实例的内部状态,因为从同一个loginAction bean定义创建的其他实例将不会在状态中看到这些更改; 它们尤其是针对单个请求。当请求处理完成时,请求的bean的作用域就会失效。

会话(session)作用域

请思考以下Bean的定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

Spring容器通过在单个HTTP会话的生存期内使用userPreferences bean定义来创建UserPreferences bean的新实例。换句话说,userPreferences bean在HTTP会话级别地有效的作用域。与请求作用域的bean一样,您可以根据需要更改创建的实例的内部状态,通过同一个userPreferences bean的定义创建的这些实例,它们使用的HTTP Session实例相互之间是看不到这些变更的。因为它们只作用于指定的单一HTTP会话。最终丢弃HTTP会话时,也会丢弃作用于该特定HTTP会话的bean。

全局会话(global session)作用域

请思考以下Bean的定义:

<bean id="userPreferences" class="com.foo.UserPreferences" scope="globalSession"/>

全局会话作用域类似于标准HTTP会话范围(如上所述),并且仅适用于基于portlet的Web应用程序的上下文。 portlet规范定义了构成单个portlet Web应用程序的所有portlet之间共享的全局会话的概念。以全局会话作用域定义的Bean是作用于(或绑定到)全局portlet会话的生存期。

应用(application)作用域

请思考以下Bean的定义:

<bean id="appPreferences" class="com.foo.AppPreferences" scope="application"/>

Spring容器为整个Web应用程序通过使用一次appPreferences bean的定义来创建AppPreferences bean的新实例。也就是说,appPreferences bean的作用域是ServletContext级别,作为常规的ServletContext属性来存储。这有点类似于Spring单例bean,但在两个重要方面有所不同:它是每个ServletContext的单例,而不是每个Spring的‘ApplicationContext‘(或者在给定的Web应用程序中可能有几个)的单例,并且实际上它是暴露的,因此作为ServletContext属性是可见的。

有依赖关系的作用域Bean

Spring IoC容器不仅管理对象(bean)的实例化,还管理着协作者(或依赖关系)的装配。 如果要将HTTP请求作用域bean注入到具有较长寿命作用域的另一个bean中,您可以选择注入AOP代理对象来取代作用域bean。也就是说,您需要注入一个代理对象来暴露与具有作用域的对象相同的公共接口,但也可以从相关作用域(例如HTTP请求)中检索真实目标对象,并将方法调用委托给真实对象。

注意:您还可以在具有单例作用域的bean之间使用<aop:scoped-proxy />,有了这个引用,从而就能够通过可序列化的中间代理,在反序列化时重新获取目标单例bean。
当对原型作用域的bean声明<aop:scoped-proxy />时,共享代理上的每个方法调用都将创建一个新的目标实例,然后该调用将被转发到该目标实例。
此外,作用域代理不是以生命周期安全的方式从较小作用域访问bean的唯一方法。您也可以简单地将您的注入点(即构造函数/ setter参数或自动装配字段)声明为ObjectFactory <MyTargetBean>,允许调用getObject()在每次需要时按需检索当前实例 - 而无需保留实例或单独存储它。
JSR-330的变体称为Provider,与Provider <MyTargetBean>声明一起使用,并且对每次检索都尝试使用相应的get()调用。有关JSR-330整体的更多详细信息,请参见此处。

以下示例中的配置只有一行,但了解“为什么”以及它背后的“怎么样”非常重要。

<?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的定义中。 请参阅“选择要创建的代理类型”一节和第40章基于XML模式的配置。)为什么bean的定义在请求,会话,globalSession和自定义范围级别上指定作用域需要<aop:scoped-proxy />元素?让我们检查下面的单例bean的定义,并将其与您需要为上述作用域定义的内容进行对比。(下面的userPreferences bean定义是不完整的。)

<bean id="userPreferences" class="com.foo.UserPreferences" scope="session"/>

<bean id="userManager" class="com.foo.UserManager">
    <property name="userPreferences" ref="userPreferences"/>
</bean>

在前面的例子中,使用HTTP会话作用域的userPreferences bean的引用去注入单例bean userManager。这里的重点是,userManager bean是一个单例:它将在每个容器中实例化一次,并且它的依赖项(在这种情况下只有一个,userPreferences bean)也只注入一次。 这意味着userManager bean将仅在完全相同的userPreferences对象(即最初注入的对象)上进行操作。

当将一个寿命较短的作用域 bean注入一个寿命较长的作用域bean时,这不是你想要的行为,例如将一个HTTP 会话作用域协作的bean作为依赖注入到单例作用域bean中。相反,如果您需要一个单一userManager对象,针对HTTP会话的生命周期,您就需要一个特定的HTTP会话的userPreferences对象。因此,容器创建一个对象,该对象暴露了与UserPreferences类(理想情况下是UserPreferences实例的对象)完全相同的公共接口,该对象可以从作用域机制(HTTP请求,会话等)中获取真实的UserPreferences对象。容器将此代理对象注入userManager bean,该bean不知道此UserPreferences引用是代理。在此示例中,当UserManager实例在依赖注入的UserPreferences对象上调用方法时,它实际上是在代理上调用方法。然后,代理从(在这种情况下)HTTP会话中获取真实的UserPreferences对象,并将方法调用委托给检索到的真实UserPreferences对象。

因此,在将request-,session-和globalSession-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代理只拦截公共方法调用! 不要在这样的代理上调用非公开方法; 它们不会被委托给实际的作用域目标对象。

或者,您可以通过为<aop:scoped-proxy />元素的proxy-target-class属性指定false值,将Spring容器配置为为此类作用域bean创建基于JDK接口的标准代理。 使用基于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>

有关选择基于类或基于接口的代理的更多详细信息,请参见第10.6节“代理机制”。

以上是关于Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.5.4 请求(request)会话(session)和全局会话(global session)作用域的主要内容,如果未能解决你的问题,请参考以下文章

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.4.5 自动装配

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.6.3 其他Aware接口

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.4.1 依赖注入

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.4.2 依赖注入和配置的细节

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.8.2 使用BeanFactoryPostProcessor定制配置元数据

Spring框架参考手册(4.2.6版本)翻译——第三部分 核心技术 6.3 Bean概述