一、首先来讲解下springMVC的底层工作流程

  1、首先我们重点放在前端控制器(DispatcherServlet)

  其类图:

  因为从流程图看,用户的请求最先到达就是DispatcherServlet。他是springmvc的核心,也是中央出处理器。因此我们分析源码,先看看他是什么样的流程:通过源码可看到:他是继承FrameworkServlet,它也是springmvc提供的类,继续往下继承关系看,FrameworkServlet继承HttpServletBean,她依旧是spring提供的.最终直到他继承HttpServlet,如代码:

  1. public class DispatcherServlet extends FrameworkServlet {}
  1. public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware {}
  1. public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware {}

  而这个类他就是servlet。因此既然是Servlet类,那么他有一个最终的方法,就是service()方法,他是serlet最核心的方法。

  因此,我们在HttpServletBean类中找service方法,发现没有,我们继续往上一层FrameworkServlet类中找,发现找到了,因此spring实现该方法在这个类去实现的。

  1. protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
  3. if (httpMethod != HttpMethod.PATCH && httpMethod != null) {
  4. super.service(request, response);
  5. } else {
  6. this.processRequest(request, response);
  7. }
    }

  这里职责主要是先拿到一个请求,然后又做了一个判断请求方式。发现不是PATCH方式就去调用父类(HttpServlet)中service()方法。他去掉用父类中的service方法其实就是去调用该类中doPost(),doGet()方法,拿到不同的请求方式然后处理不同的业务。比如以FrameworkServlet的为例:

  1. protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. this.processRequest(request, response);
  3. }
  4.  
  5. protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  6. this.processRequest(request, response);
  7. }
  8.  
  9. protected final void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  10. this.processRequest(request, response);
  11. }
  12.  
  13. protected final void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  14. this.processRequest(request, response);
  15. }

  当这个方法拿到之后,他就去调用里面的方法processRequest();该方法中一些代码我们可以不用细看,主要是跟控制器有关的代码。因此:

  1. protected final void processRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
  2. long startTime = System.currentTimeMillis();
  3. Throwable failureCause = null;
  4. LocaleContext previousLocaleContext = LocaleContextHolder.getLocaleContext();
  5. LocaleContext localeContext = this.buildLocaleContext(request);
  6. RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
  7. ServletRequestAttributes requestAttributes = this.buildRequestAttributes(request, response, previousAttributes);
  8. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  9. asyncManager.registerCallableInterceptor(FrameworkServlet.class.getName(), new FrameworkServlet.RequestBindingInterceptor());
  10. this.initContextHolders(request, localeContext, requestAttributes);
  11.  
  12. try {
  13. this.doService(request, response);
  14. } catch (IOException | ServletException var16) {
  15. failureCause = var16;
  16. throw var16;
  17. } catch (Throwable var17) {
  18. failureCause = var17;
  19. throw new NestedServletException("Request processing failed", var17);
  20. } finally {
  21. this.resetContextHolders(request, previousLocaleContext, previousAttributes);
  22. if (requestAttributes != null) {
  23. requestAttributes.requestCompleted();
  24. }
  25.  
  26. this.logResult(request, response, (Throwable)failureCause, asyncManager);
  27. this.publishRequestHandledEvent(request, response, startTime, (Throwable)failureCause);
  28. }
  29.  
  30. }

  这个方法里面可以直接看到this.doService(request, response);方法。然后进去该方法,发现这个方法直接跳到DispatcherServlet 类中,由上可知,这个方法就像一直被子类重写。

  1. protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. ... ...try {
  3. this.doDispatch(request, response);
  4. } finally {
  5. if (!WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted() && attributesSnapshot != null) {
  6. this.restoreAttributesAfterInclude(request, attributesSnapshot);
  7. }
  8.  
  9. }
  10.  
  11. }

  在这个方法中,依旧如上,主要代码是 this.doDispatch(request, response);,这个方法,由此可看,代码到这里,还没有进入核心区域。然后我们进入,这个方法。才算正式进入springMNV的最核心代码区域:如下:

  1. protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HttpServletRequest processedRequest = request;
  3. HandlerExecutionChain mappedHandler = null;
  4. boolean multipartRequestParsed = false;
  5. WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
  6.  
  7. try {
  8. try {
  9. ModelAndView mv = null;
  10. Object dispatchException = null;
  11.  
  12. try {
  13. processedRequest = this.checkMultipart(request);
  14. multipartRequestParsed = processedRequest != request;
  15. mappedHandler = this.getHandler(processedRequest);
  16. if (mappedHandler == null) {
  17. this.noHandlerFound(processedRequest, response);
  18. return;
  19. }
  20.  
  21. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());
  22. String method = request.getMethod();
  23. boolean isGet = "GET".equals(method);
  24. if (isGet || "HEAD".equals(method)) {
  25. long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
  26. if ((new ServletWebRequest(request, response)).checkNotModified(lastModified) && isGet) {
  27. return;
  28. }
  29. }
  30.  
  31. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  32. return;
  33. }
  34.  
  35. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
  36. if (asyncManager.isConcurrentHandlingStarted()) {
  37. return;
  38. }
  39.  
  40. this.applyDefaultViewName(processedRequest, mv);
  41. mappedHandler.applyPostHandle(processedRequest, response, mv);
  42. } catch (Exception var20) {
  43. dispatchException = var20;
  44. } catch (Throwable var21) {
  45. dispatchException = new NestedServletException("Handler dispatch failed", var21);
  46. }
  47.  
  48. this.processDispatchResult(processedRequest, response, mappedHandler, mv, (Exception)dispatchException);
  49. } catch (Exception var22) {
  50. this.triggerAfterCompletion(processedRequest, response, mappedHandler, var22);
  51. } catch (Throwable var23) {
  52. this.triggerAfterCompletion(processedRequest, response, mappedHandler, new NestedServletException("Handler processing failed", var23));
  53. }
  54.  
  55. } finally {
  56. if (asyncManager.isConcurrentHandlingStarted()) {
  57. if (mappedHandler != null) {
  58. mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
  59. }
  60. } else if (multipartRequestParsed) {
  61. this.cleanupMultipart(processedRequest);
  62. }
  63.  
  64. }
  65. }

  由源码可得:他首先主要是创建一个视图对象 ModelAndView mv = null;然后检查当前请求是否是二进制的请求processedRequest = this.checkMultipart(request);然后就是只要代码

