手写模拟Spring底层原理-Bean的创建与获取

京东云开发者
• 阅读 345

作者:京东物流 张鼎元

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实例。 手写模拟Spring底层原理-Bean的创建与获取

创建JdApplicationContext类,提供获取Bean实例方法,通过构造函数动态初始化bean实例。 手写模拟Spring底层原理-Bean的创建与获取

3 扫描类路径并缓存BeanDefinition数据

在JdApplicationContext类初始化的时候,通过AppConfig配置类获取类的扫描路径,在扫描路径下,找到需要创建Bean的类,通过标注Component注解的类识别需要创建的Bean。

通过Component注解识别出的类,进行封装成BeanDefinition. 再缓存到beanDefinitionMap内存中。 手写模拟Spring底层原理-Bean的创建与获取

上述的代码中,我们发现创建BeanDefinition类时,封装了class类,beanName,scope三个主要属性。用于创建bean的时候,提供class类进行初始化和属性的注入,创建单例类或原型类提供数据依据。

4 初始化Bean和依赖注入

接下来,在上面的扫描操作完成后,所有待初始化的bean数据存储beanDefinitionMap中。我们只需要遍历beanDefinitionMap数据进行逐个初始化和属性的注入。 手写模拟Spring底层原理-Bean的创建与获取

上述代码中,对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可能会产生循环依赖的问题,在这里如何解决呢?

问题代码如下 : 手写模拟Spring底层原理-Bean的创建与获取 手写模拟Spring底层原理-Bean的创建与获取 上图就是循环依赖问题代码导致的异常。重复创建bean进入死循环。

在初始化bean和属性注入之间,我们可以增加二级缓存作为突破口,解决死循环问题。

userService初始化后,需要注入orderService,通过getBean方法获取,因为orderService没有在singletonBeanMap缓存中,也需要初始化并注入userService属性, 同时userService还在初始化过程中,不能缓存到singletonBeanMap缓存中。造成彼此循环等待属性的注入。为解决此问题,我们只需要设立初始化过程中缓存到creatingBeanMap中,在userService初始化过后,未进行属性注入前缓存到creatingBeanMap中,userService需要的orderService属性在创建bean实例过程中,优先从creatingBeanMap缓存中得到userService实例,来完成bean实例的创建过程。orderService完成bean实例创建后,userService也相应的完成实例创建。 手写模拟Spring底层原理-Bean的创建与获取

5 实现InitializingBean接口

在createBean过程中,我们可以对外提供初始化扩展接口InitializingBean接口。只要实现该接口,我们就可以针对bean的初始化进行扩展功能实现。 ![] 手写模拟Spring底层原理-Bean的创建与获取

6 实现BeanPostProcessor接口模拟AOP

首先创建BeanPostProcessor接口,作为所有bean实例的对外扩展接口手写模拟Spring底层原理-Bean的创建与获取创建BeanPostProcessor接口实现类,模拟AOP功能,指定userService类进行切面。 手写模拟Spring底层原理-Bean的创建与获取 在扫描类的时候,将已实现BeanPostProcessor接口类缓存到beanPostProcessorList中。 手写模拟Spring底层原理-Bean的创建与获取 通过上面的扫描,beanPostProcessorList已缓存所有的BeanPostProcessor实现类。在createBean的时候,对已创建的bean实例进行预处理扩展。 手写模拟Spring底层原理-Bean的创建与获取 通过上述代码的实现效果如下: 手写模拟Spring底层原理-Bean的创建与获取源代码:

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

7 总结

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

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
Easter79 Easter79
3年前
springcloud eureka.instance
1.在springcloud中服务的 InstanceID默认值是:${spring.cloud.client.hostname}:${spring.application.name}:${spring.application.instance\_id:${server.port}},也就是:主机名:应用名:应用端口。如图1
Wesley13 Wesley13
3年前
VBox 启动虚拟机失败
在Vbox(5.0.8版本)启动Ubuntu的虚拟机时,遇到错误信息:NtCreateFile(\\Device\\VBoxDrvStub)failed:0xc000000034STATUS\_OBJECT\_NAME\_NOT\_FOUND(0retries) (rc101)Makesurethekern
Wesley13 Wesley13
3年前
FLV文件格式
1.        FLV文件对齐方式FLV文件以大端对齐方式存放多字节整型。如存放数字无符号16位的数字300(0x012C),那么在FLV文件中存放的顺序是:|0x01|0x2C|。如果是无符号32位数字300(0x0000012C),那么在FLV文件中的存放顺序是:|0x00|0x00|0x00|0x01|0x2C。2.  
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
PHP创建多级树型结构
<!lang:php<?php$areaarray(array('id'1,'pid'0,'name''中国'),array('id'5,'pid'0,'name''美国'),array('id'2,'pid'1,'name''吉林'),array('id'4,'pid'2,'n
Wesley13 Wesley13
3年前
0、Spring 注解驱动开发
0、Spring注解驱动开发0.1简介《Spring注解驱动开发》是一套帮助我们深入了解Spring原理机制的教程;现今SpringBoot、SpringCloud技术非常火热,作为Spring之上的框架,他们大量使用到了Spring的一些底层注解、原理,比如@Conditional、@Import、@
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Easter79 Easter79
3年前
Spring如何解决循环依赖的问题
        在关于Spring的面试中,我们经常会被问到一个问题,就是Spring是如何解决循环依赖的问题的。这个问题算是关于Spring的一个高频面试题,因为如果不刻意研读,相信即使读过源码,面试者也不一定能够一下子思考出个中奥秘。本文主要针对这个问题,从源码的角度对其实现原理进行讲解。1\.过程演示        关于Spring