前言
Spring的好处我这里不说,地球人都知道。相信有许多小伙伴,都阅读过Spring源码,毕竟它是我们项目中使用最多的框架,没有之一。不知道大家什么感觉,起初我阅读的过程中一行一行的读,结果当然是被Spring的工厂、委派、策略、模板的各种模式整的不知所措。
后来带着目的性去阅读,比如我先定个小目标,“Spring如何获取class文件的?”这一个问题入手,开始阅读。用gradle编译好源码后,从AnnotationConfigApplicationContext的构造方法开始,一阵凌乱的Debug后,得到最终“一目了然”的方法(如下图所示)。看到这个方法后,先不用看方法体,只看入参,大概就明白了Spring是怎么做的,拿到绝对路径,再根据正则表达式,获取所有本路径下的 class 文件。然而到这个“干实事儿”的方法经历了20多次调用过程,我也是Debug了十分钟才拿到最核心的方法。
然后我开始想,既然对于新手,读起来如此费力,要不干脆写出一个简化版,首先抛开复杂的设计模式,直接实现它最核心的业务;如果能理解并能写出核心业务,再回头读源码,看其对核心业务如何封装加特技,那么到时候肯定就不慌了。
说干就干,凭着自己也对源码的理解外加参考资料,写出来了一版:github地址:git@github.com:HenryWangXin/mySpringFrameV2.git 在我写的过程中,包括类名、方法名尽量靠近源码的命名规则,只是做了简化只实现其核心功能,虽然与源码相差甚远,但是麻雀虽小五脏俱全。目前功能上实现了springMVC 和 IOC以及DI,AOP正在编写中。
由于篇幅有限这篇只介绍IOC的实现和DI。
项目目录与演示效果
0 1
项目目录介绍
0 2
演示效果
输入:http://localhost:8082/wx/query?name=wangxin;就会得到下面的结果:
项目启动
01 【application.properties】配置加载类路径
0 2
【web.xml】容器项目起始文件
03
通过maven的jetty插件启动
MVC模块简介
01
MVC如何出初始化
容器启动读取Web.xml时会初始化定义的WXDispatcherServlet类:
首先会调用WXDispatcherServlet的init方法;
Init方法中做了两件事: 1、初始化IOC容器; 2、IOC容器初始化完成后,初始MVC的9大组件。
也就是说MVC架构的初始化,是建立在IOC容器初始化完成之后的。
02
MVC模块UML时序图
由于本文着重讲初始化IOC容器,所以这里只画出MVC实现的时序图:
03
MVC架构流程简单介绍
1.用户发送请求至前端控制器DispatcherServlet。
2.DispatcherServlet收到请求调用HandlerMapping处理器映射器。
3.处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
4.DispatcherServlet通过HandlerAdapter处理器适配器调用处理器。
5.执行处理器(Controller,也叫后端控制器)。
6.Controller执行完成返回ModelAndView。
7.HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
8.DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
9.ViewReslover解析后返回具体View。
10.DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)。
11.DispatcherServlet响应用户。
IOC容器初始化
上文讲到web容器启动时,初始化了WXApplicationContext,这个类是spring-framework的核心类,没有之一;通过refresh方法和getbean方法,完成了IOC、DI、AOP的操作与衔接。这个类的singletonObjects属性是map类型的,它保存了Spring帮我们整理好的,既可以帮我们干事儿的代理,又有实现了生命周期监听事件Bean;也就是大家俗称的“Spring容器”说法的具体实现;如果把它讲清讲透,大家都理解了,这篇文章的目的也就达到了。首先我们讲个重要概念BeanDefinition。
01
WxBeanDefinition介绍
BeanDefinition主要用于保存Bean相关的配置信息,Spring初始化实例不同于正常的初始化流程,如图所示:
02
WXApplicationContext属性介绍
缓存变量说明:
private final Map<String, Object> singletonObjects //一级缓存
private final Map<String, ObjectFactory> singletonFactories//二级缓存
private final Map<String, Object> earlySingletonObjects //三级缓存
03
Refresh【初始化方法】
3.1 WxBeanDefinitionReader【扫描class,初始化Beandefinition】
3.1.1 WxBeanDefinitionReader 构造方法里进行包扫描
包扫描这里用到了一个简单的递归。
3.1.2 loadBeanDefinitions 【通过扫描到的包路径生成 BeanDefinition 的 list 】
3.2 doRegisterBeanDefinition【BeanDefinition List 转为 BeanDefinitionMap】
这么做是为了后面的getBean方法提供方便。
3.3 finishBeanFacotyInit【遍历BeandefinitionMap用 getBean方法】
遍历每个Beandefinition都调用一次 getBean方法。
3.3.1 getbean 方法完成了实例初始化, DI 和 AOP
3.3.1.1 instantiateBean 【根据条件实例化一个 instance 】
3.3.1.2 populateBean 如何进行 DI 注入
04
IOC容器初始化UML时序图
05
循环依赖问题如何解决
06
完成IOC与DI
当通过finishBeanFacotyInit方法,循环把每个Beandefinition循环执行getbean时,我们的singletonObjects 也完成了最终的初始化,到此为止IOC与DI完成。
总结
手写springframwork,其实没有想象的那么难,我相信通过我的源码,和这边文章的介绍,小伙伴们都能手写出来,对自己技术和自信心都有很高的提升,当知道了核心代码再回过头看Spring源码,一定会轻松许多。