(二)万字详解SpringMVC Servlet —SpringMVC十大常用组件是什么?过滤器和拦截器的区别是什么?如何将不同Request路由到不同控制器?

112 阅读22分钟

引言

作为一名Java开发实习生,我今天想与大家分享一些个人的学习心得。在当今的Java开发领域,Spring框架已然成为了不可或缺的核心组件。它的强大功能和便捷性确实为我们的开发工作带来了巨大便利。然而,这种便利也可能让我们不经意间忽视了一些重要的底层技术细节。

表面上看,不了解这些细节似乎对日常编码影响不大。但当我们遇到棘手的bug时,这些知识就显得尤为重要。如果幸运的话,我们可能在网上搜索到解决方案。但如果搜索无果,我们可能就会陷入困境,无从下手。

在Spring Web应用的世界里,有一个看似简单却又深奥的问题值得我们深入探讨:当一个HTTP请求通过网络到达我们的服务器时,它是如何触发并执行我们编写的Java代码的?

要全面理解这个过程,我们需要从更基础的概念开始。在深入学习Spring MVC之前,我们有必要先认识一下Servlet技术。Servlet作为Java Web开发的基石,对理解Spring MVC的工作原理至关重要。

一、Servlet

什么是Servlet?Servlet的运行环境是什么?

Servlet本质上是一个Java接口,它定义了一套规范。这套规范使得所有遵循它的类都能以统一的方式处理web请求。然而,对Servlet的理解常常存在一些误区。

许多人可能误以为Servlet直接处理来自客户端的HTTP请求,但实际情况并非如此。Servlet本身并不负责监听网络端口(如常用的8080端口),也不直接与客户端通信。

事实上,直接与客户端交互的是我们通常所说的"容器"。最广为人知的Servlet容器之一就是Apache Tomcat。这些容器承担了接收HTTP请求、管理Servlet生命周期、以及协调请求与Servlet之间交互等重要任务。

Servlet接口的主要作用是standardize了Web应用程序组件的开发方式。通过实现这个接口,开发者可以创建能够响应各种Web请求的Java类。容器则负责调用适当的Servlet来处理特定的请求。

link-20240829184855261.png

link-20240829184855168.png

二、SpringMVC整体流程与十大常用组件

在深入Spring Web应用的内部机制之前,我们需要认识一个关键点:任何Spring Web应用的入口点(Entry Point),即应用程序开始处理HTTP请求的地方,本质上都是一个Servlet。

接下来,我们将聚焦于SpringMVC框架中最常用的10个核心组件。了解这些组件的功能和相互关系

image.png

SpringMVC的10个核心组件

SpringMVC框架的强大功能源于其精心设计的组件架构。以下是10个最核心的组件,它们共同协作,处理从请求接收到响应返回的整个过程:

1. DispatcherServlet(前端控制器)

作为SpringMVC处理请求的入口点,DispatcherServlet扮演着中央调度器的角色。它统一处理所有的请求和响应,是整个流程的控制中心,负责协调其他组件来处理用户的请求。

2. HandlerMapping(处理器映射器)

HandlerMapping的主要职责是根据请求找到相应的处理器。它的核心方法是:

public interface HandlerMapping {
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;
}

这个方法返回一个HandlerExecutionChain对象,包含了处理请求所需的所有信息。

3. HandlerExecutionChain(处理器执行链)

这个类封装了处理器(通常是我们自定义的Controller)和与之相关的拦截器:

public class HandlerExecutionChain {
    // 处理器,通常是Controller对象及其方法
    private final Object handler;
    // 拦截器,当前请求匹配到的拦截器列表
    private final List<HandlerInterceptor> interceptorList = new ArrayList<>();
    // 用于跟踪当前执行到哪个拦截器
    private int interceptorIndex = -1; 
}

4. Handler(处理器)

Handler通常指我们自定义的Controller。它在DispatcherServlet的调度下,负责处理具体的请求逻辑。

5. HandlerAdapter(处理器适配器)

HandlerAdapter负责调用Handler的方法。由于Handler可能有多种类型,每种类型的调用方式可能不同,HandlerAdapter通过适配器模式解决了这个问题:

