Spring(11) Bean的生命周期

目录

    • 一、简介
    • 二、Bean的流程
      • 1.BeanDefinition
      • 2.Bean 的生命周期
    • 三、代码验证
      • 1.User 实体类
      • 2.MyBeanPostProcessor 后置处理器
      • 3.SpringConfig 扫描包配置
      • 4.UserTest 测试类
      • 5.测试结果
      • 6.模拟AOP增强

一、简介

首先,为什么要学习 Spring 中 Bean 的生命周期呢?

虽然不了解 Bean 的生命周期,并不影响日常工作中的开发。但是如果我们了解了 Bean 的生命周期,可以帮助我们更好地掌握 Spring 框架,并且能够让我们更好地去理解 Spring 容器是如何管理和创建 Bean 示例的。如果以后遇到 Bean 的相关问题(比如 Spring 的循环依赖问题),就可以方便我们去调试和解决这些问题。同时也让我们可以编写出更健壮、灵活、易维护的应用程序。所以说,理解 Bean 的生命周期对掌握 Spring 框架来说是至关重要的。

这里我们主要分两部分去说明:

  1. 先从整体上去介绍 Bean 创建的整个流程 -> Bean的流程;
  2. 然后再用代码的方式去验证流程 -> 代码验证。

二、Bean的流程

1.BeanDefinition

BeanDefinition:Bean 的定义信息。Spring 容器在进行实例化时,会将 xml 配置中 <bean> 的信息(注解也是如此)封装成一个 BeanDefinition 对象,Spring 根据 BeanDefinition 来创建 Bean 对象,里面有很多用来描述 Bean 的属性。

有了 BeanDefinition 之后,再去创建 Bean 对象的时候,就可以通过反射去创建。

<bean id="userDao" class="com.demo.dao.impl.UserDaoImpl" lazy-init="true"/>
<bean id="userService" class="com.demo.service.UserServiceImpl" scope="singleton"><property name="userDao" ref="userDao"></property>
</bean>
  • beanClassName:bean 的类名。
  • initMethodName:初始化方法名称。
  • propertyValues:bean 的属性值。
  • scope:作用域。
  • lazyInit:延迟初始化。

以上这些属性都被封装到 BeanDefinition 中备用。那具体什么时候用呢?这就是下面要说的 Bean 的生命周期。

2.Bean 的生命周期

目前我们已经有了 BeanDefinition 这个对象了,那第一步就是要去创建对象。

在这里插入图片描述

  • 第1步,调用 Bean 的构造函数。 来去实例化当前 Bean 的对象。

  • 第2步,依赖注入。 像是一些 @Autowired 或者 @Value 标明的属性,这些都是在依赖注入中完成。

  • 第3步,Aware接口。 比较常见的有三个,它们都是以 Aware 结尾的接口,如果 Bean 实现了这些接口,就要重写里面的方法:

    BeanNameAware:在初始化过程中可以获取到 Bean 的名称。

    BeanFactoryAware:在初始化过程中可以获取到 Bean 工厂。

    ApplicationContextAware:在初始化过程中可以获取到应用上下文。

    以上这些内容都是为了方便对 Bean 进行扩展。

  • 第4步,BeanPostProcessor#before。 BeanPostProcessor 是 Bean 的后置处理器,用来增强 Bean 的功能的,在初始化方法调用之前(before)进行回调。这个 BeanPostProcessor 后置处理器在 Bean 的生命中其中占有非常重要的作用,需要重点记忆。

  • 第5步,调用初始化方法。 里面有两部分:

    InitializingBean:这是一个接口类,如果当前 Bean 实现了这个接口的话,就要重写里面的方法,这一步就是来执行重写之后的方法。

    自定义init方法:比如在 Bean 中的某一个方法使用了注解 @PostConstruct,这里就会去执行标明了注解的方法。其实自定义init方法也是从 BeanDefinition 中读取到的信息。

  • 第6步,BeanPostProcessor#after。 还是在 Bean 的后置处理器中执行,但是是在初始化方法之后(after)执行。在 Spring 中对 Bean 进行增强的话,都是用到这个初始化方法之后执行的后置处理器。其实在 Spring 内部就使用到了很多的后置处理器,比较典型的就是:当一个类被曾倩了,使用到了 AOP,那这个类通常都是使用后置处理器(BeanPostProcessor#after)来增强的。我们知道 AOP 的底层使用的是动态代理,有两种:JDK动态代理CGLIB动态代理

    到这一步之后,基本上这个对象就创建完成了,现在就可以从 Spring 容器中去获取和使用这个 Bean 对象了。

    注意:依赖注入 开始到 BeanPostProcessor#after 完成之后,都是针对当前的 Bean 进行初始化赋值,当然也做了一些增强。这里需要注意,Spring 中 Bean 的创建是一步一步完成的,也就是说 Bean 的创建和初始化赋值是分开的。调用构造函数就是创建 Bean 对象,但是这里创建好后是一个空对象,里面没有值,下面就是初始化赋值的过程。

  • 第6步,销毁Bean。 当 Spring 容器关闭之后,这个 Bean 对象就要执行销毁的操作了。比较典型的是,如果在某个方法上使用了 @PreDestroy 这个注解,那这个方法就是一个销毁的方法,Spring 容器关闭的时候就会执行这个销毁的方法。

