Skip to content

Spring MVC

本文介绍Spring MVC中的关键组件与流程,并会尝试自己实现一个简陋的MVC框架。

1. 前置知识

如果我们要开发WEB应用,那么就需要网络连接,即一台设备可以发送数据到另一台设备,这涉及网络通信,涉及的网络模型是TCP/IP 模型。

在Java中,我们可以通过ServerSocket来监听TCP连接,但是,从传输层到应用层,需要进行数据转换,也就是说,从传输层的数据包转换为应用层特定协议的数据,这个工作是需要完成的。

好在对于应用层的HTTP协议,市面上有很多软件完成了这一份工作,即将TCP的数据包转换为HTTP请求,比如Tomcat。

之后呢,Tomcat拿到转换完成后的HTTP请求,该交给谁呢?这时,各种各种的应用程序(业务程序)就出场了,Tomcat会将请求交给对应的应用程序,这些应用程序都有一个特点,那就是实现了Servlet接口。

java
package jakarta.servlet;

import java.io.IOException;

public interface Servlet {

    void init(ServletConfig config) throws ServletException;

    ServletConfig getServletConfig();

    void service(ServletRequest req, ServletResponse res) throws ServletException, IOException;

    String getServletInfo();

    void destroy();
}

Servlet中的方法service()就是我们要写业务代码的地方,也是由Servlet容器(Tomcat)调用的方法,其中参数ServletRequest reqServletResponse res都是由Tomcat传给我们的,并且都是HTTP相关的具体实现类,req表示HTTP请求,res表示HTTP响应。

Spring MVC就是实现了Servlet,并在其中根据路径匹配等规则,找到我们写的Controller中的方法,反射调用,并为我们封装返回值。

2. 请求到响应总体流程

根据上面的分析,请求和响应是由Servlet为我们封装好的,我们只需要操作请求和响应就可以了。

从请求到响应,主要流程如下:

  • 根据请求方法和请求路径,找到匹配的处理器方法;
  • 从请求中解析出参数,解析参数涉及类型转换;
  • 反射调用处理器方法,传入第二步解析出来的参数;
  • 获取第三步的返回值,处理返回值;
  • 由于WEB技术涉及视图渲染,所以可以通过返回值渲染视图(也就是前端界面);

3. 模拟实现

本小节将按照以上流程,模拟实现MVC框架。

3.1 模拟实现Tomcat

com.sun.net.httpserver.HttpServer是Java提供的轻量级HTTP服务器,我们可以用它来模拟Tomcat容器,从而帮助我们获取HTTP请求,并返回响应。

基础用法如下:

java
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;

import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;

public class SimpleHttpServer {

    public static void main(String[] args) throws IOException {
        // 创建一个 HTTP 服务器,监听在本地的 8080 端口
        HttpServer server = HttpServer.create(new InetSocketAddress(8080), 0);

        // 设置上下文路径 "/hello",当访问 http://localhost:8080/hello 时会触发 MyHandler
        server.createContext("/hello", new MyHandler());

        // 设置默认的执行器,或者使用默认的线程池
        server.setExecutor(null); // 使用默认的线程池

        // 启动服务器
        server.start();
        System.out.println("服务器已启动,监听端口 8080");
        System.out.println("请在浏览器中访问 http://localhost:8080/hello");
    }

    // 处理 HTTP 请求的处理器
    static class MyHandler implements HttpHandler {
        @Override
        public void handle(HttpExchange exchange) throws IOException {
            String response = "Hello from Simple Java HTTP Server!";
            // 设置响应头,告知客户端响应类型和内容长度
            exchange.sendResponseHeaders(200, response.length());
            // 获取响应输出流,写入响应内容
            OutputStream os = exchange.getResponseBody();
            os.write(response.getBytes());
            os.close(); // 关闭输出流
        }
    }
}

关于HttpServer.create()第二个参数的说明

第二个参数 0 代表的是 backlog,它指的是操作系统为 TCP 服务器 维护的一个队列的长度。这个队列用来存放那些已经完成了 TCP 三次握手,但服务器应用程序还没有来得及调用 accept() 方法接受的客户端连接。

简单来说:

  1. 当一个客户端尝试连接到服务器时,它会首先与服务器进行 TCP 三次握手,建立一个 TCP 连接。
  2. 握手完成后,这个连接就处于一个“已完成但未被接受”的状态。
  3. 这些已完成的连接会被放到一个队列里等待服务器应用程序处理。这个队列的长度就是 backlog

如果将 backlog 设置为 0,这通常意味着使用系统默认的 backlog 值。具体的默认值取决于操作系统和配置,但它通常是一个合理的小整数。

如果将 backlog 设置为一个正整数,比如 50,那么这个队列最多可以容纳 50 个已完成但未被接受的连接。

有了HttpServer,那么我们就可以将所有请求分发到一个类中(参考Spring MVC,我们也叫DispatcherServlet,但是并没有实现Servlet接口,因为我们的容器没有实现Servlet那一套接口),由该类进行后续的工作,并且该类通用处理方法接受HttpExchange作为参数。

3.2 模拟实现DispatcherServlet

DispatcherServlet类是主要的类,主要完成以下工作:根据请求方法和路径找到处理器方法,从请求中解析出参数,通过反射调用处理器方法,最后处理返回值,如果有必要,进行视图渲染。

3.2.1 缓存处理器方法

首先,我们先定义处理器方法:

java
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class InvocableHandlerMethod {
  	// 处理器对象
    private Object handler;
  	// 处理器方法
    private Method method;

    public InvocableHandlerMethod(Object handler, Method method) {
        this.handler = handler;
        this.method = method;
    }

    public Object invoke(Object... args){
        try {
            return method.invoke(handler, args);
        } catch (IllegalAccessException | InvocationTargetException e) {
            throw new RuntimeException(e);
        }
    }
  
    public Method getMethod(){
        return this.method;
    }
}

