文章目录
- SpringMVC组件原理剖析
- 一、 前端控制器初始化
- 1.1 初始化SpringMVC容器
- 1.2 注册了 SpringMVC的 九大组件
- 1.3 处理器映射器初始化细节
- 二、前端控制器执行主流程
- 2.1 定位doDispatcher方法
- 2.2 验证HandlerExecutionChain
- 2.3 HandlerAdapter执行目标方法
SpringMVC组件原理剖析
主要剖析DispatcherServlet(前端控制器 )初始化的过程,还有DispatcherServlet执行主流程
一、 前端控制器初始化
DispatcherServlet初始化做了两件事情
- 获得了一个 SpringMVC 的 ApplicationContext容器
- 注册了 SpringMVC的 九大组件
1.1 初始化SpringMVC容器
前端控制器DispatcherServlet是SpringMVC的入口,也是SpringMVC的大脑,主流程的工作都是在此完成的
DispatcherServlet 本质是个Servlet,当配置了 load-on-startup 时,会在服务器启动时就执行创建和执行初始化init方法,每次请求都会执行service方法
找一下init方法,发现DispatcherServlet类中没有,就去找他爹FrameworkServlet类,爹也没有,就一直找,直到HttpServletBean类中有一个init方法
找到init方法,看到调用了一个initServletBean方法,点进去看看
initServletBean方法如下所示,发现啥也没有,说明是子类实现的,也就是FrameworkServlet类
在FrameworkServlet类中找到initServletBean方法,然后发现有下面一条语句,获取web环境下的Spring容器
点进去看看,发现创建了一个Spring的容器
还是此方法,再往下看,把Spring容器的引用作为参数放入了下面的方法中
传入Spring容器有什么作用?
将Spring容器的引用设置为SpringMVC的一个属性,通过这个地方体现出一个父子容器的关系
父子容器有什么作用?
当SpringMVC在获取Bean的时候,首先会从自己的容器中获取,如果自己容器没有的话会使用parent找到父容器,也就是Spring容器,再从里面看看有没有对应的Bean
Spring中能不能获取到SpringMVC中的Bean?
不能,因为SpringMVC是子容器
1.2 注册了 SpringMVC的 九大组件
没太屡明白141-SpringMVC框架-组件原理剖析-前端控制器初始化-注册九大组件_哔哩哔哩_bilibili
当我们把SpringMVC容器创建出来后,会执行到下面标红的语句
configureAndRefreshWebApplicationContext,配置和刷新SpringMVC容器
继续往下走,如果没有的话就进行创建
点进上图中的方法,一直点到下图
发现也有configureAndRefreshWebApplicationContext方法的调用
经过上面两个过程,不管是新创建的SpringMVC容器还是只有就有的SpringMVC容器,都会执行configureAndRefreshWebApplicationContext方法
点进方法configureAndRefreshWebApplicationContext
发现此方法中有一个refresh方法
点进refresh方法,发现最后调用了一个finishRefresh方法,完成刷新
点进去finishRefresh方法看一下,此方法中发布了一个事件
我们设置上这个事件,事件对应的监听都会执行
此时再回到FrameworkServlet类
找到下面这个方法,是一个监听器,监听的东西就是泛型ContextRefreshedEvent,也就是我们发布的事件的类型(上图)
这段FrameworkServlet.this.onApplicationEvent(event);代码会执行
为什么会被执行?
在另一个地方会发布事件
其中在FrameworkServlet类中有一个监听器就是监听上面的事件的
监听到后这段FrameworkServlet.this.onApplicationEvent(event);代码会执行
看一下onApplicationEvent方法,里面有一个onRefresh方法,
看一下onRefresh方法,但是内部什么也没写,说明是子类进行实现
看一下FrameworkServlet类的子类DispatcherServlet类中的onRefresh方法
最后完成注册SpringMVC九大组件
1.3 处理器映射器初始化细节
注册了九个组件,我们可以选一个我们比较熟悉的initHandlerMappings进行查看
这一步的操作就是看看Spring容器之中有没有类型是HandlerMapping的组件,如果有的话,matchingBeans参数就不是空了
那此时就进不去下面这个if判断了,就不会加载默认配置文件中的组件
打断点之后发现,为什么会有四个HandlerMapping类型的参数?
我们之前配置了一个注解@EnableWebMVC
或者说spring-mvc.xml文件中的
MVC注解驱动的作用有很多,会帮我们向SpringMVC中注入一些组件,其中HandlerMapping就是在这个地方注册的
上面这几句话的操作都是@EnableWebMVC注解帮我们完成的
如果我们把@EnableWebMVC注解注释掉调后,matchingBeans参数的大小就是0,最终会加载默认配置文件中的组件,就是下图框起来的
四个HandlerMapping类型的组件,我们点开看一个
但是我们只手动配置了一个拦截器,为什么会显示两个呢?
Spring本身提供了两个
再看一下MappingRegistery参数
随便点进去一个看看
二、前端控制器执行主流程
当服务器启动时,DispatcherServlet 会执行初始化操作,接下来,每次访问都会执行service方法,我们先宏观的看一下执行流程,在去研究源码和组件执行细节
2.1 定位doDispatcher方法
在DispatcherServlet类中找service方法,但是没有,那就找他爹FrameworkServlet类,发现有Service方法,但是此方法不是最原生的,最原生的方法是ServletRequest参数
再找FrameworkServlet类的爹HttpServletBean类,但是没有Service方法
再找HttpServlet类,发现有Service方法,并且是原生的(参数前面没有http)
上图中的service方法又调用了下面的service方法(这个service进行重载了)
内部根据请求方式,看看是调用dopost还是doGet
我们可以看一下doPost方法,但是此方法左边有一个小标志,说明已经被子类覆盖了
点一下小标志,然后进入到FrameworkServlet类中,并看到doPost方法
doPost方法中又调用了一个processRequest方法
processRequest方法中又调用了doService方法,但是我们发现doService是一个抽象方法,我们需要找到对应的实现
看一下doService抽象方法的具体实现,就到了DispatcherServlet类
doService方法中调用了另外一个方法doDispatch
看一下doDispatch方法,最核心的主流程就在这里
2.2 验证HandlerExecutionChain
从DispatcherServlet类中的doDispatch开始找
此方法中有一个参数HandlerExecutionChain,这个参数内部包括Interceptor、目标对象
此方法中 this.getHandler(processedRequest)的调用对HandlerExecutionChain的参数mappedHandler进行初始化
我们看一下getHandler方法是什么,如下所示
如果参数handlerMappings不是空,就对其进行循环
那handlerMappings参数是什么呢?点进去看看
发现就是最终装HandlerMapping的集合
HandlerMapping的的填充在前面1.3进行讲解了
再回到getHandler方法,其中遍历集合的时候又调用了mapping.getHandler(request)方法
我们再看一下mapping.getHandler方法是干嘛的
找对应的实现,选择第一个
然后再这个类中对应的实现又调用了getHandlerExecutionChain方法
再看一下getHandlerExecutionChain方法
最终这个chain参数就返回到doDispatcher方法,如下标红的位置
上面的过程,就是下图中标红的地方
2.3 HandlerAdapter执行目标方法
doDispatcher方法还没有完成,继续往下看
再往下走会调用getHandlerAdapter方法
继续往下走,会执行拦截器的preHandle前置方法
执行目标方法
执行后置方法
然后我们发现执行前置方法和后置方法的时候并不是HandlerAdapter对象执行的
但是执行目标方法的时候是HandlerAdapter对象执行的
然后我们可以看一下handle方法,执行目标方法
发现没有实现,我们看一下子类的实现
选择下图中的第一个
顺着截图向下走
最终到了下面这个地方
继续执行,会进入到下面标红的方法中
继续点进去
到了下面这个地方,看一下参数
上图的参数就是在我们访问时对应的参数
再点进doInvoke方法
然后发现了执行method.invoke(this.getBean(), args)方法
反射代码,最终通过反射执行目标方法