以上就是 Bean 的生命周期,里面的步骤还是挺多的。为了方便加深印象,我们可以用代码的方式去验证一下。

三、代码验证

1.User 实体类

User.java

继承了 BeanNameAwareBeanFactoryAwareApplicationContextAwareInitializingBean 接口。

package com.demo.lifecycle;import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;import javax.annotation.PreDestroy;@Component
public class User implements BeanNameAware, BeanFactoryAware, ApplicationContextAware, InitializingBean {public User() {System.out.println("User类的构造方法被调用了...");}/*** 姓名*/private String name;@Value("张三")public void setName(String name) {System.out.println("setName方法被调用了...");this.name = name;}@Overridepublic void setBeanName(String name) {System.out.println("BeanNameAware接口的setBeanName方法被调用了,bean的名字是:" + name);}@Overridepublic void setBeanFactory(BeanFactory beanFactory) throws BeansException {System.out.println("BeanFactoryAware接口的setBeanFactory方法被调用了...");}@Overridepublic void setApplicationContext(ApplicationContext applicationContext) throws BeansException {System.out.println("ApplicationContextAware接口的setApplicationContext方法被调用了...");}@Overridepublic void afterPropertiesSet() {System.out.println("InitializingBean接口的afterPropertiesSet方法被调用了...");}@PreDestroypublic void destroy() {System.out.println("PreDestroy注解的destroy方法被调用了...");}
}

2.MyBeanPostProcessor 后置处理器

MyBeanPostProcessor.java

定义了 #before#after 方法的增强实现。

package com.demo.lifecycle;import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.InvocationHandler;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Objects;@Component
public class MyBeanPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessBeforeInitialization(Object bean, String beanName) {System.out.println("BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....");return bean;}@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) {if (Objects.equals(beanName, "user")) {System.out.println("BeanPostProcessor接口的postProcessAfterInitialization方法被调用了 -> user对象初始化方法后开始增强....");// cglib代理对象/*Enhancer enhancer = new Enhancer();// 设置需要增强的类enhancer.setSuperclass(bean.getClass());// 执行回调方法,增强方法enhancer.setCallback(new InvocationHandler() {@Overridepublic Object invoke(Object o, Method method, Object[] objects) throws Throwable {// 执行目标方法return method.invoke(bean, objects);}});// 创建代理对象return enhancer.create();*/}return bean;}
}

3.SpringConfig 扫描包配置

SpringConfig.java

配置了扫描包位置,只扫描 User 实体类和 MyBeanPostProcessor 后置处理器。

package com.demo.config;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;@Configuration
@ComponentScan("com.demo.lifecycle")
public class SpringConfig {
}

4.UserTest 测试类

UserTest.java

main 方法执行,使用 SpringConfig 配置创建上下文,并从容器中获取 user 的 Bean 对象。

package com.demo.test;import com.demo.config.SpringConfig;
import com.demo.lifecycle.User;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;public class UserTest {public static void main(String[] args) {ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);User user = ctx.getBean(User.class);System.out.println(user);}
}

5.测试结果

由于我使用的是 SpringBoot 项目进行的测试,会多打印一些日志,其中不带时间前缀的是我们手动 println 的日志。

Connected to the target VM, address: '127.0.0.1:61642', transport: 'socket'
20:03:56.949 [main] DEBUG org.springframework.context.annotation.AnnotationConfigApplicationContext - Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@9660f4e
20:03:56.985 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalConfigurationAnnotationProcessor'
20:03:57.100 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IdeaProjects\SpringBootExamples\springboot-demo\target\classes\com\demo\lifecycle\MyBeanPostProcessor.class]
20:03:57.105 [main] DEBUG org.springframework.context.annotation.ClassPathBeanDefinitionScanner - Identified candidate component class: file [D:\IdeaProjects\SpringBootExamples\springboot-demo\target\classes\com\demo\lifecycle\User.class]
20:03:57.266 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerProcessor'
20:03:57.270 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.event.internalEventListenerFactory'
20:03:57.272 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalAutowiredAnnotationProcessor'
20:03:57.275 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'org.springframework.context.annotation.internalCommonAnnotationProcessor'
20:03:57.281 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'myBeanPostProcessor'
20:03:57.304 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'springConfig'
BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....
20:03:57.305 [main] DEBUG org.springframework.beans.factory.support.DefaultListableBeanFactory - Creating shared instance of singleton bean 'user'
User类的构造方法被调用了...
setName方法被调用了...
BeanNameAware接口的setBeanName方法被调用了,bean的名字是:user
BeanFactoryAware接口的setBeanFactory方法被调用了...
ApplicationContextAware接口的setApplicationContext方法被调用了...
BeanPostProcessor接口的postProcessBeforeInitialization方法被调用了 -> user对象初始化方法前开始增强....
InitializingBean接口的afterPropertiesSet方法被调用了...
BeanPostProcessor接口的postProcessAfterInitialization方法被调用了 -> user对象初始化方法后开始增强....
User类的构造方法被调用了...
com.demo.lifecycle.User@2235eaab
Disconnected from the target VM, address: '127.0.0.1:61642', transport: 'socket'Process finished with exit code 0