很简单,就是一个Method反射方法和一个对象bean,然后有一个invoke(Object... args)方法反射调用处理器方法。

然后,为了标注哪些方法是处理器方法,我们需要在方法上添加注解@RequestMapping,其中定义了该方法可以处理哪个请求:

java
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequestMapping {
    // 请求方法
    HttpMethod method() default HttpMethod.GET;
    // 请求路径
    String value();
}
java
// 简单起见,这里只定义了两种HTTP请求方式
public enum HttpMethod {
    GET("get"),
    POST("post");

    private String value;

    public String getValue() {
        return value;
    }

    private HttpMethod(String value){
        this.value = value;
    }
}

最后,我们在DispatcherServlet的构造方法中,传入处理器类(Controller),并从这些处理器类中找到处理器方法并缓存:

java
public class DispatcherServlet {

    private HashMap<String, InvocableHandlerMethod> handlerMethodHashMap = new HashMap<>();

    /**
     * 构造方法,其中主要工作有缓存处理器方法
     * @param controllers 处理器类
     */
    public DispatcherServlet(Class... controllers) {
        // 解析controller中的处理方法
        initHandlerMethod(controllers);
    }

    private void initHandlerMethod(Class... controllers){
        if(controllers != null){
            for (Class controller : controllers) {
                Method[] methods = controller.getMethods();
                if(methods == null || methods.length == 0){
                    continue;
                }

                Object bean = null;
                for (Method method : methods) {
                    // 判断方法上是不是有 @RequestMapping 注解
                    RequestMapping requestMapping = method.getAnnotation(RequestMapping.class);
                    if(requestMapping != null){
                        if(bean == null){
                            try {
                                bean = controller.getDeclaredConstructor().newInstance();
                            }catch (Exception e){
                                break;
                            }
                        }

                      	// 缓存处理器方法,以HTTP请求方式+路径作为key,以InvocableHandlerMethod作为value
                        String key = String.format("%s %s",
                                requestMapping.method().getValue(),
                                requestMapping.value());

                        InvocableHandlerMethod invocableHandlerMethod = new InvocableHandlerMethod(bean, method);
                        handlerMethodHashMap.put(key, invocableHandlerMethod);
                    }
                }
            }
        }
    }
}

3.2.2 doDispatch()方法

我们统一将请求分发到DispatcherServlet.doDispatch(HttpExchange exchange)中进行处理:

java
public static void main( String[] args ) throws IOException {
    HttpServer httpServer = HttpServer.create(new InetSocketAddress(8888), 0);

    DispatcherServlet dispatcherServlet = new DispatcherServlet(UserController.class);
  	// "/" 表示所有路径都会进入到doDispatch()方法
    httpServer.createContext("/", exchange -> dispatcherServlet.doDispatch(exchange));

    httpServer.start();
    System.out.println("启动成功");
}

DispatcherServlet.doDispatch(HttpExchange exchange)中,主要完成以下内容:

java
public void doDispatch(HttpExchange exchange) throws IOException {

    try{
        // 找到处理器方法

        // 解析参数

        // 反射调用处理器方法

        // 处理返回值

        // 如有必要,渲染视图

    }catch (Exception e){
        // 异常处理
      FrameworkException frameworkException = null;
      if(!(e instanceof FrameworkException)){
          frameworkException = new FrameworkException(500, e.getMessage());
      }else{
          frameworkException = (FrameworkException) e;
      }

      handleException(exchange, frameworkException);
    }
}

由于在上述过程可能会发生错误,所以先定义一个框架异常:

java
public class FrameworkException extends Exception{
    private int code;
    private String message;

    public FrameworkException(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() {
        return code;
    }

    @Override
    public String getMessage() {
        return message;
    }
}

然后统一处理异常方法:

java
private void handleException(HttpExchange exchange, FrameworkException exception){
    try {
        byte[] bytes = exception.getMessage().getBytes(StandardCharsets.UTF_8);

        exchange.sendResponseHeaders(exception.getCode(), bytes.length);
        exchange.getResponseBody().write(bytes);
        exchange.getResponseBody().flush();
        exchange.getResponseBody().close();
    } catch (IOException e) {
        exchange.close();
    }
}

3.2.3 查找处理器方法

由于之前已经缓存了处理器方法,所以只需要根据缓存规则,从Map中查找即可:

java
private InvocableHandlerMethod getHandlerMethod(HttpExchange exchange){
    String key = String.format("%s %s",
            exchange.getRequestMethod().toLowerCase(),
            exchange.getRequestURI().getRawPath()
    );

    InvocableHandlerMethod invocableHandlerMethod = handlerMethodHashMap.get(key);
    return invocableHandlerMethod;
}

3.2.4 解析参数

为了简单期间,假设要解析的参数上加了@RequestParam注解,并且只从请求参数中获取:

java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface RequestParam {
    String value();
}

解析方法如下:

java
// 获取处理器方法的参数
private Object[] resolveArgs(HttpExchange exchange, InvocableHandlerMethod handlerMethod){
    Parameter[] parameters = handlerMethod.getMethod().getParameters();
    if(parameters == null || parameters.length == 0){
        return null;
    }

    Object[] result = new Object[parameters.length];

    Map<String, String> paramsFromRequest = resolveParamsFromRequest(exchange);
    for (int i = 0; i < parameters.length; i++) {
        Parameter parameter = parameters[i];
        RequestParam requestParam = parameter.getAnnotation(RequestParam.class);
        if(requestParam != null){
            String paramName = requestParam.value();
            String v = paramsFromRequest.get(paramName);
            result[i] = v;
        }
    }

    return result;
}

// 从请求中获取请求参数
private Map<String, String> resolveParamsFromRequest(HttpExchange exchange){
    Map<String, String> result = new HashMap<>();

    String query = exchange.getRequestURI().getQuery();
    if(query == null || query.isEmpty()){
        return result;
    }

    String[] split = query.split("&");
    for (String s : split) {
        String[] kv = s.split("=");
        if(kv.length == 2){
            result.put(kv[0], kv[1]);
        }
    }

    return result;
}

3.2.5 反射调用

找到处理器方法,并且从请求中解析出实际的参数后,就可以反射调用处理器方法了:

java
// 找到处理器方法
InvocableHandlerMethod invocableHandlerMethod = getHandlerMethod(exchange);
if(invocableHandlerMethod  == null){
    throw new FrameworkException(404, "no mapping handler");
}

// 解析参数
Object[] args = resolveArgs(exchange, invocableHandlerMethod);

// 反射调用处理器方法
Object returnValue = invocableHandlerMethod.invoke(args);

3.2.6 处理返回值

当拿到处理器方法返回值后,如何将返回值返回给前端,分为两阶段:

  • 在前后端不分离的项目中,一般是返回视图,也就是HTML页面,之后浏览器就可以直接渲染了,那么就需要后端处理,将返回值放入HTML页面,涉及的技术有JSP、freemarker等;
  • 在前后端分离项目中,一般是直接返回数据,需要进行序列化。在实际开发中,我们经常将Java对象转换为Json字符串,当然,也可以转换为XML或其他格式。

我们使用freemarkerjackson库,分别作为视图渲染和json序列化支持:

xml
<dependency>
  <groupId>org.freemarker</groupId>
  <artifactId>freemarker</artifactId>
  <version>2.3.32</version>
</dependency>
<dependency>
  <groupId>com.fasterxml.jackson.core</groupId>
  <artifactId>jackson-databind</artifactId>
  <version>2.19.1</version>
</dependency>

这里我们将返回值统一处理为ModelAndView

java
public class ModelAndView {
    private String viewName;
    private Map<String, String> model;

    public String getViewName() {
        return viewName;
    }

    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    public Map<String, String> getModel() {
        return model;
    }

    public void setModel(Map<String, String> model) {
        this.model = model;
    }
}

如果返回的ModelAndView不为空,则表示需要进行渲染,返回HTML格式,如果返回的ModelAndView为空,则进行序列话,返回Json格式数据。

首先定义两个注解:

java
// 表示返回格式为 Json
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ResponseBody {
}
java
// 表示返回格式为 
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface View {
    // 视图名
    String name();
}

当处理器方法上标注了@Requestbody注解时,表示返回Json;当处理器方法上标注了@View注解时,表示返回HTML;当什么注解都没标注时,默认返回HTML。

返回值处理方法如下:

java
private ModelAndView handleReturnValue(Object returnValue, 
                                       InvocableHandlerMethod handlerMethod,
                                       HttpExchange exchange) throws IOException {

    ResponseBody responseBody = handlerMethod.getMethod().getAnnotation(ResponseBody.class);
    View view = handlerMethod.getMethod().getAnnotation(View.class);
    if(responseBody == null || view != null){
        // 需要进行视图渲染
        ModelAndView modelAndView = new ModelAndView();
        // view不为空,则视图名从view中获取,否则默认取方法名
        modelAndView.setViewName(view != null ? view.name() : handlerMethod.getMethod().getName());
        modelAndView.setModel(getObjectPropertyValues(returnValue));
        return modelAndView;
    }else{
        // 不需要进行视图渲染,直接写出响应
        ObjectMapper objectMapper = new ObjectMapper();
        String data = objectMapper.writeValueAsString(returnValue);
        byte[] bytes = data.getBytes(StandardCharsets.UTF_8);

        exchange.sendResponseHeaders(200, bytes.length);
        exchange.getResponseBody().write(bytes);

        return null;
    }
}

// 反射获取返回值属性值,并封装进map中
// 简单起见,这里假设object就是简单对象
private Map<String, String> getObjectPropertyValues(Object object){
    Map<String, String> result = new HashMap<>();

    Field[] declaredFields = object.getClass().getDeclaredFields();
    for (Field field : declaredFields) {
        field.setAccessible(true);

        try {
            String fieldName = field.getName();
            Object value = field.get(object);

            result.put(fieldName, value == null ? "" : value.toString());
        } catch (IllegalAccessException e) {
        }
    }

    return result;
}

3.2.7 视图渲染

ModelAndView对象不为空,需要进行视图渲染:

java
private void render(ModelAndView modelAndView, HttpExchange exchange) throws IOException, TemplateException {
    // 创建 FreeMarker 配置实例
    Configuration cfg = new Configuration(Configuration.VERSION_2_3_32);

    // 设置模板文件所在的目录
    URL resource = this.getClass().getClassLoader().getResource("templates");
    cfg.setDirectoryForTemplateLoading(new File(resource.getPath()));

    // 设置默认编码
    cfg.setDefaultEncoding("UTF-8");

    // 获取模板
    Template template = cfg.getTemplate(modelAndView.getViewName() + ".ftl");

    // 输出结果
    Writer out = new StringWriter();
    template.process(modelAndView.getModel(), out);
    out.close();

    // 将结果写入响应流
    String data = out.toString();
    byte[] bytes = data.getBytes(StandardCharsets.UTF_8);

    exchange.sendResponseHeaders(200, bytes.length);
    exchange.getResponseHeaders().add("Content-Type", "text/html;charset=utf-8");
    exchange.getResponseBody().write(bytes);
}

3.2.8 总体

最后再看一下doDispatch()方法的总体逻辑:

java
public void doDispatch(HttpExchange exchange) throws IOException {

    try{
        // 找到处理器方法
        InvocableHandlerMethod invocableHandlerMethod = getHandlerMethod(exchange);
        if(invocableHandlerMethod  == null){
            throw new FrameworkException(404, "no mapping handler");
        }

        // 解析参数
        Object[] args = resolveArgs(exchange, invocableHandlerMethod);

        // 反射调用处理器方法
        Object returnValue = invocableHandlerMethod.invoke(args);

        // 处理返回值
        ModelAndView modelAndView = handleReturnValue(returnValue, invocableHandlerMethod, exchange);

        // 如有必要,渲染视图
        if(modelAndView != null){
            render(modelAndView, exchange);
        }
    }catch (Exception e){
        // 异常处理
        FrameworkException frameworkException = null;
        if(!(e instanceof FrameworkException)){
            frameworkException = new FrameworkException(500, e.getMessage());
        }else{
            frameworkException = (FrameworkException) e;
        }

        handleException(exchange, frameworkException);
    }finally {
        exchange.getResponseBody().flush();
        exchange.getResponseBody().close();
    }
}

3.3 编写Controller类与测试

按照业务,编写Controller类:

java
public class UserController {
    @RequestMapping(value = "/getUser1", method = HttpMethod.GET)
    @View(name = "user")
    public User getUser1(@RequestParam("name") String name){
        return new User(1L, name, "广州");
    }

