解决@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是一个类。

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

相关推荐
geovindu1 天前
CSharp: Prototype Pattern
开发语言·后端·设计模式·.net·原型模式·创建型模式
Seven凹凸Man1 天前
WorkBuddy之产品经理原型设计及PRD实战
ai·原型模式
ttod_qzstudio2 天前
【软考设计模式】原型模式:“克隆“对象与深拷贝、浅拷贝精讲
设计模式·原型模式
希冀1236 天前
【JavaScript】Javascript—JS进阶—Day03
开发语言·javascript·原型模式
想吃火锅100525 天前
【前端手撕】instanceof
前端·javascript·原型模式
UXbot25 天前
帮助企业低门槛开展AI应用开发的平台推荐
前端·低代码·ui·交互·产品经理·原型模式·web app
UXbot1 个月前
如何选择适合公司项目的UI设计工具?企业选型指南
前端·低代码·ui·团队开发·原型模式·设计规范·web app
UXbot1 个月前
原型设计工具如何帮助新人快速进入产品行业?
前端·低代码·ui·交互·团队开发·原型模式·web app
sunny.day1 个月前
js原型与原型链
开发语言·javascript·原型模式·js原型链
UXbot1 个月前
AI网页开发工具能替代工具吗?5大平台对比
前端·人工智能·低代码·ui·原型模式·web app