可以看到日志是按照我们梳理的 Bean 的生命周期顺序打印的,验证完毕。

6.模拟AOP增强

MyBeanPostProcessor.javapostProcessAfterInitialization() 方法中注释了 AOP 底层 CGLIB 动态代理的演示代码的,可以在 UserTestmain 方法中获取到 user 的 Bean 对象后,将鼠标放到 user 上看下,打开和关闭 CGLIB 代理的注释生成的 user Bean 会不同:

关闭 CGLIB 代理:

在这里插入图片描述

打开 CGLIB 代理:

在这里插入图片描述

整理完毕,完结撒花~ 🌻





参考地址:

1.框架篇-05-Spring-bean的生命周期,https://www.bilibili.com/video/BV1yT411H7YK/?p=39&spm_id_from=333.1007.top_right_bar_window_history.content.click&vd_source=cf16f2e5f7c4612add10f9f9268a2c8a

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

反弹shell的N种姿势

预备知识1. 关于反弹shell 就是控制端监听在某TCP/UDP端口&#xff0c;被控端发起请求到该端口&#xff0c;并将其命令行的输入输出转到控制端。reverse shell与telnet&#xff0c;ssh等标准shell对应&#xff0c;本质上是网络概念的客户端与服务端的角色反转。2. 反弹shel…

webpack性能优化

文章目录 1. 性能优化-分包2. 动态导入3. 自定义分包4. Prefetch和Preload5. CDN加载配置6. CSS的提取7. terser压缩7.1 Terser在webpack中配置7.2 css压缩 8. Tree Shaking 消除未使用的代码8.1 usedExports 配置8.2 sideEffects配置8.3 CSS实现Tree Shaking 9. Scope Hoistin…

k8s概念-污点与容忍

k8s 集群中可能管理着非常庞大的服务器&#xff0c;这些服务器可能是各种各样不同类型的&#xff0c;比如机房、地理位置、配置等&#xff0c;有些是计算型节点&#xff0c;有些是存储型节点&#xff0c;此时我们希望能更好的将 pod 调度到与之需求更匹配的节点上。 此时就需要…

人工智能在心电信号分类中的应用

目录 1 引言 2 传统机器学习中的特征提取与选择 3 深度学习中的特征提取与选择

ROS2学习(五)进程内topic高效通信

对ROS2有一定了解后&#xff0c;我们会发现ROS2中节点和ROS1中节点的概率有很大的区别。在ROS1中节点是最小的进程单元。在ROS2中节点与进程和线程的概念完全区分开了。具体区别可以参考 ROS2学习(四)进程&#xff0c;线程与节点的关系。 在ROS2中同一个进程中可能存在多个节点…

并查集练习 — 扩展问题(二)

根据并查集练习 —岛屿数量的问题再次扩展&#xff1a; 原题是给定一个二维数组matrix&#xff08;char[][]&#xff09;&#xff0c;里面的值不是1就是0&#xff0c;上、下、左、右相邻的1认为是一片岛。返回matrix中岛的数量。 扩展为&#xff1a;如果是中国的地图&#xff0…

Xposed回发android.os.NetworkOnMainThreadException修复

最近用xposed进行hook回发的时候&#xff0c;又出现了新的问题&#xff1b; android.os.NetworkOnMainThreadException&#xff1b; 在Android4.0以后&#xff0c;写在主线程&#xff08;就是Activity&#xff09;中的HTTP请求&#xff0c;运行时都会报错&#xff0c;这是因为…

移动开发最佳实践:为 Android 和 iOS 构建成功应用的策略

您可以将本文作为指南&#xff0c;确保您的应用程序符合可行的最重要标准。请注意&#xff0c;这份清单远非详尽无遗&#xff1b;您可以加以利用&#xff0c;并添加一些自己的见解。 了解您的目标受众 要制作一个成功的应用程序&#xff0c;你需要了解你是为谁制作的。从创建…

API接口:企业信息核验