public interface HandlerAdapter {
    // 当前 HandlerAdapter 是否支持 handler
    boolean supports(Object handler);
    // 其内部负责调用 handler 的来处理用户的请求,返回返回一个 ModelAndView 对象
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

6. ModelAndView(模型和视图)

ModelAndView封装了视图名称和模型数据:

public class ModelAndView {
    // 视图
    @Nullable private Object view;
    // 模型,用来存放共享给客户端的数据
    @Nullable private ModelMap model;
}

它作为Handler处理结果的载体,传递给视图解析器。

7. ViewResolver(视图解析器)

ViewResolver负责解析视图名称,返回对应的View对象:

public interface ViewResolver {
    // 根据视图的名称得到对应的视图对象
    @Nullable View resolveViewName(String viewName, Locale locale) throws Exception;
}

8. View(视图)

View接口定义了渲染模型数据的方法:

public interface View {
    void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}

具体的View实现负责将结果呈现给客户端。

9. HandlerExceptionResolver(处理器异常解析器)

用于全局异常处理,将异常转换为对应的ModelAndView:

public interface HandlerExceptionResolver {
    @Nullable ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

10. HttpMessageConverter(HTTP报文转换器)

负责在Java对象和HTTP请求/响应报文之间进行转换,尤其在处理@RequestBody、@ResponseBody等注解时发挥作用:
```java
public interface HttpMessageConverter<T> {
    // 是否可以将请求报文读取给方法参数指定的类型
    boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    // 是否可以将响应的报文转换为方法参数指定的类型输出
    boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    // 当前转换器支持的类型
    List<MediaType> getSupportedMediaTypes();
    // 将http报文转换为给定的类型,然后返回
    T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
    // 将给定的对象t,转换为http报文输出到客户端
    void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
}
```

三、SpringMVC处理流程详解

image.png

在SpringMVC中,所有的HTTP请求最终都会汇集到org.springframework.web.servlet.DispatcherServlet类的doDispatch方法。这个方法是整个请求处理流程的核心,它协调了各个组件的工作,确保请求被正确处理并返回响应。让我们深入了解这个方法的工作流程:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // 请求对象
    HttpServletRequest processedRequest = request;
    // 处理器执行链对象
    HandlerExecutionChain mappedHandler = null;
    boolean multipartRequestParsed = false;
    // 获取异步处理管理器,servlet3.0后支持异步处理,可以在子线程中响应用户请求
    WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
    try {
        //模型和视图
        ModelAndView mv = null;
        //异常对象
        Exception dispatchException = null;
        try {
            // 1.解析multipart类型的请求,上传文件用的就是multipart类型的请求方式
            processedRequest = checkMultipart(request);
            // 用来标记是否是multipart类型的请求
            multipartRequestParsed = (processedRequest != request);
            // 2.根据请求获取HandlerExecutionChain对象
            mappedHandler = getHandler(processedRequest);
            //如果没有找到处理器,就404了
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 3.根据处理器获取HandlerAdapter
            HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
            // 4.调用拦截器的preHandle方法,若返回false,处理结束
            if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                return;
            }
            // 5.调用handler实际处理请求,获取ModelAndView对象,这里会调用HandlerAdapter#handle方法处理请求,其内部会调用handler来处理具体的请求
            mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
            // 判断异步请求不是已经开始了,开始了就返回了
            if (asyncManager.isConcurrentHandlingStarted()) {
                return;
            }
            // 如果mv对象中没有视图 & DispatcherServlet配置了默认的视图,则给mv安排一个默认的视图
            applyDefaultViewName(processedRequest, mv);
            // 6.调用拦截器的postHandle方法
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        } catch (Exception ex) {
            dispatchException = ex;
        } catch (Throwable err) {
            dispatchException = new NestedServletException("Handler dispatch failed", err);
        }
        // 7.处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    } catch (Exception ex) {
        // 8.调用拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
    } catch (Throwable err) {
        // 8.调用拦截器的afterCompletion方法
        triggerAfterCompletion(processedRequest, response, mappedHandler,
                new NestedServletException("Handler processing failed", err));
    } finally {
        // 对于异步处理的情况,调用异步处理的拦截器AsyncHandlerInterceptor的afterConcurrentHandlingStarted方法
        if (asyncManager.isConcurrentHandlingStarted()) {
            if (mappedHandler != null) {
                mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
            }
        } else {
            // 对于multipart的请求,清理资源,比如文件上传的请求,在上传的过程中文件会被保存到临时文件中,这里就会对这些文件继续清理
            if (multipartRequestParsed) {
                cleanupMultipart(processedRequest);
            }
        }
    }
}

