• Bean的作用域和生命周期


    目录

    前景引入:

    作用域定义

    Bean的六种作用域

    单例作用域(Singleton)VS 全局作用域(application)

    如何设置 Bean 作用域?

    Bean的生命周期 


    前景引入:

    假设现在有⼀个公共的 Bean,提供给 A ⽤户和 B ⽤户使⽤,当A把这个公共Bean修改之后,会导致 B ⽤户获取到这个Bean的时候是修改之后的Bean。

    案例如下:

    公共Bean:

    1. @Component
    2. public class Users {
    3. @Bean
    4. public User user1() {
    5. User user = new User();
    6. user.setId(1);
    7. user.setName("Java"); // 【重点:名称是 Java】
    8. return user;
    9. }
    10. }

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

    1. @Controller
    2. public class BeanScopesController {
    3. @Autowired
    4. private User user1;
    5. public User getUser1() {
    6. User user = user1;
    7. System.out.println("Bean 原 Name:" + user.getName());
    8. user.setName("悟空"); // 【重点:进⾏了修改操作】
    9. return user;
    10. }
    11. }

    B用户使用公共Bean:

    1. @Controller
    2. public class BeanScopesController2 {
    3. @Autowired
    4. private User user1;
    5. public User getUser1() {
    6. User user = user1;
    7. return user;
    8. }
    9. }

     打印A用户和B用户所持有的公共Bean:

    1. public class BeanScopesTest {
    2. public static void main(String[] args) {
    3. ApplicationContext context =
    4. new ClassPathXmlApplicationContext("spring-config.xml");
    5. BeanScopesController beanScopesController =
    6. context.getBean(BeanScopesController.class);
    7. System.out.println("A 对象修改之后 Name:" +
    8. beanScopesController.getUser1().toString());
    9. BeanScopesController2 beanScopesController2 =
    10. context.getBean(BeanScopesController2.class);
    11. System.out.println("B 对象读取到的 Name:" +
    12. beanScopesController2.getUser1().toString());
    13. }
    14. }

    运行结果: 

    原因分析:

    操作以上问题的原因是因为 Bean 默认情况下是单例状态(singleton),也就是所有⼈的使⽤的都是同⼀个对象,之前我们学单例模式的时候都知道,使⽤单例可以很⼤程度上提⾼性能,所以在 Spring 中Bean 的作⽤域默认也是 singleton 单例模式。
     

    作用域定义

            限定程序中变量的可⽤范围叫做作⽤域,或者说在源代码中定义变量的某个区域就叫做作⽤域。
            ⽽ Bean 的作⽤域是指 Bean 在 Spring 整个框架中的某种⾏为模式,⽐如 singleton 单例作⽤域,就表示 Bean 在整个 Spring 中只有⼀份,它是全局共享的,那么当其他⼈修改了这个值之后,那么另⼀个⼈读取到的就是被修改的值

    Bean的六种作用域

    Spring 容器在初始化⼀个 Bean 的实例时,同时会指定该实例的作⽤域。Spring有 6 种作⽤域,最后四种是基于 Spring MVC ⽣效的:

    • singleton:单例作⽤域
    • prototype:原型作⽤域(多例作⽤域)
    • request:请求作⽤域
    • session:回话作⽤域
    • application:全局作⽤域
    • websocket:HTTP WebSocket 作⽤域

    Singleton

    • 官⽅说明:(Default) Scopes a single bean definition to a single object instance for eachSpring 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 requestThat is, each HTTP request has its own instance of a bean created off the back of asingle bean definition. Only valid in the context of a web-aware SpringApplicationContext
    • 描述: 每次 Http 请求会创建新的 Bean 实例,类似于 prototype。
    • 场景:一次 Http 的请求和响应的共享 Bean.
    • 备注: 限定 Spring MVC 框架中使用。

    session

    • 官方说明: Scopes a single bean definition to the lifecycle of an HTTP Session. Onlyvalid in the context of a web-aware Spring ApplicationContext.
    • 描述: 在一个 Http Session 中,定义一个 Bean 实例。
    • 场景: 用户会话的共享 Bean,比如: 记录一个用户的登陆信息。
    • 备注: 限定 Spring MVC 框架中使用。

    application

    • 官方说明: Scopes a single bean definition to the lifecycle of a ServletContext. Onlvalid in the context of a web-aware Spring ApplicationContext.
    • 描述: 在一个 Http Servlet Context 中,定义一个 Bean 实例。
    • 场景: Web 应用的上下文信息,比如: 记录一个应用的共享信息。
    • 备注: 限定 Spring MVC 框架中使用。

    单例作用域(Singleton)VS 全局作用域(application)

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

    如何设置 Bean 作用域?

    答: 可以通过 @Scope 注解来设置 Bean 的作用域,它的设置方式有以下两种

    • 直接设置作用域的具体值,如: @Scope("prototype");
    • 设置 ConfigurableBeanFactory 和 WebApplicationContext 提供的 SCOPE XXX 变量,如@Scope(ConfigurableBeanFactory.SCOPE PROTOTYPE).
    1. import org.springframework.context.annotation.Bean;
    2. import org.springframework.context.annotation.Scope;
    3. import org.springframework.stereotype.Component;
    4. @Component
    5. public class Users {
    6. @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    7. @Bean(name = "u1")
    8. public User user1() {
    9. User user = new User();
    10. user.setId(1);
    11. user.setName("Java"); // 【重点:名称是 Java】
    12. return user;
    13. }
    14. }

    Bean的生命周期 

    所谓的⽣命周期指的是⼀个对象从诞⽣到销毁的整个⽣命过程,我们把这个过程就叫做⼀个对象的⽣命周期。
    Bean 的⽣命周期分为以下 5 ⼤部分:

    1. 实例化 Bean(从无到有,为Bean分配内存空间,调用Bean的构造方法):实例化≠初始化
    2. 设置属性(注入属性):在创建实例后,Spring将会通过依赖注入(DI)的方式将Bean的属性赋值。
    3. 初始化 Bean:执行各种 Aware 通知的⽅法,如 BeanNameAware、BeanFactoryAware、ApplicationContextAware 的接⼝⽅法;调用初始化前置方法 -> 会调用所有实现BeanPostProcessor接口的类的postProcessBeforeInitialization方法。执行初始化方法(注解方式:@PostConstruct,XML方法:init-methodf方法),执行初始化后置方法->调用所有实现了BeanPostProcessor接口的类的postProcessAfterInitialization方法。
    4. 使⽤ Bean,在初始化后,Bean可以被使用,在这个阶段,Bean执行它的业务逻辑。
    5. 销毁 Bean:销毁Bean的实例。

    需要注意的是:第二步和第三步不能进行调换位置,因为可能在初始化时候用到Bean的属性。

    如下所示,可能Bean会在初始化时候调用自己的方法,如果没有设置属性,那么就会导致程序出错。

    1. @Service
    2. public class UserService {
    3. public UserService(){
    4. System.out.println("调⽤ User Service 构造⽅法");
    5. }
    6. public void sayHi(){
    7. System.out.println("User Service SayHi.");
    8. }
    9. }
    10. @Controller
    11. public class UserController {
    12. @Resource
    13. private UserService userService;
    14. @PostConstruct
    15. public void postConstruct() {
    16. userService.sayHi();
    17. System.out.println("执⾏ User Controller 构造⽅法");
    18. }
    19. }

    下面举个买房子的例子,方便理解Bean的生命周期:

    1. 先买房(实例化,从⽆到有);
    2. 装修(设置属性);
    3. 买家电,如洗⾐机、冰箱、电视、空调等([各种]初始化);
    4. ⼊住(使⽤ Bean);
    5. 卖出去(Bean 销毁)。

    以下为代码演示Bean的生命周期过程:

    spring-config.xml如下:(bean为BeanComponent)

    1. "1.0" encoding="UTF-8"?>
    2. <beans xmlns="http://www.springframework.org/schema/beans"
    3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    4. xmlns:content="http://www.springframework.org/schema/context"
    5. xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd">
    6. <content:component-scan base-package="com.java.demo">content:component-scan>
    7. <bean id="beanCompont"
    8. class="com.java.demo.component.BeanCompont" init-method="myInit" scope="prototype">bean>
    9. beans>

    Bean(BeanComponent)如下:

    1. package com.java.demo.component;
    2. import org.springframework.beans.BeansException;
    3. import org.springframework.beans.factory.BeanNameAware;
    4. import org.springframework.beans.factory.config.BeanPostProcessor;
    5. import javax.annotation.PostConstruct;
    6. import javax.annotation.PreDestroy;
    7. public class BeanCompont implements BeanNameAware, BeanPostProcessor {
    8. @Override
    9. public void setBeanName(String s) {
    10. System.out.println("执行了通知 BeanName -> " + s);
    11. }
    12. /**
    13. * xml 方式的初始化方法
    14. */
    15. public void myInit() {
    16. System.out.println("XML 方式初始化");
    17. }
    18. @PostConstruct
    19. public void doPostConstruct() {
    20. System.out.println("注解初始化方法");
    21. }
    22. public void sayHi() {
    23. System.out.println("执行 sayHi()");
    24. }
    25. @PreDestroy
    26. public void doPreDestroy() {
    27. System.out.println("do PreDestroy");
    28. }
    29. // 初始化前置方法
    30. @Override
    31. public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
    32. System.out.println("do postProcessBeforeInitialization");
    33. return bean;
    34. }
    35. // 初始化后置方法
    36. @Override
    37. public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
    38. System.out.println("do postProcessAfterInitialization");
    39. return bean;
    40. }
    41. }

    调用类:

    1. package com.java.demo;
    2. import com.java.demo.component.BeanCompont;
    3. import org.springframework.context.support.ClassPathXmlApplicationContext;
    4. public class App {
    5. public static void main(String[] args) {
    6. ClassPathXmlApplicationContext context =
    7. new ClassPathXmlApplicationContext("spring-config.xml");
    8. BeanCompont compont = context.getBean("beanCompont", BeanCompont.class);
    9. compont.sayHi();
    10. context.close();
    11. }
    12. }

    执行结果如下:

    说明:这里同时用注解和xml方式进行Bean的初始化,注解方式的优先级是比较高的。

  • 相关阅读:
    实现点击开屏效果,类似于上下方向的开关门,
    [附源码]Python计算机毕业设计安庆师范大学校园互助平台
    Java程序员不掌握SpringBoot怎么进大厂,你就想摆烂吗,还不来看看,卷死他们!
    安全协议缺陷
    解锁新技能《SkyWalking-aop服务搭建》
    Gateway服务网关学习笔记
    ActiveMq学习⑦__ActiveMq协议
    极值点偏移练习1
    Day7——四数相加||、赎金信、三数之和、四数之和
    架构师成长之路|Redis实现延迟队列的三种方式
  • 原文地址:https://blog.csdn.net/qq_63218110/article/details/134252992