@Scope(“prototype“)不生效

使用spring的时候,我们一般都是使用@Component实现bean的注入,这个时候我们的bean如果不指定@Scope,默认是单例模式,另外还有很多模式可用,用的最多的就是多例模式了,顾名思义就是每次使用都会创建一个新的对象,比较适用于写一些job,比如在多线程环境下可以使用全局变量之类的

创建一个测试任务,这里在网上看到大部分都是直接@Scope(“prototype”),这里测试是不生效的,再加上proxyMode才行,代码如下

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Scope;
import org.springframework.context.annotation.ScopedProxyMode;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;
@Component
@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class TestAsyncClient {
    private int a = 0;
    @Async
    public void test(int a) {
        this.a = a;
        CommonAsyncJobs.list.add(this.a   "");
    }
}

测试

import cn.hutool.core.collection.CollectionUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Set;
import java.util.Vector;
@Slf4j
@EnableAsync
@SpringBootTest
@RunWith(SpringRunner.class)
public class CommonAsyncJobs {
    @Autowired
    private TestAsyncClient testAsyncClient;
    // 多线程环境下,普通的list.add不适用,用Vector处理就行了,效率低,但无所谓反正测试的
    public static Vector<String> list = new Vector<>();
    @Test
    public void testAsync() throws Exception {
        // 循环里面异步处理
        int a = 100000;
        for (int i = 0; i < a; i  ) {
            testAsyncClient.test(i);
        }
        System.out.println("多线程结果:"   list.size());
        System.out.println("单线程结果:"   a);
        Set<String> set = CollectionUtil.newHashSet();
        Set<String> exist = CollectionUtil.newHashSet();
        for (String s : list) {
            if (set.contains(s)) {
                exist.add(s);
            } else {
                set.add(s);
            }
        }
        // 没重复的值,说明多线程环境下,全局变量没有问题
        System.out.println("重复的值:"   exist.size());
        System.out.println("重复的值:"   exist);
        // 单元测试内主线程结束会终止子线程任务
        Thread.sleep(Long.MAX_VALUE);
    }
}

结果

没重复的值,说明多线程环境下,全局变量没有问题

在这里插入图片描述

@Scope(“prototype“)正确用法——解决Bean多例问题

1.问题,Spring管理的某个Bean需要使用多例

在使用了Spring的web工程中,除非特殊情况,我们都会选择使用Spring的IOC功能来管理Bean,而不是用到时去new一个。

Spring管理的Bean默认是单例的(即Spring创建好Bean,需要时就拿来用,而不是每次用到时都去new,又快性能又好),但有时候单例并不满足要求(比如Bean中不全是方法,有成员,使用单例会有线程安全问题,可以搜索线程安全与线程不安全的相关文章),你上网可以很容易找到解决办法,即使用@Scope("prototype")注解,可以通知Spring把被注解的Bean变成多例

如下所示:

import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
 
    private String name;
    @RequestMapping(value = "/{username}",method = RequestMethod.GET)
    public void userProfile(@PathVariable("username") String username) {
        name = username;
        try {
            for(int i = 0; i < 100; i  ) {
                System.out.println(Thread.currentThread().getId()   "name:"   name);
                Thread.sleep(2000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return;
    }
}

分别发送请求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制台输出:

34name:aaa
34name:aaa
35name:bbb
34name:bbb

(34和35是两个线程的ID,每次运行都可能不同,但是两个请求使用的线程的ID肯定不一样,可以用来区分两个请求。)可以看到第二个请求bbb开始后,将name的内容改为了“bbb”,第一个请求的name也从“aaa”改为了“bbb”。要想避免这种情况,可以使用@Scope("prototype"),注解加在TestScope这个类上。加完注解后重复上面的请求,发现第一个请求一直输出“aaa”,第二个请求一直输出“bbb”,成功。

2.问题升级

多个Bean的依赖链中,有一个需要多例

第一节中是一个很简单的情况,真实的Spring Web工程起码有Controller、Service、Dao三层,假如Controller层是单例,Service层需要多例,这时候应该怎么办呢?

2.1一次失败的尝试

首先我们想到的是在Service层加注解@Scope("prototype"),如下所示:

controller类代码

import com.example.test.service.Order;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
 
    @Autowired
    private Order order;
 
    private String name;
 
    @RequestMapping(value = "/{username}", method = RequestMethod.GET)
    public void userProfile(@PathVariable("username") String username) {
        name = username;
        order.setOrderNum(name);
        try {
            for (int i = 0; i < 100; i  ) {
                System.out.println(
                        Thread.currentThread().getId()
                                  "name:"   name
                                  "--order:"
                                  order.getOrderNum());
                Thread.sleep(2000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return;
    }
}

Service类代码

import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
 
@Service
@Scope("prototype")
public class Order {
    private String orderNum;
 
    public String getOrderNum() {
        return orderNum;
    }
 
    public void setOrderNum(String orderNum) {
        this.orderNum = orderNum;
    }
 
    @Override
    public String toString() {
        return "Order{"  
                "orderNum='"   orderNum   '\''  
                '}';
    }
}

分别发送请求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制台输出:

32name:aaa--order:aaa
32name:aaa--order:aaa
34name:bbb--order:bbb
32name:bbb--order:bbb

可以看到Controller的name和Service的orderNum都被第二个请求从“aaa”改成了“bbb”,Service并不是多例,失败。

2.2 一次成功的尝试

我们再次尝试,在Controller和Service都加上@Scope("prototype"),结果成功,这里不重复贴代码,读者可以自己试试。

2.3 成功的原因(对2.1、2.2的理解)

Spring定义了多种作用域,可以基于这些作用域创建bean,包括:

  • 单例( Singleton):在整个应用中,只创建bean的一个实例。
  • 原型( Prototype):每次注入或者通过Spring应用上下文获取的时候,都会创建一个新的bean实例。

对于以上说明,我们可以这样理解:虽然Service是多例的,但是Controller是单例的。如果给一个组件加上@Scope("prototype")注解,每次请求它的实例,spring的确会给返回一个新的。问题是这个多例对象Service是被单例对象Controller依赖的。而单例服务Controller初始化的时候,多例对象Service就已经注入了;当你去使用Controller的时候,Service也不会被再次创建了(注入时创建,而注入只有一次)。

2.4 另一种成功的尝试(基于2.3的猜想)

为了验证2.3的猜想,我们在Controller钟每次去请求获取Service实例,而不是使用@Autowired注入,代码如下:

Controller类

import com.example.test.service.Order;
import com.example.test.utils.SpringBeanUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@RequestMapping(value = "/testScope")
public class TestScope {
 
    private String name;
 
    @RequestMapping(value = "/{username}", method = RequestMethod.GET)
    public void userProfile(@PathVariable("username") String username) {
        name = username;
        Order order = SpringBeanUtil.getBean(Order.class);
        order.setOrderNum(name);
        try {
            for (int i = 0; i < 100; i  ) {
                System.out.println(
                        Thread.currentThread().getId()
                                  "name:"   name
                                  "--order:"
                                  order.getOrderNum());
                Thread.sleep(2000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return;
    }
}
 

用于获取Spring管理的Bean的类

package com.example.test.utils;
 
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
 
@Component
public class SpringBeanUtil implements ApplicationContextAware {
 
    /**
     * 上下文对象实例
     */
    private static ApplicationContext applicationContext;
 
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
 
    /**
     * 获取applicationContext
     *
     * @return
     */
    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
 
    /**
     * 通过name获取 Bean.
     *
     * @param name
     * @return
     */
    public static Object getBean(String name) {
        return getApplicationContext().getBean(name);
    }
 
    /**
     * 通过class获取Bean.
     *
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(Class<T> clazz) {
        return getApplicationContext().getBean(clazz);
    }
 
    /**
     * 通过name,以及Clazz返回指定的Bean
     *
     * @param name
     * @param clazz
     * @param <T>
     * @return
     */
    public static <T> T getBean(String name, Class<T> clazz) {
        return getApplicationContext().getBean(name, clazz);
    }
}

Order的代码不变。

分别发送请求http://localhost:8043/testScope/aaa和http://localhost:8043/testScope/bbb,控制台输出:

31name:aaa--order:aaa
33name:bbb--order:bbb
31name:bbb--order:aaa
33name:bbb--order:bbb

可以看到,第二次请求的不会改变第一次请求的name和orderNum。问题解决。我们在2.3节中给出的的理解是对的。

3. Spring给出的解决问题的办法(解决Bean链中某个Bean需要多例的问题)

虽然第二节解决了问题,但是有两个问题:

  • 方法一,为了一个多例,让整个一串Bean失去了单例的优势;
  • 方法二,破坏IOC注入的优美展现形式,和new一样不便于管理和修改。

Spring作为一个优秀的、用途广、发展时间长的框架,一定有成熟的解决办法。经过一番搜索,我们发现,注解@Scope("prototype")(这个注解实际上也可以写成@Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE,使用常量比手打字符串不容易出错)还有很多用法。

首先value就分为四类:

  • ConfigurableBeanFactory.SCOPE_PROTOTYPE,即“prototype”
  • ConfigurableBeanFactory.SCOPE_SINGLETON,即“singleton”
  • WebApplicationContext.SCOPE_REQUEST,即“request”
  • WebApplicationContext.SCOPE_SESSION,即“session”

他们的含义是:

  • singleton和prototype分别代表单例和多例;
  • request表示请求,即在一次http请求中,被注解的Bean都是同一个Bean,不同的请求是不同的Bean;
  • session表示会话,即在同一个会话中,被注解的Bean都是使用的同一个Bean,不同的会话使用不同的Bean。

使用session和request产生了一个新问题,生成controller的时候需要service作为controller的成员,但是service只在收到请求(可能是request也可能是session)时才会被实例化,controller拿不到service实例。为了解决这个问题,@Scope注解添加了一个proxyMode的属性,有两个值ScopedProxyMode.INTERFACES和ScopedProxyMode.TARGET_CLASS,前一个表示表示Service是一个接口,后一个表示Service是一个类。

本文遇到的问题中,将@Scope注解改成@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)就可以了,这里就不重复贴代码了。

问题解决。以上为个人经验,希望能给大家一个参考,也希望大家多多支持Devmax。

解决@Scope(“prototype“)不生效的问题的更多相关文章

  1. prototype Element学习笔记(篇一)

    Element,哈哈哈。遇到正主了,到现在为止才遇到让我高兴的玩意。当初Ext.Element可是花三千余行代码专门来封装啊。我倒要看一看它的代码了。事实上prototype中我最想研究的只有两个内容:Element、Selector。这两个东西是精华。

  2. javascript prototype 原型链

    prototype源自法语,软件界的标准翻译为“原型”,代表事物的初始形态,也含有模型和样板的意义。

  3. prototype1.5 初体验第1/2页

    一直没有没有时间看prototype,现在好了,已经更新到了1.5pre1,呵呵,强大的功能不得不学习啊,这个是提升自己JS能力的又一个捷径.1.Prototype是什么?或许你还没有用过它,prototype.js是一个由SamStephenson写的JavaScript包。像所有在我之前的开发者一样,我只能一头扎进prototype.js的源代码中并且试验其中的每一个部分。从Prototype1.5.x版本开始,你可以更方便的如下面代码一样操作DOM对象了:这样的改变有什么益处呢?使用Try.thes

  4. Prototype源码浅析 String部分(一)之有关indexOf优化

    Prototype源码浅析 String部分(一)之有关indexOf优化介绍,需要的朋友可以参考下。

  5. Prototype Number对象 学习

    这个对象提供一些操作数值类型的工具函数

  6. Spring中@Scope的几种取值方式

    这篇文章主要介绍了Spring中@Scope的几种取值方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

  7. 深入理解JS中的Function.prototype.bind()方法

    bind 是 ES5 中新增的一个方法,可以改变函数内部的this指向。这篇文章小编将带领大家深入理解Javascript中的Function.prototype.bind()方法。有需要的朋友们可以参考借鉴,下面来一起看看吧。

  8. Javascript学习笔记9 prototype封装继承

    在上文中,我利用prototype的原理做了一个封装的New,然后我就想到,我是否可以用prototype的原理进一步封装面向对象的一些基本特征呢?比如继承。

  9. Prototype Object对象 学习

    该不是一个概念。因为C#中的命名空间后面不会直接跟方法,肯定是接一个对象然后在调用方法,不过和C++中的命名空间倒是有些类似

  10. PHP设计模式(四)原型模式Prototype实例详解【创建型】

    这篇文章主要介绍了PHP设计模式:原型模式Prototype,结合实例形式详细分析了PHP原型模式Prototype的基本概念、功能、原理、实现方法与操作注意事项,需要的朋友可以参考下

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部