Bean的生命周期详解保姆级教程,结合spring boot和spring.xml两种方式讲解,5/7/10大小阶段详细分析

文章目录

    • Spring Bean的生命周期
      • 一、为什么知道 Bean 的生命周期?
      • 二、生命周期大致了解
      • 三、详细分析生命周期
        • 3.1 ① 初步划分为 5 步:
          • 3.1.1 spring 框架中怎么理解
          • 3.1.2 spring boot 项目中怎么理解
        • 3.2 ② 细分 5 步为 7 步:
          • 3.2.1 spring 框架中怎么理解
          • 3.2.2 spring boot 项目中怎么理解
        • 3.3 ③ 细分 7 步为 10 步:
      • 四、再谈scpoe下bean的管理方式
        • 4.1 spring 框架中理解
        • 4.2 spring boot 项目中理解
      • 五 、自己new的对象如何让Spring管理

Spring Bean的生命周期

一、为什么知道 Bean 的生命周期?

生命周期的本质:在哪个时间节点上调用了哪个类的哪个方法?我们需要充分的了解在这个生命线上,都有哪些特殊的时间节点!只有我们知道了这些特殊的时间节点都在哪,到时候我们才可以确定代码写到哪。我们可能需要在某个特殊的时间节点上执行一段特定的代码,这段代码就可以放到这个节点上;当生命线走到这里的时候,自然会被调用。

二、生命周期大致了解

1、Spring Bean 的生命周期是指一个 Bean 在容器中从创建、初始化、销毁的全过程。

2、容器启动 ----> 实例化 Bean ----> 依赖注入 ----> 初始化前逻辑(BeanPostProcessor)----> 初始化(@PostConstruct/InitializingBean/init-method)----> 初始化后逻辑(BeanPostProcessor)----> 使用 Bean ----> 销毁(@PreDestory/DisposableBean/destroy-method

3、各阶段解析与方法:

阶段描述可插入的自定义方法/接口
实例化容器通过反射创建 Bean 实例,但未设置属性或依赖调用无参构造器创建对象
依赖注入容器将依赖注入到 Bean 的属性中通过依赖注入(例如 @Autowired、@Resource、XML 配置或 Java 配置)将属性注入到 Bean 中
初始化前Bean 实例化和依赖注入后,执行初始化前的一些逻辑实现 BeanPostProcessor 接口的 postProcessBeforeInitialization 方法
初始化执行 Bean 的初始化逻辑@PostConstruct、实现 InitializingBean、配置文件指定的 init-method 方法
初始化后执行初始化后的一些逻辑实现 BeanPostProcessor 接口的 postProcessAfterInitialization 方法
使用 Bean
销毁容器关闭或 Bean 的生命周期结束时,执行销毁逻辑@PreDestroy、实现 DisposableBean、配置文件指定的 destroy-method 方法

三、详细分析生命周期

3.1 ① 初步划分为 5 步:

第一步:实例化 Bean(调用无参数构造方法)
第二步:给 Bean 属性赋值(调用set方法)
第三步:初始化 Bean(会调用 Bean 的 init 方法。注意:这个 init 方法需要自己写)
第四步:使用 Bean
第五步:销毁 Bean(会调用 Bean 的 destroy 方法。注意:这个 destroy 方法需要自己写)

3.1.1 spring 框架中怎么理解

代码测试:spring.xml 版

1、定义一个 Bean:

public class User {
    private String username;

    public void setUsername(String username) {
        System.out.println("第二步:给对象的属性赋值");
        this.username = username;
    }

    public User() {
        System.out.println("第一步:实例化Bean,无参数构造方法执行了");
    }

    // 初始化Bean,需要自己写,自己配,方法名随意
    public void initBean() {
        System.out.println("第三步:初始化Bean");
    }

    // 销毁Bean,需要自己写,自己配,方法名随意
    public void destroyBean() {
        System.out.println("第五步:销毁Bean");
    }
    
    @Override
    public String toString() {
        return "User{" +
                "username='" + username + '\'' +
                '}';
    }
}

2、spring.xml 中配置:需要在 <bean>标签中 设置 init-methoddestroy-method 属性,手动指定初始化方法和销毁方法!

<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean">
      <!--给属性赋值-->
     <property name="username" value="张三"/>
</bean>

3、开始测试:

@Test
public void test(){
    ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    User user = applicationContext.getBean("user", User.class);
    System.out.println("第四步:使用Bean" + User);

    // 需要手动关闭Spring容器
    ClassPathXmlApplicationContext context = (ClassPathXmlApplicationContext) applicationContext;
    context.close();

注意:需要手动关闭 Spring 容器(调用close方法),这样 Spring 容器才会销毁 Bean,才会去调用我们定义的 destroyBean 方法

结果:

3.1.2 spring boot 项目中怎么理解

代码测试:spring boot 版(Spring Boot 中没有 xml 怎么配置?)

1、同样定义一个 User(内容不变)

2、启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        ConfigurableApplicationContext run = SpringApplication.run(Application.class, args);
        User user = run.getBean(User.class);
        //属性赋值
        user.setUsername("张三");
        //使用bean
        System.out.println("第四步:使用Bean" + user);
        //关闭容器
        run.close();
    }

    //直接在注解中指明
    @Bean(initMethod = "initBean", destroyMethod = "destroyBean")
    public User getUserBean() {
        return new User();
    }
}