    @RequestMapping(value = "/getUser2", method = HttpMethod.GET)
    @ResponseBody
    public User getUser2(){
        return new User(2L, "get user 2", "上海");
    }
}

然后将编写的Controller类传入DispatcherServlet

java
public static void main( String[] args ) throws IOException {
    HttpServer httpServer = HttpServer.create(new InetSocketAddress(8888), 0);

    DispatcherServlet dispatcherServlet = new DispatcherServlet(UserController.class);
    httpServer.createContext("/", exchange -> dispatcherServlet.doDispatch(exchange));

    httpServer.start();
    System.out.println("启动成功");
}

启动项目,然后运行测试:

image-20250702132256488

image-20250702132341794

4. Spring MVC 组件分析

4.1 初始化方法

DispatcherServletonRefresh()方法中,有一系列初始化方法:

java
protected void onRefresh(ApplicationContext context) {
  initStrategies(context);
}

protected void initStrategies(ApplicationContext context) {
  initMultipartResolver(context);
  initLocaleResolver(context);
  initThemeResolver(context);
  initHandlerMappings(context);
  initHandlerAdapters(context);
  initHandlerExceptionResolvers(context);
  initRequestToViewNameTranslator(context);
  initViewResolvers(context);
  initFlashMapManager(context);
}

从初始化方法中,主要的组件有HandlerMappingHandlerAdapterHandlerExceptionResolver

4.2 HandlerMapping

在 Spring MVC 中,HandlerMapping 是 Spring 框架的核心组件之一,负责将客户端的 HTTP 请求映射到相应的处理程序(Handler,通常是控制器方法)。它在 DispatcherServlet 的请求处理流程中扮演重要角色,决定了请求应该由哪个控制器方法来处理。

就像我们自己实现的中的:

java
private HashMap<String, InvocableHandlerMethod> handlerMethodHashMap = new HashMap<>();

常用的实现是RequestMappingHandlerMapping,处理 @RequestMapping 注解定义的控制器方法,也就是说根据 @RequestMapping 注解找到处理器方法并缓存,当请求到来时,根据@RequestMapping的 value(URL 路径)、method(HTTP 方法)、params、 headers 等进行请求匹配,找到处理器方法。

所以,在RequestMappingHandlerMapping中使用的关键类有两个:

  • RequestMappingInfo:封装 @RequestMapping 注解的元数据,用于匹配请求。

    内容有:

    • Patterns:URL 路径模式(如 /user/{id})。
    • Methods:HTTP 方法(如 GET、POST)。
    • Params:请求参数条件(如 paramName=value)。
    • Headers:请求头条件。
    • Consumes:请求的 Content-Type(如 application/json)。
    • Produces:响应的 Content-Type(如 application/json)。
  • HandlerMethod:表示控制器中的具体方法,包含方法本身、所属 Bean 及参数信息。

    方法对象(java.lang.reflect.Method)。

    控制器实例(@Controller 或 @RestController 的 Bean)。

    方法参数(如 @PathVariable、@RequestParam 等)。

    这时一个接口,主要实现为ServletInvocableHandlerMethod

  • 在Spring MVC中,还实现了拦截器HandlerInterceptor,用于在处理器方法前后进行执行。

所以,当一个请求到来时,不仅会匹配到处理器方法,还可能匹配到多个拦截器,一个处理器和多个拦截器组成HandlerExecutionChain

java
public class HandlerExecutionChain {
	// 处理器方法
	private final Object handler;
	// 多个拦截器
	private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
	// 当前执行的拦截器
	private int interceptorIndex = -1;
}

RequestMappingHandlerMapping中的结构(不准确)大致如下:

java
class RequestMappingHandlerMapping{
  Map<RequestMappingInfo, HandlerExecutionChain> handlerMapping ;
}

测试HandlerMapping代码如下:

java
public static void main(String[] args) throws Exception {
    // 创建容器
    AnnotationConfigServletWebServerApplicationContext applicationContext =
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    // 创建RequestMappingHandlerMapping
    RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
    requestMappingHandlerMapping.setApplicationContext(applicationContext);

    TestController controller = new TestController();

    // 添加第一个匹配项
    RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
            .paths("/test1")
            .methods(RequestMethod.GET)
            .build();
    requestMappingHandlerMapping.registerMapping(
            requestMappingInfo1,
            controller,
            TestController.class.getMethod("test1")
    );

    // 添加第二个匹配项
    RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
            .paths("/test2")
            .methods(RequestMethod.POST)
            .build();
    requestMappingHandlerMapping.registerMapping(
            requestMappingInfo2,
            controller,
            TestController.class.getMethod("test2")
    );


    // MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
    MockHttpServletRequest httpServletRequest =
            new MockHttpServletRequest("GET", "/test1");
    // 通过请求获取匹配的处理器方法和拦截器
    HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);

