手写模拟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工厂,延迟加载等。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
接口优化的常见方案实战总结
一、背景 针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。 二、接口优化方案总结 1.批处理 批量思想:批量操作数据库,这个很好理解,我们在循环插入场景的接口中,可以在批处理执行完成后一次性插入或更新数据库,避免多次IO。 //for循环单笔入库 list.stream().forEatch(msg->{ insert(); }); //批量入库 batchInsert(); 2.异步处理 异步思想:针对耗时比较长且不是结果必须的逻辑,我们可以考虑放到异步执行,这样能降低接口耗时。 例如一个理财的申购接口,入账和写入申购文件是同步执行的,因为是T+1交易,后面这两个逻辑其实不是结果必须的,我们并不需要关注它的实时结果,所以我们考虑把入账和写入申购文件改为异步处理。如图所示: 至于异步的实现方式,可以用线程池,也可以用消息队列,还可以用一些调度任务框架。 3.空间换时间 一个很好理解的空间换时间的例子是合理使用缓存,针对一些频繁使用且不频繁...
- 下一篇
从 Netflix 传奇看,结果导向的产品路线图如何制定?(下篇)
写在前面:本文译自 Jason Doherty、Kelsey Stevenson 和 Thomas Vela 于「2019年丹佛创业周」发表的「Outcome Based Roadmaps」主题演讲实录;原作者为 Jason Doherty。 👉如何制定产品路线图(上篇)👈中提到,今天优秀的产研团队不只关注功能的上线交付,还更加在意功能交付后是否达成了预期的结果,而结果(Outcome)是功能所产生的、可测量的、与企业目标一致的用户行为的变化。 跟随路线图制定的前四部曲,我们设立了一个产品愿景,多个目标、结果和机会。在敲下第一行代码或键入第一个词语之前,我们先明确了「什么是产品成功」,并清楚地指出实现目标所需关注的一系列事情。 你可能不信,大多数团队并不这么工作。他们实施的功能通常来自 CEO 或 SVP 在晨间沐浴时蹦出的想法——典型的 HiPPOs(Highest Paid Person's Opinion,最高薪人士意见)现象。Jez Humble 在 2012 年的一项研究中指出,超过 60% 的产品团队会通过 HiPPOs 决定待构建的事项。 产品成功被定义为「功能上线」...
相关文章
文章评论
共有0条评论来说两句吧...