结果:

为什么?当前的行为是因为属性赋值是手动触发的,而非通过 Spring 的依赖注入机制完成。如果希望严格按照 Spring 生命周期,请确保所有操作都交由 Spring 管理,而不是手动调用属性方法。

  • Spring 在生命周期的 第二步 时(属性赋值),它会使用 依赖注入机制 来完成,但你的 User 类没有定义依赖属性,也没有配置 @Value 或 XML 属性,因此 Spring 不会自动调用 setUsername 方法。
  • 当 Spring 完成了实例化和初始化过程后,你手动调用 setUsername,此时才出现 “第二步” 的输出。

修改:使用 @Value;

3.2 ② 细分 5 步为 7 步:

在以上的5步中,第3步是初始化Bean,如果你还想在初始化前初始化后添加代码,可以编写一个类(这个类叫做 Bean 后处理器)实现BeanPostProcessor接口重写里面的 befor 和 after 方法。

第一步:实例化 Bean
第二步:Bean 属性赋值
第三步:初始化前逻辑
第四步:初始化 Bean
第五步:初始化后逻辑
第六步:使用 Bean
第七步:销毁 Bean

3.2.1 spring 框架中怎么理解

代码测试:spring.xml 版

1、编写类实现接口,重写方法,该方法有两个参数:创建的 Bean 对象、Bean 的名字

public class MyBeanPostProcessor implements BeanPostProcessor {
    
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的before方法执行,即将开始初始化");
        return BeanPostProcessor.super.postProcessBeforeInitialization(bean, beanName);
    }
 
    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        System.out.println("Bean后处理器的after方法执行,已完成初始化");
        return BeanPostProcessor.super.postProcessAfterInitialization(bean, beanName);
    }
}

2、在 spring.xml 文件中配置 Bean后处理器(MyBeanPostProcessor)

<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean">
      <!--给属性赋值-->
     <property name="username" value="张三"/>
</bean>

<!-- 配置Bean后处理器。这个后处理器将作用于当前配置文件中所有的bean。-->
<bean class="MyBeanPostProcessor的全类名"/>

注意:是当前配置文件中所有的 bean。也就是有几个 bean,就会执行几次这个 后处理器,怎么解决?----> 下面会讲

运行结果:

3.2.2 spring boot 项目中怎么理解

代码测试:spring boot 版(Spring Boot 中没有 xml 怎么配置?)

同样:跟 xml 版一样,需要写个类去实现 BeanPostProcessor 接口,然后只需要在类上直接加 @Configuration 注解

注意:也会执行很多次,比如:

原因:这两个方法对 每个注册到容器的 Bean 都会被调用,Spring 容器默认会加载一些内部 Bean,因此输出会多次触发。

解决:加条件判断即可

@Configuration
public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof User)
            System.out.println("Bean后处理器的before方法执行,即将开始初始化");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if(bean instanceof User)
            System.out.println("Bean后处理器的after方法执行,已完成初始化");
        return bean;
    }
}

结果:

3.3 ③ 细分 7 步为 10 步:

问:在 Bean 后处理器 before方法之前 干了什么事?

答:检查 Bean 是否实现了 Aware 相关的接口,如果实现了接口则调用这些接口中的方法;调用这些方法的目的是为了给你传递一些数据,让你更加方便使用。