    System.out.println(handler);
}
java
@Configuration
public class WebConfig {

    @Bean
    public ServletWebServerFactory webServerFactory(){
        return new TomcatServletWebServerFactory();
    }

    @Bean
    public DispatcherServlet dispatcherServlet(){
        return new DispatcherServlet();
    }

    @Bean
    public DispatcherServletRegistrationBean dispatcherServletRegistrationBean(DispatcherServlet dispatcherServlet){
        return new DispatcherServletRegistrationBean(dispatcherServlet,"/");
    }
}
java
class TestController{
    public String test1(){
        return "test1";
    }

    public String test2(){
        return "test2";
    }
}

结果如下:

java
HandlerExecutionChain with [com.lee.springmvc.TestController#test1()] and 0 interceptors

可以看到,根据HTTP请求匹配到了处理器方法。

4.3 HandlerAdapter

在 Spring MVC 中,HandlerAdapter 是 Spring 框架的核心组件之一,负责执行由 HandlerMapping 匹配到的处理程序(Handler),并将执行结果转换为 ModelAndView 对象。

为什么要专门引入HandlerAdapter?我认为有两点原因:

  • 执行处理程序逻辑复杂,包括解析参数、类型转换、处理返回值等,为了解耦;
  • 在Spring MVC中,处理程序不仅仅只是由@RequestMapping标注的方法,还可能实现特定接口,所以执行处理程序的逻辑不同;

由于执行处理程序包括解析参数和处理返回值,所以HandlerAdapter依赖两组组件:

  • HandlerMethodArgumentResolverComposite:其中聚合了多个HandlerMethodArgumentResolver,用于解析参数;
  • HandlerMethodReturnValueHandlerComposite:其中聚合了HandlerMethodReturnValueHandler,用于处理返回值;

用于调用@RequestMapping标注的处理器方法的是RequestMappingHandlerAdapter,使用案例如下:

java
public static void main(String[] args) throws Exception {
    // 创建容器
    AnnotationConfigServletWebServerApplicationContext applicationContext =
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    // 创建RequestMappingHandlerMapping
    RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
    requestMappingHandlerMapping.setApplicationContext(applicationContext);

    TestController controller = new TestController();

    // 添加第一个匹配项
    RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
            .paths("/test1")
            .methods(RequestMethod.GET)
            .params("name")
            .build();
    requestMappingHandlerMapping.registerMapping(
            requestMappingInfo1,
            controller,
            TestController.class.getMethod("test1", String.class)
    );

    // 添加第二个匹配项
    RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
            .paths("/test2")
            .methods(RequestMethod.POST)
            .build();
    requestMappingHandlerMapping.registerMapping(
            requestMappingInfo2,
            controller,
            TestController.class.getMethod("test2")
    );


    // MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
    MockHttpServletRequest httpServletRequest =
            new MockHttpServletRequest("GET", "/test1");
    httpServletRequest.addParameter("name","zs");
    MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
    // 通过请求获取匹配的处理器方法和拦截器
    HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);

  	// 创建RequestMappingHandlerAdapter,执行处理器方法
    RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
    requestMappingHandlerAdapter.setApplicationContext(applicationContext);
    requestMappingHandlerAdapter.afterPropertiesSet();

    ModelAndView modelAndView = requestMappingHandlerAdapter.handle(httpServletRequest, httpServletResponse, handler.getHandler());

    System.out.println(httpServletResponse.getContentAsString());
    System.out.println(modelAndView);
}
java
class TestController{
    @ResponseBody
    public String test1(String name){
        return "test1 " + name;
    }

    public String test2(){
        return "test2";
    }
}

结果如下:

txt
test1 zs
null

如果在处理器方法上去除@ResponseBody,结果如下:

txt

ModelAndView [view="test1 zs"; model={}]

4.4 HandlerExceptionResolver

在Spring MVC中,通过HandlerAdapter执行处理方法,最终调用DispatcherServlet中的processDispatchResult()方法,该方法用于处理异常或渲染视图:

TIP

对于没有异常和不需要渲染视图的情况,由返回值处理器已经直接写入了HttpServletResponse,所以在这个方法中不用管。

java
private void processDispatchResult(
  HttpServletRequest request,   // 请求
  HttpServletResponse response,  // 响应
  @Nullable HandlerExecutionChain mappedHandler,   // 执行链
  @Nullable ModelAndView mv,  // 处理结果,有可能为null
  @Nullable Exception exception  // 异常
) throws Exception {

  boolean errorView = false;

  // 异常不为空,需要处理异常
  if (exception != null) {
    if (exception instanceof ModelAndViewDefiningException mavDefiningException) {
      logger.debug("ModelAndViewDefiningException encountered", exception);
      mv = mavDefiningException.getModelAndView();
    }
    else {
      Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
      mv = processHandlerException(request, response, handler, exception);
      errorView = (mv != null);
    }
  }

  // ModelAndView不为空,需要渲染视图
  if (mv != null && !mv.wasCleared()) {
    render(mv, request, response);
    if (errorView) {
      WebUtils.clearErrorRequestAttributes(request);
    }
  }
  else {
    if (logger.isTraceEnabled()) {
      logger.trace("No view rendering, null ModelAndView returned.");
    }
  }

  if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
    return;
  }

  if (mappedHandler != null) {
    // 渲染视图完成后,调用拦截器的AfterCompletion方法
    mappedHandler.triggerAfterCompletion(request, response, null);
  }
}

processHandlerException()中,主要逻辑如下,调用异常处理器逐一处理异常,并返回异常视图:

java
ModelAndView exMv = null;
if (this.handlerExceptionResolvers != null) {
  for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
    exMv = resolver.resolveException(request, response, handler, ex);
    if (exMv != null) {
      break;
    }
  }
}

4.5 渲染

DispatcherServlet中,调用render(mv, request, response);进行视图渲染,主要逻辑分为两部分:

  • 如果ModelAndView中只有视图名称,则通过ViewResolvers解析视图,返回View对象;如果ModelAndView中直接包含View对象,则直接获取;
  • 调用View.render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response)方法进行渲染;

在Spring MVC中,视图解析器默认为InternalResourceViewResolver,会将逻辑视图名称解析为实际的 JSP 文件路径(如 /WEB-INF/views/home.jsp),返回为InternalResourceView对象。

Spring MVC 的InternalResourceView 使用 RequestDispatcher.forward()将请求转发到 JSP 文件的路径。

转发的请求到达Servlet容器(如Tomcat)时,由 Web 容器的 JSP 引擎(如 Tomcat 的 Jasper)在服务器端完成渲染。

所以整个流程如下:

txt
控制器返回视图名称 → InternalResourceViewResolver 解析 → InternalResourceView 转发 → JSP 引擎渲染 → 返回 HTML

由于我们不是在Tomcat环境下,所以我们这里使用freemarker实验渲染功能。

首先引入freemarker依赖库:

java
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-freemarker</artifactId>
</dependency>

然后在配置文件中加入freemarker视图解析器和相关配置类:

java
@Configuration
public class WebConfig {

    @Bean
    public ViewResolver viewResolver(){
      	// 设置模板前缀和后缀
        return new FreeMarkerViewResolver("/WEB-INF/views/",".ftl");
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer(){

        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();

        URL resource = this.getClass().getClassLoader().getResource("");

      	// 设置模板加载器,ClassTemplateLoader会从classpath路径下加载模板
        configurer.setPreTemplateLoaders(new ClassTemplateLoader());  
      	// 设置模板加载路径
        configurer.setTemplateLoaderPath(resource.getPath()); 
      	// 设置模板编码
        configurer.setDefaultEncoding("UTF-8"); 

        return configurer;
    }
}

然后,在resources/WEB-INF/views/目录下编写模板:

java
<html>
<body>
<h1>hello </h1>

<#if user?? && user.name??>
    <h3>用户名: ${user.name}</h3>
<#else>
    用户不存在或姓名为空
</#if>

<#if user?? && user.address??>
    <h3>家乡: ${user.address}</h3>
<#else>
    家乡为空
</#if>

</body>
</html>

最后,使用模板解析器渲染ModelAndView对象:

java
public class App {
    public static void main(String[] args) throws Exception {
        // 创建容器
        AnnotationConfigServletWebServerApplicationContext applicationContext =
                new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

        // 创建RequestMappingHandlerMapping
        RequestMappingHandlerMapping requestMappingHandlerMapping = new RequestMappingHandlerMapping();
        requestMappingHandlerMapping.setApplicationContext(applicationContext);

        TestController controller = new TestController();

        // 添加第一个匹配项
        RequestMappingInfo requestMappingInfo1 = RequestMappingInfo
                .paths("/test1")
                .methods(RequestMethod.GET)
                .params("name")
                .build();
        requestMappingHandlerMapping.registerMapping(
                requestMappingInfo1,
                controller,
                TestController.class.getMethod("test1", String.class)
        );

        // 添加第二个匹配项
        RequestMappingInfo requestMappingInfo2 = RequestMappingInfo
                .paths("/test2")
                .methods(RequestMethod.POST)
                .build();
        requestMappingHandlerMapping.registerMapping(
                requestMappingInfo2,
                controller,
                TestController.class.getMethod("test2")
        );


        // MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
        MockHttpServletRequest httpServletRequest =
                new MockHttpServletRequest("GET", "/test1");
        httpServletRequest.addParameter("name","zs");
        MockHttpServletResponse httpServletResponse = new MockHttpServletResponse();
        // 通过请求获取匹配的处理器方法和拦截器
        HandlerExecutionChain handler = requestMappingHandlerMapping.getHandler(httpServletRequest);

        RequestMappingHandlerAdapter requestMappingHandlerAdapter = new RequestMappingHandlerAdapter();
        requestMappingHandlerAdapter.setApplicationContext(applicationContext);
        requestMappingHandlerAdapter.afterPropertiesSet();

        ModelAndView modelAndView = requestMappingHandlerAdapter.handle(httpServletRequest, httpServletResponse, handler.getHandler());

      	// 从容器中获取视图解析器
        ViewResolver viewResolver = applicationContext.getBean(ViewResolver.class);
      	// 获取视图
        View view = viewResolver.resolveViewName(modelAndView.getViewName(), Locale.CHINA);
      	// 解析视图
        view.render(modelAndView.getModel(), httpServletRequest, httpServletResponse);

      	// 输出结果
        System.out.println(httpServletResponse.getContentAsString());
    }
}

class TestController{

  	// 注意返回 ModelAndView 对象
    public ModelAndView test1(String name){
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("hello");
        modelAndView.addAllObjects(Map.of("user", new User(name, "gz")));
        return modelAndView;
    }

    public String test2(){
        return "test2";
    }
}

结果如下:

java
<html>
<body>
<h1>hello </h1>

    <h3>用户名: zs</h3>

