2024-12-16  阅读(11)
版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接:https://www.skjava.com/mianshi/baodian/detail/1391187278

回答

Spring MVC 是一个用于构建基于 MVC(Model-View-Controller)设计模式的 Web 应用,它的核心组件包括如下几个:

  • DispatcherServlet:前端控制器,复杂接收所有 HTTP 请求并将其分发到具体的处理器。
  • HandlerMapping:根据请求 URL 找到对应的处理器 Handler。
  • HandlerAdapter:适配具体的处理器,负责调用对应的处理方法。
  • Controller:Handler,具体的业务逻辑处理器,处理用户的请求并返回数据获视图。
  • ViewResolver:视图解析器,负责解析视图名称并返回具体的视图实现。
  • ModelAndView:用于在 Controller 与 View 之间传递数据和逻辑视图名称的对象。
  • HttpMessageConverter:用于在请求和响应中处理 JSON、XML 等消息体的转换。
  • ExceptionResolver:处理请求过程中抛出的异常并生成相应的错误响应。

详解

DispatcherServlet

DispatcherServlet 是 Spring MVC 的核心组件,也是前端控制器,负责接收所有的 HTTP 请求并将其分发到具体的处理器。同时它还协调其他组件(如HandlerMapping、HandlerAdapter、ViewResolver等等)完成请求的处理和响应。主要工作流程是:

  • 接收 HTTP 请求。
  • 根据 URL 找到对应的 Handler。
  • 调用 Handler 处理请求并返回 ModelAndView
  • 使用 ViewResolver 渲染视图,生成 HTTP 响应。

配置 DispatcherServlet :

<servlet>
    <servlet-name>dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
</servlet-mapping>

我们简单看下它的源码部分。我们进去 DispatcherServlet 找对应的 doGet()doPost()(开发过 Servlet 的小伙伴一定知道为什么要找这两个),发现并没有,在父类 FrameworkServlet 中找到:

  protected final void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {
    // 委托给 processRequest
    processRequest(request, response);
  }
  
  protected final void processRequest(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException {

    // 省略代码...

    try {
      doService(request, response);
    }
    catch (ServletException | IOException ex) {
      // ...
    }
    catch (Throwable ex) {
       // ...
    }

    finally {
       // ...
    }
  }

最后委托 doService(),该方法就是在 DispatcherServlet 实现:

  protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
    // ...

    try {
      doDispatch(request, response);
    }
    finally {
      //...
    }
  }

继续委托 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);

        // 获取当前请求的 Handler
        mappedHandler = getHandler(processedRequest);
        if (mappedHandler == null) {
          noHandlerFound(processedRequest, response);
          return;
        }

        // 获取当前请求的 HandlerAdapter
        HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

        //...
        
        // 拦截器的前置
        if (!mappedHandler.applyPreHandle(processedRequest, response)) {
          return;
        }

        // 执行业务逻辑
        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 {
      // ...
    }
  }

从这个方法我们可以看到整个 Spring MVC 处理请求的逻辑。

HandlerMapping

根据请求 URL 找到对应的处理器 Handler

DispatcherServlet 中,它利用 getHandler() 获取对应的 Handler:

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

逻辑很简单就是迭代 handlerMappings 找到对应的 HandlerExecutionChain 对象,找到就停止。

在 Spring MVC 中,当一个 HTTP 请求到达时,DispatcherServlet 会将其分发给相应的处理器,而 HandlerExecutionChain 是这个分发过程中的一部分,它主要负责存储和管理与一个具体请求处理器相关的一系列拦截器,并在请求处理时依次执行这些拦截器。所以,HandlerExecutionChain 主要由两个部分组成:

  • Handler:实际的请求处理器
  • Interceptor:一系列的拦截器。

在调用 Handler 的实际业务逻辑时,DispatcherServlet 会优先一次调用拦截器的 preHandle() ,如果所有拦截器都返回 true,那么请求会被传递给实际的处理器进行处理。如下:

