@Scope("prototype")的正确用法——解决Bean的多例问题
转自:
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/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)
就可以了,这里就不重复贴代码了。
问题解决。
参考:
作者:猫仙草
链接:https://www.jianshu.com/p/54b0711a8ec8
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
@Scope("prototype")的正确用法——解决Bean的多例问题的更多相关文章
- spring bean中scope="prototype“的作用
今天写代码时,遇到个问题,问题大概如下:在写一个新增模块,当各文本框等输入值后,提交存入数据库,跳到其它页面,当再次进入该新增页面时,上次输入的数据还存在. 经过检查发现是,spring配置文件中,配 ...
- Spring框架是怎么解决Bean之间的循环依赖的 (转)
问题: 循环依赖其实就是循环引用,也就是两个或则两个以上的bean互相持有对方,最终形成闭环.比如A依赖于B,B依赖于C,C又依赖于A.如下图: 如何理解“依赖”呢,在Spring中有: 构造器循 ...
- Spring解决bean之间的循环依赖
转自链接:https://blog.csdn.net/lyc_liyanchao/article/details/83099675通过前几节的分析,已经成功将bean实例化,但是大家一定要将bean的 ...
- 转载~kxcfzyk:Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解
Linux C语言多线程库Pthread中条件变量的的正确用法逐步详解 多线程c语言linuxsemaphore条件变量 (本文的读者定位是了解Pthread常用多线程API和Pthread互斥锁 ...
- spring scope="prototype", scope="session"
转自: http://www.cnblogs.com/JemBai/archive/2010/11/10/1873954.html struts+spring action应配置为scope=&quo ...
- struts+spring action应配置为scope="prototype"
truts+spring action应配置为scope="prototype" <bean id="personAction" scope=" ...
- StringBuilder在高性能场景下的正确用法
转载:<StringBuilder在高性能场景下的正确用法> by 江南白衣 关于StringBuilder,一般同学只简单记住了,字符串拼接要用StringBuilder,不要用+,也不 ...
- Spring课程 Spring入门篇 4-7 Spring bean装配之基于java的容器注解说明--@Scope 控制bean的单例和多例
1 解析 1.1 bean的单例和多例的应用场景 1.2 单例多例的验证方式 1.3 @Scope注解单例多例应用 2 代码演练 2.1 @Scope代码应用 1 解析 1.1 bean的单例和多例的 ...
- spring scope prototype与singleton区别
1.singleton作用域 当一个bean的作用域设置为singleton, 那么Spring IOC容器中只会存在一个共享的bean实例,并且所有对bean的请求,只要id与该bean定义相匹配 ...
随机推荐
- 必须使用适当的属性或方法修改“ContentType”标头
只能通过属性修改ContentType,不能添加到header属性里
- 输入web网站点击之后发生的事情
(1)用户在浏览器(客户端)里输入或者点击一个连接: (2)浏览器向服务器发送HTTP请求: (3)服务器处理请求,如果查询字符串或者请求体里含有参数,服务器也会把这些参数信息考虑进去: (4)服务器 ...
- PLC编程的基础知识的总结
1.Programmable Logic Controller PLC 代替计算机实现复杂的逻辑控制系统 ,可编程控制器是一种数字运算操作电子系统,转门在工业控制系统中使用而设计的,采用一种可以 ...
- centos 下 yum安装python3
2.安装IUS软件源:sudo yum -y install https://centos7.iuscommunity.org/ius-release.rpm3.安装Python3.6:sudo yu ...
- 本地安装部署ActiveCollab
ActiveCollab是一个非常易于使用.基于Web.开源的协作开发与项目管理工具. 我们公司一直在用这款工具,进行任务分配和时间填写,十分简便 ActiveCollab可以利用它轻松地搭建一个包括 ...
- 文本编辑器Vim/Neovim任意代码执行漏洞(CVE-2019-12735)
受影响版本: Vim < 8.1.1365, Neovim < 0.3.6 前提:开启modeline 0x01 开启modeline 在你的home下的.vimrc文件中增加一行: se ...
- Something is already running on port 3000. Would you like to run the app on another port instead?
查看端口sudo lsof -i :3000 删除进程 sudo kill -9 12297[pid]
- java连接mysql数据库时的时区设置问题(time_zone)
java在连接mysql数据库时,会由于时区设置不正确导致报以下的错误: The server time zone value '???ú±ê×??±??' is unrecognized or ...
- python3 xml_to_dict、dict_to_xml等互相转换的方法
from basic_config_vars.config_vars import moviesAllFilePath,moviesFilePath #自定义的一些文件变量 import xmltod ...
- php 实现密码错误三次锁定账号10分钟
/** * 登录 * 1.接收数据 * 2.正则判断接收到的数据是否合理 * 3.根据用户名获取用户数据 * 获取到数据 -> 继续执行 * 没有获取到数据 -> 提示:用户名密码错误 * ...