手撕spring bean的加载过程

这里我们采用手撕源码的方式,开始探索spring boot源码中最有意思的部分-bean的生命周期,也可以通过其中的原理理解很多面试以及工作中偶发遇到的问题。

springboot基于约定大于配置的思想对spring进行优化,使得这个框架变得更加轻量化,集成各种starter组件时使其能够更加全面。

1、SpringApplication启动类的配置与软件包的反射加载

通常我们在建立一个新的spring boot项目时,利用idea脚手架生成模板内部会自带一个标注有SpringApplication注解的启动类,如下所示:

/*** @author : spring* {@code @description:}* {@code @date} : 2024/2/4* {@code @modified} By: spring* {@code @project:} spring-plus*/
@SpringApplication(scanBeanPackagePath = "com/hlc/springplus/test")
public class ApplicationStarter {public static void main(String[] args) {ApplicationContext applicationContext = new DefaultApplicationContext(ApplicationStarter.class);TestBean contextBean = (TestBean) applicationContext.getBean("test");contextBean.test();}
}

由于本文的代码全部都是手撕,所以会与spring boot的源码有所不同,个人主义完美凸显。

言回正传,用过spring boot的人都知道它主要的特色基础是它的“容器”的概念,我们可以通过配置文件、注解、导入以及反射实例化后调用通用应用上下文注入的方式将我们的bean交给spring容器管理,那么这里启动类启动后“容器”是怎么识别出我们标准或配置的bean信息同时将其实例化、配置属性、配置名称.....的呢?

那么下面就是一个过程

获取启动类上标注注解中的包路径值
SpringApplication annotation = appconfig.getAnnotation(SpringApplication.class);String beanPackagePath = annotation.scanBeanPackagePath();
String path = beanPackagePath.replace('.', '/');

这里的path就是我们实际的包路径,为什么需要将.替换城/呢?实际上我们配置的包路径是软件包中的相对路径,并不是Resource获取时规定的路径格式。

获取当前类的类加载器并根据路径加载URL获取文件
 ClassLoader classLoader = DefaultApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);File file = new File(resource.getFile());

DefaultApplicationContext就是我当前类的名称,后续串完全部的流程会将全部的代码挂出的,这里的类加载器获取资源的方式是比较常用的。

需要注意的是这里拿到的file有可能是文件夹,也可能是文件。

