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

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

@Scope("prototype")不生效

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

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

?

|-------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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 + ``""``); ``} } |

测试

?

|----------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | 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变成多例

如下所示:

?

|----------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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类代码

?

|-------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | 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类代码

?

|-------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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类

?

|----------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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的类

?

|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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是一个类。

相关推荐
WaaTong4 天前
《重学Java设计模式》之 原型模式
java·设计模式·原型模式
morning_judger6 天前
【设计模式系列】原型模式(十一)
java·设计模式·原型模式
飞升不如收破烂~7 天前
在Spring框架中,容器管理的bean可以有不同的作用域(scope),其中最常用的两种是单例(singleton)和原型(prototype)。
spring·单例模式·原型模式
安泽13148 天前
【修订中】js 中apply call bind 用法
原型模式
Komorebi_99998 天前
JavaScript 判断数据类型有哪些方法?
开发语言·javascript·原型模式
无敌岩雀9 天前
C++设计模式创建型模式———原型模式
c++·设计模式·原型模式
一条晒干的咸魚9 天前
【Web前端】JavaScript 对象原型与继承机制
开发语言·前端·javascript·原型模式·web前端
shinelord明9 天前
【再谈设计模式】原型模式~复制的魔法师
开发语言·设计模式·原型模式
wrx繁星点点11 天前
解释器模式:有效处理语言的设计模式
java·开发语言·spring·servlet·设计模式·解释器模式·原型模式
wrx繁星点点13 天前
原型模式:高效的对象克隆解决方案
数据结构·spring·spring cloud·java-ee·maven·intellij-idea·原型模式