从源码角度Debug一步一步分析Spring MVC一次接口调用的过程

源码版本

Spring-web:5.3.7

测试demo

@RestController
@RequestMapping("test/v1")
public class ZouController {


    @GetMapping("/test")
    public Student test(int a, int b) {
        System.out.println("请求进来了,开始休眠");
        System.out.println("调用成功");
        Student test = new Student(null18, LocalDateTime.now());
        test.setLocalDate(LocalDate.now());
        return test;
    }

    @PostMapping("/post/test")
    public String postTest() {
        return "Success";

    }

}

这里简单准备了一个Controller

然后使用postman调用接口localhost:8080/test/v1/test?a=1&b=2

主要目标

了解整个执行过程核心原理

源码分析

我们都知道Spring MVC的核心路口肯定是从DispatcherServletdoDispatch方法开始的,具体原因可以Google。这里不展开讲解,因为这不是本文重点

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.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null) {
     noHandlerFound(processedRequest, response);
     return;
    }

    // Determine handler adapter for the current request.
    HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

    // Process last-modified header, if supported by the handler.
    String method = request.getMethod();
    boolean isGet = HttpMethod.GET.matches(method);
    if (isGet || HttpMethod.HEAD.matches(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);
    }
   }
  }
 }

这段方法很长,我们一步一步分析。首先看这个方法

    mappedHandler = getHandler(processedRequest);

这里主要是获取一个HandlerExecutionChain这个类里面有什么属性呢?我们debug看一下

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程
在这里插入图片描述

可以看到HandlerExecutionChain对象就已经通过url获取到了对应的controller和method。所以我们想要了解Spring MVC 是如何通过url找到对应的controller和method,就必须看看是如何获取到HandlerExecutionChain对象的

获取HandlerExecutionChain对象

我们进入getHandler方法

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  if (this.handlerMappings != null) {
   for (HandlerMapping mapping : this.handlerMappings) {
    HandlerExecutionChain handler = mapping.getHandler(request);
    if (handler != null) {
     return handler;
    }
   }
  }
  return null;
 }

可以看到这里的HandlerExecutionChain 是通过List<HandlerMapping> handlerMappings遍历调用getHandler获取的,我们debug看看有多少个HandlerMapping

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程可以看到有四个HandlerMapping

  • RequestMappingHandlerMapping
  • BeanNameUrlHandlerMapping
  • RouterFunctionMapping
  • SimpleUrlHandlerMapping
  • WelcomePageHandlerMapping:

都是处理请求的处理器,比如WelcomePageHandlerMapping就是专门用来处理默认请求/的,例如:http://localhost:8080/,就会返回规定路径下的index.html。其他处理器就不一一介绍了,感兴趣可以自己去研究,今天我们要研究的重点也是最常用的RequestMappingHandlerMapping处理器。

RequestMappingHandlerMapping会将 @Controller, @RequestMapping 注解的类方法注册为 handler,每个请求url可以对应一个 handler method 来处理

RequestMappingHandlerMapping

我们看看RequestMappingHandlerMapping的类UML图

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程我们会发现RequestMappingHandlerMapping并没有自己实现HandlerMapping接口,是通过继承抽象类AbstractHandlerMapping间接实现了HandlerMapping,而方法getHandler也是直接调用AbstractHandlerMapping 中的getHandler方法,所以我们直接去分析AbstractHandlerMappinggetHandler方法即可

AbstractHandlerMapping

记住我们的主线目标如何获取到HandlerExecutionChain

public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
  Object handler = getHandlerInternal(request);
  if (handler == null) {
   handler = getDefaultHandler();
  }
  if (handler == null) {
   return null;
  }
  // Bean name or resolved handler?
  if (handler instanceof String) {
   String handlerName = (String) handler;
   handler = obtainApplicationContext().getBean(handlerName);
  }

  // Ensure presence of cached lookupPath for interceptors and others
  if (!ServletRequestPathUtils.hasCachedPath(request)) {
   initLookupPath(request);
  }

  HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

  if (logger.isTraceEnabled()) {
   logger.trace("Mapped to " + handler);
  }
  else if (logger.isDebugEnabled() && !DispatcherType.ASYNC.equals(request.getDispatcherType())) {
   logger.debug("Mapped to " + executionChain.getHandler());
  }

  if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
   CorsConfiguration config = getCorsConfiguration(handler, request);
   if (getCorsConfigurationSource() != null) {
    CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
    config = (globalConfig != null ? globalConfig.combine(config) : config);
   }
   if (config != null) {
    config.validateAllowCredentials();
   }
   executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
  }

  return executionChain;
 }

可以看到有个方法

Object handler = getHandlerInternal(request);

我们debug看看从源码角度Debug一步一步分析Spring MVC一次接口调用的过程

