IOTXING

记录技术学习之路

0%

Spring scope学习

spring scope

spring scope

Spring定义的scope包含五种,singleton,prototype,request,session,global session,每种scope都有自己的使用范围以及使用场景。

singleton

Scopes a single bean definition to a single object instance per Spring IoC container.

个人理解:在Spring 的IOC容器中,全局只会存在一个对象实例。每次spring启动之后,就会生成一个,也只会有一个唯一的bean对象

prototype

Scopes a single bean definition to any number of object instances.

个人理解:有无数个对象的实例,也就是说每个上层在引用时,都是一个独立的对象

request

Scopes a single bean definition to the lifecycle of a single HTTP request; that is each and every HTTP request will have its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext

个人理解:在每个http请求的生成周期内,使用同一个对象的实例,不同的http之间使用的对象都是不同的实例。只有在使用了spring的ApplicationContext的上下文才有效

session

Scopes a single bean definition to the lifecycle of a HTTP Session,Only valid in the context of a web-aware Spring ApplicationContext

个人理解:在同一个session的生命周期内,使用同一个对象的实例

global session

Scopes a single bean definition to the lifecycle of a global HTTP Session. Typically only valid when used in a portlet context. Only valid in the context of a web-aware Spring ApplicationContext

个人理解:在同一个全局session的生命周期内,使用同一个对象的实例,跟上面的session的区别在于global,也就是全局,例如使用redis等分布式session的场景。

实例解读

为了方便更好的理解上面的内容,我们通过一个例子,来对不同范围下的bean进行一个测试

测试类图:

类图

VehicleController:请求入口,用于验证不同情况下的结果输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@RestController
public class VehicleController {

@Resource
private TruckManager truckManager;

@Resource
private CarManager carManager;

@GetMapping
public void getRun(@RequestParam("type") int type) {
if (type==1) {
carManager.doRun();
} else {
truckManager.doRun();
}
}

}

CarManager:调用WheelsManager,不做赋值

1
2
3
4
5
6
7
8
9
10
11
@Component
@Slf4j
public class CarManagerImpl implements CarManager {
@Resource
private WheelsManager wheelsManager;

@Override
public void doRun() {
log.info("CarManagerImpl doRun {}",wheelsManager.getWheelsCount());
}
}

TruckManager:调用WheelsManager,给wheelsCount赋值为4

1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Slf4j
public class TruckManagerImpl implements TruckManager {

@Resource
private WheelsManager wheelsManager;

@Override
public void doRun() {
wheelsManager.setWheelsCount(4);
log.info("TruckManagerImpl doRun {}", wheelsManager.getWheelsCount());
}
}

WheelsManager:内部包含属性wheelsCount,并且提供get和set

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Component
public class WheelsManagerImpl implements WheelsManager {

private Integer wheelsCount;

@Override
public Integer getWheelsCount() {
return wheelsCount;
}

public void setWheelsCount(Integer wheelCount) {
wheelsCount = wheelCount;
}
}

测试方法

通过更改bean的scope的范围,接口调用分别传入1,2,来判断各种情况下wheelsCount的结果

singleton

验证不同bean,引入相同bean的场景

请求顺序:type = 1 → type = 2 → type = 1

1
2
3
4
5
6
7
2022-03-20 11:08:27.204  INFO 31046 --- [  restartedMain] c.iotxing.blog.test.TestDemoApplication  : TestDemoApplication main 启动成功
2022-03-20 11:08:32.526 INFO 31046 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-20 11:08:32.526 INFO 31046 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-03-20 11:08:32.527 INFO 31046 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2022-03-20 11:08:32.552 INFO 31046 --- [nio-8080-exec-1] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun null
2022-03-20 11:08:43.003 INFO 31046 --- [nio-8080-exec-3] c.i.b.t.manager.impl.TruckManagerImpl : TruckManagerImpl doRun 4
2022-03-20 11:08:49.842 INFO 31046 --- [nio-8080-exec-5] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun 4

可以看出,在第一次调用时,因为wheelsCount没有赋值,所以打印出来的结果是null,第二次调用时,truckManager赋值了4,第三次调用时,carManager打印出来的也是4(上一次调用truckManager赋值的结果)

