Spring -- Bean的作用域

前言

Spring 的主要功能就是存储和读取 Bean,因此在 Spring 中 Bean 是最核心的操作资源。那么什么是 Bean 的作用域呢?

一、案例

假设现在有⼀个公共的 Bean,提供给 A 用户和 B 用户使用,然而在使用过程中 A 用户却“悄悄”地修改了公共 Bean 的数据,导致 B 用户在使用时发生了预期之外的逻辑错误。

我们预期的结果是,公共 Bean 可以在各自的类中被修改,但不能影响到其他类。

1.1 被修改的 Bean

User 类:

package yyhgo;

public class User {
    
    
    int id;
    String name;

    public int getId() {
    
    
        return id;
    }

    public void setId(int id) {
    
    
        this.id = id;
    }

    public String getName() {
    
    
        return name;
    }

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

    @Override
    public String toString() {
    
    
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                '}';
    }
}

存入公共 Bean:

package yyhgo;

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import yyhgo.User;

@Component
public class Users {
    
    
    @Bean
    public User user1() {
    
    
        User user = new User();
        user.setId(1);
        user.setName("Java"); // 【重点:名称是 Java】
        return user;
    }
}

A 用户使⽤时,进行了修改操作:

package yyhgo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BeanScopesController {
    
    
    @Autowired
    private User user1;

    public User getUser1() {
    
    
        User user = user1;
        System.out.println("Bean 原 Name:" + user.getName());
        user.setName("悟空"); // 【重点:进⾏了修改操作】
        return user;
    }
}

B ⽤户再去使用公共 Bean 的时候:

package yyhgo;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class BeanScopesController2 {
    
    
    @Autowired
    private User user1;

    public User getUser1() {
    
    
        User user = user1;
        return user;
    }
}

打印 A 用户和 B 用户公共 Bean 的值:

package yyhgo;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;


public class BeanScopesTest {
    
    
    public static void main(String[] args) {
    
    
        ApplicationContext context = new ClassPathXmlApplicationContext("spring-config.xml");
        BeanScopesController beanScopesController = context.getBean(BeanScopesController.class);
        System.out.println("A 对象修改之后 Name:" + beanScopesController.getUser1().toString());
        BeanScopesController2 beanScopesController2 = context.getBean(BeanScopesController2.class);
        System.out.println("B 对象读取到的 Name:" + beanScopesController2.getUser1().toString());
    }
}

执行结果如下:
在这里插入图片描述

1.2 原因分析

出现以上问题的原因是 Bean 默认情况下是单例状态(singleton),也就是所有人使用的都是同⼀个对象。之前我们学习单例模式的时候知道:使用单例可以很大程度地提高性能,所以在 Spring 中 Bean 的作用域默认也是 singleton 单例模式。

二、作用域定义

以往程序中变量的可用范围就是作用域,或者说在源代码中定义变量的某个区域就是作用域。
而 Bean 的作用域是指 Bean 在 Spring 整个框架中的某种行为模式。
比如 singleton (单例作用域),就表示 Bean 在整个 Spring 中只有一份,它是全局共享的。那么当一个人修改了这个值之后,其他人读取到的就是被修改的值。

三、Bean 的 6 种作用域

在这里插入图片描述

singleton
官⽅说明:(Default) Scopes a single bean definition to a single object instance for each Spring IoC container.
描述:该作⽤域下的Bean在IoC容器中只存在⼀个实例:获取Bean(即通过 applicationContext.getBean 等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是同⼀个对象。
场景:通常 无状态 的Bean使⽤该作⽤域。⽆状态表示Bean对象的属性状态不需要更新
备注:Spring 默认选择该作用域

prototype
官⽅说明:Scopes a single bean definition to any number of object instances.
描述:每次对该作⽤域下的Bean的请求都会创建新的实例:获取Bean(即通过 applicationContext.getBean 等⽅法获取)及装配Bean(即通过@Autowired注⼊)都是新的对象实例。
场景:通常 有状态 的Bean使⽤该作⽤域

request
官⽅说明:Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has 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请求会创建新的Bean实例,类似于prototype
场景:⼀次http的请求和响应的共享Bean
备注:限定SpringMVC中使⽤

session
官⽅说明:Scopes a single bean definition to the lifecycle of an HTTP Session. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在⼀个http session中,定义⼀个Bean实例
场景:⽤户回话的共享Bean, ⽐如:记录⼀个⽤户的登陆信息
备注:限定SpringMVC中使⽤

application(了解)
官⽅说明:Scopes a single bean definition to the lifecycle of a ServletContext. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在⼀个http servlet Context中,定义⼀个Bean实例
场景:Web应⽤的上下⽂信息,⽐如:记录⼀个应⽤的共享信息
备注:限定SpringMVC中使⽤

websocket(了解)
官⽅说明:Scopes a single bean definition to the lifecycle of a WebSocket. Only valid in the context of a web-aware Spring ApplicationContext.
描述:在⼀个HTTP WebSocket的⽣命周期中,定义⼀个Bean实例
场景:WebSocket的每次会话中,保存了⼀个Map结构的头信息,将⽤来包裹客户端消息头。第⼀次初始化后,直到WebSocket结束都是同⼀个Bean。
备注:限定Spring WebSocket中使⽤

单例作⽤域(singleton) VS 全局作⽤域(application):

  • singleton 是 Spring Core 的作⽤域;application 是 Spring Web 中的作⽤域;
  • singleton 作⽤于 IoC 的容器,而 application 作⽤于 Servlet 容器。

四、设置作用域

使用 @Scope 标签就可以设置 Bean 的作用域,如下代码所示:

package yyhgo;

import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class Users {
    
    
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Bean
    public User user1() {
    
    
        User user = new User();
        user.setId(1);
        user.setName("Java"); // 【重点:名称是 Java】
        return user;
    }
}

@Scope 标签既可以修饰方法也可以修饰类!

此时运行结果:
在这里插入图片描述

达到预期。

@Scope 有两种设置方式:

  1. 直接设置值:@Scope(“prototype”)
  2. 类似“枚举”设置:@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    (点开源码发现,其实只是 String 变量)

猜你喜欢

转载自blog.csdn.net/yyhgo_/article/details/128628941