解决@Scope(“prototype“)不生效的问题

目录
  • @Scope(“prototype“)不生效
  • @Scope(“prototype“)正确用法——解决Bean多例问题
    • 1.问题,Spring管理的某个Bean需要使用多例
    • 2.问题升级
    • 3. Spring给出的解决问题的办法(解决Bean链中某个Bean需要多例的问题)

@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)就可以了,这里就不重复贴代码了。

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

(0)

相关推荐

  • maven依赖关系中的<scope>provided</scope>使用详解

    关于maven依赖中的<scope>provided</scope>使用 scope的其他参数如下: compile 默认的scope,表示 dependency 都可以在生命周期中使用.而且,这些dependencies 会传递到依赖的项目中.适用于所有阶段,会随着项目一起发布 provided 跟compile相似,但是表明了dependency 由JDK或者容器提供,例如Servlet AP和一些Java EE APIs.这个scope 只能作用在编译和测试时,同时没有传递性

  • 使用Spring中的scope配置和@scope注解

    目录 Spring的scope配置和@scope注解 1. Spring的作用域在装配Bean 2. 基于注解开发时 Spring中的scope详解 1. scope概论 2. scope历史及分类 3. 单个scope详解 4. scope配置 Spring的scope配置和@scope注解 Scope,也称作用域,在 Spring IoC 容器是指其创建的 Bean 对象相对于其他 Bean 对象的请求可见范围. 在 Spring IoC 容器中具有以下几种作用域: 基本作用域(single

  • Maven中optional和scope元素的使用弄明白了吗

    在梳理项目的过程中发现很多开发同学对 Maven 依赖文件的配置并不了解,特别是对 Maven 的 optional 元素和 scope 元素的使用也非常随意.这就会导致发布的 jar 包或 war 包非常 "胖".编译速度慢,而且还很容易生产 jar 冲突等问题.本篇文章从 optional 和 scope 的使用场景入手,让项目实现一波瘦身. optional 元素 这里以 Spring Boot 项目中的使用为例,比如我们在项目中经常使用的热部署组件 spring-boot-de

  • 使用Maven中的scope总结

    目录 Maven中的scope总结 Maven中的scope主要有以下6种 Maven中<scope>参数</scope>配置 解决maven项目中无法打包生成空文件夹的问题 Maven中的scope总结 Maven中的scope主要有以下6种 接下来分别介绍下这几种scope: compile 不声明scope元素的情况下的默认值:compile表示被依赖包需要参与当前项目的编译,包括后续的测试,运行周期也参与其中,是一个比较强的依赖:打包的时候通常需要包含进去. provide

  • Spring中@Scope的几种取值方式

    目录 @Scope的几种取值 Spring入门详解scope属性 一.scope属性 二.scope分类 三.scope取值 @Scope的几种取值 1.singleton:一个Spring容器中只有一个Bean的实例,此为Spring的默认配置,全容器共享一个实例 2.prototype:每次调用新建一个Bean的实例 3.Request:Web项目中,给每一个http request新建一个Bean实例 4.Session:Web项目中,给每一个http session新建一个Bean实例.

  • 解决@Scope(“prototype“)不生效的问题

    目录 @Scope(“prototype“)不生效 @Scope(“prototype“)正确用法——解决Bean多例问题 1.问题,Spring管理的某个Bean需要使用多例 2.问题升级 3. Spring给出的解决问题的办法(解决Bean链中某个Bean需要多例的问题) @Scope(“prototype“)不生效 使用spring的时候,我们一般都是使用@Component实现bean的注入,这个时候我们的bean如果不指定@Scope,默认是单例模式,另外还有很多模式可用,用的最多的就

  • 解决vue addRoutes不生效问题

    动态添加导航栏时,addRoutes不生效解觉 1.在addroutes前,使用router.options.routes=XXXXX的方法手动添加 2.使用作者的方法,在store里维护一个routes对象,然后使用这个对象遍历生成侧面导航栏 补充知识:vue-router 动态添加路由 router.addRoutes(routes)遇到的二次登陆路由冲突问题解决 起因 在当前项目中使用的iview-admin,路由要根据权限动态生成,是在登录后获取当前用户权限内的路由使用 vue-rout

  • 关于idea的gitignore文件编写及解决ignore文件不生效问题

    1.下载idea的 <.ignore> 插件,重启idea生效 2.添加自己想要忽略的文件夹及文件,一般选这个就够了 3.如果想要忽略提交的文件夹名称变成黄色了,就代表成功忽略该文件夹了! 4.一般要忽略的文件夹的.ignore文件的书写格式: .idea -- .idea整个文件夹,要注意的是这个是相对路径,也就是.ignore文件和.idea文件夹同一级目录 *.iws -- 所有以.iws为文件后缀名的文件,下面两个以此类推 *.iml *.ipr PS:若该操作未能实现忽略提交,是因为

  • springboot prototype设置多例不起作用的解决操作

    大多数人会直接这样写: @Bean @Scope(value = ConfigurableBeanFactory.SCOPE_PROTOTYPE) public TestBean getTestBean() { return new TestBean(); } ConfigurableBeanFactory.SCOPE_PROTOTYPE的值就是prototype 但是发现Autowire的时候,每一个请求用的还是同一个单例对象,这是因为没设置多例的代理模式的问题,改成如下配置就可以了: @Be

  • 详谈Feign的配置类是如何生效的

    目录 1. Feign 1.1 配置类:ApiConfiguration.java 1.2 FeignClientsRegistrar 1.3 FeignAutoConfiguration 1.4 FeignClientFactoryBean的定义 1.5 FeignClientsConfiguration 1.6 FeignClientProperties 1.7 再看FeignClientFactoryBean 1.8 FeignRibbonClientAutoConfiguration 1

  • 关于spring中单例Bean引用原型Bean产生的问题及解决

    目录 spring单例Bean引用原型Bean产生的问题及解决 问题描述 为了更直观的发现问题,下面我们用代码演示一遍 问题分析 spring Bean的几个相关问题 1.Spring Bean 作用域 2.什么是Spring inner beans 3.什么是有状态.无状态 4.Spring框架中的单例Bean是线程安全的么 5.Spring Bean 的自动装配 6.各种自动装配模式的区别 7.在Spring中可以注入null或空字符串吗 8.Spring框架中有哪些不同类型的事件(都继承自

  • 深入理解AngularJs-scope的脏检查(一)

    进入正文前的说明:本文中的示例代码并非AngularJs源码,而是来自书籍<<Build Your Own AngularJs>>, 这本书的作者仅依赖jquery和lodash一步一步构建出AngularJs的各核心模块,对全面理解AngularJs有非常巨大的帮助.若有正在使用AngulaJs攻城拔寨并且希望完全掌握手中武器的小伙伴,相信能对你理解AngularJs带来莫大帮助,感谢作者. 在这篇文章中,希望能让您理清楚以下几项与scope相关的功能: 1.dirty-chec

  • java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list错误解决办法

    java.lang.NoSuchMethodException: com.sun.proxy.$Proxy58.list错误解决办法 玩web的SSH总会有些令你意想不到的exception,这里其中有很多事自己不小心,或者马虎所造成.因此,解决的方案会各有不同,别人出现的异常解决方案对你的可能无效,就像上面的我报的异常一样,百度了很多很多次,给我的答案无非就是在aop上加上一句,但是非常抱歉,我加上去无效!所以还是那句话,对于自己的异常,还是要自己解决. 首先说明一下,我这次的练习的ssh结构

  • 浅谈Spring解决循环依赖的三种方式

    引言:循环依赖就是N个类中循环嵌套引用,如果在日常开发中我们用new 对象的方式发生这种循环依赖的话程序会在运行时一直循环调用,直至内存溢出报错.下面说一下Spring是如果解决循环依赖的. 第一种:构造器参数循环依赖 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyIn CreationException异常表示循环依赖. 如在创建TestA类时,构造器需要TestB类,那将去创建TestB,在创建TestB类时又发现需要TestC类,则又去创建Test

  • Spring如何解决单例bean线程不安全的问题

    首先我们应该知道线程安全问题一般发生在成员变量上,这是为什么啦? 因为成员变量是存放在堆内存中,而堆内存又是线程共享的,这就造成了线程安全问题 因为Spring中的Bean默认是单例的,所以在定义成员变量时也有可能会发生线程安全问题.下面我们就来研究下如何解决Spring中单例Bean的线程安全问题 @RestController //@Scope("prototype") public class BeanController { private int content=0; //基

随机推荐