通过文件夹或文件夹获取字节码
 for (File item : files) {int begin = item.getAbsolutePath().indexOf("com");int end = item.getAbsolutePath().indexOf('.');String className = item.getAbsolutePath().substring(begin, end).replace('\\', '.');try {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(Component.class)) {beanClazzList.add(clazz);//收集后置处理器(意图是收集后置处理器而不是收集bean对象)if (BeanPostprocessor.class.isAssignableFrom(clazz)) {beanPostprocessorList.add((BeanPostprocessor) clazz.getDeclaredConstructor().newInstance());}}} catch (Exception e) {throw new RuntimeException(e.getMessage());}                            }

上面的类路径获取方式以及判断字节码是否实现了接口BeanPostprocessor的判断、字节码是否标注了注解Component的判断都是比较常用的方法。

那么通过此三步就将全部需要加载的字节码文件都获取到我们的成员变量beanClazzList列表中去了。

2、ApplicationContext接口的定义以及相关注解的配置

虽然我们解决了待加载bean的字节码列表的收集问题,但是spring boot的容器我们还没有加载出来,也没有实现相关注解的配置,注解标注了bean的身份、名称、类型、加载方式、加载条件、加载顺序、依赖关系等。

ApplicationContext接口的定义
public interface ApplicationContext extends BeanFactory {Object getBean(String beanName);<T> void registerBean(T bean, Class<T> clazz, String name);
}

ApplicationContext接口的释义是“应用上下文”,在计算机科学中,上下文表示进程在执行过程中系统内部的资源情况与中断向量表的记录情况,总之代表的是进程所处的逻辑环境。这里顾名思义ApplicationContext代表的也自然就是bean所处的环境。也就是我们口中的spring boot的容器。

对于基本的ApplicationContext能力而言,它应当具备获取bean对象与注册bean对象的能力。所以这里定义的两个基础能力接口。在考虑如何实现它之前,我们还需要配置以下bean相关的其他注解:

组件注解(标注类型为bean组件)

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Component {String name() default "";
}

注入注解(标注为某类型注入某bean的属性值)

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {String value() default "";
}

作用域注解(标注单例、原型等生存周期的bean类型)

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

懒加载注解(标注即用即加载还是立刻加载)

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
}

初始化方法注解(标注bean的初始化方法)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InitBeanMethod {
}

3、实现ApplicationContext接口与bean加载原理

上述我们定义了相关接口与注解,接下来我们实现容器上下文接口以及描述bean是如何加载到容器内部管理的。

那么这里我们就先将实现ApplicationContext接口的DefaultApplicationContext.java代码放在下方:

public class DefaultApplicationContext implements ApplicationContext {private final Class<?> appconfig;private List<Class<?>> beanClazzList = new LinkedList<>();private Map<String, Object> singletonBeanMap = new ConcurrentHashMap<>();private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();private List<BeanPostprocessor> beanPostprocessorList = new LinkedList<>();public DefaultApplicationContext(Class<?> appconfig) {this.appconfig = appconfig;//1、扫描启动类注解的字节码列表scanBeansByPackage(beanClazzList);//2、注册bean的BeanDefinition初始配置信息initBeanDefinition(beanClazzList, beanDefinitionMap);//3、实例化单例bean并存入map中instanceSingletonBeans(beanDefinitionMap, singletonBeanMap);}@Overridepublic Object getBean(String beanName) {BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);if (null != beanDefinition && "prototype".equals(beanDefinition.getScope())) {try {return beanDefinition.getBeanClazz().getDeclaredConstructor().newInstance();} catch (InstantiationException | IllegalAccessException | InvocationTargetException |NoSuchMethodException e) {throw new RuntimeException(e.getMessage());}}return singletonBeanMap.get(beanName);}@Overridepublic <T> void registerBean(T bean, Class<T> clazz, String name) {singletonBeanMap.put(name, bean);}/*** 扫描bean的字节码列表** @param beanClazzList bean的字节码列表(待填充)*/protected void scanBeansByPackage(List<Class<?>> beanClazzList) {if (null != appconfig && appconfig.isAnnotationPresent(SpringApplication.class)) {SpringApplication annotation = appconfig.getAnnotation(SpringApplication.class);if (null != annotation) {String beanPackagePath = annotation.scanBeanPackagePath();String path = beanPackagePath.replace('.', '/');ClassLoader classLoader = DefaultApplicationContext.class.getClassLoader();URL resource = classLoader.getResource(path);if (resource != null) {File file = new File(resource.getFile());if (file.isDirectory()) {File[] files = file.listFiles();if (files != null) {for (File item : files) {loadAndFilterBeanClazzes(beanClazzList, item);}}} else {loadAndFilterBeanClazzes(beanClazzList, file);}}} else {throw new RuntimeException("Annotation SpringApplication is not exist");}} else {throw new RuntimeException("Annotation SpringApplication is not exist and appconfig is null");}}/*** 加载bean的字节码列表并过滤** @param beanClazzList bean的字节码列表(待填充)* @param item          文件或文件夹*/private void loadAndFilterBeanClazzes(List<Class<?>> beanClazzList, File item) {int begin = item.getAbsolutePath().indexOf("com");int end = item.getAbsolutePath().indexOf('.');String className = item.getAbsolutePath().substring(begin, end).replace('\\', '.');try {Class<?> clazz = Class.forName(className);if (clazz.isAnnotationPresent(Component.class)) {beanClazzList.add(clazz);//收集后置处理器(意图是收集后置处理器而不是收集bean对象)if (BeanPostprocessor.class.isAssignableFrom(clazz)) {beanPostprocessorList.add((BeanPostprocessor) clazz.getDeclaredConstructor().newInstance());}}} catch (Exception e) {throw new RuntimeException(e.getMessage());}}/*** 注册bean的BeanDefinition初始配置信息** @param beanClazzList     bean的字节类型列表* @param beanDefinitionMap bean的BeanDefinition初始配置信息池子*/private void initBeanDefinition(List<Class<?>> beanClazzList, Map<String, BeanDefinition> beanDefinitionMap) {if (null != beanClazzList && !beanClazzList.isEmpty()) {for (Class<?> clazz : beanClazzList) {BeanDefinition beanDefinition = new BeanDefinition();Component component = clazz.getAnnotation(Component.class);Scope scope = clazz.getAnnotation(Scope.class);Lazy lazy = clazz.getAnnotation(Lazy.class);beanDefinition.setBeanClazz(clazz);beanDefinition.setLazy(null != lazy);beanDefinition.setScope(null != scope ? scope.value() : "prototype");String beanName = component.name();if (beanName.isEmpty()) {beanName = clazz.getSimpleName();}beanDefinitionMap.put(beanName, beanDefinition);}}}/*** 实例化单例bean** @param beanDefinitionMap bean定义信息* @param singletonBeanMap  单例bean池子*/private void instanceSingletonBeans(Map<String, BeanDefinition> beanDefinitionMap, Map<String, Object> singletonBeanMap) {if (null != beanDefinitionMap && !beanDefinitionMap.isEmpty()) {for (Class<?> clazz : beanDefinitionMap.values().stream().map(BeanDefinition::getBeanClazz).toList()) {if (clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {continue;}if (!clazz.isAnnotationPresent(Lazy.class)) {//实例化beantry {Component component = clazz.getAnnotation(Component.class);String beanName = component.name();if (null == beanName || beanName.isEmpty()) {beanName = clazz.getSimpleName();}//1、实例化beanObject newInstance = clazz.getDeclaredConstructor().newInstance();//2、属性填充attributeAutoWiredPadding(clazz, newInstance);//3、aware能力透传awareBeanInstancePadding(newInstance);//4、初始化//4.1、后置处理器 初始化前执行for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {newInstance = beanPostprocessor.beforeInitialization(newInstance, beanName);}//4.2、初始化bean执行//检查是否实现初始化Bean的接口initializeBeanInstancePadding(newInstance);//检查是否配置过init方法initBeanMethodInstancePadding(newInstance);//4.3、后置处理器能力 初始化后执行for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {newInstance = beanPostprocessor.afterInitialization(newInstance, beanName);}singletonBeanMap.put(beanName, newInstance);} catch (InvocationTargetException | NoSuchMethodException | InstantiationException |IllegalAccessException e) {throw new RuntimeException(e.getMessage());}}}}}/*** bean的属性填充** @param beanClazz   bean的字节类型* @param newInstance 实例化的bean*/private void attributeAutoWiredPadding(Class<?> beanClazz, Object newInstance) {if (null != beanClazz) {Field[] fields = beanClazz.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(AutoWired.class)) {field.setAccessible(true);Class<?> declaringClass = field.getType();AutoWired autoWired = field.getAnnotation(AutoWired.class);String name = autoWired.value();if (null == name || name.isEmpty()) {name = declaringClass.getSimpleName();}Object fieldBean = singletonBeanMap.get(name);if (null == fieldBean) {List<Class<?>> beanClazzList = new LinkedList<>();beanClazzList.add(declaringClass);initBeanDefinition(beanClazzList, beanDefinitionMap);Map<String, BeanDefinition> definitionMap = new HashMap<>();definitionMap.put(name, beanDefinitionMap.get(name));instanceSingletonBeans(definitionMap, singletonBeanMap);try {field.set(newInstance, singletonBeanMap.get(name));} catch (IllegalAccessException e) {throw new RuntimeException(e.getMessage());}} else {try {field.set(newInstance, fieldBean);} catch (IllegalAccessException e) {throw new RuntimeException(e.getMessage());}}}}}}/*** bean的Aware接口的实现类填充** @param bean bean实例对象*/private void awareBeanInstancePadding(Object bean) {if (null != bean) {if (bean instanceof Aware) {if (bean instanceof ApplicationContextAware) {((ApplicationContextAware) bean).setApplicationContext(this);}if (bean instanceof BeanNameAware) {((BeanNameAware) bean).setBeanName();}}}}/*** bean的初始化方法填充** @param bean 实例化的bean*/private void initializeBeanInstancePadding(Object bean) {if (null != bean) {if (bean instanceof InitializingBean) {((InitializingBean) bean).afterPropertiesSet();}}}/*** bean的初始化方法填充** @param newInstance 实例化的bean*/private void initBeanMethodInstancePadding(Object newInstance) {if (null != newInstance) {Method[] methods = newInstance.getClass().getDeclaredMethods();for (Method method : methods) {if (method.isAnnotationPresent(InitBeanMethod.class)) {method.setAccessible(true);try {method.invoke(newInstance);} catch (IllegalAccessException | InvocationTargetException e) {throw new RuntimeException(e.getMessage());}}}}}private void sortBeanInstanceClazzList() {}
}

这里我们着重描述一下关于bean加载原理的这一块代码:

private void instanceSingletonBeans(Map<String, BeanDefinition> beanDefinitionMap, Map<String, Object> singletonBeanMap) {if (null != beanDefinitionMap && !beanDefinitionMap.isEmpty()) {for (Class<?> clazz : beanDefinitionMap.values().stream().map(BeanDefinition::getBeanClazz).toList()) {if (clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {continue;}if (!clazz.isAnnotationPresent(Lazy.class)) {//实例化beantry {Component component = clazz.getAnnotation(Component.class);String beanName = component.name();if (null == beanName || beanName.isEmpty()) {beanName = clazz.getSimpleName();}//1、实例化beanObject newInstance = clazz.getDeclaredConstructor().newInstance();//2、属性填充attributeAutoWiredPadding(clazz, newInstance);//3、aware能力透传awareBeanInstancePadding(newInstance);//4、初始化//4.1、后置处理器 初始化前执行for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {newInstance = beanPostprocessor.beforeInitialization(newInstance, beanName);}//4.2、初始化bean执行//检查是否实现初始化Bean的接口initializeBeanInstancePadding(newInstance);//检查是否配置过init方法initBeanMethodInstancePadding(newInstance);//4.3、后置处理器能力 初始化后执行for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {newInstance = beanPostprocessor.afterInitialization(newInstance, beanName);}singletonBeanMap.put(beanName, newInstance);} catch (InvocationTargetException | NoSuchMethodException | InstantiationException |IllegalAccessException e) {throw new RuntimeException(e.getMessage());}}}}}

到这里我们已经了解在获取待加载bean的字节码列表之后,我们需要将bean的配置信息存储到我们的beanDefinitionMap中,再根据beanDefinitionMap将其中的单例bean信息加载成一个个bean放入单例bean map中,这里的存储key统一都是beanName。

看以上代码我们不难分析出通过bean配置信息加载bean的过程中,一个bean需要经过6步周期性工作才会被放入容器中给我们使用。以下是图示:

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

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

相关文章

Backtrader 文档学习- Observers

Backtrader 文档学习- Observers 1.概述 在backtrader中运行的策略主要处理数据源和指标。 数据源被加载到Cerebro实例中&#xff0c;并最终成为策略的一部分&#xff08;解析和提供实例的属性&#xff09;&#xff0c;而指标则由策略本身声明和管理。 到目前为止&#xff0c…

LabVIEW多功能接口卡驱动

LabVIEW多功能接口卡驱动 随着自动化测试系统的复杂性增加&#xff0c;对数据采集与处理的需求不断提高。研究基于LabVIEW开发平台&#xff0c;实现对一种通用多功能接口卡的驱动&#xff0c;以支持多通道数据采集及处理功能&#xff0c;展现LabVIEW在自动化和测量领域的强大能…

如何部署Docker Registry并实现无公网ip远程连接本地镜像仓库

文章目录 1. 部署Docker Registry2. 本地测试推送镜像3. Linux 安装cpolar4. 配置Docker Registry公网访问地址5. 公网远程推送Docker Registry6. 固定Docker Registry公网地址 Docker Registry 本地镜像仓库,简单几步结合cpolar内网穿透工具实现远程pull or push (拉取和推送)…

IPv4的公网地址不够?NAT机制可能是当下最好的解决方案

目录 1.前言 2.介绍 3.NAT机制详解 1.前言 我们都知道IPv4的地址范围是32个字节,这其中还有很多地址是不可用的.比如127.*,这些都是环回地址.那么在网路发展日新月异的今天,互联网设备越来越多,我们该如何解决IP地址不够用的问题呢?目前有一种主流的解决方案,也是大家都在用…

Visual Studio 2022 查看类关系图

这里写自定义目录标题 右键要查看的项目 -“查看”-“查看类图”效果展示&#xff1a; 原文地址 www.cnblogs.com 步骤1&#xff1a;勾选扩展开发 步骤2: 勾选类设计器 右键要查看的项目 -“查看”-“查看类图” 效果展示&#xff1a;

操作系统-02-Ubuntu 常用命令等汇总

Ubuntu Ubuntu是一个开源软件平台&#xff0c;可以在从智能手机、平板电脑和个人电脑到服务器和云端等各种设备上运行。 ubuntu Title: Desktop Ubuntu desktop->command: CtrlAltF2~F6 command->desktop: CtrlAltF7 Login Change to root user $ sudu suLogin to al…

AI嵌入式K210项目(27)-条形码识别

文章目录 前言一、什么是条形码&#xff1f;二、实验准备三、实验过程四、API接口总结 前言 本章介绍基于机器视觉实现条形码识别&#xff0c;主要包含两个过程&#xff0c;首先检测图像中是否有条形码&#xff0c;如果有则框出并打印条形码信息&#xff1b; 一、什么是条形码…

ShardingSphere实现openGauss分布式架构

本文档采用openGauss结合ShardingSphere中间件的架构&#xff0c;实现openGauss数据库分布式OLAP场景的环境部署。 术语说明&#xff1a; 开源数据库引擎&#xff1a;openGauss shardingsphere Proxy&#xff1a;定位为透明化的数据库代理端&#xff0c;提供封装了数据库二进…

示波器的电压怎么和测量的电压不一样,空比增加以后 示波器显示的电压反而降低了?

这两天在调电机&#xff0c;发现我用pwm信号&#xff0c;发送给示波器&#xff0c;但是示波器测量的电压和万用表测量的完全不一样&#xff0c;而有奇怪的现象&#xff0c;空比增加以后 示波器显示的电压反而降低了。 最后发现是通道没有设置&#xff0c;要设置成直流模式 点ch…

docker之程序镜像的制作

目录 一、每种资源的预安装&#xff08;基础&#xff09; 安装 nginx安装 redis 二、dockerfile文件制作&#xff08;基础&#xff09; 打包 redis 镜像 创建镜像制作空间制作dockerfile 打包 nginx 镜像 三、创建组合镜像&#xff08;方式一&#xff09; 生成centos容器并…

补题:leetcode第382场周赛 3022. 给定操作次数内使剩余元素的或值最小

3022. 给定操作次数内使剩余元素的或值最小 - 力扣&#xff08;LeetCode&#xff09; 拆位 n个数进行或运算的结果最小&#xff0c;每次操作可以对相邻的两个数进行与运算&#xff0c;至多进行k次操作 n个数进行或运算&#xff0c;可以对每个数进行拆解&#xff0c;拆解成最小…

分享springboot框架的一个开源的本地开发部署教程(若依开源项目开发部署过程分享持续更新二开宝藏项目PostgresSQL数据库版)

1首先介绍下若依项目&#xff1a; 若依是一个基于Spring Boot和Spring Cloud技术栈开发的多租户权限管理系统。该开源项目提供了一套完整的权限管理解决方案&#xff0c;包括用户管理、角色管理、菜单管理、部门管理、岗位管理等功能。 若依项目采用前后端分离的架构&#xf…

使用代理IP有风险吗?如何安全使用代理IP?

代理IP用途无处不在。它们允许您隐藏真实IP地址&#xff0c;从而实现匿名性和隐私保护。这对于保护个人信息、绕过地理受限的内容或访问特定网站都至关重要。 然而&#xff0c;正如任何技术工具一样&#xff0c;代理IP地址也伴随着潜在的风险和威胁。不法分子可能会滥用代理IP…

ue5 蓝图内修改组件附加的插槽

Target是目标,Parent是要加到哪个骨骼上,socketName是骨骼对应的插槽

基于Springboot的考编论坛网站的设计与实现(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的考编论坛网站的设计与实现&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层…

【华为云】云上两地三中心实践实操

写在前面 应用上云之后&#xff0c;如何进行数据可靠性以及业务连续性的保障是非常关键的&#xff0c;通过华为云云上两地三中心方案了解相关方案认证地址&#xff1a;https://connect.huaweicloud.com/courses/learn/course-v1:HuaweiXCBUCNXI057Self-paced/about当前内容为华…

vulnhub中Beelzebub靶机

渗透思路 一.信息收集1.网段探测2.端口探测3.常见漏洞扫描4.目录扫描5.web页面分析 二.渗透继续目录扫描ssh连接提权提权&#xff0c;flag 一.信息收集 1.网段探测 ┌──(root㉿kali)-[~] └─# nmap -Pn 192.168.0.0/24 --min-rate 10000 Starting …

awd总结

总结&#xff1a; 由于是第一次参加AWD比赛&#xff0c;各方面经验都不足&#xff0c;在参赛的前几天也是疯狂搜集各种脚本、框架、工具等&#xff0c;同时也参考b站的视频进行学习&#xff0c;我发现就是还是实操才能更快的学习 我觉得就是我前期的准备工作不足&#xff0c;…

【sentinel流量卫兵配置持久化到Nacos】

sentinel流量卫兵配置持久化到Nacos 概述&#xff1a; 一、添加配置二、配置说明限流规则配置&#xff1a;降级规则配置&#xff1a;热点规则配置&#xff1a;授权规则配置&#xff1a;系统规则配置&#xff1a; 三、服务整合 概述&#xff1a; 控制台配置的参数&#xff0c;默…

STM32WLE5JC

Sub-GHz 无线电介绍 sub-GHz无线电是一种超低功耗sub-GHz无线电&#xff0c;工作在150-960MHz ISM频段。 在发送和接收中采用LoRa和&#xff08;G&#xff09;FSK调制&#xff0c;仅在发送中采用BPSK/(G)MSK调制&#xff0c;可以在距离、数据速率和功耗之间实现最佳权衡。 这…