// 拦截器的前置
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
   return;
}

HandlerAdapter

适配具体的处理器,负责调用对应的处理方法。它的主要作用是为不同类型的处理器提供统一的调用适配机制。

HandlerAdapter 提供了一种抽象机制,用于支持多种类型的处理器,同时,它也解耦了 DispatcherServlet 和具体的处理器,负责处理器的请求调用进行适配,使得不同的控制器可以被统一调用。

Spring MVC 内置了几个 HandlerAdapter

  • RequestMappingHandlerAdapter:用于支持基于注解的处理器(如 @Controller@RequestMapping 标注的方法)。
  • SimpleControllerHandlerAdapter:用于支持实现了 org.springframework.web.servlet.mvc.Controller 接口的传统控制器。
  • HttpRequestHandlerAdapter:用于支持实现了 org.springframework.web.HttpRequestHandler 接口的处理器。
  • HandlerFunctionAdapter:用于支持基于函数式编程模型的处理器。在 Spring WebFlux 中较为常用。

DispatcherServlet 的 doService() 方法中会调用 getHandlerAdapter() 获取对应的 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");
  }

ViewResolver

ViewResolver 主要负责将逻辑视图名称解析为实际 View 对象的接口。它是视图渲染流程中的重要一环,将控制器返回的 ModelAndView 中的逻辑视图名称解析为可以渲染的视图实例。主要职责包括:

  • 解析视图名称:将逻辑视图名称(如 "home" 或 "user/list")映射为具体的视图实现。
  • 返回 View 实例:生成并返回 View 对象,最终负责渲染输出。

在 Spring MVC 中,视图的渲染流程一般如下:

  • Controller 处理请求后,返回一个 ModelAndView 对象。
  • 其中的逻辑视图名称由 ViewResolver 转换为具体的视图。
  • 视图实例调用其 render(),结合模型数据完成响应渲染。

ViewResolver 是一个接口,定义如下:

public interface ViewResolver {
  @Nullable
  View resolveViewName(String viewName, Locale locale) throws Exception;

}
  • viewName:逻辑视图名称
  • locale:用于国际化视图选择的区域信息

返回的是一个 View 对象,表示可执行渲染的视图。

Spring MVC 提供了很多 ViewResolver 的实现,主要有:

  • InternalResourceViewResolver:用于解析 JSP 文件
  • ThymeleafViewResolver:用于支持 Thymeleaf 模板引擎
  • FreeMarkerViewResolver:用于支持 FreeMarker 模板引擎
  • BeanNameViewResolver:根据视图名称从 Spring 容器中直接查找对应的 View Bean
  • XmlViewResolver:从 XML 配置文件中加载视图定义
  • ContentNegotiatingViewResolver:根据请求的内容类型(Content-Type)动态选择视图。可用于返回多种类型的响应,如 JSON、XML 或 HTML。

ModelAndView

ModelAndView 主要用于在控制器方法中返回数据模型和视图信息的组合,它是 Controller 和 View 之间的桥梁,包含了渲染视图所需的逻辑视图名称以及模型数据。通过 ModelAndView,控制器可以同时定义:

  • Model:数据模型,用于传递业务数据到视图。
  • View:视图信息,用于指定渲染的页面或返回的结果。

所以,ModelAndView 的定义如下:

public class ModelAndView {

  // 逻辑视图名称或具体 View 对象
  @Nullable
  private Object view;

  // 模型数据
  @Nullable
  private ModelMap model;

  //  HTTP 状态码
  @Nullable
  private HttpStatus status;
  

}

HttpMessageConverter

在 Spring MVC 中,HttpMessageConverter 是将 HTTP 请求和响应进行转换的核心组件,负责在 HTTP 消息和 Java 对象之间进行序列化与反序列化操作。

HttpMessageConverter 是一个接口,它提供了一种机制:

  • 将 HTTP 请求的内容转换为 Java 对象(反序列化),体现在 @RequestBody 注解上
  • 将 Java 对象转换为 HTTP 响应的内容(序列化),体现在 @ResponseBody 注解上

