手写模拟Spring底层原理-Bean的创建与获取
作者:京东物流 张鼎元
1 引言
大家好,相信大家对Spring的底层原理都有一定的了解,这里我们会针对Spring底层原理,在海量的Spring源代码中进行抽丝剥茧手动实现一个Spring简易版本,来促进我们对Spring架构有个更深的理解,对Spring的常用功能进行手写模拟实现。
2 启动Spring
针对Bean的创建和获取功能,我们来进行功能的实
首先我们创建JdApplicationContext类做为Spring启动类,实现bean的加载和获取功能。
UserService和OrderService类作为Bean的实现类,通过JdApplicationContext类中的getBean方法获取到前面两个类的实现。
- App为启动测试类
- AppConfig为启动配置类
注:下面的代码会顺着内容讲解逐步完成
首先创建App类做为入口,测试Spring功能。通过初始化JdApplicationContext类,动态加载bean实例。 通过getBean方法获取bean实例。 
创建JdApplicationContext类,提供获取Bean实例方法,通过构造函数动态初始化bean实例。 
3 扫描类路径并缓存BeanDefinition数据
在JdApplicationContext类初始化的时候,通过AppConfig配置类获取类的扫描路径,在扫描路径下,找到需要创建Bean的类,通过标注Component注解的类识别需要创建的Bean。
通过Component注解识别出的类,进行封装成BeanDefinition. 再缓存到beanDefinitionMap内存中。 
上述的代码中,我们发现创建BeanDefinition类时,封装了class类,beanName,scope三个主要属性。用于创建bean的时候,提供class类进行初始化和属性的注入,创建单例类或原型类提供数据依据。
4 初始化Bean和依赖注入
接下来,在上面的扫描操作完成后,所有待初始化的bean数据存储beanDefinitionMap中。我们只需要遍历beanDefinitionMap数据进行逐个初始化和属性的注入。 
上述代码中,对bean进行初始化时候,从beanDefinition中获取要初始化的class,通过反射机构进行无参初始化。
初始化完成后,再对有Autowired注解的属性进行依赖注入,Autowired注解没有传递value值时默认取属性名称作为beanName,通过getBean方法获取bean实例。
getBean方法会通过beanName,从beanDefinitionMap中取得beanDefinition数据。通过beanDefinition确认该bean为单例类原型类
如果为原型类,直接调用createBean方法进行bean初始化。
如果为单例类,首先从singletonBeanMap缓存中获取bean实例。如果未获取到,调用createBean方法获取bean实例,同时将已创建bean实例缓存到singletonBeanMap缓存中。
此时,在上述的功能中,依赖注入简易版本已实现。同时我们注意到UserService和OrderService可能会产生循环依赖的问题,在这里如何解决呢?
问题代码如下 :
上图就是循环依赖问题代码导致的异常。重复创建bean进入死循环。
在初始化bean和属性注入之间,我们可以增加二级缓存作为突破口,解决死循环问题。
userService初始化后,需要注入orderService,通过getBean方法获取,因为orderService没有在singletonBeanMap缓存中,也需要初始化并注入userService属性, 同时userService还在初始化过程中,不能缓存到singletonBeanMap缓存中。造成彼此循环等待属性的注入。为解决此问题,我们只需要设立初始化过程中缓存到creatingBeanMap中,在userService初始化过后,未进行属性注入前缓存到creatingBeanMap中,userService需要的orderService属性在创建bean实例过程中,优先从creatingBeanMap缓存中得到userService实例,来完成bean实例的创建过程。orderService完成bean实例创建后,userService也相应的完成实例创建。 
5 实现InitializingBean接口
在createBean过程中,我们可以对外提供初始化扩展接口InitializingBean接口。只要实现该接口,我们就可以针对bean的初始化进行扩展功能实现。 ![] 
6 实现BeanPostProcessor接口模拟AOP
首先创建BeanPostProcessor接口,作为所有bean实例的对外扩展接口
创建BeanPostProcessor接口实现类,模拟AOP功能,指定userService类进行切面。
在扫描类的时候,将已实现BeanPostProcessor接口类缓存到beanPostProcessorList中。
通过上面的扫描,beanPostProcessorList已缓存所有的BeanPostProcessor实现类。在createBean的时候,对已创建的bean实例进行预处理扩展。
通过上述代码的实现效果如下:
源代码:
7 总结
在上述的讲解中,我们对Spring底层原理进行简单的实现,通过对类的扫描,注解标识的判断,beanDefinition的定义和缓存。通过反射和代理进行bean实例的创建和扩展。相信大家也看出来在实现过程中,有很多地方需要改进,还可以继续扩展Spring很多其它功能。例如扩展beanDefinition的注册,引入Bean工厂,延迟加载等。
手写模拟Spring底层原理-Bean的创建与获取的更多相关文章
- 【Spring系列】- 手写模拟Spring框架
简单模拟Spring 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 前言 上次已经学习了 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍
概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...
- Spring源码 20 手写模拟源码
参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...
- 细数那些不懂Spring底层原理带来的伤与痛
1. 什么是spring? Spring 是个Java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring 框架目标是简化Jav ...
- 细数不懂Spring底层原理带来的伤与痛
原文链接:https://www.jianshu.com/p/c9de414221ac?utm_campaign=haruki&utm_content=note&utm_medium= ...
- 摊牌了!我要手写一个“Spring Boot”
目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...
- 【面试题】手写async await核心原理,再也不怕面试官问我async await原理
前言 async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的 generator函数 在之前我专门讲个generator的使用与原理实现,大家没了解过的 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构
前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动
上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...
- 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块
在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...
随机推荐
- iOS代码安全加固利器:深入探讨字符串和代码混淆器的作用
在网上搜"代码混淆"关键词,可以看到n多教程.包括本篇博客,大部分重要内容也是从网上各位大神的博客里面看到然后摘取和总结出来的.虽然网上都有,但是对于我个人来说,很难找到一篇博 ...
- that the pod didn't tolerate, 2 Insufficient cpu.
K8S Pod 一直处于 Pending 状态 有几个原因可以阻止 Pod 运行,但我们将描述三个主要问题: 调度问题:无法在任何节点上调度 Pod. 镜像问题:下载容器镜像时出现问题. 依赖性问题: ...
- Spring事务传播机制解析
确保数据一致性的关键 在Java的Spring框架中,事务管理是保证应用数据一致性和可靠性的关键.Spring提供了灵活的事务传播机制,它定义了事务边界,以及在嵌套方法调用时如何处理事务.本文旨在深入 ...
- 深入了解浮点运算——CPU 和 GPU 算力是如何计算的
随着国家大力发展数字经济,算力的提升和普惠变得越来越重要.在数字化时代,算力已成为推动科技发展和创新的关键要素.它不仅仅是衡量计算机处理速度的标准,还涉及计算机系统或设备执行计算任务的能力.数据处理能 ...
- CO40/CO41转生产订单下达时不能创建采购申请
一.配置 CO01创建生产订单,创建时生成采购申请,改为下达时创建采购申请.通过配置,将预留/采购申请 更改为2即可. 但是CO41和CO40通过配置,并不能达到更改预留/采购申请 为2. 二.调试源 ...
- 【3rd Party】nlohmann json 基础用法
参考链接:Here 什么是nlohman json ? nlohman json GitHub - nlohmann/json: JSON for Modern C++ 是一个为现代C++(C++11 ...
- AtCoder Beginner Contest 208 A~E个人题解
比赛链接:Here A - Rolling Dice 水题 一个六面的骰子,请问摇动 \(A\) 次最后的点数和能否为 \(B\) 如果 \(B \in [a,6a]\) 输出 YES C++ voi ...
- Spring的@Async使用防坑
很多人会直接只用@Async来执行异步操作.但是这里面有一个问题,如果都是用spring的默认实现,是有坑的. 当我们开启 EnableAsync 只有就使用Async来执行异步操作了. 走JDK的动 ...
- 关闭 cockpit 登陆提示
sudo rm /etc/issue.d/cockpit.issue sudo rm /etc/motd.d/cockpit
- RL 基础 | 如何注册自定义 gym 环境
如何 搭建 自定义 gym 环境:https://www.cnblogs.com/moonout/p/17174833.html 如何注册自定义 gym 环境: 博客:https://zhuanlan ...