Spring MVC 之 DispatcherServlet

Spring MVC 是什么

Spring Web MVC (Spring MVC) 是一套以 Servlet API 为基础平台的优雅的 Web 框架,一直是 Spring Framework 中重要的一个组成部分。 正式名称 “Spring Web MVC” 来自其源模块 spring-webmvc 的名称,但它通常被称为“Spring MVC”。

与 Spring Web MVC 并行,Spring Framework 5.0 引入了一个 Reactive stack —— Web框架,其名称 Spring WebFlux 也基于它的源模块 spring-webflux。

DispatcherServlet

与许多其他 Web 框架一样,Spring MVC 同样围绕前端页面的控制器模式 (Controller) 进行设计,其中最为核心的 Servlet —— DispatcherServlet 为来自客户端的请求处理提供通用的方法,而实际的工作交由可自定义配置的组件来执行。 这种模型使用方式非常灵活,可以满足多样化的项目需求。

和任何普通的 Servlet 一样,DispatcherServlet 需要根据 Servlet 规范使用 Java 代码配置或在 web.xml 文件中声明请求和 Servlet 的映射关系。 DispatcherServlet 通过读取 Spring 的配置来发现它在请求映射,视图解析,异常处理等方面所依赖的组件。

以下是注册和初始化 DispatcherServlet 的 Java 代码配置示例。 该类将被 Servlet 容器自动检测到:

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

        // 加载 Spring Web Application 的配置
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();

        // 创建并注册 DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

以下是在 web.xml 中注册和初始化 DispatcherServlet 的方法:

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- Spring 上下文的配置 -->
        <param-value>/WEB-INF/app-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value></param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app</servlet-name>
        <!-- "/*" 表示将所有请求交由 DispatcherServlet 处理 -->
        <url-pattern>/*</url-pattern>
    </servlet-mapping>

</web-app>

应用上下文的层次结构

DispatcherServlet 依赖于一个 WebApplicationContext(对普通 ApplicationContext 的功能扩展)来实现自己的配置。 WebApplicationContext 中包含了一个指向它所关联的 ServletContext 和 Servlet 的链接。 它同时还绑定到 Servlet 上下文中,以便应用程序可以使用 RequestContextUtils 中的静态方法在 WebApplicationContext 进行查找,来判断是否需要调用 DispatcherServlet 中的方法。

对于只有一个 WebApplicationContext 应用程序来说,这已经可以满足使用了。 同时也可以使用具有层次结构的上下文,其中有一个根上下文(或者叫基上下文) 被多个 DispatcherServlet(或其他普通 Servlet)实例所共享,每个实例都有属于自己的子上下文配置。

根上下文通常包含被多个 Servlet 实例共享的公共 bean,例如数据仓库和业务。 这些 bean 被继承下来使用,还可以在特定的 Servlet 的子上下文中重写(即重新声明一个 bean 的配置),子上下文中拥有该 Servlet 所独有的局部 bean 实例:
在这里插入图片描述

以下是使用 WebApplicationContext 层次结构的示例配置:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class<?>[] { RootConfig.class };
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class<?>[] { App1Config.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/app1/*" };
    }
}

在 web.xml 中的配置

<web-app>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <!-- 根上下文的配置 -->
        <param-value>/WEB-INF/root-context.xml</param-value>
    </context-param>

    <servlet>
        <servlet-name>app1</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <!-- app1 专属的的子上下文 -->
            <param-value>/WEB-INF/app1-context.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
        <servlet-name>app1</servlet-name>
        <url-pattern>/app1/*</url-pattern>
    </servlet-mapping>

</web-app>

2. 带有特殊功能的 Bean
DispatcherServlet 依靠这些特殊的 bean 来处理请求并返回响应。 这些特殊的 bean 是指实现 WebFlux 框架协议的,同样由 Spring 管理的对象。 这些对象都含有一套默认的配置,但也可以自定义各种属性,从而进行灵活扩展或功能改写。

3. Web MVC 的配置

应用程序可以单独声明上文中“带有特殊功能的 Bean”中列出的基础版的 bean。 DispatcherServlet 扫描这些 bean 所属的 WebApplicationContext。 如果没有匹配的 bean 类型,它将返回 DispatcherServlet.properties 中的默认类型。

在大多数情况下,MVC 默认配置是最好的实现。 它采用 Java 代码或 XML 文件来配置所需的 bean,同时提供更高级别的配置回调 API 用于改写默认配置。

6. 拦截器

所有 HandlerMapping 的实现类都支持使用拦截器,特别是将某些功能只应用到特定的请求上时(比如判断权限),拦截器就非常有用。 拦截器必须实现 org.springframework.web.servlet 包中的 HandlerInterceptor,它提供了三个方法,供不同时刻来调用:

preHandle(…):在请求处理前执行…
postHandle(…):在请求处理后执行…
afterCompletion(…):在请求处理完全结束后执行…

preHandle(…) 方法返回布尔值,可以通过这一点选择来切断或继续请求的处理链。当返回 true 的时候,处理器链会继续执行;返回 false 的时候, DispatcherServlet 就会认为拦截器已经处理了请求或返回了视图,并不会继续被处理链中的其他处理器或拦截器所处理。

请注意,postHandle(…) 对使用 @ResponseBody 和 ResponseEntity 方法的用处不大,在 HandlerAdapter 中, postHandle(…) 调用之前就已经提交响应了。 所以这时再修改响应什么用也没有了。 这种情况下,可以实现 ResponseBodyAdvice,并将其声明为 Controller Advice bean (在 Controller 类上添加 @ControllerAdvice 注解)或直接在 RequestMappingHandlerAdapter 中进行配置。

7. 异常处理

在映射或调用请求处理程序 (例如带有 @Controller 注解的控制器) 处理请求时,如果抛出了异常,则 DispatcherServlet 调用 HandlerExceptionResolver bean 来处理异常,然后将错误页面或错误状态码等信息返回个客户端。

下面列出 HandlerExceptionResolver 的几个实现类:

HandlerExceptionResolver 实现类

处理方式

HandlerExceptionResolver 中可以返回:

ModelAndView: 指向视图名
不带属性的 ModelAndView: 如果已经通过权重更低的方法处理过异常了
null: 这个异常无法被当前异常处理器识别,需要丢给接下来的处理器,如果所有处理器都不能处理这个异常,异常会传到 Servlet 容器中,由容器来处理
自定义异常处理请求也非常简单,比如,在 xml 中配置一个 HandlerExceptionResolver 的 bean, Spring MVC 会自动使用内部的默认异常处理器来处理 Spring MVC 抛出的异常(带有 @ResponseStatus 注解的异常或带有 @ExceptionHandler 注解的方法),可以修改这些默认配置或干脆直接重写新的配置。

Servlet 容器中定义的错误页面 (error-page)

如果抛出的异常没能被任何 HandlerExceptionResolver 处理,就会传到 Servlet 容器中。如果将 HTTP 响应码设置为 4xx 或 5xx, Servlet 容器会直接返回默认的错误页面。可以在 web.xml 中配置自定义的错误页面:

<error-page>
    <location>/error</location>
</error-page>

此时,如果想要对异常处理进一步自定义,可以这样做:

@RestController
public class ErrorController {

@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("status", request.getAttribute("javax.servlet.error.status_code"));
    map.put("reason", request.getAttribute("javax.servlet.error.message"));
    return map;
}

}
这样就能再次将 /error 请求交给 DispatcherServlet 来处理,这种方式解决了使用 RestController 返回 JSON,而不是返回视图的情况。

8. 视图名解析
Spring MVC 定义了 View 和 ViewResolver 这两个接口,View 负责返回视图前的数据准备, ViewResolver 则负责将逻辑视图名映射到实际视图。 屏蔽了具体的视图实现细节。

ViewResolver 的几个实现类

语言环境拦截器
LocaleChangeInterceptor bean 可以通过自定义配置来修改请求中的参数,达到修改语言环境的目的。调用 LocaleResolver 中的 setLocal() 方法即可。下面的例子演示将所有请求中的 siteLanguage 参数修改为荷兰语:

<bean id="localeChangeInterceptor"
        class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
    <property name="paramName" value="siteLanguage"/>
</bean>

<bean id="localeResolver"
        class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>

<bean id="urlMapping"
        class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
    <property name="interceptors">
        <list>
            <ref bean="localeChangeInterceptor"/>
        </list>
    </property>
    <property name="mappings">
        <value>/**/*.view=someController</value>
    </property>