HttpMessageConverter 接口定义如下:

public interface HttpMessageConverter<T> {

  // 检查是否支持某种类型
  boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
  boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

  // 获取支持的媒体类型列表
  List<MediaType> getSupportedMediaTypes();

  default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
    return (canRead(clazz, null) || canWrite(clazz, null) ?
        getSupportedMediaTypes() : Collections.emptyList());
  }

  // 读取请求体并转换为目标类型
  T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
      throws IOException, HttpMessageNotReadableException;

  // 将对象写入响应体
  void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
      throws IOException, HttpMessageNotWritableException;

}

常见的实现类有:

  • MappingJackson2HttpMessageConverter:基于 Jackson 库,将 JSON 数据与 Java 对象之间进行转换。
  • GsonHttpMessageConverter:基于 Gson 库实现的 JSON 数据的处理。
  • Jaxb2RootElementHttpMessageConverter:基于 JAXB 实现,用于将 XML 数据与 Java 对象之间进行转换。
  • StringHttpMessageConverter:用于处理普通的 String 类型数据。
  • ByteArrayHttpMessageConverter:用于处理字节数组数据。

目前我们最常用的就是 MappingJackson2HttpMessageConverter

ExceptionResolver

ExceptionResolver 是 Spring MVC 处理应用程序中未捕获异常的核心接口,它负责捕获控制器方法中的异常,并将其转换为合适的视图或响应结果,从而避免异常直接暴露给用户。其接口定义如下:

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

}

  • HttpServletRequest request:当前的 HTTP 请求对象。
  • HttpServletResponse response:当前的 HTTP 响应对象。
  • Object handler:触发异常的处理器对象(通常是控制器方法)。
  • Exception ex:抛出的异常对象。

返回值 ModelAndView 表示返回的视图和模型数据。

就我们实际开发过程中,@ControllerAdvice 全局异常处理是我们目前使用最为广泛和优雅的方式。详细情况见面试题:Spring MVC 如何做统一异常处理?


Java 面试宝典是大明哥全力打造的 Java 精品面试题,它是一份靠谱、强大、详细、经典的 Java 后端面试宝典。它不仅仅只是一道道面试题,而是一套完整的 Java 知识体系,一套你 Java 知识点的扫盲贴。

它的内容包括:

  • 大厂真题:Java 面试宝典里面的题目都是最近几年的高频的大厂面试真题。
  • 原创内容:Java 面试宝典内容全部都是大明哥原创,内容全面且通俗易懂,回答部分可以直接作为面试回答内容。
  • 持续更新:一次购买,永久有效。大明哥会持续更新 3+ 年,累计更新 1000+,宝典会不断迭代更新,保证最新、最全面。
  • 覆盖全面:本宝典累计更新 1000+,从 Java 入门到 Java 架构的高频面试题,实现 360° 全覆盖。
  • 不止面试:内容包含面试题解析、内容详解、知识扩展,它不仅仅只是一份面试题,更是一套完整的 Java 知识体系。
  • 宝典详情:https://www.yuque.com/chenssy/sike-java/xvlo920axlp7sf4k
  • 宝典总览:https://www.yuque.com/chenssy/sike-java/yogsehzntzgp4ly1
  • 宝典进展:https://www.yuque.com/chenssy/sike-java/en9ned7loo47z5aw

目前 Java 面试宝典累计更新 400+ 道,总字数 42w+。大明哥还在持续更新中,下图是大明哥在 2024-12 月份的更新情况:

想了解详情的小伙伴,扫描下面二维码加大明哥微信【daming091】咨询

同时,大明哥也整理一套目前市面最常见的热点面试题。微信搜[大明哥聊 Java]或扫描下方二维码关注大明哥的原创公众号[大明哥聊 Java] ,回复【面试题】 即可免费领取。

阅读全文