Spring复习--后处理器、生命周期

news/2025/2/22 20:30:18/文章来源:https://www.cnblogs.com/Helix6/p/18697243

Spring实例化的基本流程

将xml中的bean标签封装到一个BeanDefinition对象中,然后将BeanDefinition对象存储到一个BeanDefinitionMap<String,BeanDefinition>集合中去,通过对BeanDefinitionMap进行遍历,使用反射创建bean示例对象存储到singletonObjects<String,Object>的map集合中,调用getBean(beanName)方法取出bean示例。

Spring后处理器

介入到bean的实例化过程中,实现动态注册BeanDefinition、动态修改BeanDifinition、动态修改BeanDefinition,Spring有两种后处理器:
(1)BeanFactoryPostProcessor:在BeanDefinition填充之后,bean实例化之前执行。
(2)BeanPostProcessor:在bean实例化之后,添加到singletonObjects之前执行。

BeanFactoryPostProcessor

BeanFactoryPostProcessor实现:写一个processor类实现接口,重写方法,将类配置到xml。
bean中的配置:

<!-- 后处理器 -->
<bean class="com.demo.processor.MyBeanFactoryPostProcessor"></bean>

案例:
(1)修改已经注册bean标签的beanName:通过beanFactory获取指定的beanDefinition进行更改

BeanDefinition userDaoBeanDefinition = beanFactory.getBeanDefinition("userDao");
userDaoBeanDefinition.setBeanClassName("com.demo.service.impl.UserServiceImpl");

(2)添加xml没有配置的bean信息:
new BeanDefinition的实现类对象,并填入参数,再注册到beanFactory

//类型强制转换(使用其中的方法)
BeanDefinition beanDefinition = new RootBeanDefinition("com.demo.dao.impl.PersonDaoImpl");
DefaultListableBeanFactory defaultListableBeanFactory = (DefaultListableBeanFactory) beanFactory;
defaultListableBeanFactory.registerBeanDefinition("personDao",beanDefinition);//注册

另外介绍一个实现类BeanDefinitionRegistryPostProcessor,他有两个方法,一个原来的postProcessBeanFactory,一个提供注册功能的postProcessBeanDefinitionRegistry
使用它实现注册

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {BeanDefinition beanDefinition = new RootBeanDefinition("com.demo.dao.impl.PersonDaoImpl");beanDefinitionRegistry.registerBeanDefinition("personDao",beanDefinition);//注册
}

各个后处理器以及方法的执行顺序

MyBeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry
MyBeanDefinitionRegistryPostProcessor的postProcessBeanFactory
MyBeanFactoryPostProcessor的postProcessBeanFactory

案例:自定义注解扫描自动注入

案例:使用自定义注解,实现将加注解的类自动注册到beanDefinitionMap中。
自定义注解,类型是加到类上的,在运行时生效,可以指定一个String属性。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyComponent {String value();
}

注解使用:

@MyComponent("other")
public class OtherBean {
}

使用写好的工具类进行包扫描,最终返回包含指定注解的<beanName,反射对象>的map集合
最后实现后处理器并加到xml中,后处理器对获取到的map反射集合进行创建beanDefinition并注册。

@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {//扫描使用注解的类,获取反射对象,构造beanDefinition并注册Map<String, Class> classMap = BaseClassScanUtils.scanMyComponentAnnotation("com.demo");//注解中的name、反射classMap.forEach((beanName,clazz)->{RootBeanDefinition beanDefinition = new RootBeanDefinition(clazz.getName());beanDefinitionRegistry.registerBeanDefinition(beanName,beanDefinition);});
}

BeanPostProcessor

Bean被实例化后,到最终缓存到名为singletonObjects单例池之前,中间会经过Bean的初始化过程,例如:属性的填充、初始方法init的执行等,其中有一个对外进行扩展的点BeanPostProcessor,我们称为Bean后处理。跟上面的Bean工厂后处理器相似,它也是一个接口,实现了该接口并被容器管理的BeanPostProcessor,会在流程节点上被Spring自动调用。
两个方法postProcessBeforeInitialization和postProcessAfterInitialization
生命周期的执行顺序:实例化、后处理器BeforeInitialization、属性赋值、init初始化方法、后处理器AfterInitialization
同样后处理器也需要配置