其中,关键步骤如下:

  1. Multipart请求处理:检查并处理文件上传等multipart请求。
  2. Handler查找:根据请求URL找到对应的Handler(通常是我们定义的Controller方法)。
  3. HandlerAdapter获取:为找到的Handler选择合适的HandlerAdapter。
  4. 前置处理:执行所有已注册拦截器的preHandle方法。
  5. 请求处理:通过HandlerAdapter调用实际的Handler处理请求。
  6. 后置处理:执行所有已注册拦截器的postHandle方法。
  7. 结果处理:处理视图渲染,异常处理等。
  8. 完成处理:触发拦截器的afterCompletion方法,清理资源。

3.1 解析 multipart 类型的请求**

在SpringMVC框架中,文件上传是通过multipart类型的请求来实现的。

protected HttpServletRequest checkMultipart(HttpServletRequest request) throws MultipartException {
    // 判断multipartResolver解析器是否存在 && 请求是否是multipart类型
    if (this.multipartResolver != null && this.multipartResolver.isMultipart(request)) {
        // 将请求转换为multipart类型的请求对象,通常为MultipartHttpServletRequest类型
        return this.multipartResolver.resolveMultipart(request);
    }
    return request;
}

3.2 根据请求获取 HandlerExecutionChain 对象

在SpringMVC的请求处理流程中,获取HandlerExecutionChain是一个关键步骤。HandlerExecutionChain包含了处理请求所需的所有信息,主要包括:

  1. handler:实际处理请求的对象
  2. interceptorList:与请求相关的拦截器列表
  3. interceptorIndex:用于跟踪拦截器执行进度的索引

让我们看看DispatcherServlet是如何获取HandlerExecutionChain的:

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

这个方法遍历所有的HandlerMapping,尝试为当前请求找到一个合适的handler。一旦找到,就立即返回,体现了SpringMVC的"第一个匹配"原则。

在大多数情况下,返回的handler实际上是HandlerMethod类型的对象。HandlerMethod封装了处理请求的方法相关的所有信息:

public class HandlerMethod {
    // 通常是我们自定义的controller对象
    private final Object bean;
    private final Class<?> beanType;
    // 能够处理当前请求的方法
    private final Method method;
    private final MethodParameter[] parameters;
    ...
}

HandlerMethod的主要属性:

  • bean:实际处理请求的对象,通常是我们自定义的controller实例。
  • beanType:bean的类型。
  • method:用于处理当前请求的具体方法。
  • parameters:方法的参数信息。

通过HandlerMethod,SpringMVC能够精确地知道应该调用哪个对象的哪个方法来处理当前请求,以及如何处理方法的参数和返回值。

这种设计使得SpringMVC能够灵活地支持各种类型的处理器,从传统的Controller到基于注解的@RequestMapping方法,都能够统一管理和调用。

3.3 根据处理器获取 HandlerAdapter

HandlerAdapter负责实际调用handler(通常是我们的Controller方法)并处理其返回结果。下面是DispatcherServlet中获取HandlerAdapter的核心方法:

protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                return adapter;
            }
        }
    } 
    throw new ServletException("No adapter for handler [" + handler +
                               "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
}
  1. 遍历所有已配置的HandlerAdapter。
  2. 对每个HandlerAdapter,调用其supports(handler)方法,检查是否支持当前handler。
  3. 如果找到支持的适配器,立即返回。
  4. 如果遍历完所有适配器都没有找到合适的,抛出ServletException。