</bean>

10. 主题

Spring Web MVC 框架支持自定义主题,应用程序的整体外观可以实现统一修改。主题即静态资源的集合,通常是 CSS 和图片,两者决定了应用的整体风格。

主题的定义

必须定义一个 org.springframework.ui.context.ThemeSource 接口的实现类才能使用主题功能。默认情况下,WebApplicationContext 通过 org.springframework.ui.context.support.ResourceBundleThemeSource 实现主题功能,它从 classpath 的根目录读取配置文件。 要使用自定义的 ThemeSource,需要配置 ResourceBundleThemeSource 的主题名称前缀,在应用程序上下文中注册一个 themeSource bean。

使用 ResourceBundleThemeSource 自定义主题,需要将配置写在 properties 文件中,这个文件中包含了构成主题的所有资源。

11.Multipart

org.springframework.web.multipart.MultipartResolver 提供了一种处理表单上传文件的解决方案,有两种实现方式,一种是基于 Apache 的 Commons-Fileupload,另一种是基于 Servlet 3.0 的。

首先要在 Spring 的配置文件中为 DispatcherServlet 声明一个叫做 MultipartResolver 的 bean,DispatcherServlet 会自动识别并调用它来处理文件上传请求。它会将 content-type 为 multipart/form-data 的请求包装成 MultipartHttpServletRequest,从而将这些 “part” 暴露为请求的一个参数。

Apache FileUpload

要想使用 Apache Commons-Fileupload,需要将 multipartResolver bean 配置为 CommonsMultipartResolver,另外不要忘了添加 commons-fileupload 的依赖。

Servlet 3.0

如果通过 Servlet 3.0 处理 multipart 请求,则同样需要在 DispatcherServlet 中注册。在 Java 代码中配置的话,需要添加一个 MultipartConfigElement;在 xml 文件中配置的话,添加 节点。文件大小限制和文件保存位置等选项同样需要这样配置,因为在 Servlet 3.0 以后,不允许 MultipartResolver 这么干了。

Servlet 3.0 配置好后,只需要在 xml 文件中将 multipartResolver hean 配置为 StandartMultipartResolver 即可。

猜你喜欢

转载自blog.csdn.net/weixin_42868638/article/details/82951849
今日推荐