问:在 Bean 后处理器 before方法之后 干了什么事?

答:检查 Bean 是否实现了 InitializingBean 接口,如果实现了,则调用接口中的方法。

问:使用 Bean 之后,或者说 销毁 Bean 之前 干了什么事?

答:检查 Bean 是否实现了 DisposableBean 接口,如果实现了,则调用接口中的方法。

总结: 添加的这三个点位的特点,都是在检查你这个 Bean 是否实现了某些特定的接口,如果实现了这些接口,则 Spring 容器会调用这个接口中的方法!

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Aware 相关的接口包括:BeanNameAware、BeanClassLoaderAware、BeanFactoryAware

① 当 Bean 实现了 BeanNameAware,Spring 会将 Bean 的名字传递给 Bean。

② 当 Bean 实现了 BeanClassLoaderAware,Spring 会将加载该 Bean 的类加载器传递给 Bean。

③ 当 Bean实现了 BeanFactoryAware,Spring 会将 Bean 工厂对象传递给 Bean。

代码测试:两个版本都一样:只需修改 Bean(User)即可:

public class User implements
        BeanNameAware, BeanClassLoaderAware, BeanFactoryAware,
        InitializingBean, DisposableBean {

    private String username;

    @Value("张三")
    public void setUsername(String username) {
        System.out.println("第二步:给对象的属性赋值");
        this.username = username;
    }

    public User() {
        System.out.println("第一步:实例化Bean,无参数构造方法执行了");
    }

    // 初始化Bean,需要自己写,自己配,方法名随意
    public void initBean() {
        System.out.println("第三步:初始化Bean");
    }

    // 销毁Bean,需要自己写,自己配,方法名随意
    public void destroyBean() {
        System.out.println("第五步:销毁Bean");
    }

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

    @Override
    public void setBeanClassLoader(ClassLoader classLoader) {
        System.out.println("类加载器:" + classLoader);
    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        System.out.println("Bean工厂:" + beanFactory);
    }

    @Override
    public void setBeanName(String s) {
        System.out.println("Bean的名字:" + s);
    }

    @Override
    public void destroy() throws Exception {
        System.out.println("DisposableBean destroy方法执行了");
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("InitializingBean afterPropertiesSet方法执行了");
    }
}

结果:

四、再谈scpoe下bean的管理方式

我们已经知道 Bean 中的 scpoe 就是 Bean 的作用域:

这里结合 Bean 的生命周期再谈 scope。

1、Spring 根据 Bean 的作用域来选择管理方式:

  • 对于 singleton(单例)作用域的 Bean:Spring 能够精确地知道该 Bean 何时被创建、何时初始化完成、以及何时被销毁。

  • 而对于 prototype(多例/原型) 作用域的 Bean:Spring 只负责创建,当容器创建了 Bean 的实例后,Bean 的实例就交给客户端代码管理,Spring 容器将不再跟踪其生命周期。

    Spring 容器关闭,不会销毁类的对象,而是交给 Java 内存回收机制。

4.1 spring 框架中理解

2、测试:spring.xml 版

<!-- 指明scope属性-->
<bean id="user" class="全类名" init-method="initBean" destroy-method="destroyBean" scope="prototype">
      <!--给属性赋值-->
     <property name="username" value="张三"/>
</bean>

再次测试 10 步的代码,发现:检查 Bean 是否实现了 DisposableBean接口 和 销毁Bean不管了

4.2 spring boot 项目中理解

spring boot 版:只需要在返回对象那里加个注解即可:

@Bean(initMethod = "initBean", destroyMethod = "destroyBean")
@Scope(value = "prototype")
public User getUserBean() {
    return new User();
}

五 、自己new的对象如何让Spring管理

需求:有些时候可能会遇到这样的需求,某个 java 对象是我们自己手动 new 的,然后希望这个对象被 Spring 容器管理,怎么实现呢?

解决:

  • 创建 DefaultListableBeanFactory 对象!
  • 注册Bean:调用上面创建的对象的 registerSingleton() 方法,把自己创建的对象传进去,并起一个名字!
  • 根据名字,调用 getBean 方法从 Spring 容器当中获取 Bean 对象!

测试:

