• Bean 作用域和生命周期


    Spring 容器是用来存储和读取 Bean 的 , 因此 Bean 是 Spring 中最核心的操作资源.

    引入 Lombok

    编写代码过程中 , bean 对象如果有多个属性 , 创建 Getter , Setter, 构造方法 等方法 , 会产生大量冗长的代码. 那么为了使代码更加简洁 , 我们可以使用 Lombok 框架 , 只需要一行注释 , 就可以避免大量冗长的代码. 需要注意的是,Lombok并不是Java语言的一部分,而是一个第三方库,需要在项目中引入Lombok的jar包才能使用。同时,由于Lombok是通过注解来实现代码生成的,因此在使用Lombok时需要确保IDE和编译器支持注解处理。

    Maven 中复制 lombok 依赖

    image-20230416221144704

    pom.xml 插入 lombok 依赖

    image-20230416221529325

    代码示例:

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public class User {
        private String name;
        private int age;
        private String email;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这个示例中,我们使用了Lombok的@Data、@NoArgsConstructor和@AllArgsConstructor注解。

    @Data注解可以自动生成getter、setter、equals、hashCode和toString等方法,省去了手动编写这些方法的麻烦。

    @NoArgsConstructor注解可以自动生成无参构造函数,方便我们在创建对象时使用。

    @AllArgsConstructor注解可以自动生成全参构造函数,方便我们在创建对象时同时设置对象的属性值。


    1. Bean 的作用域问题

    假设有一个公共的 Bean , 提供给用户 A 和 用户 B 使用 , 如果 A 修改了 Bean 的公共数据 , 导致 B 在使用时发生与预期不符的错误.

    创建一个实体类

    @Setter
    @Getter
    @ToString
    public class User {
        private int id;
        private String name;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    存储 User 对象

    @Component
    public class UserBeans {
        @Bean
        public User user(){
            //伪代码
            User user = new User();
            user.setName("王五");
            user.setId(10);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    UserController 修改 Bean 的公共数据:

    @Controller
    public class UserController {
        @Autowired
        private User user;
    
        public void sayHi(){
            System.out.println(user);
            //修改 User
            User myUser = user;
            myUser.setName("张三");
            System.out.println("myUser->" + myUser.getName());
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    UserController2 访问 Bean 中的公共数据:

    @Controller
    public class UserController2 {
        @Resource
        private User user;
    
        public void sayHi2(){
            System.out.println("user ->" + user);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    启动类中调用:

    public class App {
        public static void main(String[] args) {
            ApplicationContext context =
                    new ClassPathXmlApplicationContext("spring-config.xml");
            UserController userController = context.getBean("userController", UserController.class);
            userController.sayHi();
            UserController2 userController2 = context.getBean("userController2", UserController2.class);
            userController2.sayHi2();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    结果发现公共数据被修改:

    image-20230416223136761

    出现上述问题的原因是 , Bean 默认状态下是单例模式 , 即所有人使用的都是同一个对象 , 那么当多个用户并发执行时 , 一定会篡改公共数据.


    2. 作用域定义

    2.1 作用域类型

    1. singleton(单例作用域)

    • 描述: 由于 Spring 框架除了追求高效还追求性能 , 因此使用单例模式作为默认作用域. 该作用域下的 Bean 在整个 IoC容器中只存在一份 , 无论是获取还是注入都是同一个对象.
    • 场景: 通常无状态的 Bean 使用该作用域. (无状态指对象的属性无需更新)

    2. prototype(原型作用域)

    • 描述: 在该作用域下 , 每次 Bean 的请求都会创建新的实例.
    • 场景: 通常有状态的 Bean 使用该作用域.

    3. request(请求作用域)

    • 描述: 每次 Http 请求都会创建一个 Bean 对象.
    • 场景: 一次 Http 请求和响应共享的 Bean 对象 , 适用于 Spring MVC 项目

    4. session(会话作用域)

    • 描述: 一次 Http session 中 , 定义一个 Bean 对象 , 适用于 Spring MVC项目
    • 场景: 每次 Session 会话共享一个 Bean 对象.

    5. application(全局作用域)

    • 描述: 一个 Http Servlet context 中共享一个 Bean
    • Web 应用的上下文 , Spring MVC.

    **6. websocket(Http WebSocket 作用域) **

    • 描述: 在一个 Http WebSocket 生命周期中 , 定义一个 Bean对象.
    • 场景: 只适用于 Spring WebSocket 项目

    总结:

    • 我们目前使用的是 Spring core 项目 , 因此只能使用 singleton 和 prototype 作用域.
    • singleton 作用于 IoC 容器 , application 作用域 Servlet 容器.

    2.2 Bean 作用域的设置

    我们可以在存储Bean对象的时候通过 @Scope 注解设置作用域.

    代码示例:

    我们可以通过两种方式设置作用域:

    • 直接使用 prototype 设置作用域

    存储 Bean 时设置作用域:

    @Component
    public class UserBeans {
        @Bean
        @Scope("prototype")
        public User user(){
            //伪代码
            User user = new User();
            user.setName("王五");
            user.setId(10);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    注入时会创建新对象:

    @Controller
    public class UserController {
        @Autowired
        private User user;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    再次运行, 结果与预期一致.

    image-20230417085702030

    • 使用 @ Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)

    为什么使用这么麻烦的方式 , 因为有自动提示 , 可以防止单词拼错 以及 使用不合法的作用域.

    @Component
    public class UserBeans {
        @Bean
        @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
        public User user(){
            //伪代码
            User user = new User();
            user.setName("王五");
            user.setId(10);
            return user;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3. Bean 的生命周期

    所谓 Bean 的生命周期就是 Bean 从创建到销毁的过程.

    3.1 执行流程:

    大致流程分为以下五步:

    1.实例化 Bean (为Bean 分配内存空间)

    当 Spring 容器加载配置文件时 , 会根据其中注册的 Bean 利用反射机制 Bean 实例.

    2.设置属性(Bean 对象的注入和装配)

    Bean 实例化之后, Spring 容器会自动将配置文件中 , 定义的属性值注入到 Bean 实例中(包括基本类型, 对象, 集合).

    3.Bean 初始化

    • 实现了各种 Aware 通知的方法 , 如 BeanNameAware , BeanFactoryAware , ApplicationContextAware等.
    • 初始化前置方法: Spring执行所有实现了BeanPostProcessor接口的类的postProcessBeforeInitialization()方法,这些类可以在bean初始化之前进行一些自定义的处理。
    • 执行初始化方法: (有两种) 1.注解方式: @PostConstruct , 2.xml 方式: init-method方法. 如果bean实现了InitializingBean接口,Spring将调用其afterPropertiesSet()方法
    • 初始化后置方法: Spring执行所有实现了BeanPostProcessor接口的类的postProcessAfterInitialization()方法,这些类可以在bean初始化之后进行一些自定义的处理

    4.使用 Bean

    5.销毁 Bean 对象

    • 常用方法: 如 @PreDestroy , DisposableBean 接口方法

    image-20230418164754381

    例如: 将买房子视为 Bean 的生命周期

    1. 买一个房子 , 相当于实例化 Bean 开辟内存空间
    2. 装修房子 , 相当于配置文件给 Bean 注入各种属性
    3. 买家具 , 相当于初始化 Bean
    4. 住房 , 相当于使用 Bean
    5. 将房子卖出 , 相当于销毁 Bean

    由此也可以得出 , 配置文件给 Bean 注入属性 , 必须排在初始化之前 , 因为初始化可能调用属性.(装修完才能安家具)

    3.2 代码示例:

    PostConstruct 版

    @Component
    public class UserBeans implements BeanNameAware {
        @PostConstruct
        public void PostConstruct(){
            System.out.println("执行了 PostConstruct");
        }
        @PreDestroy
        public void PreDestroy(){
            System.out.println("执行了 PreDestroy");
        }
        @Override
        public void setBeanName(String s) {
            System.out.println("执行了 setName" + s);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    xml 版

    我们可以在配置文件中设置各种属性.

    image-20230417095529414

    @Component
    public class BeanComponent implements BeanNameAware {
    
        @Override
        public void setBeanName(String s) {
            System.out.println("执行了通知 BeanName ->" + s);
        }
        //xml 方式的初始化方法
        public void myInit(){
            System.out.println("XML 方式初始化");
        }
        public void sayHi(){
            System.out.println("执行 sayHi");
        }
       //xml 方式的销毁
        public void preDestroy(){
            System.out.println("执行了销毁方法");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    调用启动类:

    public class App {
        public static void main(String[] args) {
            ClassPathXmlApplicationContext context =
                    new ClassPathXmlApplicationContext("spring-config.xml");
            BeanComponent component = context.getBean("beanComponent" , BeanComponent.class);
            component.sayHi();
            context.destroy();
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    结果与预期一致:

    image-20230417100941727

    4.Spring 生命周期

    Spring执行流程如下:

    1. 应用程序启动时,Spring容器被创建。
    2. Spring容器读取并解析配置文件,将其中的bean定义加载到内存中。
    3. Spring容器使用Java反射机制创建bean实例。
    4. Spring容器将配置文件中指定的属性值或引用注入到bean实例中。
    5. 如果bean实现了InitializingBean接口,Spring容器将调用其afterPropertiesSet()方法。如果在配置文件中指定了init-method属性,则Spring容器将调用该方法。
    6. 如果bean实现了BeanPostProcessor接口,Spring容器将执行其postProcessBeforeInitialization()方法,进行一些自定义的处理。
    7. Spring容器将bean实例化后,将其放入容器中管理。
    8. 应用程序向Spring容器请求一个bean,Spring容器根据请求的名称或类型,从容器中返回一个bean实例。
    9. 如果bean实现了BeanPostProcessor接口,Spring容器将执行其postProcessAfterInitialization()方法,进行一些自定义的处理。
    10. 应用程序使用bean实例完成相应的业务逻辑。
    11. 应用程序关闭时,Spring容器将销毁所有的bean实例。
    12. 如果bean实现了DisposableBean接口,Spring容器将调用其destroy()方法。如果在配置文件中指定了destroy-method属性,则Spring容器将调用该方法。

    需要注意的是,Spring执行流程中的每一步都可以进行自定义的配置和处理,以满足不同的业务需求
    用程序向Spring容器请求一个bean,Spring容器根据请求的名称或类型,从容器中返回一个bean实例。

  • 相关阅读:
    机器学习算法基础--K-means聚类方法
    关系代数表达式练习(针对难题)
    【英雄哥七月集训】第 21天:无限集中的最小数字
    04.OpenWrt-连接有线网络
    C/C++ 数据结构 - 栈
    Linux拷贝查找和压缩文件
    胺液(MDEA)净化树脂A-98FM
    NLP_GPT生成式自回归模型
    FPGA_状态机工作原理
    简单ELK配置实现生产级别的日志采集和查询实践
  • 原文地址:https://blog.csdn.net/liu_xuixui/article/details/130513983