    <h3>家乡: gz</h3>

</body>
</html>

解析成功。

4.6 doDispatch()方法

我们再来看看DispatcherServlet中的doDispatch()方法:

java
// 参数就是请求和响应
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);

      // 获取处理器方法
      mappedHandler = getHandler(processedRequest);
      if (mappedHandler == null) {
        noHandlerFound(processedRequest, response);
        return;
      }

      // 获取执行器
      HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

      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;
        }
      }

      // 应用拦截器链的preHandle()方法
      if (!mappedHandler.applyPreHandle(processedRequest, response)) {
        return;
      }

      // 调用处理器方法
      mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

      if (asyncManager.isConcurrentHandlingStarted()) {
        return;
      }
			// 处理默认视图名
      applyDefaultViewName(processedRequest, mv);
      // 应用拦截器链的postHandle()方法
      mappedHandler.applyPostHandle(processedRequest, response, mv);
    }
    catch (Exception ex) {
      // 上述执行过程中发生异常,保存在dispatchException中
      dispatchException = ex;
    }
    catch (Throwable err) {
      // 上述执行过程中发生异常,保存在dispatchException中
      dispatchException = new ServletException("Handler dispatch failed: " + err, err);
    }
    
    // 最后处理,包括视图解析、异常处理、执行拦截器链的afterCompletion()方法
    processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
  }
  catch (Exception ex) {
    triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
  }
  catch (Throwable err) {
    triggerAfterCompletion(processedRequest, response, mappedHandler,
        new ServletException("Handler processing failed: " + err, err));
  }
  finally {
    if (asyncManager.isConcurrentHandlingStarted()) {
      // Instead of postHandle and afterCompletion
      if (mappedHandler != null) {
        mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
      }
      asyncManager.setMultipartRequestParsed(multipartRequestParsed);
    }
    else {
      // Clean up any resources used by a multipart request.
      if (multipartRequestParsed || asyncManager.isMultipartRequestParsed()) {
        cleanupMultipart(processedRequest);
      }
    }
  }
}

5. HandlerMapping 和 HandlerAdapter

本小节介绍其他的HanlerMapping 和 HandlerAdapter。

5.1 BeanNameUrlHandlerMapping

BeanNameUrlHandlerMapping用于将请求路径映射到名称为路径的Bean,也就是说,假设请求路径为/c1,容器中有名称为/c1的Bean,那么就由该Bean处理该请求。

注意,这种方式要求Bean的名称以/开始。

java
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, ServletException {
    // 创建容器
    AnnotationConfigServletWebServerApplicationContext applicationContext =
            new AnnotationConfigServletWebServerApplicationContext(WebConfig.class);

    DispatcherServlet dispatcherServlet = applicationContext.getBean(DispatcherServlet.class);
    // 为了触发 DispatcherServlet.onRefresh() 方法,加载 BeanNameUrlHandlerMapping
    dispatcherServlet.onApplicationEvent(new ContextRefreshedEvent(applicationContext));

    // MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
    MockHttpServletRequest httpServletRequest =
            new MockHttpServletRequest("GET", "/c1");

    // 反射调用获取 getHandler方法
    Method getHandler = DispatcherServlet.class.getDeclaredMethod("getHandler", HttpServletRequest.class);
    getHandler.setAccessible(true);

    // 获取匹配的处理器和拦截器
    HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
    System.out.println(handlerExecutionChain);
}
java
@Configuration
public class WebConfig {
    @Bean("/c1")
    public Object object(){
        return new Object();
    }
}

结果为:

java
HandlerExecutionChain with [java.lang.Object@55caeb35] and 1 interceptors

可以发现,匹配到的处理器就是Object,就是我们放入容器中的名称为/c1的Bean。

5.2 HttpRequestHandlerAdapter

从上一小节我们已经找到了处理器,但是,仔细想想,这个处理器可以处理请求吗?我们应该调用哪个方法来处理请求呢?

显然,我们找到的处理器并不能处理请求。那要如何才能处理请求呢?可以让我们的处理器类实现HttpRequestHandler接口:

java
@FunctionalInterface
public interface HttpRequestHandler {
	void handleRequest(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException;
}

处理器实现HttpRequestHandler接口:

java
@Bean("/c1")
public Object object(){
    return new HttpRequestHandler() {
        @Override
        public void handleRequest(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
            response.getWriter().write("hello world");
        }
    };
}

之后,我们找到的处理器,就可以判断其是否属于HttpRequesthandler类,如果是,就可以调用handleRequest()处理请求了:

java
// 获取匹配的处理器和拦截器
HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
Object handler = handlerExecutionChain.getHandler();
if(handler instanceof HttpRequestHandler httpRequestHandler){
    MockHttpServletResponse response = new MockHttpServletResponse();
    httpRequestHandler.handleRequest(httpServletRequest, response);
    System.out.println(response.getContentAsString());
}

Spring MVC将上述判断和调用handleRequest()的流程封装在了HttpRequestHandlerAdapter中:

java
public class HttpRequestHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof HttpRequestHandler);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		((HttpRequestHandler) handler).handleRequest(request, response);
		return null;
	}

	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified lastModified) {
			return lastModified.getLastModified(request);
		}
		return -1L;
	}

}

所以,只需要我们的处理器实现了HttpRequestHandler接口,那么Spring MVC就能帮我们调用方法处理请求。

5.3 SimpleControllerHandlerAdapter

除了实现HttpRequestHandler接口,我们的处理器也可以实现Controller接口来处理请求:

java
@FunctionalInterface
public interface Controller {
	@Nullable
	ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception;
}

ControllerHttpRequestHandler最大的不同是返回值

而对于实现了Controller的处理器的支持,Spring MVC 提供了SimpleControllerHandlerAdapter

java
public class SimpleControllerHandlerAdapter implements HandlerAdapter {

	@Override
	public boolean supports(Object handler) {
		return (handler instanceof Controller);
	}

	@Override
	@Nullable
	public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {

		return ((Controller) handler).handleRequest(request, response);
	}