mappedHandler = this.getHandler(processedRequest);

  就是根据当前的请求去拿一个Handler.(在这个源码中springNVC都是使用的Handler,那么他到时是什么?这个Handler其实就是我们的控制器,包括我们写Controller)。进入这个方法源码如下:

  1. @Nullable
  2. protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  3. if (this.handlerMappings != null) {
  4. Iterator var2 = this.handlerMappings.iterator();
  5.  
  6. while(var2.hasNext()) {
  7. HandlerMapping mapping = (HandlerMapping)var2.next();
  8. HandlerExecutionChain handler = mapping.getHandler(request);
  9. if (handler != null) {
  10. return handler;
  11. }
  12. }
  13. }
  14.  
  15. return null;
  16. }

  由流程图可知,发送清求到控制器,控制器第二个节点就是发送第二个请求就是去拿Handler,因此可知这里才是最核心代码。由图可知他取Handler最终要去找HandlerMapping,然后他再去拿一个Handler。那么为什么要去找HandlerMapping去要一个Handler呢?首先我们在配置控制器的时候有两种方式1.xml方式,2.注解的方式。因此spring源码他给我们不止一种控制器 。因为两种方式控制器 。因此spring并不知道我们使用的事哪一种控制器。因为两种控制器,spring去底层去找的控制的实现方式是不一样的。因此这就是为什么第二步他要去找Handler(控制器)的了。但是Handler怎么找的到呢?就是通过HandlerMapping这样一个处理器映射器。如代码可知他首先是判断当前HandlerMappers是否为空:this.handlerMappings

  由程序运行可知:

  这是拷贝出来数组数据:

  由此可看当前HandlerMappers有两个对象也就是两个Handler。因此可知上面说的,spring不止一个控制器;相当于他来这里面找他所要的控制器。一个是xml对应的,一个是注解对应的.(注:因此这里主要以xml方式进行讲解。注解形式其实一样,只不过去匹配注解对象的HandlerMapping以及HandlerAdapter)

  那么接下来他会怎么处理呢?由源码得知,spring首先是遍历HandlerMappers,怎么遍历呢?他会问当前数组中有没有我这控制器。也就是代码中的hm对应的是当前控制器;如图:

  找到之后;他返回的是一个HandlerExecutionChain类型的Handler;如图:

  这里面封装了一个BeanController,也就是我们自己的创建controlller,如图:

  这里就是把我们的Controller分装了,目前还拿不到,通知它里面还封装一个拦截器:

  因此Handler分装了我们创建的Controller和一个拦截器。

  因此到这里我们就拿到了对应的也是最合适的Handler,然后返回中央处理器。

  然后到第二个方法:

  1. HandlerAdapter ha = this.getHandlerAdapter(mappedHandler.getHandler());

  获取控制器的适配器。也就是我们之前拿到了控制器,接下来要去执行控制器,也就是拿到控制器适配器中执行控制器。这里为什么要获取适配器呢?因为跟控制器映射器(也就是配置方式)一样。你就有不同的适配器。因此适配器也不是一个。跟我们上面Handler原理一样。

  其源码是:

  1. protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
  2. if (this.handlerAdapters != null) {
  3. Iterator var2 = this.handlerAdapters.iterator();
  4.  
  5. while(var2.hasNext()) {
  6. HandlerAdapter adapter = (HandlerAdapter)var2.next();
  7. if (adapter.supports(handler)) {
  8. return adapter;
  9. }
  10. }
  11. }
  12.  
  13. throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
  14. }

  执行debug如图:

  所以一共找到了三个适配器。因此又要匹配当前适配器跟这里面那个适配器一直。

  因此到这一步也获取到了控制器适配器。

  接下来重要方法是:

  1. if (!mappedHandler.applyPreHandle(processedRequest, response)) {
  2. return;
  3. }

  判断再往下执行的时候还要判断你有没有需要执行的拦截器。

  这个方法源码如图:

  1. boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
  2. HandlerInterceptor[] interceptors = this.getInterceptors();
  3. if (!ObjectUtils.isEmpty(interceptors)) {
  4. for(int i = 0; i < interceptors.length; this.interceptorIndex = i++) {
  5. HandlerInterceptor interceptor = interceptors[i];
  6. if (!interceptor.preHandle(request, response, this.handler)) {
  7. this.triggerAfterCompletion(request, response, (Exception)null);
  8. return false;
  9. }
  10. }
  11. }
  12.  
  13. return true;
  14. }

  这里面就是一堆拦截器。

  接下来就是适配器去执行Handler

  1. mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

  如果你有ModelAndView,就返回一个ModelAndView.然后返回给试图对象,然后把视图对象交给视图解析器,去渲染,最后响应给用户。

  因此总结,spring提供了两种HandlerMapping以及三种HandlerAdapter.他们运行匹配的关系如图:

  那么运行时他怎么能找到这些呢?spring是怎么配置提供的呢?

  其实他们在spring配置文件就已经配置好了,当springMVC初始化时就加载实例化,获取这些对象。他们是被配置在spring的SpringwebMVC架包的servlet架包中的DispatcherServlet.properties配置文件中.

            

  其配置就是我们程序获取到的对应的值,如图:

  因此这就是怎么能够去拿当前HandlerMapping 去spring中匹配,获取对应控制器处理器以及控制器适配器。

  这里是以spring5.0.8版本讲解。最新版本是spring5.1.1。因此看了下最新源码获取Handler以及适配器的方式。源码有所变更。但原理一致。他也是在spring初始化时把控制器处理器与适配器提供好。以及把Controller在对应的自定义的Controller对象名在控制器处理器中携带着,它被放在一个map中(路径为key,对象名为value);然后程序去遍历控制器处理器,通过请求路径去找到对应的处理器获取其中的Controller对象名称,最后与拦截器一起封装在HandlerExecutionChain一起返回。关于5.1.1我们可以自己去debug去探究。

  到这里:其控制器原理大致可以理解为:第一步是去找那个控制器,第二步是去执行控制器,然后返回给试图对象,然后把视图对象交给视图解析器,去渲染,最后响应给用户。