企业信息核验是现代企业管理中必不可少的一项业务&#xff0c;它可以帮助企业做出正确的决策。在这篇文章里&#xff0c;我们将会介绍如何使用API接口来对企业信息进行核验&#xff0c;并实现快捷、准确的查询。 一、API接口 在这里我们使用的是挖数据提供的企业信息核验API接…

SQL-方法论

写SQL时可以考虑的手段&#xff1a; 行转列 先分为多个临时表&#xff0c;然后JOIN到一起 select uid,t1.name YuWen,t2.name ShuXue from (select uid,namefrom tableAwhere naem 语文) t1join (select uid,namefrom tableAwhere naem 数学) t2on t1.uid t2.uid; 用sum(if…

数据结构 10-排序6 Sort with Swap(0, i)(C语言)

10-排序6 Sort with Swap(0, i) Given any permutation of the numbers {0, 1, 2,..., N−1}, it is easy to sort them in increasing order. But what if Swap(0, *) is the ONLY operation that is allowed to use? For example, to sort {4, 0, 2, 1, 3} we may apply the…

arm交叉编译lmbench

一、下载lmbench www.bitmover.com/lmbench 官网下载&#xff0c;http://www.bitmover.com/lmbench/lmbench3.tar.gz 我没有下载下来&#xff0c;找的别人的百度云下载 链接: https://pan.baidu.com/s/1tGo1clCqY-jQPN8G1eWSsg 提取码: f6jd 二、修改makefile 修改三个文件…

Last-Mile Embodied Visual Navigation 论文阅读

论文阅读 题目&#xff1a;Last-Mile Embodied Visual Navigation 作者&#xff1a;JustinWasserman, Karmesh Yadav 来源&#xff1a;CoRL 时间&#xff1a;2023 代码地址&#xff1a;https://jbwasse2.github.io/portfolio/SLING Abstract 现实的长期任务&#xff08;例如…

W5500-EVB-PICO做DNS Client进行域名解析(四)

前言 在上一章节中我们用W5500-EVB-PICO通过dhcp获取ip地址&#xff08;网关&#xff0c;子网掩码&#xff0c;dns服务器&#xff09;等信息&#xff0c;给我们的开发板配置网络信息&#xff0c;成功的接入网络中&#xff0c;那么本章将教大家如何让我们的开发板进行DNS域名解析…

小程序 view下拉滑动导致scrollview滑动事件失效

小程序页面需要滑动功能 下拉时滑动&#xff0c;展示整个会员卡内容&#xff0c; 下拉view里包含了最近播放&#xff1a;有scrollview&#xff0c;加了下拉功能后&#xff0c;scrollview滑动失败了。 <view class"cover-section" catchtouchstart"handletou…

CS 144 Lab Four 收尾 -- 网络交互全流程解析

CS 144 Lab Four 收尾 -- 网络交互全流程解析 引言Tun/Tap简介tcp_ipv4.cc文件配置信息初始化cs144实现的fd家族体系基于自定义fd体系进行数据读写的adapter适配器体系自定义socket体系自定义事件循环EventLoop模板类TCPSpongeSocket详解listen_and_accept方法_tcp_main方法_in…

注解 @JsonFormat 与 @DateTimeFormat 的使用

文章目录 JsonFormat (双端互传)DateTimeFormat &#xff08;前端传后端日期格式转化&#xff09;情况一 前端是时间组件 <el-date-picker 或其他情况二 前端未设置组件 JsonFormat (双端互传) com.fasterxml.jackson.annotation.JsonFormat; 将字符串的时间转换成Date类型…

webSocket 与传统的 http 有什么优势

webSocket 与传统的 http 有什么优势 当页面中需要观察实时数据的变化&#xff08;比如聊天、k 线图&#xff09;时&#xff0c;过去我们往往使用两种方式完成 第一种是短轮询&#xff0c;即客户端每隔一段时间就向服务器发送消息&#xff0c;询问有没有新的数据 第二种是长轮询…

[整合]无root权限的服务器上安装screen

安装ncurses 命令行输入以下指令安装ncurses&#xff0c;这个是安装screen的前置依赖包。 wget http://ftp.gnu.org/gnu/ncurses/ncurses-6.0.tar.gz #下载所需的ncurses版本tar zxvf ncurses-6.0.tar.gz #解压到当前目录cd ncurses-6.0 #打开ncurses-6.0文件夹mkdi…

---------------- 部署 Zookeeper 集群 ----------------

部署 Zookeeper 集群 1.安装前准备2.安装 Zookeeper修改配置文件在每个节点上创建数据目录和日志目录在每个节点的dataDir指定的目录下创建一个 myid 的文件配置 Zookeeper 启动脚本 //准备 3 台服务器做 Zookeeper 集群 192.168.109.1 192.168.109.2 192.168.109.3 1.安装前准…