3.4 调用拦截器的 preHandle 方法

在SpringMVC中,拦截器(Interceptor)是一个强大的机制,用于在请求处理的不同阶段执行自定义逻辑。

DispatcherServlet中,拦截器的preHandle方法是这样被调用的:

// 调用拦截器的preHandle方法,若返回false,处理结束
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

这行代码看似简单,但背后涉及了复杂的逻辑。让我们看看applyPreHandle方法的实现: 这个方法主要完成三个任务:

  1. 顺序调用拦截器的preHandle方法:遍历所有拦截器,依次调用它们的preHandle方法。
  2. 处理拦截器返回false的情况:如果某个拦截器的preHandle方法返回false,立即触发afterCompletion方法的调用,并结束处理。
  3. 记录拦截器的执行位置:通过interceptorIndex记录当前执行到的拦截器索引。
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        // 调用拦截器的preHandle方法
        if (!interceptor.preHandle(request, response, this.handler)) {
            // 如果拦截器返回false,则反向依次调用那些preHandle方法返回ture的拦截器的afterCompletion方法
            triggerAfterCompletion(request, response, null);
            return false;
        }
        // 记录当前拦截器执行的位置
        this.interceptorIndex = i;
    }
    return true;
}

当某个拦截器的preHandle返回false时,会调用triggerAfterCompletion方法:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

这个方法的关键点:

  1. 逆序调用:从当前执行位置(interceptorIndex)开始,逆序调用之前成功执行的拦截器的afterCompletion方法。
  2. 异常处理:即使某个afterCompletion方法抛出异常,也会继续执行其他拦截器的afterCompletion方法,确保所有拦截器都有机会执行清理操作。

3.5 调用 handler 实际处理请求,获取 ModelAndView 对象

在SpringMVC的请求处理流程中,调用handler并获取ModelAndView对象是一个关键步骤。这个过程涉及到复杂的参数解析、方法调用和返回值处理。

// 调用handler实际处理请求,获取ModelAndView对象,
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

这里的haHandlerAdapter,它负责实际调用我们的Controller方法。最常用的是RequestMappingHandlerAdapter,它处理带有@RequestMapping注解的方法。

RequestMappingHandlerAdapterinvokeHandlerMethod方法是整个调用过程的核心。它主要完成三个重要任务:

3.5.1 组装目标方法的参数

SpringMVC使用HandlerMethodArgumentResolver接口来解析方法参数:

public interface HandlerMethodArgumentResolver {
    // 判断当前解析器是否能处理这个parameter这个参数
    boolean supportsParameter(MethodParameter parameter);
    // 解析参数:从http请求中解析出控制器需要的参数的值
    Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

常见的实现包括:

实现类对应的控制器参数说明
PathVariableMapMethodArgumentResolver@PathVariable 标注参数从 url 中提取参数的值
RequestParamMethodArgumentResolver@RequestParam 标注参数从http 请求参数中获取值
RequestResponseBodyMethodProcessor@RequestBody 标注参数提取 body 数据,转换为参数类型
.........

3.5.2 反射调用目标方法

通过ServletInvocableHandlerMethod类的invokeAndHandle方法实现: 反射调用目标方法

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 通过反射调用目标方法,内部会组装目标方法需要的参数
    Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
    // 如果返回值为空,表示目标方法中已经完成了请求的所有处理,表示请求处理结束了,将执行mavContainer.setRequestHandled(true)标记请求处理完毕
    if (returnValue == null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            mavContainer.setRequestHandled(true);
            return;
        }
    } // 若getResponseStatusReason()不为空,表示请求已经处理过了
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }
    // 走到这里,说明有返回值,标记请求未处理完毕
    mavContainer.setRequestHandled(false);
    // 对返回值进行处理
    this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}

这个方法不仅调用目标方法,还处理了各种情况,如请求已处理、响应状态等。

3.5.3 处理方法的返回值

会对上述反射调用的结果 returnValue 进行处理,org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite#handleReturnValue

