SpringMvc源码分析和手写Mvc

先简单看下代码结构,springmvc是springframework的子项目,gradle中只依赖了外部的servlet,且是provided,我将他理解成纯粹的基于servlet的web框架

servlet的核心在于网络请求的分发,可以理解成web请求的uri和具体的处理方法的映射关系,可以按照流程划分成两部分,一部分做映射关系的初始化,另一部分是根据具体的http请求找到处理的方法,反射执行,返回结果。

springframework 5.0.x

那么springmvc核心就是继承HttpServlet,看下类图

DispatcherServlet类是核心,除了极少部分的工作在FrameworkServlet或者HttpServletBean中做,绝大多数核心业务在DispatcherServlet

核心过程1--初始化

我本以为他的init会在启动项目的时候直接执行,调试发现他会在第一次接收到http请求时执行onRefresh方法,这个调用的开关放在FrameworkServlet里

FrameworkServlet

DispatcherServlet

在initStrategies方法中去执行servlet需要的对象,红框之外的都是一些注册文件处理器,多语言处理器,主题处理,异常处理,视图处理等,最核心的是红框内

initHandlerMappings 注册路径方法映射关系,initHandlerAdapters 注册处理方法的适配器

mapping的获取不是自己去扫描找,而是直接在context拿到HandlerMapping 类型的对象,所以这个mapping的创建是在初始化bean的时候做的

如果是获取所有的mapper,现在一共有5种mapping,也就是在注册映射时 springmvc 把它分了几类,我了解的就RequestMappingHandlerMapping

和beanNameUrlHandlerMapping,比如RequestMappingHandlerMapping就是按照Controller注解的方式来注册

这个HandlerMapping 接口的getHandler方法返回一个HandlerExecutionChain 对象,里面包含了对应的Method和bean

到这,初始化的节点结束;

核心过程2 --响应请求

一个http请求到servlet,他会根据是否已经初始化选择是否执行onRefresh,一般会根据不同的请求来执行具体的doGet/doPost等

但是不管是什么都会进入到doService

doService里面调用doDispatch,不挂截图,挂代码,解释在注释

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
   HttpServletRequest processedRequest = request;
   HandlerExecutionChain mappedHandler = null;
   boolean multipartRequestParsed = false;

   WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);

   try {
      ModelAndView mv = null;
      Exception dispatchException = null;

      try {
          // 这里是针对二进制文件,比如图片视频的处理
         processedRequest = checkMultipart(request);
         multipartRequestParsed = (processedRequest != request);

         // Determine handler for the current request.
         // 这里是根据当前的请求去匹配上面说的5种mapping中的哪一种,再使用mapping找到当前的path对应的HandlerExecutionChain
         mappedHandler = getHandler(processedRequest);
         if (mappedHandler == null) {
            noHandlerFound(processedRequest, response);
            return;
         }

         // Determine handler adapter for the current request.
         // 用mapping.getHandler拿到HandlerMethod,再从适配器mapping中选择合适的adapter
         HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

         // Process last-modified header, if supported by the handler.
         String method = request.getMethod();
         boolean isGet = "GET".equals(method);
         // 这里是Get缓存上次的请求
         if (isGet || "HEAD".equals(method)) {
            long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
            if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
               return;
            }
         }

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

         // Actually invoke the handler.
         //这是执行的核心,使用适配器反射执行
         mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

         if (asyncManager.isConcurrentHandlingStarted()) {
            return;
         }

         applyDefaultViewName(processedRequest, mv);
         mappedHandler.applyPostHandle(processedRequest, response, mv);
      }
      catch (Exception ex) {
         dispatchException = ex;
      }
      catch (Throwable err) {
         // As of 4.3, we're processing Errors thrown from handler methods as well,
         // making them available for @ExceptionHandler methods and other scenarios.
         dispatchException = new NestedServletException("Handler dispatch failed", err);
      }
      processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
   }
   catch (Exception ex) {
      triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
   }
   catch (Throwable err) {
      triggerAfterCompletion(processedRequest, response, mappedHandler,
            new NestedServletException("Handler processing failed", err));
   }
   finally {
      if (asyncManager.isConcurrentHandlingStarted()) {
         // Instead of postHandle and afterCompletion
         if (mappedHandler != null) {
            mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
         }
      }
      else {
         // Clean up any resources used by a multipart request.
         if (multipartRequestParsed) {
            cleanupMultipart(processedRequest);
         }
      }
   }
}

拿到adapter之后,HandleAdapter接口下适配当前的adapter,执行handleInternal 

 

最终执行invoke,得到返回值是在ServletInvocableHandlerMethod

 

实际上最终执行就是一个method.invoke(bean,args),bean在HandlerMethod能拿到,args从request拿到

 

执行完之后,会执行接下来的响应返回,异常处理,拦截器处理等等,不过mvc的核心流程已经走完了。

手写一个简单的mvc

思路梳理一下

1.定义好需要的注解,包扫描注解,依赖注入,路径注解,参数

初始化过程:

2.扫描包(这里可以写成从xml读)

3.bean实例化

4.依赖注入

5.生成路径方法映射关系

处理请求:

6.根据请求path找到method

7.根据method所属class找到实例bean

8.处理method的参数列表

9.执行method.invoke(bean,args)

这里我写的处理请求只处理到get,其他的原理类似

其他倒没什么,处理参数列表的时候注意一点,如果我们是用注解定义的参数名,那么可以从注解的value拿到;如果不是,那么可能需要利用反射去获取函数的参数名,

java8之前都是用spring-core的LocalVariableTableParameterNameDiscoverer,java8之后新增Parameter类,可以Parameter.getName获取参数名,但是调试的时候会发现拿到的是args0之类的,这是因为javac生成class文件的时候,参数信息默认是不保存的,可以javac -parameters来做(我在idea2019里加了参数也不好用,拿到的还是args0,索性定义了注解)

贴一个demo用例

 

代码结构

具体的代码我放在Github上,欢迎Star和issue

https://github.com/wjkcoder/phorcys-mvc

说下怎么跑的,Jetty

用springboot用的多了,都忘了需要配置tomcat;用物理的又很麻烦,用tomcat-plugin也有问题,我选择了jetty,更快

<plugin>
  <groupId>org.eclipse.jetty</groupId>
  <artifactId>jetty-maven-plugin</artifactId>
  <version>9.4.32.v20200930</version>
</plugin>

pom里加个依赖,加个mvn的启动参数就ok

 

好了,就分享到这里,手撕源码少有的快乐!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值