https://www.jianshu.com/p/54b0711a8ec8

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/aaahttp://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/aaahttp://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/aaahttp://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.INTERFACESScopedProxyMode.TARGET_CLASS,前一个表示表示Service是一个接口,后一个表示Service是一个类。
  本文遇到的问题中,将@Scope注解改成@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)就可以了,这里就不重复贴代码了。
  问题解决。

参考:

[转帖]@Scope("prototype")的正确用法——解决Bean的多例问题的更多相关文章

  1. @Scope("prototype")的正确用法——解决Bean的多例问题

    转自: https://www.jianshu.com/p/54b0711a8ec8 1. 问题,Spring管理的某个Bean需要使用多例   在使用了Spring的web工程中,除非特殊情况,我们 ...

  2. 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解

    Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解   多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...

  3. StringBuilder在高性能场景下的正确用法

    转载:<StringBuilder在高性能场景下的正确用法> by 江南白衣 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不 ...

  4. Spring MVC中Session的正确用法<转>

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

  5. C#中dynamic的正确用法

    C#中dynamic的正确用法  http://www.cnblogs.com/qiuweiguo/archive/2011/08/03/2125982.html dynamic是FrameWork4 ...

  6. C# string.Split对于换行符的分隔正确用法

    C# string.Split对于换行符的分隔正确用法 tmpCase "11117144-8c91-4817-9b92-99ec2f9d784a\r\n23D95A26-012C-4332 ...

  7. 【转】Spring MVC中Session的正确用法之我见

    Spring MVC是个非常优秀的框架,其优秀之处继承自Spring本身依赖注入(Dependency Injection)的强大的模块化和可配置性,其设计处处透露着易用性.可复用性与易集成性.优良的 ...

  8. Spring的scope="prototype"属性

    spring 默认scope 是单例模式这样只会创建一个Action对象每次访问都是同一个Action对象,数据不安全struts2 是要求 每次次访问 都对应不同的Actionscope=" ...

  9. spring bean中scope="prototype“的作用

    今天写代码时,遇到个问题,问题大概如下:在写一个新增模块,当各文本框等输入值后,提交存入数据库,跳到其它页面,当再次进入该新增页面时,上次输入的数据还存在. 经过检查发现是,spring配置文件中,配 ...

  10. spring scope="prototype", scope="session"

    转自: http://www.cnblogs.com/JemBai/archive/2010/11/10/1873954.html struts+spring action应配置为scope=&quo ...

随机推荐

  1. 神经网络优化篇:详解神经网络的权重初始化(Weight Initialization for Deep NetworksVanishing / Exploding gradients)

    神经网络的权重初始化 这是一个神经单元初始化地例子,然后再演变到整个深度网络. 来看看只有一个神经元的情况,然后才是深度网络. 单个神经元可能有4个输入特征,从\(x_{1}\)到\(x_{4}\), ...

  2. Zabbix自带模板监控MySQL服务

    Zabbix的服务端与客户端的安装这里不再赘述了,前面也有相应的文章介绍过了,感兴趣的伙伴们可以看看历史文章就可以了,今天主要介绍下如何利用zabbix自带的模板来监控MySQL服务的一些状态,同时通 ...

  3. Pikachu漏洞靶场 敏感信息泄露

    敏感信息泄露 概述 由于后台人员的疏忽或者不当的设计,导致不应该被前端用户看到的数据被轻易的访问到. 比如: 通过访问url下的目录,可以直接列出目录下的文件列表; 输入错误的url参数后报错信息里面 ...

  4. 文心一言 VS 讯飞星火 VS chatgpt (51)-- 算法导论6.2 3题

    三.当元素 A[i] 比其孩子的值都大时,调用 MAX-HEAPIFY(A.i)会有什么结果? 文心一言: 当元素 A[i] 比其孩子的的值都大时,调用 MAX-HEAPIFY(A, i) 会将 A[ ...

  5. 【云享·人物】华为云AI高级专家白小龙:AI如何释放应用生产力,向AI工程化前行?

    摘要:AI技术发展,正由应用落地阶段向效率化生产阶段演进,AI工程化能力将会不断深入业务,释放企业生产力. 本文分享自华为云社区<[云享·人物]华为云AI高级专家白小龙:AI如何释放应用生产力, ...

  6. 数仓无损压缩算法:gzip算法

    摘要:一种无损的压缩数据格式,是一个在类Unix上的一种文件解压缩软件. 本文分享自华为云社区<GaussDB(DWS) gzip算法简介>,作者:hw0086. [算法原理] gzip是 ...

  7. 乐高式扩展:在Seal软件供应链防火墙中轻松集成代码规范工具

    上个月,Seal 软件供应链防火墙 v0.2(以下简称"Seal")正式发布,这一版本实现了可扩展架构,用户可以根据自身需求插件式集成原生或第三方解决方案,灵活扩展扫描能力. 在前 ...

  8. Solon 编译打包怎样保持参数名不变?

    1.Java 项目 Java maven 项目 <plugin> <groupId>org.apache.maven.plugins</groupId> <a ...

  9. 浅谈locust 性能压测使用

    1. 基本介绍 Locust是一个开源的负载测试工具,用于模拟大量用户并发访问一个系统或服务,以评估其性能和稳定性.编写语言为Python,可通过Python来自定义构建性能压测场景脚本.Locust ...

  10. 数据结构——AVL树

    AVL树是一种特殊的二叉查找树,其特征在于:对所有节点来说,其左子树和右子树间的高度差小于等于1.本文简要总结下AVL树的几种基本操作. 节点结构体定义 typedef struct Node_s { ...