Spring源码解析 - springMVC核心代码的更多相关文章

  1. spring 源码解析

    1. [文件] spring源码.txt ~ 15B     下载(167) ? 1 springн┤┬вио╬Ш: 2. [文件] spring源码分析之AOP.txt ~ 15KB     下载( ...

  2. Spring源码解析系列汇总

    相信我,你会收藏这篇文章的 本篇文章是这段时间撸出来的Spring源码解析系列文章的汇总,总共包含以下专题.喜欢的同学可以收藏起来以备不时之需 SpringIOC源码解析(上) 本篇文章搭建了IOC源 ...

  3. Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean

    Spring源码解析之八finishBeanFactoryInitialization方法即初始化单例bean 七千字长文深刻解读,Spirng中是如何初始化单例bean的,和面试中最常问的Sprin ...

  4. Spring源码解析 - AbstractBeanFactory 实现接口与父类分析

    我们先来看类图吧: 除了BeanFactory这一支的接口,AbstractBeanFactory主要实现了AliasRegistry和SingletonBeanRegistry接口. 这边主要提供了 ...

  5. Spring源码解析——循环依赖的解决方案

    一.前言 承接<Spring源码解析--创建bean>.<Spring源码解析--创建bean的实例>,我们今天接着聊聊,循环依赖的解决方案,即创建bean的ObjectFac ...

  6. Spring源码解析-ioc容器的设计

    Spring源码解析-ioc容器的设计 1 IoC容器系列的设计:BeanFactory和ApplicatioContext 在Spring容器中,主要分为两个主要的容器系列,一个是实现BeanFac ...

  7. AspNetCore3.1_Secutiry源码解析_2_Authentication_核心对象

    系列文章目录 AspNetCore3.1_Secutiry源码解析_1_目录 AspNetCore3.1_Secutiry源码解析_2_Authentication_核心项目 AspNetCore3. ...

  8. Spring源码解析之BeanFactoryPostProcessor(三)

    在上一章中笔者介绍了refresh()的<1>处是如何获取beanFactory对象,下面我们要来学习refresh()方法的<2>处是如何调用invokeBeanFactor ...

  9. Spring源码解析之ConfigurationClassPostProcessor(二)

    上一个章节,笔者向大家介绍了spring是如何来过滤配置类的,下面我们来看看在过滤出配置类后,spring是如何来解析配置类的.首先过滤出来的配置类会存放在configCandidates列表, 在代 ...

随机推荐

  1. Qt编写自定义控件20-自定义饼图

    前言 上次在写可视化数据大屏电子看板项目的时候,为了逐步移除对QChart的依赖(主要是因为QChart真的太垃圾了,是所有Qt的模块中源码最烂的一个,看过源码的人没有一个不吐槽,不仅不支持10W级别 ...

  2. charles-mock数据的方法(有空自己写一个)

    https://www.jianshu.com/p/75d24f264ce2 这个也可以参考下 https://www.cnblogs.com/kaibindirver/p/9104996.html ...

  3. 使用 Metrics.net + influxdb + grafana 搭建项目自动化监控和预警方案

    版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明.本文链接:https://blog.csdn.net/qq_25042791/article/d ...

  4. WordPress创建多个page页面模板文件

    一般我们使用WordPress创建多个page页面模板文件,有两种方法: 一种是,创建page-$id.php文件 这样的文件是通过WordPress默认的链接查询来创建page页面模板文件,就是使用 ...

  5. 【DSP开发】CCS数据格式 load

    CCS支持的.dat文件详解(转载于hellodsp) CCS支持的.dat文件的格式为: 定数 数据格式 起始地址 页类型 数据块大小 1651 其后是文件内容,每行表示一个数据. 定数固定为&qu ...

  6. MemCache在.NET中使用Memcached.ClientLibrary详解

    本文说明:memcached分布式缓存的负载均衡配置比例,数据压缩,socket的详细配置等,以及在.net中的常用方法 首先下载客户端的3个dll,ICSharpCode.SharpZipLib.d ...

  7. zTree入门使用

    简单入门使用,熟悉其功能,没有与异步调用后台数据,用的是本地设置的数据. zTree的API:http://www.treejs.cn/v3/api.php 源码:https://github.com ...

  8. LoadRunner编程之文件的操作

    这篇文章主要写下LoadRunner下如何进行文件的操作. 1,文件的声明 LoadRunner不支持FILE数据类型,所以在LoadRunner中用int来声明一个文件: int MyFile; 2 ...

  9. 粒子群算法(PSO)

    这几天看书的时候看到一个算法,叫粒子群算法,这个算法挺有意思的,下面说说我个人的理解: 粒子群算法(PSO)是一种进化算法,是一种求得近似最优解的算法,这种算法的时间复杂度可能会达到O(n!),得到的 ...

  10. LC 20 Valid Parentheses

    问题 Given a string containing just the characters '(', ')', '{', '}', '[' and ']', determine if the i ...