public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
                              ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
    // 根据返回值找到HandlerMethodReturnValueHandler
    HandlerMethodReturnValueHandler handler = selectHandler(returnValue, returnType);
    if (handler == null) {
        throw new IllegalArgumentException("Unknown return value type: " + returnType.getParameterType().getName());
    }
    // 调用HandlerMethodReturnValueHandler#handleReturnValue处理返回值
    handler.handleReturnValue(returnValue, returnType, mavContainer, webRequest);
}
@Nullable
private HandlerMethodReturnValueHandler selectHandler(@Nullable Object value, MethodParameter returnType) {
    // 根据返回值判断是否是异步请求
    boolean isAsyncValue = isAsyncReturnValue(value, returnType);
    for (HandlerMethodReturnValueHandler handler : this.returnValueHandlers) {
        if (isAsyncValue && !(handler instanceof AsyncHandlerMethodReturnValueHandler)) {
            continue;
        }
        if (handler.supportsReturnType(returnType)) {
            return handler;
        }
    }
    return null;
}

返回值处理由HandlerMethodReturnValueHandler接口的实现类完成:

public interface HandlerMethodReturnValueHandler {
    // 是否能够处理 returnType 参数指定的返回值
    boolean supportsReturnType(MethodParameter returnType);
    // 处理返回值
    void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
    ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception;
}

常见的实现包括:

实现类处理的返回值类型说明
ViewNameMethodReturnValueHandlerString处理视图名称
RequestResponseBodyMethodProcessor@ResponseBody处理JSON/XML响应
ModelAndViewMethodReturnValueHandlerModelAndView处理ModelAndView对象

3.6 调用拦截器的 postHandle 方法

在SpringMVC的请求处理流程中,拦截器的postHandle方法扮演着至关重要的角色。它是在主要请求处理完成后,但在视图渲染之前执行的一个关键步骤,为开发者提供了最后的机会来修改请求的处理结果。

DispatcherServlet中,postHandle方法的调用是这样实现的:

// 调用拦截器的postHandle方法
mappedHandler.applyPostHandle(processedRequest, response, mv);

这行代码确保了所有注册的拦截器都有机会在视图渲染之前对请求进行后处理。

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {
    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

这个方法的实现有几个值得注意的特点:

  1. 逆序调用:拦截器的postHandle方法是按照与preHandle相反的顺序调用的。这种设计确保了最先执行preHandle的拦截器最后执行postHandle,形成了一个对称的结构。
  2. 无返回值:与preHandle不同,postHandle方法没有返回值。这意味着它不能中断请求的处理流程,而只能对已经得到的结果进行修改。
  3. ModelAndView参数postHandle方法接收ModelAndView参数,这使得拦截器能够在视图渲染前修改模型数据或更改视图,提供了极大的灵活性。

3.7 渲染视图

在SpringMVC的请求处理流程中,视图渲染是最后且至关重要的一步。这个过程由DispatcherServletprocessDispatchResult方法完成,它不仅处理正常情况下的视图渲染,还负责异常情况的处理。让我们深入了解这个过程。

 // 7处理分发结果,渲染视图(包含了正常处理和异常情况的处理),将结果输出到客户端
private void processDispatchResult(HttpServletRequest request, HttpServletResponse response,
                                   @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv,
                                   @Nullable Exception exception) throws Exception {
    boolean errorView = false;
    if (exception != null) {
        Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
        // 如果有异常,进行全局异常处理
        mv = processHandlerException(request, response, handler, exception);
        errorView = (mv != null);
    }
    if (mv != null && !mv.wasCleared()) {
        // 渲染视图
        render(mv, request, response);
        if (errorView) {
            //调用request.removeAttribute方法清理request中错误信息
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    if (mappedHandler != null) {
        // 调用拦截器的afterCompletion方法
        mappedHandler.triggerAfterCompletion(request, response, null);
    }
}

这个方法主要完成三个关键任务:

  1. 异常处理
  2. 视图渲染
  3. 拦截器的afterCompletion方法调用

3.7.1 异常处理

当发生异常时,SpringMVC会尝试使用配置的异常解析器来处理:

protected ModelAndView processHandlerException(HttpServletRequest request, HttpServletResponse response,
   @Nullable Object handler, Exception ex) throws Exception {
    // 调用处理器异常解析器解析异常,得到ModelAndView
    ModelAndView exMv = null;
    if (this.handlerExceptionResolvers != null) {
        for (HandlerExceptionResolver resolver : this.handlerExceptionResolvers) {
            exMv = resolver.resolveException(request, response, handler, ex);
            if (exMv != null) {
                break;
            }
        }
    }
    if (exMv != null) { // 暴露异常信息到request对象中(request.setAttribute)
        WebUtils.exposeErrorRequestAttributes(request, ex, getServletName());
        return exMv;
    }
    throw ex;
}

HandlerExceptionResolver接口定义了异常处理的核心方法:

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}

3.7.2 视图渲染

视图渲染是通过render方法完成的:

// 7-2 渲染视图
protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // 调用视图解析器解析视图名称得到视图View对象
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
    } else {
        view = mv.getView();
    }
    // 调用视图的render方法渲染视图,将结果输出到客户端
    view.render(mv.getModelInternal(), request, response);
}

