浅尝Spring中的Scope

浅尝Spring中的Scope

Scope顾名思义是作用域的意思,用于描述Spring容器如何新建Bean实例。

概述

Spring框架中提供了多种不同类型的Bean scope,这些Scope定义了bean在其使用的上下文中的生命周期和可见性。当前版本的Spring中定义了如下6种不同类型的Scope:

  • singleton

    bean会被限制在每一个Spring IOC容器中只有一个实例,Spring默认配置即为singleton

  • prototype

    每次调用都会新建一个Bean实例

下面四种类型只能在web应用程序中使用

  • request

    Web项目中,给每一个http request新建一个Bean实例

  • session

    Web项目中,给每一个http session新建一个Bean实例

  • application

    Web项目中,会在整个ServletContext的生命周期中新建一个Bean实例

  • websocket

    首次访问时,会新建一个Bean实例存储在WebSocket会话属性中,每当在WebSocket会话期间访问Bean时,都会返回该Bean的相同实例

举个栗子

singleton scope
定义成singleton的bean会被限制在每一个Spring IOC容器中只有一个实例,Spring默认配置即为singleton

创建一个Person实体,来验证作用域的功能

public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

使用@Scope注解来标识Bean的作用域

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_SINGLETON)
public Person personSingleton() {
    return new Person();
}

编写一个单元测试,来测试引用同一个Bean的两个对象是否相同

@Test
public void singletonScopeTest() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);

    Person personA = (Person) applicationContext.getBean("personSingleton");
    Person personB = (Person) applicationContext.getBean("personSingleton");
    personA.setName("Thor");

    Assert.assertEquals(personA, personB);
}

运行测试用例通过,说明即使改变其中一个对象的状态,两个对象仍然引用同一个Bean实例

prototype scope
定义成prototype的bean,在每次都会新建一个实例,只需要把@Scope注解的value值设置为Prototype

@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Person personPrototype() {
    return new Person();
}

同样,编写一个测试用例来进行测试

@Test
public void prototypeScopeTest() {
    ApplicationContext applicationContext = new AnnotationConfigApplicationContext(BeanConfig.class);

    Person personA = (Person) applicationContext.getBean("personPrototype");
    Person personB = (Person) applicationContext.getBean("personPrototype");

    Assert.assertEquals(personA, personB);
}

运行测试,可以看到两个对象没有引用同一个Bean

前面提到,有四种只能在web应用程序中使用的scope,WebSocket的用得较少,所以只列出前三种

request scope

在request scope下,每一个http请求都会创建一个bean实例

编写一个HelloMessageGenerator类用于实例化bean

public class HelloMessageGenerator {
    public String message;

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }
}

接下来,在@Scope注解中,把value值声明为request,代码如下

@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator requestScopedBean() {
    return new HelloMessageGenerator();
}

@Scope注解中声明proxyMode属性是非常有必要的,因为在web应用程序上下文初始化的过程中,这时候没有有效的请求,声明为TARGET_CLASS,Spring将会创建一个代理作为依赖注入,在请求的时候实例化Bean

编写一个controller,注入requestScopedBean,用于测试Bean的scope

@RestController
public class ScopesController {
    private static final Logger logger = LoggerFactory.getLogger(ScopesController.class);
    @Resource(name = "requestScopedBean")
    private HelloMessageGenerator requestScopedBean;

    @RequestMapping("/scopes/request")
    public String getRequestScopeMessage() {
        logger.debug("previousMessage:{}", requestScopedBean.getMessage());
        requestScopedBean.setMessage("Good Morning!");
        logger.debug("currentMessage:{}", requestScopedBean.getMessage());

        return "scopesExample";
    }
}

使用Chrome访问localhost:8081/scopes/request两次,可以看到,每次输出的日志如下

previousMessage:null
currentMessage:Good Morning!
previousMessage:null
currentMessage:Good Morning!

说明Spring为每一个请求都创建了一个Bean实例

session scope

@Scope注解中,把value值声明为session,Spring会为每一个http session实例化一个Bean

@Bean
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator sessionScopedBean() {
    return new HelloMessageGenerator();
}

同样地,在Controller中注入sessionScopedBean

...
@Resource(name = "sessionScopedBean")
private HelloMessageGenerator sessionScopedBean;

@RequestMapping("/scopes/session")
public String getSessionScopeMessage() {
    logger.debug("previousMessage:{}", sessionScopedBean.getMessage());
    sessionScopedBean.setMessage("Good Afternoon!");
    logger.debug("currentMessage:{}", sessionScopedBean.getMessage());

    return "scopesExample";
}
...

使用Chrome访问两次这个接口,观察message的变化

previousMessage:null
currentMessage:Good Afternoon!
previousMessage:Good Afternoon!
currentMessage:Good Afternoon!

可以看到,在第一次访问的时候message为null,当第二次返回时,message的值已经改变,说明在同一个Session当中的值被保留了下来,整个会话中都返回了相同的Bean实例

application scope

@Bean
@Scope(value = WebApplicationContext.SCOPE_APPLICATION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public HelloMessageGenerator applicationScopedBean() {
    return new HelloMessageGenerator();
}

后续代码和上面一样,在此处省略,可以使用多个不同的浏览器进行访问,测试是否在整个ServletContext的生命周期都是同一个Bean实例,这其实有点类似于单例模式,但是两者有一个非常重要的区别。当scope为application时,Bean的相同实例会在同一个ServletContext中运行的多个基于Servlet的应用程序之间共享,而scope为singleton仅作用于单个应用程序上下文。

结束

本篇学习记录到此结束,如有问题请指出,代码在github


   转载规则

本文不允许转载。
 上一篇
CentOS7 安装Libreoffice CentOS7 安装Libreoffice
CentOS7 安装Libreoffice 来自官网的介绍,LibreOffice 是一款功能强大的办公软件,默认使用开放文档格式 (OpenDocument Format , ODF), 并支持 .docx, .xlsx, *.pptx
2019-01-02
下一篇 
在SpringBoot中使用MongoDB 在SpringBoot中使用MongoDB
在SpringBoot中使用MongoDB 最近项目中使用了MongoDB,在SpringBoot中集成了MongoDB,MongoDB是当前非常火的一个非关系型数据库,同时也是最接近关系型数据库的,本篇文章用于记录SpringBoot中集
2018-10-26
  目录