一.基础介绍
在springboot中我们创建bean对象的时候,需要考虑bean的作用域。我们可以使用@Scope的注解来指定bean的作用域。先看下scope的源码(依赖:spring-boot-starter-web,版本:2.3.5.RELEASE)
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Scope {
/**
* Alias for {@link #scopeName}.
* @see #scopeName
*/
@AliasFor("scopeName")
String value() default "";
/**
* Specifies the name of the scope to use for the annotated component/bean.
* <p>Defaults to an empty string ({@code ""}) which implies
* {@link ConfigurableBeanFactory#SCOPE_SINGLETON SCOPE_SINGLETON}.
* @since 4.2
* @see ConfigurableBeanFactory#SCOPE_PROTOTYPE
* @see ConfigurableBeanFactory#SCOPE_SINGLETON
* @see org.springframework.web.context.WebApplicationContext#SCOPE_REQUEST
* @see org.springframework.web.context.WebApplicationContext#SCOPE_SESSION
* @see #value
*/
@AliasFor("value")
String scopeName() default "";
/**
* Specifies whether a component should be configured as a scoped proxy
* and if so, whether the proxy should be interface-based or subclass-based.
* <p>Defaults to {@link ScopedProxyMode#DEFAULT}, which typically indicates
* that no scoped proxy should be created unless a different default
* has been configured at the component-scan instruction level.
* <p>Analogous to {@code <aop:scoped-proxy/>} support in Spring XML.
* @see ScopedProxyMode
*/
ScopedProxyMode proxyMode() default ScopedProxyMode.DEFAULT;
看scopeName的注释,可以看到4个作用域+WebApplicationContext中还有一个scope
ConfigurableBeanFactory#SCOPE_PROTOTYPE:prototype
ConfigurableBeanFactory#SCOPE_SINGLETON:singleton
WebApplicationContext#SCOPE_REQUEST:request
WebApplicationContext#SCOPE_SESSION:session
WebApplicationContext#SCOPE_APPLICATION:application(注释中没有,但是代码中有这个作用域)
prototype:原型模式,每次get bean都会创建一个新的
singleton:单例模式,全局唯一,只创建一次
request:每个request创建一次
session:每个session创建一次
application:每个web应用只创建一次(在一个spirngboot应用中和singleton基本一致)
二. 代码验证
我们通过一个controller来验证作用域的作用,先上代码
@RestController(value = "/scope")
//@Scope(value = "prototype")
//@Scope(value = "request")
//@Scope(value = "application")
//@Scope(value = "session")
public class ScopeController {
private String scope = "scopeOriginal";
@ResponseBody
@PostMapping(value = "/getScope")
public String getScope() {
return scope;
}
@ResponseBody
@PostMapping(value = "/modifyScope")
public String modifyScope() {
scope = "scopeModified";
return scope;
}
}
上面是一个非常简单的controller的代码,controller中有一个类变量:scope。
1. 单例&application
我们连续请求getScope,modifyScope,getScope接口,看下结果
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/getScope; echo
scopeOriginal
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/modifyScope; echo
scopeModified
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/getScope; echo
scopeModified
可以看到第二次getScope时得到的是修改好的值。scope的默认作用域是singleton(单例),@RestController也是注册bean的方式。也就是说ScopeController是全局单例的,所以不同请求进来是一样的,所以每个请求进来拿到的是其他请求进来修改后的值。
@controller,@component等bean是否线程安全?
答:不是线程安全的,因为默认情况下scope是单例模式,但是bean所在类的变量是可能会被多个线程同时修改的,所以@component等注解中不要使用非静态变量;另一种说法是一般来说@component所在类中若不使用类变量,成为无状态模式,勉强称之为线程安全。
使用application也会得到上面的结果
2. 原型&request
将//@Scope(value = "prototype")的注释放开,再次请求看下结果
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/getScope; echo
scopeOriginal
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/modifyScope; echo
scopeModified
yuhan@ubuntu:~/Documents/workspace$ curl -X POST http://localhost:8080/getScope; echo
scopeOriginal
第二次请求getScope,结果是scopeOriginal,即可了解到原型模式每次都是创建新的对象。
将//@Scope(value = "request")放开,执行测试步骤会和prototype得到相同的结果,就不贴了。request就是每次请求时得到新的bean,prototype是只要每次获取bean对象都会得到新的。比较好理解,就不举例了。
3. session
session是每次会话使用相同的实例,怎么模拟会话呢,我们可以使用cookie来模拟。首先如果我们放开//@Scope(value = "session"),然后执行测试的话,会和request是一样的结果,表明默认每次请求是不同的session。我们带上cookie试下,看下结果:
### 使用idea自带的post工具用以带上cookie
###
POST http://localhost:8080/getScope
Cookie: session=scope
###
POST http://localhost:8080/modifyScope
Cookie: session=scope
scopeModified
此时能看到第三次请求的结果是scopeModified,就表明这三次请求是在同一个session会话中。