手写Spring Framework新实践

237 阅读5分钟
原文链接: mp.weixin.qq.com
前言

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。

项目目录与演示效果
    01 项目目录介绍

    02 演示效果

输入:http://localhost:8082/wx/query?name=wangxin;就会得到下面的结果:

    项目启动 01【application.properties】配置加载类路径

    02 【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源码,一定会轻松许多。