结论:全局范围内,正常方式注入的bean只有一个实例,因此如果bean存在属性,不同的请求之间都是的更改都是可见的,属于线程不安全的场景。如果bean中没有属性,只有逻辑,也就不存在安不安全的问题了。

prototype

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Component
@Scope("prototype")
public class WheelsManagerImpl implements WheelsManager {

private Integer wheelsCount;

@Override
public Integer getWheelsCount() {
return wheelsCount;
}

public void setWheelsCount(Integer wheelCount) {

wheelsCount = wheelCount;

}
}

spring默认的scope是singleton,更改为prototype的话,需要我们手动指定对应的scope

验证不同bean,引入相同bean的场景

请求顺序:type = 1 → type = 2 → type = 1

1
2
3
4
5
6
2022-03-20 11:31:07.252  INFO 31888 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
2022-03-20 11:31:07.252 INFO 31888 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet'
2022-03-20 11:31:07.253 INFO 31888 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms
2022-03-20 11:31:07.278 INFO 31888 --- [nio-8080-exec-1] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun null
2022-03-20 11:31:16.509 INFO 31888 --- [nio-8080-exec-3] c.i.b.t.manager.impl.TruckManagerImpl : TruckManagerImpl doRun 4
2022-03-20 11:31:21.201 INFO 31888 --- [nio-8080-exec-5] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun null

可以发现更改了scope之后,CarManager和TruckManager在初始化的时候,各自都创建了一个WheelsManager的实例出来

验证同一个bean,不同请求下的使用场景

针对单个CarManager,我们做一下改造,看一下prototype对单个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
@Component
@Slf4j
public class CarManagerImpl implements CarManager {
@Resource
private WheelsManager wheelsManager;

@Override
public void doRun() {
log.info("CarManagerImpl doRun {}",wheelsManager.getWheelsCount());
addPreWheel();
addWholeWheel();
}

public void addPreWheel(){
wheelsManager.setWheelsCount(wheelsManager.getWheelsCount()+2);
log.info("CarManagerImpl addPreWheel {}",wheelsManager.getWheelsCount());
}

public void addWholeWheel(){
wheelsManager.setWheelsCount(wheelsManager.getWheelsCount()+10);
log.info("CarManagerImpl addWholeWheel {}",wheelsManager.getWheelsCount());
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
@Component
@Slf4j
public class TruckManagerImpl implements TruckManager {

@Resource
private WheelsManager wheelsManager;

@Override
public void doRun() {
wheelsManager.setWheelsCount(wheelsManager.getWheelsCount()+4);
log.info("TruckManagerImpl doRun {}", wheelsManager.getWheelsCount());
}
}

上面我们增加了两个方法,调用addPreWheel的时候,会给wheelsCount+2,调用WholeWheel的时候,会给wheelsCount+10,我们验证下分多次调用时候的结果

验证顺序: type = 1 → type = 1 → type = 2

1
2
3
4
5
6
7
8
2022-03-20 11:52:56.917  INFO 32882 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
2022-03-20 11:52:56.944 INFO 32882 --- [nio-8080-exec-1] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun 0
2022-03-20 11:52:56.944 INFO 32882 --- [nio-8080-exec-1] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl addPreWheel 2
2022-03-20 11:52:56.944 INFO 32882 --- [nio-8080-exec-1] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl addWholeWheel 12
2022-03-20 11:52:58.922 INFO 32882 --- [nio-8080-exec-3] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl doRun 12
2022-03-20 11:52:58.926 INFO 32882 --- [nio-8080-exec-3] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl addPreWheel 14
2022-03-20 11:52:58.926 INFO 32882 --- [nio-8080-exec-3] c.i.b.test.manager.impl.CarManagerImpl : CarManagerImpl addWholeWheel 24
2022-03-20 11:53:01.678 INFO 32882 --- [nio-8080-exec-5] c.i.b.t.manager.impl.TruckManagerImpl : TruckManagerImpl doRun 4

可以发现,相同的bean在不同的请求时候,使用的是同一个对象,因此第二次请求时候的值是在第一次的基础上累加的,不同的bean在使用时,使用的是不同的对象,因此TruckManager的初始值是4

结论:spring启动时,对于不同的类依赖的相同的bean,注入的时候是注入的同一个bean对象的不同实例。

其它

由于request,session和global session对验证起来要求较多,并且实际中这种场景我接触不多,因此不做专门的测试了