目标

写一个简单的 Spring MVC Controller ,GET 请求无参,返回 JSON 内容,大致搞清楚框架是如何找到我们自定义的 Controller 然后执行方法的,忽略一部分细节。

代码

使用 spring-boot-web-starter 构建 maven 项目:

<dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
      <version>2.7.2</version>
    </dependency>

编写启动类:

package org.snailgary;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

/**
 * <p>
 * </p>
 *
 * @author 蜗牛格里
 * @since 2022/8/3
 */
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

编写Controller

package org.snailgary;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

/**
 * <p>
 * </p>
 *
 * @author 蜗牛格里
 * @since 2022/8/14
 */
@RestController
public class HelloController {

    @GetMapping("/hello")
    public Map<String, String> sayHells(String name) {
        Map<String, String> map = new HashMap<String, String>();
        map.put("hello", "world");
        return map;
    }

}

启动 main 方法,通过浏览器访问:http://localhost:8080/hello 浏览器返回如图:


源码分析

idea 开启 debug,暂不分期 bean 的加载过程,从浏览器请求开始,到了下面这个方法:

org.apache.catalina.core.StandardWrapperValve#invoke


boolean unavailable = false; 这里会走到:org.apache.catalina.core.StandardWrapper#allocate 如果未初始化执行初始化相关方法(否则返回DsipatcherServlet实例),主要是下面两个:

org.springframework.web.servlet.DispatcherServlet#initHandlerMappings

org.apache.catalina.core.ApplicationFilterChain#internalDoFilter

初始化请求参数处理类、执行处理器(controller)、结果处理类(返回json、html或者文件)等,过程不做详细说明,debug 流程很长,这里只看一下我们的 Controller 在 Spring MVC 是什么样子的:


注意类:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#namingStrategy 属性,它其实在抽象类中:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#namingStrategy

这里看到了编写的 Controller 信息,请求方法 GET,路径 /hello 其中 handlerMathod 直接为全类名定位到了方法(方法签名):org.snailgary.HelloController#sayHells 后面会用到。

doFilter

在准备工作完成后,在这个 invoke 方法中最终来到了下面的方法:

filterChain.doFilter(request.getRequest(), response.getResponse());

如图的逻辑分支:


然后继续 debug 进入到下面的两个方法:

org.apache.catalina.core.ApplicationFilterChain#doFilter -->

org.apache.catalina.core.ApplicationFilterChain#internalDoFilter


internalDoFilter 方法中,最终会执行到 servlet.service(request, response); 这一行:


这其实是类:org.springframework.web.servlet.FrameworkServlet#service 的方法,最终会来到Spring MVC 的核心类:DispatcherServlet 的方法 org.springframework.web.servlet.DispatcherServlet#doService 在 doService 方法中,会执行到 org.springframework.web.servlet.DispatcherServlet#doDispatch 方法,很快就要到本次旅程的一半了。在 doDsipatch 方法中主要分三个个步骤:

  1. 获取处理器
  2. 获取适配器
  3. 执行方法

下面分开进行说明。

获取处理器

doDispatch 方法中,核心为 getHandler 方法:


org.springframework.web.servlet.DispatcherServlet#getHandler


这里又看到了自己编写的 GET hello 的字样, 信息存储对应的类为:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping 继续执行就会进入 lookupHandlerMethod 方法:org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod


List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);

继续


啊哈!一个 MAP,虽迟但到,当然跟 HashMap 不一样,这里不去深究,总之到这里,寻找 Controller 的旅程暂时告一断落,然后回到 DispatcherServlet 类里面。

获取适配器

处理器获取成功后,接下来是获取适配器:org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter


可以看到匹配到的类为:org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

从类上的注释可以看到,这个类就是为例支持 @RequestMapping 注解,就是 Controller 上的注解

/**
 * Extension of {@link AbstractHandlerMethodAdapter} that supports
 * {@link RequestMapping @RequestMapping} annotated {@link HandlerMethod HandlerMethods}.
 *
 * <p>Support for custom argument and return value types can be added via
 * {@link #setCustomArgumentResolvers} and {@link #setCustomReturnValueHandlers},
 * or alternatively, to re-configure all argument and return value types,
 * use {@link #setArgumentResolvers} and {@link #setReturnValueHandlers}.
 *
 * @author Rossen Stoyanchev
 * @author Juergen Hoeller
 * @author Sebastien Deleuze
 * @since 3.1
 * @see HandlerMethodArgumentResolver
 * @see HandlerMethodReturnValueHandler
 */

拿到适配器后揭下来就是执行方法了,

执行 Controller 方法

在 org.springframework.web.servlet.DispatcherServlet#doDispatch 中,执行方法是下面这行:

// Actually invoke the handler.
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

mv 就是 ModelAndView 对象,执行的是抽象类中的方法,最终由子类执行具体逻辑 org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter#handle -->    org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal --> org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod


到这里执行方法需要的东西已经都具备了,下面就是执行了:


// 执行方法
invocableMethod.invokeAndHandle(webRequest, mavContainer); 

org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod#invokeAndHandle --> 

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest 


--> org.springframework.web.method.support.InvocableHandlerMethod#doInvoke

Method method = getBridgedMethod();

try {

if (KotlinDetector.isSuspendingFunction(method)) {

return CoroutinesUtils.invokeSuspendingFunction(method, getBean(), args);

}

return method.invoke(getBean(), args);

}

这里的 Method 对象就是 JDK 的反射库方法对象,最终执行:java.lang.reflect.Method#invoke(java.lang.Object, java.lang.Object...) // 这就是一行很熟悉的代码了。

这时候继续 debug 就会发现代码终于进入到了 Controller 中


然后继续回来,就会看到有结果返回,后面的流程就是处理返回结果,以及返回 ModelAndView 了,就是下面的代码,致此,本次目标达成。

// 处理返回结果
this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
 org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue
 // 返回 ModelAndView 根据类型不同返回 json 或者 html 页面或者其他内容,如文件:等
 org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getModelAndView

总结

一个简单的 Get 请求,Spring MVC 的处理流程大致为以下步骤:

  1. 获取 DispatcherServlet 实例,如果为空则执行初始化方法;
  2. 获取处理器;
  3. 获取适配器;
  4. 执行适配器获取 Controller 返回结果;
  5. 返回 ModelAndView