目录
- @Scope("prototype")不生效
- @Scope("prototype")正确用法------解决Bean多例问题
- 1.问题,Spring管理的某个Bean需要使用多例
- 2.问题升级
-
- 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是一个类。