	@Override
	@SuppressWarnings("deprecation")
	public long getLastModified(HttpServletRequest request, Object handler) {
		if (handler instanceof LastModified lastModified) {
			return lastModified.getLastModified(request);
		}
		return -1L;
	}

}

5.4 RouterFunctionMapping 和 HandlerFunctionAdapter

我们也可以同时声明请求的路径和与之匹配的处理方法,使用RouterFunction

java
@Bean
public RouterFunction routerFunction(){
    return RouterFunctions.route(
            RequestPredicates.GET("/r1"),  // 匹配 GET /r1 请求
            new HandlerFunction<ServerResponse>() {
              	// 处理方法
                @Override
                public ServerResponse handle(ServerRequest request) throws Exception {
                    ServerResponse serverResponse = ServerResponse.ok().body("hello router function");
                    return serverResponse;
                }
            }
    );
}

支持这一功能的是RouterFunctionMappingHandlerFunctionAdapter

  • RouterFunctionMapping:用来存储匹配关系;
  • HandlerFunctionAdapter:用来处理请求;

例如:

java
// MockHttpServletRequest 是 spring-test中的类,模拟HTTP请求
MockHttpServletRequest httpServletRequest =
        new MockHttpServletRequest("GET", "/r1");

// 反射调用获取 getHandler方法
Method getHandler = DispatcherServlet.class.getDeclaredMethod("getHandler", HttpServletRequest.class);
getHandler.setAccessible(true);

// 获取匹配的处理器和拦截器
HandlerExecutionChain handlerExecutionChain = (HandlerExecutionChain) getHandler.invoke(dispatcherServlet, httpServletRequest);
Object handler = handlerExecutionChain.getHandler();

HandlerFunctionAdapter handlerFunctionAdapter = new HandlerFunctionAdapter();
if(handlerFunctionAdapter.supports(handler)){
    MockHttpServletResponse response = new MockHttpServletResponse();
    handlerFunctionAdapter.handle(httpServletRequest, response, handler);
    System.out.println(response.getContentAsString());
}

结果:

java
hello router function

5.5 ResourceHttpRequestHandler

ResourceHttpRequestHandler其本质就是一个请求处理器,其实现了HttpRequestHandler接口,专用于处理静态资源的,我们只需要将其注入到容器中,配合BeanNameUrlHandlerMapping,就能发挥作用。

java
@Bean("/img/**")
public ResourceHttpRequestHandler resourceHttpRequestHandler(){
    ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
    // 设置静态资源路径
    resourceHttpRequestHandler.setLocations(List.of(new ClassPathResource("/WEB-INF/img/")));

    return resourceHttpRequestHandler;
}

由于有BeanNameUrlHandlerMapping,所以当请求到来时,如果是/img/的请求,则会找到ResourceHttpRequestHandler处理器进行处理,该处理器就会去设定的资源路径下找到相应资源,返回给前端。

image-20250703142229063

为了更高效使用ResourceHttpRequestHandler,可以做如下优化:

java
@Bean("/img/**")
public ResourceHttpRequestHandler resourceHttpRequestHandler(){
    ResourceHttpRequestHandler resourceHttpRequestHandler = new ResourceHttpRequestHandler();
    // 设置静态资源路径
    resourceHttpRequestHandler.setLocations(List.of(new ClassPathResource("/WEB-INF/img/")));

    resourceHttpRequestHandler.setResourceResolvers(List.of(
            // 从缓存中获取资源
            new CachingResourceResolver(new ConcurrentMapCache("cache")),
            // 压缩资源支持
            new EncodedResourceResolver(),
            // 根据路径查找资源
            new PathResourceResolver()
    ));

    return resourceHttpRequestHandler;
}

6.流程总结

  1. Spring MVC 提供了DispatcherServlet,它使用的是标准Servlet技术

    • 路径:默认映射路径为/,即会匹配到所有请求URL,可以作为请求的统一入口,也被称为前控制器

      例外:JSP不会匹配到DispatcherServlet,因为Tomcat拦截处理了;

    • 创建:在Spring Boot中,由DispatcherServletAutoConfiguration这个自动配置类提供DispatcherServlet这个组件;

    • 初始化:DispatcherServlet初始化时会优先到容器中寻找各种组件,作为它的成员变量:

      • HandlerMapping:初始化时记录映射关系,即请求由哪个处理器处理;
      • HandlerAdapter:初始化时准备参数解析器、返回值处理器、消息转换器;
      • HandlerExceptionResolver:初始化时准备参数解析器、返回值处理器、消息转换器;
      • ViewResolver:视图解析器

      如果在容器中没有找到,那么会使用默认配置,在org/springframework/web/servlet/DispatcherServlet.properties中定义

  2. 当请求来时,DispatcherServlet会利用HandlerMapping进行匹配,找到对应的处理器:

    • 处理器对象会和拦截器一起返回给DispatcherServlet,结合在一起称为 HandlerExecutionChain 对象;
  3. DispatherServlet接下来开始处理请求:

    • 首先调用拦截器的 preHandle()方法
    • 然后查找可以调用处理器方法的 HandlerAdapter,由HandlerAdapter调用处理器方法:
      • 使用HandlerMethodArgumentResolver获取参数;
      • 调用处理器方法HandlerMethod;
      • 使用HandlerMethodReturnValueHandler处理返回值
        • 如果处理后的返回值ModelAndView为null,则不走视图解析及渲染流程;
        • 如果处理后的返回值ModelAndView为null,则表示直接将数据写入到响应Response中了;
      • 注意:在执行处理器方法时,会涉及 @ControllerAdvice 增强
    • 然后调用拦截器的 postHandle()方法
    • 如有必要,进行视图解析与渲染
      • 如果前面发生了异常,则进入异常处理流程;
      • 如果没有发生异常,并且需要进行渲染,则进入渲染流程;
    • 最后,执行拦截器的 afterCompletion()方法