public class Student {
}
@Test
public void test(){
    //自己new的对象,没有被spring管理
    Student student = new Student();
    System.out.println(student);
    
    DefaultListableBeanFactory factory = new DefaultListableBeanFactoru();
    factory.registerSingleton("studentBean", student);
    factory.getBean("studentBean", Student.class);
    System.out.println(studentBean);
}

结果:输出的对象地址一摸一样,说明:获取到的是同一个 Bean 对象,说明确实是把我们创建的对象放到Spring 容器当中进行管理!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/923767.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

gRPC 双向流(Bidirectional Streaming RPC)的使用方法

gRPC 是一个支持多种语言的高性能 RPC 框架&#xff0c;拥有丰富的 API 来简化服务端和客户端的开发过程。gRPC 支持四种 RPC 类型&#xff1a;Unary RPC、Server Streaming RPC、Client Streaming RPC 和 Bidirectional Streaming RPC。下面是双向流 API 的使用方法。 双向流…

ffmpeg视频滤镜:替换部分帧-freezeframes

滤镜描述 freezeframes 官网地址 > FFmpeg Filters Documentation 这个滤镜接收两个输入&#xff0c;然后会将第一个视频中的部分帧替换为第二个视频的某一帧。 滤镜使用 参数 freezeframes AVOptions:first <int64> ..FV....... set first fra…

解决SpringBoot连接Websocket报:请求路径 404 No static resource websocket.

问题发现 最近在工作中用到了WebSocket进行前后端的消息通信&#xff0c;后端代码编写完后&#xff0c;测试一下是否连接成功&#xff0c;发现报No static resource websocket.&#xff0c;看这个错貌似将接口变成了静态资源来访问了&#xff0c;第一时间觉得是端点没有注册成…

Leetcode322.零钱兑换(HOT100)

链接 代码&#xff1a; class Solution { public:int coinChange(vector<int>& coins, int amount) {vector<int> dp(amount1,amount1);//要兑换amount元硬币&#xff0c;我们就算是全选择1元的硬币&#xff0c;也不过是amount个&#xff0c;所以初始化amoun…

网络安全期末复习

第1章 网络安全概括 &#xff08;1&#xff09;用户模式切换到系统配置模式&#xff08;enable&#xff09;。 &#xff08;2&#xff09;显示当前位置的设置信息&#xff0c;很方便了解系统设置&#xff08;show running-config&#xff09;。 &#xff08;3&#xff09;显…

鸿蒙进阶篇-自定义组件

大家好&#xff0c;我是鸿蒙开天组&#xff0c;今天咱们来学习自定义组件。 一、自定义组件定义 在ArkUI中&#xff0c;UI显示的内容均为组件&#xff0c;由框架直接提供的称为系统组件&#xff0c;由开发者定义的称为自定义组件。在进行 UI 界面开发时&#xff0c;通常不是简…

深入浅出 WebSocket:构建实时数据大屏的高级实践

简介 请参考下方&#xff0c;学习入门操作 基于 Flask 和 Socket.IO 的 WebSocket 实时数据更新实现 在当今数字化时代&#xff0c;实时性是衡量互联网应用的重要指标之一。无论是股票交易、在线游戏&#xff0c;还是实时监控大屏&#xff0c;WebSocket 已成为实现高效、双向…

一键AI换脸软件,支持表情控制,唇形同步Facefusion-3.0.0发布!支持N卡和CPU,一键启动包

嗨,小伙伴们!还记得小编之前介绍的FaceFusion 2.6.1吗?今天给大家带来超级exciting的消息 —— FaceFusion 3.0.0闪亮登场啦! &#x1f31f; 3.0.0版本更新 &#x1f3d7;️ 全面重构:修复了不少小虫子,运行更稳定,再也不怕突然罢工啦! &#x1f600; Live Portrait功能:新增…

spring boot框架漏洞复现

spring - java开源框架有五种 Spring MVC、SpringBoot、SpringFramework、SpringSecurity、SpringCloud spring boot版本 版本1: 直接就在根下 / 版本2:根下的必须目录 /actuator/ 端口:9093 spring boot搭建 1:直接下载源码打包 2:运行编译好的jar包:actuator-testb…

hhdb数据库介绍(10-8)

首页 管理平台通过数据可视方式在首页功能中实时展示计算节点集群的数据量、访问流量、集群组件状态、告警事件、安全防控等用户关心的信息。 集群安全 邮件通知&#xff1a;根据通知设置中监控开关是否打开判断&#xff0c;分为&#xff1a;全部开启、未开启、部分开启&…

