本文将对Spring IoC容器的仿写进行剖析,梳理仿写过程中需要解决的问题,并附全部仿写代码进行说明教学,本文的仿写中不引用任何外部类,纯手动实现简要IoC控制反转执行流程。
想要顺利并且理解的仿写一个IoC框架,在动手前应该先理清要解决的问题,或者说IoC框架的执行流程。
笔者将IoC执行流程大致归纳为了以下几个步骤:
- 对指定包路径进行扫描,找出所有添加了IoC注解的目标类。
- 获取目标类的信息,Class对象以及类名beanName,并将其封装以便后续使用。
- 根据封装好的信息类动态创建bean对象。
- bean的自动装载。
以上四步是IoC容器仿写的大概流程,也基本是Spring IoC运作的大致流程,下文将对具体的工作进行介绍和分析。
本部分代码解释说明性质的文字都包含在注释中,重点关注代码注释部分。
准备测试类
首先准备测试类,非必须,本部分可以跳过,读者仿写时可自行设计,笔者仿写完整测试类代码如下(为便于理解,给出的是未添加任何注解版本):
User类:
Role类:
Relationship类:
准备IoC容器入口类
准备一个AnnotationApplicationContext,在本类中完成上文中阐述的四个步骤,实际分步骤测试时,直接准备一个主类在其main方法下创建AnnotationApplicationContext对象测试即可。
AnnotationApplicationContext类结构如下(省略了方法内的内容,下文中会给出):
首先要进行扫描包,此步骤只负责将目标路径下的所有类,即所有.class文件保存,以便后续使用,判断类是否有IoC注解的步骤整合到下一步。
笔者仿写时单独准备了一个工具类专门用于包扫描,工具类PackageScanner代码如下:
扫包工具类总结
在工具类PackageScanner中
- getClasses(String)方法起预处理作用。负责将类名转换为包名,并在判断传入包名是本地文件后调用findLocalClasses(String)。
- findLocalClasses(String)方法实际扫描包并保存.class文件。本方法递归调用,遇到文件夹则继续调用查找,将所有.class文件保存到一个Set集合中。
关于File部分的说明
非核心部分,只是为了更好理解代码,可忽略。
文中使用了File类中包含的方法listFiles,该方法需要传入一个文件拦截器FileFilter,并且返回一个File[]数组,本文中没有接受该数组,因为在判断过程中就已经可以将.class文件加入Set集合了。
FileFilter是一个接口,接口中只有一个accept方法需要实现,该方法返回值类型为boolean,重写accept方法时需要写出判断逻辑,因为File.listFiles方法就是依靠accept方法的返回值来判断文件是否要添加到File[]数组中。本文直接在accept中判断文件类型,如果为文件夹则递归调用findLocalClasses(String)方法向下查找,如果是.class文件则添加到Set集合。
File中的listFiles方法源码如下:
本部分操作均在AnnotationApplicationContext的getBeanDefinitions(String)方法下完成。
@Component注解
此处模仿Spring IoC,在目标路径下,打上了@Component注解注解的类表示该类交由IoC容器管理,在SpringBoot中经常使用的@Service、@Repository等注解与本文实现的原理大致相同,只是针对不同功能的类有不同的处理,本文只实现@Component。
笔者仿写@Component注解中value值设置了默认值,为的就是在使用@Component注解时,可以使用@Component(“xxxx”)指定beanName,也可以直接使用@Component使用默认值,直接使用时会将类名首字母小写作为默认beanName,处理详见下文。
@Component注解代码如下:
使用示例如下:
BeanDefinition类
BeanDefinition类用于保存每个受管对象的Class对象和类名beanName。
代码如下:
将目标类封装为BeanDefinition
getBeanDefinitions(String)方法中会先调用扫包工具类获取目标路径下的所有.class文件的Class对象,并使用反射技术获取每个Class类对象的注解,如果发现包含@Component注解,说明该对象应交给IoC容器管理,将Class和beanName封装成BeanDefinition添加到Set集合,最终方法返回Set集合。

getBeanDefinitions(String)方法代码如下:
我们现在获取了目标路径下的所有受管对象的必要信息,现在可以开始根据这些信息创建bean对象了,本部分操作全部在AnnotationApplicationContext的createObject(Set<BeanDefinition>)方法下完成。
@Value注解
在创建bean对象时,开发人员期待可以对对象进行初始化操作,仍然通过注解+反射实现这种操作, 详细见下文。
@Value注解代码如下:
@Value注解代码中可以窥见一个问题,就是我们的value值是被写死为String的,因此处理时需要判断目标字段的类型并进行正确的类型转换。
使用示例如下:
创建bean对象
实现过程中,由于只探讨/仿写IoC核心部分,笔者对两个细枝末节的地方进行了简化。
- 默认bean对象均使用单例模式。
- 对于@Value注解,只处理了Long的类型转换,并且没有添加任何关于传入值的判断,期望使用人员每次传入的值都是合理正确的。Long类型除外的类型转换与本文的Long类型处理完全一致。
createObject(Set<BeanDefinition>)方法代码如下:
到了这一步,我们成功创建了目标类的对象,并且为部分字段做了初始化处理,bean对象已经接近可以使用了,此部分需要解决最后一个问题,即将bean注入到其他bean中,bean的自动装载。翻译成人话就是,我们的bean对象中可能会引用其他的bean对象,我们需要对这部分对象进行初始化处理(注入)。
Spring中提供了两种方法进行bean注入
- byName:顾名思义就是直接通过指定beanName注入。
- byType:顾名思义就是查找匹配的类型进行bean注入。
本部分操作全部在AnnotationApplicationContext的autowireObject(Set<BeanDefinition>)方法下完成。
@Qualifier注解
先从较为简单的byName入手,下文方法代码内读者也可以先挑读Qualifier部分。
@Qualifier注解代码如下:
使用示例如下:
@Autowired注解
@Autowired注解模仿Spring IoC添加一个判断是否必要注入的字段required,默认false不需要。
@Autowired注解代码如下:
使用示例如下:
自动装载
autowireObject(Set<BeanDefinition>)方法代码如下:
至此我们的IoC框架的简单仿写已经彻底完成,从零手动实现了控制反转,下面演示代码的测试。

最总测试时entity类下三个用于测试的模拟类代码如下:
User类:
Role类:
Relationship类:
Main类代码准备如下:
打印结果如下:
由打印结果可见我们从零仿写的IoC成功完成了对象的管理、注入等工作,除展示的测试部分除外,剩余各部分代码笔者均已测试无误。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/160433.html