Spring Boot「14」MVC 与前端控制器模式

241 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情

Spring MVC 采用典型的 Front Controller 架构。 DispatcherServlet 作为其中的 Front Controller 负责将用户请求匹配到对应的 Controller 中, 并将 Controller 返回的数据交给 ViewResolver 根据不同的模板引擎(例如 JSP、Thymeleaf 等)渲染出响应的 View 返回给用户。 下图源自于baeldung,详细描述了 Spring MVC 的具体工作流程:

mvc_front-controller.png

01-基础概念

在学习 Spring MVC 时,与 Servlet 相关地诸多概念让初学者非常的头疼。 本节中就对这些基本概念进行一下梳理,帮助各位更好地理解这些概念。

01.1-Servlet

首先,第一个概念就是 Servlet。 简单来说,Servlet 就是一个 Java 类,它能够处理请求,并返回一个响应。 它的接口定义是:

public interface Servlet {
    void init(ServletConfig var1) throws ServletException;
    void service(ServletRequest var1, ServletResponse var2) throws ServletException, IOException;
    void destroy();
    
    String getServletInfo();
    ServletConfig getServletConfig();
}

init\destroy\service 是 Servlet 整个声明周期中三个比较重要的函数。 其中 service 就是其处理 request 并返回 response 的接口。

Spring MVC 中最重要的一个 Servlet 实现就是 DispatcherServlet。

01.2-Servlet 容器

Servlet 运行在 Servlet 容器中,由容器管理。 Servlet 容器是 Web 服务器或应用服务器的一部分,为 Servlet 提供运行时上下文,例如网络服务、MIME 解析等; Servlet 容器可以运行在 Web 服务器所在的进程中,也可以是同一主机上的不同进程,甚至可以是不同主机上的进程。 当 Web 服务器收到 Request 请求时,会转交给 Servlet 容器。 之后,Servlet 容器会选择一个 Servlet 来处理请求,并将响应返回给 Web 服务器。

01.3-ServletContext

ServletContext 定义了 Web 应用程序的 servlet 视图。 以下为 Servlet Specification 对 ServletContext 的描述:

The ServletContext interface defines a servlet's view of the Web application within which the servlet is running.

怎么来理解这句话呢?一个 Web 应用中所定义的所有的 Servlet 都是运行在某个 ServletContext 下的。 而且,每个 ServletContext 都关联了一个路径,称之为 context path。 Servlet 容器会根据请求的 URL 来选择与其匹配的 ServletContext,并将请求交与其处理。

与 context path 相对应的,ServletContext 中的所有 Servlet 也都关联了一个路径,称之为 servlet path。 它是相对于 context path 的一个相对路径。 举例说明,如果我们的应用关联的 context path 为 /demo,并且有一个 Servlet 且 它的 servlet path 为 /hi。 那么,对 URL 为 /demo/hi 的请求将由此 Servlet 处理。

当我们在 Tomcat 中部署 Web 应用时,需要增加配置文件 conf/Catalina/localhost/${web-app-name}.xml,且里面的内容与下面类似:

<Context path="/demo" docBase="demo" > 

docBase 指定了我们 Web 应用所在的磁盘位置,path 就是应用对应的 ServletContext 的 context path。

如何定义一个应用的 context path 呢?有如下几种方式:

  1. 以 Property 的方式,在 application.properties 或 application.ymal 中指定:server.servlet.context-path=/demo
  2. 通过环境变量的方式指定
    • (2.x) export SERVER_SERVLET_CONTEXT_PATH=/demo unix | set SERVER_SERVLET_CONTEXT_PATH=/baeldung windows
    • (1.x) SERVER_CONTEXT_PATH
  3. 通过编程方式指定:
    • (2.x) WebServerFactoryCustomizer
    • (1.x) EmbeddedServletContainerCustomizer

如何向 ServletContext 中注册一个 Servlet 呢?

  1. 在 Jakarta EE 中,可以通过:
    • web.xml 中的<servlet></servlet>标签
    • @WebServlet注解
  2. 在 Spring Boot 中,可以通过:
    • web.xml 中的<servlet></servlet>标签
    • 编程方式指定: WebApplicationInitializer & WebMvcConfigurer | ServletRegistrationBean
    • 通过 Property 指定:
      • servlet.name=dispatcherExample & servlet.mapping=/dispatcherExampleURL
      • System.setProperty("custom.config.location", "classpath:custom.properties"); & System.getProperty("custom.config.location");
      • spring.mvc.servlet.path=/test

DispatcherServlet

在 Spring MVC 中,DispatcherServlet 是应用的入口,负责将 HttpRequests 定向到 handler。 HandlerAdapter 接口主要是为了方便处理 HttpServletRequest。

public interface HandlerAdapter {
    boolean supports(Object handler);
    @Nullable
    ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
}

supports 用来判断是否支持某个 handler 实例; handle 方法接收 request & response & handler 对象,返回一个 ModelAndView 对象,后续会被 DispatcherServlet 处理。 调用 DispatcherServlet#getHandler 时,每个实现了 HandlerAdapter 接口的对象会被放置到 HandlerExecutionChain 中, 随着处理过程的推进,它们的 handle() 方法会被应用到 HttpServletRequest 上。

常用的 HandlerAdapter 实现:

  • SimpleControllerHandlerAdapter & BeanNameUrlHandlerMapping,负责将 request 定向到实现了 Controller 接口的实例中。 同样是 Spring MVC 中默认的 HandlerAdapter。
  • SimpleServletHandlerAdapter,负责将 DispatcherServlet 收到的 request 定向到其他实现了 Servlet 接口的实例,调用其 service() 方法。
  • (deprecated in Spring 3.2)AnnotationMethodHandlerAdapter & DefaultAnnotationHandlerMapping,负责将 request 定向到标注了@RequestMapping注解的实例中。
  • RequestMappingHandlerAdapter & RequestMappingHandlerMapping,将 request 定向到@RequestMapping注解的方法。 @EnableWebMvc<mvc:annotation-driven />会自动向容器中注入这两个 bean。
  • HttpRequestHandlerAdapter ,负责处理 HttpRequests,将 request 定向到实现了 HttpRequestHandler 接口的实例中,调用其 handleRequest() 方法,返回值为 void 而非 ModelAndView。 一般用在不需要渲染视图的场景。

除上述部分外,Spring MVC 中还包括如下组件:

  • DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE 关联到一个 WebApplicationContext
  • DispatcherServlet 通过 getHandler 方法获得所有实现了 HandlerAdapter 接口的对象,HandlerAdapter 需要与 HandlerMapping 一起使用。
  • (可选)ViewResolver,主要通能:决定提供何种类型的视图,以及从哪里获取对应的视图。
  • (可选)LocaleResolver,主要功能:customize session, request, or cookie information
  • (可选)ThemeResolver,主要功能:视图主题相关
  • (可选)MultipartResolver,主要功能:将 request 包装成为 MultipartHttpServletRequest
  • HandlerExceptionResolver,主要功能:Spring 3.2 之前统一异常处理方式