Vue前端开发-slot传参

slot 又称插槽&#xff0c;它是在子组件中为父组件提供的一个占位符&#xff0c;使用来表示&#xff0c;通过这个占位符&#xff0c;父组件可以向中填充任意的内容代码&#xff0c;这些代码将自动替换占位符的位置&#xff0c;从而轻松实现在父组件中控制子组件内容的需求。 作…

18:(标准库)DMA二:DMA+串口收发数据

DMA串口收发数据 1、DMA串口发送数据2、DMA中断串口接收定长数据包3、串口空闲中断DMA接收不定长数据包 1、DMA串口发送数据 当串口的波特率大于115200时&#xff0c;可以通过DMA1进行数据搬运&#xff0c;以防止数据的丢失。如上图所示&#xff1a;UART1的Tx发送请求使用DMA1的…

2024 java大厂面试复习总结(一)(持续更新)

10年java程序员&#xff0c;2024年正好35岁&#xff0c;2024年11月公司裁员&#xff0c;记录自己找工作时候复习的一些要点。 java基础 hashCode()与equals()的相关规定 如果两个对象相等&#xff0c;则hashcode一定也是相同的两个对象相等&#xff0c;对两个对象分别调用eq…

深度学习5

一、模型保存与加载 1、序列化方式 保存方式&#xff1a;torch.save(model, "model.pkl") 打开方式&#xff1a;model torch.load("model.pkl", map_location"cpu") ​ import torch import torch.nn as nnclass MyModle(nn.Module):def __ini…

Redis五大基本类型——Zset有序集合命令详解(命令用法详解+思维导图详解)

目录 一、Zset有序集合类型介绍 二、常见命令 1、ZADD 2、ZCARD 3、ZCOUNT 4、ZRANGE 5、ZREVRANGE 6、ZRANGEBYSCORE 7、ZREVRANGEBYSCORE 8、ZPOPMAX 9、ZPOPMIN 10、ZRANK 11、ZREVRANK 12、ZSCORE 13、ZREM 14、ZREMRANGEBYRANK 15、ZREMRANGEBYSCORE 16…

ARM架构 AArch64 基础知识介绍

介绍 aarch64是 ARM 架构的 64 位版本&#xff0c;它是 ARMv8 架构的一部分&#xff0c;被设计用来提供更高的性能和更大的地址空间&#xff0c;同时保持与 32 位 ARM 架构的兼容性。AArch64 是 ARMv8 的 64 位指令集架构&#xff08;ISA&#xff09;&#xff0c;它提供了丰富的…

Rust中Tracing 应用指南

欢迎来到这篇全面的Rust跟踪入门指南。Rust 的tracing是一个用于应用程序级别的诊断和调试的库。它提供了一种结构化的、异步感知的方式来记录日志和跟踪事件。与传统的日志记录相比&#xff0c;tracing能够更好地处理复杂的异步系统和分布式系统中的事件跟踪&#xff0c;帮助开…

极狐GitLab 17.6 正式发布几十项与 DevSecOps 相关的功能【三】

GitLab 是一个全球知名的一体化 DevOps 平台&#xff0c;很多人都通过私有化部署 GitLab 来进行源代码托管。极狐GitLab 是 GitLab 在中国的发行版&#xff0c;专门为中国程序员服务。可以一键式部署极狐GitLab。 学习极狐GitLab 的相关资料&#xff1a; 极狐GitLab 官网极狐…

WinFrom调用webapi接口另一个方法及其应用实例

1.调用接口方法 代码如下&#xff1a; public class WebAPI{#region WebAPI调用 public async Task<string> Call_Webapi(string Url, string Json) //url传入的是接口名称&#xff0c;json传入的是接口参数{string responseBody string.Empty; //responseBod…

elasticsearch的索引模版使用方法

5 索引模版⭐️⭐️⭐️⭐️⭐️ 索引模板就是创建索引时要遵循的模板规则索引模板仅对新创建的索引有效&#xff0c;已经创建的索引并不受索引模板的影响 5.1 索引模版的基本使用 1.查看所有的索引模板 GET 10.0.0.91:9200/_index_template2.创建自定义索引模板 xixi &…