<bean class="com.demo.processor.TimeLogPostProcessor"></bean>

案例:对每一个实例化bean的方法都增强记录日志

实现:
(1)对方法进行增强主要就是代理设计模式和包装设计模式;
(2)由于Bean方法不确定,所以使用动态代理在运行期间执行增强操作:
(3)在Bean实例创建完毕后,进入到单例池之前,使用Proxy代替真是的目标Bean
后处理器的实现代码

public class TimeLogPostProcessor implements BeanPostProcessor {@Overridepublic Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {//通过动态代理对实例的方法进行增强Object instance = Proxy.newProxyInstance(bean.getClass().getClassLoader(),bean.getClass().getInterfaces(),(proxy, method, args) -> {System.out.println(method.getName() + "方法startTime=" + LocalDateTime.now());Object result = method.invoke(bean, args);System.out.println(method.getName() + "方法endTime=" + LocalDateTime.now());return result;});return instance;}
}

结果:

构造函数执行了
afterPropertiesSet方法执行了
自定义初始化方法执行了
say方法startTime=2025-02-04T15:24:42.115
say方法endTime=2025-02-04T15:24:42.119

实例化总体流程的总结

Spring Bean生命周期

Spring Bean的生命周期是从 Bean 实例化之后,即通过反射创建出对象之后,到Bean成为一个完整对象,最终存储到单例池中,这个过程被称为Spring Bean的生命周期。Spring Bean的生命周期大体上分为三个阶段:
(1)Bean的实例化阶段:Spring框架会取出BeanDefinition的信息进行判断当前Bean的范围是否是singleton的是否不是延迟加载的,是否不是FactoryBean等,最终将一个普通的singleton的Bean通过反射进行实例化;
(2)Bean的初始化阶段:Bean创建之后还仅仅是个"半成品",还需要对Bean实例的属性进行填充。执行一些Aware接口方法、执行BeanPostProcessor方法、执行InitializingBean接口的初始化方法、执行自定义初始化init方法等。该阶段是Spring最具技术含量和复杂度的阶段,Aop增强功能,后面要学习的Spring的注解功能等spring高频面试题Bean的循环引用问题都是在这个阶段体现的;
(3)Bean的完成阶段:经过初始化阶段,Bean就成为了一个完整的Spring Bean,被存储到单例池singletonObjects中去了,即完成了Spring Bean的整个生命周期。

由于Bean的初始化阶段的步骤比较复杂,所以着重研究Bean的初始化阶段
(1)Spring Bean的初始化过程涉及如下几个过程
(2)Bean实例的属性填充
(3)Aware接口属性注入
(4)BeanPostProcessor的before()方法回调
(5)InitializingBean接囗的初始化方法回调
(5)自定义初始化方法init回调
(6)BeanPostProcessor的after()方法回调

Bean实例属性填充

Spring在进行属性注入时,会分为如下几种情况:
(1)注入普通属性,String、int或存储基本类型的集合时,直接通过set方法的反射设置进去
(2)注入单向对象引用属性时,从容器中getBean获取后通过set方法反射设置进去,如果容器中没有,则先创建被注入对象Bean实例(完成整个生命周期)后,在进行注入操作;
(3)注入双向对象引用属性时,就比较复杂了,涉及了循环引用(循环依赖)问题,下面会详细阐述解决方案。

循环依赖

循环依赖问题图解:

Spring提供了三级缓存存储 完整Bean实例 和 半成品Bean实例 ,用于解决循环引用问题
在DefaultListableBeanFactory的上四级父类DefaultSingletonBeanRegistry中提供如下三个Map:

//最终存储单例Bean成品的容器,即实例化和初始化都完成的Bean,称之为"一级缓存"
private final Map<String, Object> singletonObjects = new ConcurrentHashMap(256);
//早期Bean单例池,缓存半成品对象,且当前对象已经被其他对象引用了,称之为"二级缓存"
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap(16);
//单例Bean的工厂池,缓存半成品对象,对象未被引用,使用时在通过工厂创建Bean,称之为"三级缓存"
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap(16);

循环依赖源码流程剖析

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

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

相关文章

Profinet转EtherNet/IP:驱动西门子1500与罗克韦尔PLC高效通讯

Profinet转EtherNet/IP:驱动西门子1500与罗克韦尔PLC高效通讯一、项目背景在某大型自动化生产车间内,生产架构呈现多元化。一部分生产线基于罗克韦尔自动化(AB)体系搭建,核心控制由AB的PLC承担;与此同时,车间新添了采用西门子S7-1500 PLC控制的设备。 为确保整个车间生产…

Xposed模块开发简单上手

起因 最近接到一个新的项目,需要开发Xposed模块相关代码,之前没有开发过Xposed模块,只能看着网上的教程简单开发个demo, 熟悉一下Xposed模块开发流程.环境下载安装Android Studio 并且有java开发环境 一台支持Xposed环境的手机 使用到的案例app wuaipojie 和 xp api.jar链接:…

Apache-CC6链审计笔记

java-CC6链审计笔记 一、审计过程 1、lazyMap 在之前CC1的审计中发现ChainedTransformer的transform方法还可以被LazyMap的get方法调用public Object get(Object key) {// create value for key if key is not currently in the mapif (map.containsKey(key) == false) {Object…

3年经验来面试20K的测试岗,连基本功都不会,还不如去招应届生

这段时间面试了几十个人。发现一个很奇怪的现象,面试中一问到元素定位、脚本编写之类的,很多人都能对答如流。但只要一深入,比如“项目中UI自动化和接口自动化如何搭配使用用?”、“项目采用什么策略来保证自动化脚本的稳定性?”。大多数人都避重就轻、含糊其辞。📝 博主…

harbor升级(最详细记录)

1.harbor升级说明Harbor 升级过程需要按照官方推荐的升级路径逐步进行,不能直接跨版本升级。 此次是从Harbor 2.6.4 升级到 Harbor 2.12.2版本 单机版升级,Harbor服务器172.16.4.60 docker版本19.03.8, /etc/docker/daemon.json"log-driver": "json-file&quo…

搭建本地NCBI病毒库用于Blast

搭建本地NCBI病毒库用于Blast目的:为了通过Blast剔除我数据集中所有与Human任意片段相似度超过97%的序列 日期:2022/11/171. Nt库下载 创建conda环境 conda create -n aspera conda activate aspera conda install -y -c hcc aspera-cli conda install -y -c bioconda sr…

你还不会使用Pycham Remote development 打开远程主机工作目录吗?这篇文章帮你解决!

前言必备: 本地开发机与远程主机都要安装Pycharm专业版!!!废话不多说直接开始!! 1、打开pycharm2、依次点击File、Remote Development3、依次点击SSH、New Project4、这里我们选择设置New Connection5、点击 +6、依次输入IP、端口、用户名、密码(可以选择其他认证方式)7、点击t…

Mybatisplus自动生成代码

第一Maven中添加依赖点击查看代码 <!-- MyBatis-Plus 扩展库 --><dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-extension</artifactId><version>3.5.7</version></dependency><dependency&…

LinkedBlockingQueue的poll方法底层原理

一、LinkedBlockingQueue的poll方法底层原理 LinkedBlockingQueue 的 poll 方法用于从队列头部移除并返回元素。如果队列为空,poll 方法会立即返回 null,而不会阻塞线程 1、poll 方法的作用从队列头部移除并返回元素。如果队列为空,立即返回 null。该方法是非阻塞的,适用于…

【R3 RootKit 病毒】基础知识研究

# 恶意样本 # RootKit RootKit简介 RootKit是一种特殊的恶意软件,它的功能是在安装目标上隐藏自身及指定的文件、进程和网络连接等信息,比较多见到的是Rootkit一般都和木马、后门等恶意程序结合使用。 技术研究入门 一般的恶意程序使用RootKit技术,主要功能分为下面两类: (…

vscode配置免密登录

Host 192.168.233.130HostName 192.168.233.130User rootPort 22IdentityFile C:\\Users\\username\\.ssh\\id_rsa

Camstar设置textbox只允许扫码

😘宝子:除非不再醒来,除非太阳不再升起,不然都请你好好生活,挣扎着前进,开心的笑。(●◡●)