bean的作用域

277 阅读2分钟

一.基础介绍

在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会话中。