视图解析过程,此方法干了 2 件事

  1. 调用视图解析器解析视图名称得到视图 View 对象
  2. 调用视图的 render 方法渲染视图,将结果输出到客户端
// 调用视图解析器解析视图名称得到视图 View 对象
protected View resolveViewName(String viewName, @Nullable Map<String, Object> model,
   Locale locale, HttpServletRequest request) throws Exception {
    if (this.viewResolvers != null) {
        for (ViewResolver viewResolver : this.viewResolvers) {
            View view = viewResolver.resolveViewName(viewName, locale);
            if (view != null) {
                return view;
            }
        }
    }
    return null;
}
// 调用视图的 render 方法渲染视图,将结果输出到客户端
view.render(mv.getModelInternal(), request, response);

3.7.3 拦截器的 afterCompletion 方法

最后,触发所有拦截器的afterCompletion方法:

// 反向调用拦截器的afterCompletion方法
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex) {
    for (int i = this.interceptorIndex; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        try {
            interceptor.afterCompletion(request, response, this.handler, ex);
        } catch (Throwable ex2) {
            logger.error("HandlerInterceptor.afterCompletion threw exception", ex2);
        }
    }
}

最后,我们对这个关键点进行一个总结

  1. 异常处理的灵活性:SpringMVC提供了一种可扩展的异常处理机制,允许开发者自定义异常处理逻辑。
  2. 视图解析的多样性:通过ViewResolver接口,SpringMVC支持多种类型的视图技术,如JSP、Thymeleaf等。
  3. 拦截器的完整生命周期afterCompletion方法的调用确保了拦截器可以执行一些清理工作,无论请求处理是否成功。
  4. 错误视图的特殊处理:对于由异常产生的错误视图,SpringMVC会清理请求中的错误属性,以防止信息泄露。

通过这个流程,SpringMVC确保了请求处理的完整性和灵活性,同时为开发者提供了丰富的扩展点。

四、扩展:过滤器和拦截器的区别是什么

在Java Web开发中,过滤器(Filter)和拦截器(Interceptor)都是常用的请求处理组件,但它们在实现机制和应用场景上有着显著的差异。

link-20240829184855330.png

过滤器和拦截器在Java Web开发中扮演着不同但同样重要的角色。过滤器基于Java Servlet规范,在Tomcat容器中首先处理请求,适用于所有Java Web应用。它通常通过web.xml配置或@WebFilter注解实现,主要用于处理请求的前置和后置操作,如字符编码设置和跨域请求处理。

相比之下,拦截器是Spring框架的一部分,在Servlet容器内部运作,在Spring MVC的DispatcherServlet执行前后被调用。拦截器通过Spring配置文件或Java配置类设置,能够更精细地控制请求处理流程,特别适合进行权限验证、日志记录和事务管理等操作。

在选择使用过滤器还是拦截器时,需要考虑具体的应用场景。对于与Web容器紧密相关的功能(如编码转换),过滤器是更好的选择;而对于涉及应用程序业务逻辑的功能(如权限检查),拦截器则更为合适。

