作者:京东物流 张鼎元

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实例进行预处理扩展。 通过上述代码的实现效果如下: 源代码:

https://3.cn/109Aj-Zok

7 总结

在上述的讲解中,我们对Spring底层原理进行简单的实现,通过对类的扫描,注解标识的判断,beanDefinition的定义和缓存。通过反射和代理进行bean实例的创建和扩展。相信大家也看出来在实现过程中,有很多地方需要改进,还可以继续扩展Spring很多其它功能。例如扩展beanDefinition的注册,引入Bean工厂,延迟加载等。

手写模拟Spring底层原理-Bean的创建与获取的更多相关文章

  1. 【Spring系列】- 手写模拟Spring框架

    简单模拟Spring 生命不息,写作不止 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 一个有梦有戏的人 @怒放吧德德 分享学习心得,欢迎指正,大家一起学习成长! 前言 上次已经学习了 ...

  2. 带你手写基于 Spring 的可插拔式 RPC 框架(一)介绍

    概述 首先这篇文章是要带大家来实现一个框架,听到框架大家可能会觉得非常高大上,其实这和我们平时写业务员代码没什么区别,但是框架是要给别人使用的,所以我们要换位思考,怎么才能让别人用着舒服,怎么样才能让 ...

  3. Spring源码 20 手写模拟源码

    参考源 https://www.bilibili.com/video/BV1tR4y1F75R?spm_id_from=333.337.search-card.all.click https://ww ...

  4. 细数那些不懂Spring底层原理带来的伤与痛

    1. 什么是spring? Spring 是个Java企业级应用的开源开发框架.Spring主要用来开发Java应用,但是有些扩展是针对构建J2EE平台的web应用.Spring 框架目标是简化Jav ...

  5. 细数不懂Spring底层原理带来的伤与痛

    原文链接:https://www.jianshu.com/p/c9de414221ac?utm_campaign=haruki&utm_content=note&utm_medium= ...

  6. 摊牌了!我要手写一个“Spring Boot”

    目前的话,已经把 Spring MVC 相关常用的注解比如@GetMapping .@PostMapping .@PathVariable 写完了.我也已经将项目开源出来了,地址:https://gi ...

  7. 【面试题】手写async await核心原理,再也不怕面试官问我async await原理

    前言 async await 语法是 ES7出现的,是基于ES6的 promise和generator实现的 generator函数 在之前我专门讲个generator的使用与原理实现,大家没了解过的 ...

  8. 带你手写基于 Spring 的可插拔式 RPC 框架(二)整体结构

    前言 上一篇文章中我们已经知道了什么是 RPC 框架和为什么要做一个 RPC 框架了,这一章我们来从宏观上分析,怎么来实现一个 RPC 框架,这个框架都有那些模块以及这些模块的作用. 总体设计 在我们 ...

  9. 带你手写基于 Spring 的可插拔式 RPC 框架(四)代理类的注入与服务启动

    上一章节我们已经实现了从客户端往服务端发送数据并且通过反射方法调用服务端的实现类最后返回给客户端的底层协议. 这一章节我们来实现客户端代理类的注入. 承接上一章,我们实现了多个底层协议,procoto ...

  10. 带你手写基于 Spring 的可插拔式 RPC 框架(三)通信协议模块

    在写代码之前我们先要想清楚几个问题. 我们的框架到底要实现什么功能? 我们要实现一个远程调用的 RPC 协议. 最终实现效果是什么样的? 我们能像调用本地服务一样调用远程的服务. 怎样实现上面的效果? ...

随机推荐

  1. GaussDB(for Redis)双活容灾支持4大应用场景,为业务安全保驾护航

    摘要:GaussDB(for Redis)的双活解决方案,支持同域主备.同域双主.异地主备.异地双主四大应用场景,提供了安全可靠的容灾能力. 一场火灾引发的思考 2021年3月10日,欧洲某云服务提供 ...

  2. ServiceWorker工作机制与生命周期:资源缓存与协作通信处理

    在 <web messaging与Woker分类:漫谈postMessage跨线程跨页面通信>介绍过ServiceWorker,这里摘抄跟多的内容,补全 Service Worker 理解 ...

  3. 字节跳动基于 ClickHouse 优化实践之“查询优化器”

    更多技术交流.求职机会,欢迎关注字节跳动数据平台微信公众号,回复[1]进入官方交流群 相信大家都对大名鼎鼎的 ClickHouse 有一定的了解了,它强大的数据分析性能让人印象深刻.但在字节大量生产使 ...

  4. MAC zsh:no matches found

    jimmy@MacBook-Pro bin % wsdl2java http://www.webxml.com.cn/WebServices/IpAddressSearchWebService.asm ...

  5. 【docker】运维相关名词 Iaas-Paas和Saas docker镜像设置 启动与停止常用命令 镜像相关命令 容器相关命令

    目录 上节回顾 今日内容 1 什么是Iaas-Paas和Saas 2 docker 启动设置镜像 2.1 启动与停止常用命令 3 镜像相关命令 4 容器相关命令 练习 上节回顾 # 1 flask-s ...

  6. [完整]流程解决Vue3项目搭建步骤

    Vue3项目完整搭建步骤 一. 使用vite创建vue3项目 npm init vue@latest 或者npm create vite@latest进行初始化项目并创建项目名称code,进入code ...

  7. var _ I = (*T)(nil)

    学习的时候看到这样一行代码 var _ Codec = (*GobCodec)(nil) 查了一下后,得到该语句的作用为:检查GobCodec这个结构体是否实现了Codec这个接口 空白标识符_代表变 ...

  8. 技术分享 | 不同格式标准SBOM清单横评:SPDX、CDX和DSDX

    为了保证安全性.降低开发.采购及维护的相关成本,复杂动态的现代软件供应链对软件资产透明度提出了更高的要求.使用清晰的软件物料清单(SBOM)收集和共享信息,并在此基础上进行漏洞.许可证和授权管理等,可 ...

  9. the server responded with a status of 413 (Request Entity Too Large) 解决

    前端上传文件,本地测试好的,放到服务器上出现了这个错误:the server responded with a status of 413 (Request Entity Too Large) 问题原 ...

  10. C# 从桌面右下角显示 Popup 窗口提醒

    上图演示 private void display_Click(object sender, EventArgs e) { Frm_Info.Instance().ShowForm();//显示窗体 ...