2023.11.11 关于 Spring 中 Bean 的作用域

目录

Bean 的作用域

作用域的定义

Singleton(单例作用域)

Prototype(原型作用域)

Request(请求作用域)

Session(会话请求)

Application(全局作用域)

WebSocket(WebSocket 会话作用域 )

设置 Bean 的作用域

@Scope 注解

使用方式建议


Bean 的作用域

作用域的定义

  • 作用域(Scope)指变量、函数或对象在程序中可见和访问的范围
  • Bean 的作用域 指 Bean 在 Spring 整个框架中的某种 行为模式

Bean 的 6 种作用域

  • Spring 容器在初始化一个 Bean 的实例时,同时会指定该实例的作用域

Singleton(单例作用域)

  • 该作用域下的 Bean 在 Spring 容器中只存在一个实例:获取 Bean(即通过 context.getBean 等方法获取)及装配 Bean(通过 @Autowired 注入)都是同一个对象
  • Spring 容器在第一次请求时创建该实例,并在后续请求中返回相同的实例
  • 默认情况下,Spring 的 Bean 作用域为 Singleton
  • 因为 单例模式 的性能更好

Prototype(原型作用域)

  • 可理解为 多例作用域

  • 每次请求获取 Bean 时,都会创建一个新的实例

  • 每次获取 Bean 时都会返回一个新的对象,即原型作用域的 Bean 实例不会被共享

Request(请求作用域)

  • 在 Web 应用程序中,每个 HTTP 请求将创建一个新的 Bean 实例,并且该实例仅在当前请求的范围内可见
  • 对于每个请求,都会有一个单独的 Bean 实例

Session(会话请求)

  • 在 Web 应用程序中,每个用户会话均会创建一个新的 Bean 实例,并且该实例仅在当前用户会话的范围内可见
  • 对于每个用户会话,都会有一个单独的 Bean 实例

Application(全局作用域)

  • 在 Web 应用程序中,整个应用程序范围内只会创建一个Bean 实例,并且该实例将被共享和重用

WebSocket(WebSocket 会话作用域 )

  • 在基于 WebSocket 的应用程序中,每个 WebSocket 会话均会创建一个新的 Bean 实例,并且该实例仅在当前 WebSocket 会话的范围内可见

注意:

  • 在普通的 Spring 项目中只有前两种作用域,即前两种为 Spring 核心作用域
  • 后四种状态是 Spring MVC 中的作用域

建议阅读下文之前 点击下方链接了解 Lombok 的作用

Lombok 的作用和使用


实例理解

  • 我们先创建一个实体类 User
import lombok.Data;

@Data
public class User {
    public int id;
    public String name;
}
  • 再创建一个 UserBean 类,用来向 Spring 容器中 存储 User 类型的 Bean 对象
  • 此处向 Spring 容器中存入一个 id 为 user,且 name = 张三的 Bean 对象
import com.java.demo.enity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

@Component
public class UserBeans {
    @Bean
    public User user() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}
  • 新创建 UserController 类
  • 该类中新创建了一个 myUser 对象,且该对象赋值于注入进来的 user Bean 对象
  • 并修改 myUser 对象的 name 属性
  • 将从 user Bean 对象赋值而来的 name = 张三,修改为 name = 小林
import com.java.demo.enity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController {

    @Autowired
    private User user;

    public void printUser() {
        System.out.println("UserController 类注入的 user Bean 对象,也就是 user 变量的初始值 -> " + user.toString());
//        修改 User
        User myUser = user;
        myUser.setName("小林");
        System.out.println("UserController 类中的 myUser 对象 -> " + myUser.toString());
        System.out.println("UserController 类修改 myUser 对象 name 属性后再次打印 user 变量 -> " + user.toString());
    }
}
  • 新创建 UserController2 类
  • 同样在该类中注入 user Bean 对象
import com.java.demo.enity.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;

@Controller
public class UserController2 {

    @Autowired
    private User user;

    public void printUser2() {
        System.out.println("UserController2 类注入的 user Bean 对象 -> " + user.toString());
    }
}
  • 最后再创建一个启动类
  • 该类用来从 Spring 容器中拿 UserController 类和 UserController2 类的 Bean 对象,并调用它们的成员方法
import com.java.demo.controller.UserController;
import com.java.demo.controller.UserController2;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class App {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("Spring-config.xml");

        UserController userController = context.getBean("userController",UserController.class);
        userController.printUser();

        UserController2 userController2 = context.getBean("userController2",UserController2.class);
        userController2.printUser2();
    }
}

运行结果:

  • 该运行结果与我们的预期结果不同诶!
  • 为啥 UserController2 类中从 Spring 容器中拿到的 user Bean 对象其 name = 小林 ?

原因解释:

  • 正因为默认情况下,Spring 的 Bean 作用域为 Singleton,即 单例作用域
  • 即存储在 Spring 容器中的 id = user 的 Bean 对象仅有一份
  • 所以此处的 user 变量和 myUser 变量 均指向同一个对象(引用)
  • 即 myUser 变量在对其 name 进行修改时
  • 其他任何注入了 id = user 的 Bean 对象 的变量,也会跟着修改 name

设置 Bean 的作用域

@Scope 注解

  • 我们可以通过 @Scope 注解来设置 Bean 对象的作用域

实例理解

  • 如果我们不主动设置该 Bean 对象的作用域,其默认为 Singleton(单例作用域)
  • 所以为了解决上述实例问题所产生的问题,我们可以将该 Bean 对象主动设置为 prototype(原型作用域),即多例模式

方式一:直接在 @Scope 注解中填入 prototype

import com.java.demo.enity.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

@Component
public class UserBeans {
    @Bean
    @Scope("prototype")
    public User user() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}

方式二:使用 ConfigurableBeanFactory 接口中定义的常量 SCOPE_PROTOTYPE,该常量表示原型作用域

import com.java.demo.enity.User;
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 UserBeans {
    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    public User user() {
        User user = new User();
        user.setId(1);
        user.setName("张三");
        return user;
    }
}
  • 当我们修改完 Bean 对象的作用域时,再次运行启动类,观察运行结果

运行结果:

  •  UserController2 类中从 Spring 容器中拿到的 user Bean 对象其 name = 张三
  • 也就是说,在 UserController 类中,进行的修改操作并未影响到 Spring 容器中的初始 Bean 对象

使用方式建议

  • 上述两种方式起到的效果是相同的,但是推荐使用第二种方式
  • 使用第一种 直接填入 prototype 方式的前提是 你能够清楚的记得该单词是如何拼写的
  • 而第二种方式,IDEA 自带自动补全提示,可以保证我们不出错!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

茂大师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值