• Spring Scope:Web项目中如何安全使用有状态的Bean对象?


      Web系统是最常见的Java应用系统之一,现在流行的Web项目多使用ssm或ssh框架,使用spring进行bean的管理,这为我们编写web项目带来了很多方便,通常,我们的controler层使用注入的service层的bean对象,service层使用注入的dao层的bean对象。但是大家在使用的时候有没有思考这样一个问题:如果有两个一样的请求进入我们的系统,那么他们使用的spring注入的service层的bean对象和dao层的bean对象,是同一个对象吗?

      这个问题的答案就要讲到bean的作用域了,在spring2.0之前bean只有2种作用域即:singleton(单例)、non-singleton(也称 prototype), Spring2.0以后,增加了session、request、global session三种专用于Web应用程序上下文的Bean。singleton是默认的作用域,当一个bean的 作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配,则只会返回bean的同一实例。而prototype作用域部署的bean,每一次请求(将其注入到另一个bean中,或者以程序的方式调用容器的 getBean()方法)都会产生一个新的bean实例,相当与一个new的操作。我以前没有关注过这个问题,是因为我在项目中通常使用的bean,无论是service层的,还是dao层的,都是没有状态的bean,里面只有方法,没有成员变量。在使用这样的bean的时候,多个线程访问同一个bean不会产生线程安全问题。

      而当我们需要使用有状态的bean的时候,这个bean中具有可变的成员变量,不同的特定用户使用的bean具有不同的状态(成员变量的值),这些状态不在整个Web工程中通用。当多线程访问bean修改成员变量时,就会产生与预期不符的结果。

      举个例子,下面自定义了一个bean,使用注解的方式进行注册,其中只有一个String的param参数:  

    @Component
    public
    class RequestInfo { private String param; public String getParam() { return param; } public void setParam(String param) { this.param = param; } }

      然后定义两个java类(我使用的webx框架进行的测试,功能类似与struts2,读者可自行编写类似的两个请求及对应java类),一个将url中携带的param参数值存放到RequestInfo对象中:

    public class SetParamValve implements Valve {
        
        @Autowired
        private HttpServletRequest  request;
        
        @Autowired
        private RequestInfo requestInfo;
    
        @Override
        public void invoke(PipelineContext pipelineContext) throws Exception {
            
            System.out.println("SetParamValve--now param is:" + requestInfo.getParam());
            
            //获取请求中的参数值
            String param = HttpUtil.getRequestParameter(request, "param");
            
         //存放参数 requestInfo.setParam(param);
    //睡眠10s TimeUnit.SECONDS.sleep(10); pipelineContext.invokeNext(); } }

      一个取出RequestInfo对象中存放的参数值:

    public class GetParamValve implements Valve {
            
        @Autowired
        private RequestInfo requestInfo;
    
        @Override
        public void invoke(PipelineContext pipelineContext) throws Exception {
            
            System.out.println("GetParamValve--now param is:" + requestInfo.getParam());
            
            pipelineContext.invokeNext();
            
        }
    
    }

      

      下面开始实验:当一个请求url:”http://localhost:8080/index.htm?param=a“进行访问时,控制台打印:

      

      这是符合预期输出的,我们再使用两个url:”http://localhost:8080/index.htm?param=a“ 和 ”http://localhost:8080/index.htm?param=b“ 在同一浏览器先后进行请求,打印:

      

      可以看到,GetParamValve输出了两个”b“,而我们的预期是:”http://localhost:8080/index.htm?param=a“的请求打印a,”http://localhost:8080/index.htm?param=b“的请求打印b,与预期不一致的原因是因为两个请求共用一个”RequestInfo“bean对象,param的值只能保存一份。

      解决这个问题的一个方式就是使用spring的scope作用域,scope的取值及区别如下:

    singleton

    在每个Spring IoC容器中一个bean定义对应一个对象实例。

    prototype

    一个bean定义对应多个对象实例。

    request

    在一次HTTP请求中,一个bean定义对应一个实例;即每次HTTP请求将会有各自的bean实例, 它们依据某个bean定义创建而成。该作用域仅在基于web的Spring ApplicationContext 情形下有效。

    session

    在一个HTTP Session 中,一个bean定义对应一个实例。该作用域仅在基于web的SpringApplicationContext 情形下有效。

    global session

    在一个全局的HTTP Session 中,一个bean定义对应一个实例。典型情况下,仅在使用portlet context的时候有效。该作用域仅在基于web的Spring ApplicationContext 情形下有效。

      我们使用”requset“范围的scope,使用注解进行配置:

    @Component
    @Scope(value="request")

      当使用request和session范围时,需要在web.xml中加入如下配置,

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

      启动项目,却发现项目报错如下:

      大意是一个singleton类型的bean对象如果使用我们的bean,我们需要提供一个代理,解决方法如下:

    @Component
    @Scope(value="request",proxyMode=ScopedProxyMode.TARGET_CLASS)

      这时还有另一个问题,当没有requset请求时(在非web环境中使用),我们调用这个bean对象失败怎么办?解决方法是我们为每一个使用这个bean对象的线程提供一个默认的request上下文:

    <bean class="org.springframework.beans.factory.config.CustomScopeConfigurer">
        <property name="scopes">
            <map>
                <entry key="session">
                    <bean class="org.springframework.context.support.SimpleThreadScope"/>
                </entry>
            </map>
        </property>
    </bean>

      做好这些,我们启动项目,使用”http://localhost:8080/index.htm?param=a“和”http://localhost:8080/index.htm?param=b“在同一浏览器先后进行请求时,打印如下:

      

      可以发现两次请求分别拥有独自的RequestInfo bean对象,它们的param值分别是我们期望的a和b。

      小结:本文主要说了在多线程环境下使用spring的有状态bean对象可能会发生的线程安全问题以及解决方法,并举了一个小例子进行说明,可能会有大牛觉得我又把简单的问题说复杂了。。。但是如果有读者原来不了解spring的scope,看了这篇文章后感觉有一点收获,我就很开心啦,文中有说的不好的地方欢迎大家指出~

      

     
  • 相关阅读:
    C# 关键字总结
    C# .NET、Mono、跨平台 的简单介绍
    Leetcode---剑指Offer题15---二进制中1的个数
    Leetcode---剑指Offer题14---剪绳子
    C# string方法总结
    Unity XML的使用
    C# 文件类总结 File、Directory、FileStream、StreamWriter、StreamReader
    自定义博客园---固定推荐反对到右下角
    自定义博客园---返回顶部
    CentOS 安装Python3.x常见问题
  • 原文地址:https://www.cnblogs.com/qilong853/p/5939915.html
Copyright © 2020-2023  润新知