可以看到getHandlerInternal方法就映射到了具体的controllermethod,所以我们在深入看看getHandlerInternal方法

getHandlerInternal方法是个抽象方法,调用子类RequestMappingInfoHandlerMappinggetHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
  try {
   return super.getHandlerInternal(request);
  }
  finally {
   ProducesRequestCondition.clearMediaTypesAttribute(request);
  }
 }

但是子类又调回了父类AbstractHandlerMethodMappinggetHandlerInternal方法,所以实际获取映射的方法还是父类的getHandlerInternal方法,我们看看父类的getHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
  String lookupPath = initLookupPath(request);
  this.mappingRegistry.acquireReadLock();
  try {
   HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
   return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
  }
  finally {
   this.mappingRegistry.releaseReadLock();
  }
 }

可以看到这里这个实际其实就是用到了模板设计模式

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程通过debug我们可以很清晰的看到initLookupPath方法通过request获取到了我们请求的url:/test/v1/test,而实际通过url获取到HandlerMethod还得去看lookupHandlerMethod方法

AbstractHandlerMethodMapping lookupHandlerMethod方法

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
  List<Match> matches = new ArrayList<>();
  List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
  if (directPathMatches != null) {
   addMatchingMappings(directPathMatches, matches, request);
  }
  if (matches.isEmpty()) {
   addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
  }
  if (!matches.isEmpty()) {
   Match bestMatch = matches.get(0);
   if (matches.size() > 1) {
    Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
    matches.sort(comparator);
    bestMatch = matches.get(0);
    if (logger.isTraceEnabled()) {
     logger.trace(matches.size() + " matching mappings: " + matches);
    }
    if (CorsUtils.isPreFlightRequest(request)) {
     for (Match match : matches) {
      if (match.hasCorsConfig()) {
       return PREFLIGHT_AMBIGUOUS_MATCH;
      }
     }
    }
    else {
     Match secondBestMatch = matches.get(1);
     if (comparator.compare(bestMatch, secondBestMatch) == 0) {
      Method m1 = bestMatch.getHandlerMethod().getMethod();
      Method m2 = secondBestMatch.getHandlerMethod().getMethod();
      String uri = request.getRequestURI();
      throw new IllegalStateException(
        "Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
     }
    }
   }
   request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
   handleMatch(bestMatch.mapping, lookupPath, request);
   return bestMatch.getHandlerMethod();
  }
  else {
   return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
  }
 }

这里代码有点长,我们挑重点看,首先是this.mappingRegistry这个对象,我们看看里面有什么

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程
在这里插入图片描述

可以看到这里面有很多个RequestMappingInfo,看属性是不是很熟悉,就是我们写的controller和method,包括注解@RequestMapping上的url也在 所以如何通过url获取到对应的controller和method在这里就得到了答案,至于this.mappingRegistry中的RequestMappingInfo对象是如何初始化填充的,不在我们之类的重点,但是我们也可以很容易猜到无非就是在Spring容器启动的时候去扫描类上有@controller注解的类,然后解析方法转换为RequestMappingInfo对象

方法执行

回到我们的主线类DispatcherServletdoDispatch方法中。我们会发现在通过url获取到类和方法后,我们执行方法是在这一行执行的

// 这里获取到的 HandlerAdapter 实际为 RequestMappingHandlerAdapter
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

getHandlerAdapter 中默认有四个HandlerAdapter其中这里获取到的是RequestMappingHandlerAdapter从源码角度Debug一步一步分析Spring MVC一次接口调用的过程

最后执行方法的逻辑也就是在RequestMappingHandlerAdapter handleInternal方法

protected ModelAndView handleInternal(HttpServletRequest request,
   // 省略部分代码
   else {
   // No synchronization on session demanded at all...
   mav = invokeHandlerMethod(request, response, handlerMethod)
;
  }

其中稍微在核心一点的一个方法就是方法参数的绑定 这段代码的逻辑是在InvocableHandlerMethod中的getMethodArgumentValues方法

从源码角度Debug一步一步分析Spring MVC一次接口调用的过程
在这里插入图片描述

关于方法参数的绑定这里提一点,如果使用注解@RequestParam是直接可以获取到方法参数名字的,但是如果没有使用注解@RequestParam在jdk 1.8之前都只能通过字节码技术去获取方法参数名字。

整个MVC的核心流程到这里就结束了。

总结

其实整体流程看到是很长的,核心思路还是很清晰的。就是通过请求url找到对应的controller的method。然后执行对应的方法,然后返回数据。我们这次只是大致过了一遍整体流程,实际比较多的细节我们并没有重点分析,后续有机会在继续研究


原文始发于微信公众号(小奏技术):从源码角度Debug一步一步分析Spring MVC一次接口调用的过程

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/30016.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!