当需要对请求进行细粒度控制时,拦截器通常是优先考虑的选项。过滤器基于Servlet规范,可以通过web.xml文件配置;而拦截器作为Spring框架的一部分,通过定义拦截器类实现,能够更好地集成Spring的特性,如AOP、更细致的权限控制和事务管理。

4.1 过滤器的实现

需要实现javax.servlet.Filter接口

public class LoginFilter implements Filter {
    public void init(FilterConfig config) throws ServletException { // 初始化 }
    public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) throws ServletException, IOException {
        // 登录验证和处理
        chain.doFilter(req, resp);
    }
    public void destroy() { // 销毁 }
}

配置web.xml:

<filter>
    <filter-name>LoginFilter</filter-name>
    <filter-class>com.jd.LoginFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>LoginFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

4.2 拦截器的实现

需要实现org.springframework.web.servlet.HandlerInterceptor接口:

@Component
public class LoginInterceptor implements HandlerInterceptor {
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
       // 登录验证和处理
        return true;
    }
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        // 后处理
    }
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 完成处理
    }
}

并且在SpringMVC中进行配置:

<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/api/*"/>
        <bean class="com.jd.LoginInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

或者通过Spring配置(Java配置方式):

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LoginInterceptor()).addPathPatterns("/api/*");
    }
}

五、扩展:SpringMVC是如何将不同的Request路由到不同Controller中的?

SpringMVC如何将不同的请求精确地路由到对应的Controller方法?这个问题的核心在于理解SpringMVC是如何匹配和选择合适的HandlerMethod

SpringMVC的路由过程主要包含以下步骤:

  1. 启动时,将带有@RequestMapping注解的方法封装为RequestMappingInfoHandlerMethod对。
  2. 将这些对注册到MappingRegistry中。
  3. 请求到来时,通过AbstractHandlerMethodMapping#lookupHandlerMethod方法查找匹配的HandlerMethod
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 1.先通过url获取到对应的RequestMappingInfo集合
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if (directPathMatches != null) {
        // 2.把RequestMappingInfo和HandlerMethod放到match里面
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        Match bestMatch = matches.get(0);
        // 3.如果匹配到多个Match(譬如url相同但是方法不同),则通过RequestMappingInfo中的各种condition匹配出对应的bestMatch
        if (matches.size() > 1) {
        }
        // 4.获取match中的HandlerMethod
        return bestMatch.getHandlerMethod();
    } else {
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    }
}

匹配优先级示例

考虑以下两个Handler方法:

  1. @RequestMapping("/users/{id}")
  2. @RequestMapping("/users/123")

对于请求 /users/123,SpringMVC会比较这两个方法的RequestMappingInfo,第一个方法的路径模式更通用,匹配度较低。第二个方法完全匹配请求路径,因此优先级更高。

SpringMVC不仅考虑URL,还会评估其他因素HTTP方法(GET, POST等)、请求参数、请求头、媒体类型(Produces/Consumes)

这些因素被封装在Match类中,通过compare方法综合评估,选出最佳匹配的HandlerMethod

六、总结

深入研究SpringMVC的源码不仅让我们全面理解了框架的内部工作机制,更为日常开发提供了宝贵的洞察。这种探索过程带来了多方面的收益:它增强了我们对代码执行流程的把握,提升了解决复杂问题和定位难懂bug的能力,同时也启发了我们在代码设计和架构方面的思考。

通过学习成熟框架的设计模式和架构思想,我们不仅能够提高自身项目的代码质量,还能培养评估新技术和做出明智技术决策的能力。虽然源码分析过程充满挑战,但每次深入都会带来新的发现和思考,这正是保持技术敏锐度和竞争力的关键。

希望本文的分享能为读者提供有价值的见解,也欢迎各位指出不足之处,让我们在交流中共同进步,不断提升技术水平。


image.png

感谢大家的观看!!!创作不易,如果觉得我写的好的话麻烦点点赞👍支持一下,谢谢!!!