20220507 1. Web Servlet - Spring Web MVC

前言

文档地址

Spring Web MVC 是最初建立在 Servlet API 之上的 Web 框架,从一开始就包含在 Spring Framework 中。正式名称 Spring Web MVC 来自其源码模块的名称 ( spring-webmvc ),但通常称为 Spring MVC

与 Spring Web MVC 并行,Spring Framework 5.0 引入了一个反应式 Web 框架,其名称 Spring WebFlux 也基于其源码模块 ( spring-webflux )

相关依赖:

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

DispatcherServlet

WebFlux

Spring MVC 与许多其他 Web 框架一样,是围绕前端控制器模式设计的,其中核心 ServletDispatcherServlet 为请求处理提供共享算法,而实际工作由可配置的委托组件执行。该模型非常灵活,支持多种工作流程。

DispatcherServlet ,就像任何 Servlet ,需要根据 Servlet 规范通过使用 Java 配置或在 web.xml 中声明和映射。反过来,DispatcherServlet 使用 Spring 配置文件来发现它需要的委托组件,用于请求映射,视图解析,异常处理等。

以下 Java 配置示例注册并初始化 DispatcherServlet ,Servlet 容器自动检测到(请参阅 Servlet Config ):

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) {

        // Load Spring web application configuration
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(AppConfig.class);

        // Create and register the DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(context);
        ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}

除了直接使用 ServletContext API 之外,您还可以扩展 AbstractAnnotationConfigDispatcherServletInitializer 和覆盖特定的方法(参见 Context Hierarchy 下的示例)。

以下 web.xml 配置示例注册和初始化 DispatcherServlet

<web-app>

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

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <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>
        <url-pattern>/app/*</url-pattern>
    </servlet-mapping>

</web-app>

Spring Boot 遵循不同的初始化顺序。Spring Boot 没有接入 Servlet 容器的生命周期,而是使用 Spring 配置来引导自身和嵌入的 Servlet 容器。在 Spring 配置中检测 FilterServlet 声明,并注册到 Servlet 容器。有关更多详细信息,请参阅 Spring Boot 文档

  • org.springframework.web.servlet.DispatcherServlet
  • org.springframework.web.WebApplicationInitializer
  • org.springframework.web.context.ContextLoaderListener

上下文层次结构

DispatcherServlet 需要 WebApplicationContext(普通 ApplicationContext 的扩展 )用于其自身的配置。WebApplicationContext 有一个链接到 ServletContext 和与它相关联的 Servlet 。它还绑定到 ServletContext ,因此应用程序可以使用 RequestContextUtils 上的静态方法来查找 WebApplicationContext

对于许多应用程序,拥有一个 WebApplicationContext 就足够了。但是也可以有一个上下文层次结构,其中根 WebApplicationContext 在多个 DispatcherServlet(或其他 Servlet )实例之间共享,每个实例都有自己的子 WebApplicationContext 配置。

WebApplicationContext 通常包含基础设施 bean,例如需要跨多个 Servlet 实例共享的数据存储库和业务服务。这些 bean 被有效地继承,并且可以在 Servlet 特定的子 WebApplicationContext 中被覆盖(即重新声明),后者通常包含针对特定 Servlet 的 bean 。下图显示了这种关系:

mvc context hierarchy

以下示例配置 WebApplicationContext 层次结构:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

如果不需要应用上下文层次结构,应用程序可以通过 getRootConfigClasses() 返回所有配置, getServletConfigClasses() 返回 null

以下示例显示了等效的 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>
            <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>

如果不需要应用上下文层次结构,应用程序可以仅配置根上下文并将 Servlet 参数 contextConfigLocation 留空。

特殊 Bean 类型

WebFlux

DispatcherServlet 委托特殊的 bean 来处理请求和渲染适当的响应。“特殊 bean” 是指实现框架契约的 Spring 管理的 Object 实例。这些通常带有内置契约,但您可以自定义它们的属性并扩展或替换它们。

下表列出了 DispatcherServlet 检测的特殊 bean :

Bean 类型 解释
HandlerMapping 将请求与用于预处理和后处理的 拦截器 列表一起映射到处理器(handler)。映射基于某些标准,其细节因 HandlerMapping 实现而异。
两个主要 HandlerMapping 实现是 RequestMappingHandlerMapping (支持带 @RequestMapping 注解的方法)和 SimpleUrlHandlerMapping (维护 URI 路径模式到处理器的显式注册)
HandlerAdapter 帮助 DispatcherServlet 调用映射到请求的处理器,而不管处理器是如何处理的。例如,调用带注解的控制器需要解析注解。HandlerAdapter 的主要目的是为 DispatcherServlet 屏蔽这些细节
HandlerExceptionResolver 解决异常的策略,可能将它们映射到处理器、HTML 错误视图或其他目标。请参阅 异常处理
ViewResolver 将处理器返回的视图名称 String 解析为实际 View ,并将其渲染给响应。请参阅 视图解析视图技术
LocaleResolverLocaleContextResolver 解析客户端正在使用的 Locale 以及时区,以便能够提供国际化的视图。请参阅 Locale
ThemeResolver 解析 Web 应用程序可以使用的主题,例如,提供个性化布局。请参阅 Themes
MultipartResolver 在一些多部分解析库的帮助下解析多部分请求(例如,浏览器表单文件上传)的抽象。请参阅 多部分解析器
FlashMapManager 存储和检索可用于将属性从一个请求传递到另一个请求的“输入”和“输出” FlashMap ,通常是通过重定向。请参阅 Flash 属性
  • DispatcherServlet.properties

Web MVC 配置

WebFlux

应用程序可以声明在处理请求所需的 特殊 Bean 类型 中列出的基础设施 Bean 。DispatcherServlet 检索 WebApplicationContext 里每个特殊的 bean 。如果没有匹配的 bean 类型,则使用 DispatcherServlet.properties 中列出的默认类型 。

在大多数情况下,MVC 配置 是最好的起点。它在 Java 或 XML 中声明了所需的 bean,并提供了一个更高级别的配置回调 API 来进行自定义。

Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC,并提供了许多额外的方便选项。

Servlet 配置

在 Servlet 3.0+ 环境中,您可以选择以编程方式配置 Servlet 容器作为替代方案或与 web.xml 文件结合使用。以下示例注册了一个 DispatcherServlet

import org.springframework.web.WebApplicationInitializer;

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext container) {
        XmlWebApplicationContext appContext = new XmlWebApplicationContext();
        appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");

        ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
        registration.setLoadOnStartup(1);
        registration.addMapping("/");
    }
}

WebApplicationInitializer 是 Spring MVC 提供的接口,可确保检测到您的实现并自动用于初始化任何 Servlet 3 容器。WebApplicationInitializer 的抽象基类实现 AbstractDispatcherServletInitializer 通过覆盖方法来指定 servlet 映射和 DispatcherServlet 配置的位置,使得注册 DispatcherServlet 变得更加容易 。

使用基于 Java 的 Spring 配置的应用程序,建议如以下示例所示:

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

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

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

如果您使用基于 XML 的 Spring 配置,则应直接从 AbstractDispatcherServletInitializer 扩展 ,如以下示例所示:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    @Override
    protected WebApplicationContext createRootApplicationContext() {
        return null;
    }

    @Override
    protected WebApplicationContext createServletApplicationContext() {
        XmlWebApplicationContext cxt = new XmlWebApplicationContext();
        cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
        return cxt;
    }

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

AbstractDispatcherServletInitializer 还提供了一种方便的方法来添加 Filter 实例并使它们自动映射到 DispatcherServlet ,如以下示例所示:

public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {

    // ...

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] {
            new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
    }
}

每个过滤器都根据其具体类型使用默认名称添加,并自动映射到 DispatcherServlet

AbstractDispatcherServletInitializerisAsyncSupported protected 方法提供了配置在 DispatcherServlet 上启用异步支持和所有映射到它的过滤器。默认情况下,此标志设置为 true

最后,如果需要进一步自定义 DispatcherServlet ,可以覆盖 createDispatcherServlet 方法。

请求的处理流程

WebFlux

DispatcherServlet 处理请求的流程:

  • WebApplicationContext 被搜索并被绑定为请求的一个属性,在控制器和其它组件中可以使用。默认 key 是 DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
  • 语言环境( locale )解析器绑定到请求,让流程中的组件在处理请求(渲染视图、准备数据等)时解析要使用的语言环境。如果不需要语言环境解析,则不需要语言环境解析器
  • 主题解析器绑定到请求,让视图等组件决定使用哪个主题。如果您不使用主题,则可以忽略它
  • 如果您指定多部分文件解析器,则会检查请求的多部分。如果找到 multiparts ,则请求将被包装在 MultipartHttpServletRequest 中,以供流程中的其他组件进一步处理。有关多部分处理的更多信息,请参阅 多部分解析器
  • 搜索合适的处理器。如果找到处理器,则运行与处理器关联的执行链(预处理器、后处理器和控制器)以准备用于渲染的模型。或者,对于带注解的控制器,可以(在 HandlerAdapter 内)渲染响应而不是返回视图
  • 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理器或后处理器拦截了请求,可能是出于安全原因),则不会呈现视图,因为请求可能已经被满足

WebApplicationContext 里声明的 HandlerExceptionResolver bean 用于解决请求处理期间抛出的异常。这些异常解析器允许自定义解决异常的逻辑。有关更多详细信息,请参阅 异常

Spring DispatcherServlet 还支持 Servlet API 规范里的 last-modification-date 返回。确定特定请求的最后修改日期的过程很简单: DispatcherServlet 查找适当的处理器映射并测试找到的处理器是否实现了 LastModified 接口。如果是,则将 LastModified 接口 long getLastModified(request) 方法的值返回给客户端。

您可以通过将 Servlet 初始化参数( init-param 元素)添加到 web.xml 文件中的 Servlet 声明来自定义 DispatcherServlet 实例。下表列出了支持的参数:

DispatcherServlet 初始化参数

参数 描述
contextClass 实现 ConfigurableWebApplicationContext 的类,由该 Servlet 实例化和本地配置。默认情况下,使用 XmlWebApplicationContext
contextConfigLocation 传递给上下文实例(由 contextClass 指定)的字符串,以指示可以在何处找到上下文。可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。对于定义了两次 bean 的多个上下文位置,最新的位置优先
namespace WebApplicationContext 的命名空间。默认为 [servlet-name]-servlet
throwExceptionIfNoHandlerFound 当没有找到请求的处理器时是否抛出 NoHandlerFoundException 。然后可以用 HandlerExceptionResolver 捕获异常(例如,通过使用 @ExceptionHandler 控制器方法)并像任何其他异常一样处理
默认情况下为 false ,在这种情况下,DispatcherServlet 将响应状态设置为 404 (NOT_FOUND) 而不引发异常。
注意,如果还配置了 默认 servlet 处理,则无法解析的请求始终转发到默认 servlet,并且永远不会引发 404
  • org.springframework.web.servlet.FrameworkServlet#processRequest
    • org.springframework.web.servlet.DispatcherServlet#doService
      • org.springframework.web.servlet.DispatcherServlet#doDispatch

路径匹配

Servlet API 公开 requestURI 作为完整的请求路径,并进一步被细分成 contextPathservletPathpathInfo ,其值取决于 Servlet 如何映射。从这些输入中,Spring MVC 需要确定用于处理器映射的查找路径,它是 DispatcherServlet 自身映射中的路径,不包括 contextPath 和任何 servletMapping 前缀(如果存在)。

servletPathpathInfo 被解码,这让他们无法直接与完整的 requestURI 比较,以得出 lookupPath ,这使得 requestUri 需要解码 。然而,这会引入其自身的问题,因为路径可能包含编码的保留字符,例如 /; ,这可能会在解码后反过来改变路径的结构,这也可能导致安全问题。此外,Servlet 容器可能会在不同程度上标准化 servletPath ,这使得比较 startsWithrequestUri 更加不可能。

这就是为什么最好避免依赖基于前缀的 servletPath 映射类型附带的 servletPath 。如果 DispatcherServlet 被映射为带有 / 或不带 /* 前缀的默认 Servlet,并且 Servlet 容器是 4.0+,那么 Spring MVC 能够检测 Servlet 映射类型并避免使用 servletPathpathInfo 。在 3.1 Servlet 容器上,假设相同的 Servlet 映射类型,可以通过 MVC 配置中的路径匹配提供一个具有 alwaysUseFullPath=trueUrlPathHelper 来实现等效。

幸运的是,默认的 Servlet 映射 / 是一个不错的选择。然而,仍然存在一个问题,即 requestUri 需要解码才能与控制器映射进行比较。这又是不可取的,因为有可能对改变路径结构的保留字符进行解码。如果不需要这些字符,那么您可以拒绝它们(如 Spring Security HTTP 防火墙),或者您可以配置 UrlPathHelperurlDecode=false ,但控制器映射需要与编码路径匹配,这可能并不总是有效。此外,有时 DispatcherServlet 需要与另一个 Servlet 共享 URL 空间,并且可能需要通过前缀进行映射。

通过从 PathMatcher 切换到 5.3 或更高版本中提供的解析 PathPattern ,可以更全面地解决上述问题,请参阅 模式比较 。与 AntPathMatcher 不同,AntPathMatcher 需要解码的查找路径或编码的控制器映射,解析的 PathPattern 与名为 RequestPath 的路径的解析表示匹配,一次一个路径段。这允许单独解码和清理路径段值,而没有改变路径结构的风险。解析的 PathPattern 还支持使用 servletPath 前缀映射,只要前缀保持简单并且没有任何需要编码的字符。

拦截器

所有 HandlerMapping 实现都支持处理器拦截器,当您想将特定功能应用于某些请求时非常有用——例如,检查主体(principal)。拦截器必须实现 org.springframework.web.servlet 包下 HandlerInterceptor 接口的三种方法,这些方法应该提供足够的灵活性来进行各种预处理和后处理:

  • preHandle(..) : 在实际的处理器运行前
  • postHandle(..) : 处理器运行后
  • afterCompletion(..) : 完整请求完成后

preHandle(..) 方法返回一个布尔值。您可以使用此方法中断或继续执行链的处理。当此方法返回 true 时,处理器执行链继续。当它返回 false 时,DispatcherServlet 假定拦截器本身已经处理了请求(例如,呈现了一个适当的视图)并且不会继续执行其他拦截器和执行链中的实际处理器。

您还可以通过在各个 HandlerMapping 实现上使用 setter 来直接注册它们 。

注意,postHandle 对于 @ResponseBodyResponseEntity 方法不太有用,因为在 HandlerAdapter 中和 postHandle 之前为它们写入和提交响应。这意味着对响应进行任何更改(例如添加额外的头信息)为时已晚。对于这样的场景,您可以实现 ResponseBodyAdvice 并将其声明为 控制器通知 bean ,或者直接在 RequestMappingHandlerAdapter 上配置它。

  • org.springframework.web.servlet.HandlerInterceptor

异常处理

WebFlux

如果在请求映射期间发生异常或从请求处理器(例如 @Controller )抛出异常,DispatcherServlet 将委托给 HandlerExceptionResolver bean 链来解决异常并提供替代处理,通常是错误响应。

下表列出了可用的 HandlerExceptionResolver 实现:

HandlerExceptionResolver 描述
SimpleMappingExceptionResolver 异常类名称和错误视图名称之间的映射。用于在浏览器中呈现错误页面
DefaultHandlerExceptionResolver 解决由 Spring MVC 引发的异常并将它们映射到 HTTP 状态代码。另请参阅 ResponseEntityExceptionHandlerREST API 异常
ResponseStatusExceptionResolver 使用 @ResponseStatus 注解解析异常,并根据注解中的值将它们映射到 HTTP 状态代码
ExceptionHandlerExceptionResolver 通过调用 @Controller@ControllerAdvice 类中的 @ExceptionHandler 方法来解析异常。请参阅 @ExceptionHandler 方法
  • org.springframework.web.servlet.HandlerExceptionResolver
解析器链

您可以通过在 Spring 配置中声明多个 HandlerExceptionResolver bean 并根据需要设置它们的 order 属性来形成异常解析器链。order 属性越高,异常解析器定位得越晚。

HandlerExceptionResolver 约定可以返回:

  • ModelAndView 指向错误视图
  • 如果在解析器中处理了异常,则为空 ModelAndView
  • 如果异常仍未解决,返回 null ,以便后续解析器尝试,如果异常仍然存在,则允许冒泡到 Servlet 容器

MVC 配置 自动为默认Spring MVC异常、@ResponseStatus 注解异常和 @ExceptionHandler 方法的支持声明内置解析器。您可以自定义或替换该列表。

容器错误页面

如果异常仍未被任何 HandlerExceptionResolver 解决并因此被传播,或者如果响应状态设置为错误状态(即 4xx、5xx),则 Servlet 容器可以在 HTML 中呈现默认错误页面。要自定义容器的默认错误页面,您可以在 web.xml 中声明。

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

给定前面的示例,当异常冒泡或响应具有错误状态时,Servlet 容器会在容器内将 ERROR 转发到配置的 URL(例如 /error )。然后由 DispatcherServlet 处理,可能将其映射到 @Controller ,可以实现它以返回带有模型的错误视图名称或呈现 JSON 响应,如以下示例所示:

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

Servlet API 不提供在 Java 中创建错误页面映射的方法。但是,您可以同时使用 WebApplicationInitializer 和一个最小的 web.xml

视图解析

WebFlux

Spring MVC 定义了 ViewResolverView 接口,使您可以在浏览器中呈现模型,而无需使用特定的视图技术。ViewResolver 提供视图名称和实际视图之间的映射。View 处理数据在移交给特定视图技术之前的准备工作。

下表提供了有关 ViewResolver 层次结构的更多详细信息:

ViewResolver 实现

ViewResolver 描述
AbstractCachingViewResolver AbstractCachingViewResolver 的子类缓存它们解析的视图实例。缓存提高了某些视图技术的性能。您可以通过将 cache 属性设置为 false 来关闭缓存。此外,如果您必须在运行时刷新某个视图(例如,当修改 FreeMarker 模板时),您可以使用 removeFromCache(String viewName, Locale loc) 方法
UrlBasedViewResolver ViewResolver 接口的简单实现会影响逻辑视图名称到 URL 的直接解析,而无需显式映射定义。如果您的逻辑名称以一种直接的方式与您的视图资源的名称匹配,而无需任意映射,则这是合适的
InternalResourceViewResolver UrlBasedViewResolver 的便捷子类,支持 InternalResourceView(实际上是 Servlet 和 JSP)和子类,例如 JstlViewTilesView 。您可以使用 setViewClass(..) 为该解析器生成的所有视图指定视图类。有关 UrlBasedViewResolver 详细信息,请参阅javadoc
FreeMarkerViewResolver 支持 FreeMarkerViewUrlBasedViewResolver 的方便子类
ContentNegotiatingViewResolver 基于请求文件名或 Accept 标头解析视图的 ViewResolver 接口的实现。请参阅 内容协商
BeanNameViewResolver 将视图名称解释为当前应用上下文中的 bean 名称的 ViewResolver 接口的实现。这是一个非常灵活的变体,它允许基于不同的视图名称混合和匹配不同的视图类型。每个这样的 View 都可以定义为一个 bean,例如在 XML 或配置类中。
  • org.springframework.web.servlet.ViewResolver
  • org.springframework.web.servlet.View
处理

WebFlux

您可以通过声明多个解析器 bean 并在必要时通过设置 order 属性来指定排序来链接视图解析器。请记住, order 属性越高,视图解析器在链中的位置就越晚。

ViewResolver 的约定指定它可以返回 null 以指示无法找到视图。但是,在 JSP 和 InternalResourceViewResolver 的情况下,确定 JSP 是否存在的唯一方法是通过 RequestDispatcher 执行分派 。因此,您必须始终将 InternalResourceViewResolver 配置为在视图解析器的整体顺序中排在最后。

配置视图解析就像在 Spring 配置中添加 ViewResolver bean 一样简单。MVC 配置为视图解析器和添加无逻辑视图控制器提供了专用的配置 API ,这对于无控制器逻辑的 HTML 模板呈现非常有用。

重定向

WebFlux

视图名称中的特殊前缀 redirect: 可让您执行重定向。UrlBasedViewResolver(和它的子类)识别它为需要重定向的指令。视图名称的其余部分是重定向 URL。

最终效果与控制器返回 RedirectView 相同,但现在控制器本身可以根据逻辑视图名称进行操作。逻辑视图名称(例如 redirect:/myapp/some/resource )相对于当前 Servlet 上下文重定向,而视图名称例如 redirect:https://myhost.com/some/arbitrary/path 重定向到绝对 URL。

请注意,如果使用 @ResponseStatus 注解控制器方法,则注解值优先于由 RedirectView 设置的响应状态。

转发

您还可以对最终由 UrlBasedViewResolver 及其子类解析的视图名称使用特殊前缀 forward: 。这将创建一个 InternalResourceView ,它执行 RequestDispatcher.forward() 。因此,这个前缀对 InternalResourceViewResolverInternalResourceView(对于 JSP)没有用,但是如果您使用另一种视图技术但仍然希望强制转发由 Servlet/JSP 引擎处理的资源,它会很有帮助。请注意,您也可以链接多个视图解析器。

内容协商( Content Negotiation )

WebFlux

ContentNegotiatingViewResolver 不解析视图本身,而是委托给其他视图解析器并选择与客户端请求的表示相似的视图。表示可以根据 Accept 标头或查询参数(例如 "/path?format=pdf" )来确定。

ContentNegotiatingViewResolver 通过将请求媒体类型与每个 ViewResolver 关联的 View 支持的媒体类型(也称为 Content-Type )进行比较,选择适当的 View 来处理请求。列表中具有兼容 Content-Type 的第一个 View 将表示返回给客户端。如果 ViewResolver 链无法提供兼容的视图,将查阅通过 DefaultViews 属性指定的视图列表。后一个选项适用于可以呈现当前资源的适当表示的单例 Views ,而不管逻辑视图名称如何。Accept 标头可以包含通配符(例如 text/* ),在这种情况下,Content-Typetext/xmlView 是兼容匹配的。

  • org.springframework.web.servlet.view.ContentNegotiatingViewResolver

Locale

Spring 架构的大部分都支持国际化,就像 Spring Web MVC 框架一样。DispatcherServlet 允许您使用客户端的 locale 自动解析消息。这是通过 LocaleResolver 对象完成的。

当一个请求进来时,DispatcherServlet 会寻找一个语言环境解析器,如果找到了,它会尝试使用它来设置语言地区环境。通过使用 RequestContext.getLocale() 方法,您始终可以检索由语言环境解析器解析的语言环境。

除了自动语言环境解析之外,您还可以将拦截器附加到处理器映射以在特定情况下(例如,基于请求中的参数)更改语言环境。

语言环境解析器和拦截器在 org.springframework.web.servlet.i18n 包中定义, 并以正常方式在您的应用上下文中配置。Spring 中包含以下的语言环境解析器

  • 时区(Time Zone)

  • 标头解析器(Header Resolver)

  • Cookie 解析器(Cookie Resolver)

  • 会话解析器(Session Resolver)

  • 语言环境拦截器(Locale Interceptor)

  • org.springframework.web.servlet.LocaleResolver

时区(Time Zone)

除了获取客户端的语言环境之外,了解其时区通常也很有用。LocaleContextResolver 接口提供了 LocaleResolver 的一个扩展,让解析器提供更丰富的 LocaleContext ,其中可能包括时区信息

时区信息可用时,可以使用 RequestContext.getTimeZone() 方法获取用户的 TimeZone

通过 Spring 的 ConversionService 注册的任何日期/时间 ConverterFormatter 对象都会自动使用时区信息

  • org.springframework.web.servlet.LocaleContextResolver
标头解析器(Header Resolver)

此语言环境解析器检查 accept-language (例如,Web 浏览器)发送的请求中的标头。通常,此标头字段包含客户端操作系统的语言环境。请注意,此解析器不支持时区信息。

  • org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver

此语言环境解析器检查可能存在于客户端上的 Cookie 以查看是否指定了 LocaleTimeZone 。如果是,则使用指定的详细信息。通过使用此语言环境解析器的属性,您可以指定 Cookie 的名称以及最长期限。以下示例定义了一个 CookieLocaleResolver

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

    <property name="cookieName" value="clientlanguage"/>

    <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
    <property name="cookieMaxAge" value="100000"/>

</bean>

下表描述了 CookieLocaleResolver 的属性:

属性 默认值 描述
cookieName 类名 + LOCALE Cookie 的名称
cookieMaxAge Servlet 容器默认值 Cookie 在客户端上保留的最长时间。如果指定 -1 ,Cookie 将不会被持久化。它仅在客户端关闭浏览器之前可用
cookiePath / 将 Cookie 的可见性限制在您网站的某个部分。当 cookiePath 被指定,Cookie 是仅对于该路径和它下面的路径可见
  • org.springframework.web.servlet.i18n.CookieLocaleResolver
会话解析器(Session Resolver)

SessionLocaleResolver 允许您从可能与用户的请求相关的会话检索 LocaleTimeZone 。与 CookieLocaleResolver 相反 ,此策略将本地选择的语言环境存储在 Servlet 容器的 HttpSession 中。因此,这些设置对于每个会话都是临时的,在每个会话结束时都会丢失。

请注意,与外部会话管理机制没有直接关系,例如 Spring Session 项目。SessionLocaleResolver 会根据当前 HttpServletRequest 计算和修改相应的 HttpSession 属性。

  • org.springframework.web.servlet.i18n.SessionLocaleResolver
语言环境拦截器(Locale Interceptor)

您可以通过将 LocaleChangeInterceptor 添加到 HandlerMapping 定义之一来启用语言环境的更改。它检测在请求中的参数,并相应地更改语言环境,在应用上下文里调用 LocaleResolversetLocale 方法。

下一个示例显示对所有包含命名参数 siteLanguage*.view 资源的调用现在会更改语言环境。例如,对 URL https://www.sf.net/home.view?siteLanguage=nl 的请求将站点语言更改为荷兰语。以下示例显示了如何拦截语言环境:

<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>
  • org.springframework.web.servlet.i18n.LocaleChangeInterceptor

主题

您可以应用 Spring Web MVC 框架主题来设置应用程序的整体外观,从而增强用户体验。主题是影响应用程序视觉样式的静态资源的集合,通常是样式表和图像。

定义主题

要在 Web 应用程序中使用主题,您必须设置 org.springframework.ui.context.ThemeSource 接口的实现 。WebApplicationContext 接口扩展 ThemeSource ,但将职责委托给专门实现。默认情况下,委托是从类路径的根加载属性文件的 org.springframework.ui.context.support.ResourceBundleThemeSource 实现。要使用自定义 ThemeSource 实现或配置 ResourceBundleThemeSource 的基本名称前缀,您可以在应用上下文中使用保留名称 themeSource 注册一个 bean 。Web 应用上下文会自动检测具有该名称的 bean 并使用它。

当您使用 ResourceBundleThemeSource 时,主题在一个简单的属性文件中定义。属性文件列出了构成主题的资源,如以下示例所示:

styleSheet=/themes/cool/style.css
background=/themes/cool/img/coolBg.jpg

属性的键是从视图代码中引用主题元素的名称。对于 JSP,您通常使用与 spring:message 标签非常相似的自定义标签 spring:theme 来执行此操作。以下 JSP 片段使用前面示例中定义的主题来自定义外观:

<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<html>
    <head>
        <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
    </head>
    <body style="background=<spring:theme code='background'/>">
        ...
    </body>
</html>

默认情况下,ResourceBundleThemeSource 使用空的基本名称前缀。因此,属性文件从类路径的根目录加载。因此,您可以将 cool.properties 主题定义放在类路径根目录下(例如,/WEB-INF/classes 中)。ResourceBundleThemeSource 使用标准的 Java 资源包加载机制,允许主题的国际化。例如,我们可以有一个 /WEB-INF/classes/cool_nl.properties 引用带有荷兰语文本的特殊背景图像。

解析主题

DispatcherServlet 查找名为 themeResolverThemeResolver bean 。主题解析器的工作方式与 LocaleResolver 相同。它检测用于特定请求的主题,还可以更改请求的主题。下表描述了 Spring 提供的主题解析器:

ThemeResolver 实现

描述
FixedThemeResolver 选择使用 defaultThemeName 属性设置的固定主题
SessionThemeResolver 主题在用户的 HTTP 会话中维护。它只需要为每个会话设置一次,但不会在会话之间持久化
CookieThemeResolver 所选主题存储在客户端的 Cookie 中

Spring 还提供了 ThemeChangeInterceptor ,它允许通过一个简单的请求参数对每个请求进行主题更改。

  • org.springframework.web.servlet.ThemeResolver
  • org.springframework.web.servlet.theme.ThemeChangeInterceptor

多部分解析器

WebFlux

org.springframework.web.multipart 包下的 MultipartResolver 是一种用于解析包括文件上传在内的多部分请求的策略。有一种基于 Commons FileUpload 的实现,另一种基于 Servlet 3.0 多部分请求解析。

要启用多部分处理,您需要在 DispatcherServlet Spring 配置中声明一个名为 multipartResolverMultipartResolver bean 。DispatcherServlet 检测到它,并将其应用于传入请求。当内容类型为 multipart/form-data 的 POST 请求被接收时,解析器解析内容并包装当前 HttpServletRequest 作为 MultipartHttpServletRequest ,以提供对已解析部分的访问,此外还将其作为请求参数公开。

  • org.springframework.web.multipart.MultipartResolver
Apache Commons FileUpload

要使用 Apache Commons FileUpload,您可以配置一个名为 multipartResolverCommonsMultipartResolver bean 。 您还需要类路径上引入 commons-fileupload 依赖。

Servlet 3.0

Servlet 3.0 多部分解析需要通过 Servlet 容器配置开启:

  • 在 Servlet 注册上设置 MultipartConfigElement
  • web.xml 中,向 servlet 声明添加 <multipart-config>

下面的例子展示了如何在 Servlet 注册上设置 MultipartConfigElement

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    // ...

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {

        // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold
        registration.setMultipartConfig(new MultipartConfigElement("/tmp"));
    }

}

一旦 Servlet 3.0 配置就绪,就可以添加一个命名为 multipartResolver 、类型为 StandardServletMultipartResolver 的 bean

日志记录

WebFlux

Spring MVC 中的 DEBUG 级别日志记录被设计为紧凑、最小化和人性化的。它侧重于重复有用的高价值信息,而不是仅在调试特定问题时有用的信息。

TRACE 级别的日志记录通常遵循与 DEBUG 相同的原则,但可用于调试任何问题。此外,某些日志消息可能会在 TRACEDEBUG 中显示不同级别的详细信息。

良好的日志记录来自于使用日志的经验。

敏感数据

WebFlux

DEBUG 和 TRACE 日志记录可能会记录敏感信息。这就是为什么默认情况下会屏蔽请求参数和标头,并且必须通过 DispatcherServlet 上的 enableLoggingRequestDetails 属性显式启用它们的完整日志记录。

以下示例显示了如何使用 Java 配置执行此操作:

public class MyInitializer
        extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

    @Override
    protected String[] getServletMappings() {
        return ... ;
    }

    @Override
    protected void customizeRegistration(ServletRegistration.Dynamic registration) {
        registration.setInitParameter("enableLoggingRequestDetails", "true");
    }

}

Filters

WebFlux

表单数据(Form Data)

浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器客户端也可以使用 PUT、PATCH 和 DELETE。Servlet API 要求 ServletRequest.getParameter*() 方法仅支持 HTTP POST 的表单字段访问。

spring-web 模块提供 FormContentFilter 拦截内容类型为 application/x-www-form-urlencoded 的 HTTP PUTPATCHDELETE 请求,从请求正文中读取表单数据,并包装 ServletRequest 以通过 ServletRequest.getParameter*() 系列方法使表单数据可用。

  • org.springframework.web.filter.FormContentFilter

转发标头(Forwarded Headers)

WebFlux

当请求通过代理(例如负载均衡器)时,主机、端口和协议(scheme)可能会发生变化,这使得从客户端的角度创建指向正确主机、端口和协议的链接成为一项挑战。

scheme://host:port/path

RFC 7239 定义了 Forwarded HTTP 标头,代理可以用它来提供有关原始请求的信息。还有其他非标准标头,包括 X-Forwarded-HostX-Forwarded-PortX-Forwarded-ProtoX-Forwarded-SslX-Forwarded-Prefix

ForwardedHeaderFilter 是一个 Servlet 过滤器,它修改请求以便

  • 根据 Forwarded 标头更改主机、端口和协议
  • 删除这些标头以消除进一步的影响

此过滤器依赖于对请求的包装,因此它必须排序在其他过滤器之前,例如 RequestContextFilter 这样应该与修改后的而不是原始请求一起工作的过滤器

转发标头有安全考虑,因为应用程序无法知道标头是由代理添加的、按预期添加的,还是由恶意客户端添加的。这就是为什么应该配置信任边界处的代理以删除来自外部的不受信任的 Forwarded 标头。您还可以配置带 removeOnly=trueForwardedHeaderFilter ,在这种情况下它会删除但不使用标头。

为了支持 异步请求 和错误调度,这个过滤器也应该映射到 DispatcherType.ASYNCDispatcherType.ERROR 。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer (请参阅 Servlet Config ),所有过滤器都会为所有调度类型自动注册。但是,如果通过 web.xml 或通过 Spring Boot FilterRegistrationBean 注册过滤器时,一定要包括 DispatcherType.ASYNCDispatcherType.ERROR 以及 DispatcherType.REQUEST

  • org.springframework.web.filter.ForwardedHeaderFilter
  • org.springframework.boot.web.servlet.FilterRegistrationBean

Shallow ETag

ShallowEtagHeaderFilter 过滤器通过缓存写入响应的内容,并从它计算 MD5 哈希创建一个 “浅” 的ETag。下次客户端发送时,它会执行相同的操作,但它还会将计算值与 If-None-Match 请求标头进行比较,如果两者相等,则返回 304 (NOT_MODIFIED)。

这种策略节省了网络带宽而不是 CPU,因为必须为每个请求计算完整的响应。前面描述的控制器级别的其他策略可以避免计算。请参阅HTTP 缓存

此过滤器有一个 writeWeakETag 参数,用于将过滤器配置为编写类似于以下内容的弱 ETag:W/"02a2d595e6ed9a0b24f027f2b63b134d6" (如RFC 7232 第 2.3 节中所定义 )。

为了支持异步请求 ,此过滤器必须使用 DispatcherType.ASYNC 映射,以便过滤器可以延迟并成功生成 ETag 到最后一次异步调度的末尾。如果使用 Spring Framework 的 AbstractAnnotationConfigDispatcherServletInitializer(请参阅Servlet Config),所有过滤器都会为所有调度类型自动注册。但是,如果通过 web.xml 或 Spring Boot FilterRegistrationBean 注册此过滤器,请务必包含 DispatcherType.ASYNC

  • org.springframework.web.filter.ShallowEtagHeaderFilter

CORS

WebFlux

Spring MVC 通过控制器上的注解为 CORS 配置提供了细粒度的支持。但是,当与 Spring Security 一起使用时,我们建议依赖内置 CorsFilter 组件必须排序在 Spring Security 的过滤器链之前。

有关更多详细信息,请参阅有关 CORSCORS 过滤器 的部分。

带注解的控制器

WebFlux

Spring MVC中提供了基于注解的编程模型,其中的 @Controller@RestController 组件使用注解来表达请求映射,请求输入,异常处理等。带注解的控制器具有灵活的方法签名,无需扩展基类或实现特定接口。以下示例显示了由注解定义的控制器:

@Controller
public class HelloController {

    @GetMapping("/hello")
    public String handle(Model model) {
        model.addAttribute("message", "Hello World!");
        return "index";
    }
}

在前面的示例中,该方法接受 Model 并将视图名称作为 String 返回。

  • org.springframework.stereotype.Controller
  • org.springframework.web.bind.annotation.RestController

声明

WebFlux

您可以通过在 Servlet 的 WebApplicationContext 中使用标准 Spring bean 定义来定义控制器 bean 。@Controller 支持自动检测,这与 Spring 对检测类路径中的 @Component 类以及为它们自动注册 bean 定义的通用支持一致。

要启用 @Controller bean 的自动检测,您可以将组件扫描添加到您的 Java 配置中,如以下示例所示:

@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

    // ...
}

等效的 XML 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:p="http://www.springframework.org/schema/p"
    xmlns:context="http://www.springframework.org/schema/context"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="org.example.web"/>

    <!-- ... -->

</beans>

@RestController 是一个组合注解,它本身被 @Controller@ResponseBody 元注解指示一个控制器,控制器每个方法都继承了类型级 @ResponseBody 注解,因此,直接写入响应主体,而不是使用视图解析和 HTML 模板呈现。

AOP 代理

在某些情况下,您可能需要在运行时使用 AOP 代理来装饰控制器。例如,如果您选择直接在控制器上添加 @Transactional 注解。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果一个控制器必须实现一个接口,并且此接口不是一个 Spring 上下文回调接口(例如 InitializingBean*Aware 等),您可能需要显式配置基于类的代理。例如,<tx:annotation-driven/> 可以更改为 <tx:annotation-driven proxy-target-class="true"/>@EnableTransactionManagement 可以更改为 @EnableTransactionManagement(proxyTargetClass = true)

请求映射

WebFlux

您可以使用 @RequestMapping 注解将请求映射到控制器方法。它有多种属性可以通过 URL、HTTP 方法、请求参数、标头和媒体类型进行匹配。您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小到特定端点映射。

@RequestMapping 的特定 HTTP 方法的变体:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

这些快捷方式是 自定义注解 ,因为可以说,大多数控制器方法应该映射到特定的 HTTP 方法,而不是使用 @RequestMapping默认情况下,@RequestMapping 与所有 HTTP 方法匹配。

同时,在类级别仍然需要 @RequestMapping 来表达共享映射。

以下示例具有类型和方法级别的映射:

@RestController
@RequestMapping("/persons")
class PersonController {

    @GetMapping("/{id}")
    public Person getPerson(@PathVariable Long id) {
        // ...
    }

    @PostMapping
    @ResponseStatus(HttpStatus.CREATED)
    public void add(@RequestBody Person person) {
        // ...
    }
}
  • org.springframework.web.bind.annotation.RequestMapping
URI 模式

WebFlux

@RequestMapping 方法可以使用 URL 模式映射。有两种选择:

  • PathPattern :与 URL 路径匹配的预解析模式也预解析为 PathContainer 。该解决方案专为 Web 设计,可有效处理编码和路径参数,并高效匹配
  • AntPathMatcher :将字符串模式与字符串路径匹配。这是 Spring 配置中也使用的原始解决方案,用于选择类路径、文件系统和其他位置上的资源。它的效率较低,并且字符串路径输入对于有效处理 URL 的编码和其他问题是一个挑战

PathPattern 是 Web 应用程序的推荐解决方案,也是 Spring WebFlux 中的唯一选择。在 5.3 版本之前,AntPathMatcher 是 Spring MVC 中的唯一选择,并且仍然是默认值。但是 PathPattern 可以在 MVC 配置中 启用 。

PathPattern 支持与 AntPathMatcher 相同的语法。此外,它还支持捕获模式,例如 {*spring} ,用于匹配路径末尾的 0 个或多个路径段。PathPattern 还限制了用于匹配多个路径段的 ** 使用,这样它只能在模式的末尾使用。在为给定请求选择最佳匹配模式时,这消除了许多歧义的情况。有关完整的模式语法,请参阅 PathPatternAntPathMatcher

一些示例模式:

  • "/resources/ima?e.png" :匹配路径段中的一个字符
  • "/resources/*.png" :匹配路径段中的零个或多个字符
  • "/resources/**" :匹配多个路径段
  • "/projects/{project}/versions" :匹配路径段并将其捕获为变量
  • "/projects/{project:[a-z]+}/versions" :使用正则表达式匹配并捕获变量

捕获的 URI 变量可以使用 @PathVariable 访问,例如:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

您可以在类和方法级别声明 URI 变量,如以下示例所示:

@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {

    @GetMapping("/pets/{petId}")
    public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
        // ...
    }
}

URI 变量会自动转换为适当的类型,或者引发异常 TypeMismatchException 。默认情况下支持简单类型( intlongDate 等 ),您可以注册对任何其他数据类型的支持。请参阅 类型转换DataBinder

您可以显式命名 URI 变量(例如,@PathVariable("customId") ),但如果名称相同并且您的代码是使用调试信息或 Java 8 上的 -parameters 编译器标志编译的,则您可以省略该细节。

语法 {varName:regex} 声明了一个带有正则表达式的 URI 变量,其语法为 {varName:regex} 。例如,给定 URL "/spring-web-3.0.5.jar" ,以下方法提取名称、版本和文件扩展名:

@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
    // ...
}

URI 路径模式还可以具有嵌入 ${…} 占位符,这些占位符在启动时通过 PropertyPlaceHolderConfigurer 针对本地、系统、环境和其他属性源进行解析。例如,您可以使用它来基于某些外部配置参数化基本 URL 。

模式比较

WebFlux

当多个模式匹配一个 URL 时,必须选择最佳匹配。这是通过以下方法之一完成的,具体取决于是否启用了对已解析 PathPattern 的使用:

这两种方法都有助于对模式进行排序,并将更具体的模式放在顶部。如果模式具有较少的 URI 变量计数(计为 1)、单个通配符(计为 1)和双通配符(计为 2),则该模式不太具体。给定相同的分数,选择较长的模式。给定相同的分数和长度,将选择 URI 变量多于通配符的模式。

默认映射模式 ( /** ) 从评分中排除并始终排在最后。此外,前缀模式(例如 /public/** )被认为不如其他没有双通配符的模式具体。

有关完整的详细信息,请按照上述模式比较器的链接。

后缀匹配

从 5.3 开始,默认情况下 Spring MVC 不再执行 .* 后缀模式匹配,其中映射到 /person 的控制器也隐式映射到 /person.* 。因此,路径扩展不再用于解释请求的响应内容类型,例如,/person.pdf/person.xml,等。

当浏览器过去常常发送难以一致解释的 Accept 标头时,以这种方式使用文件扩展名是必需的。目前,这不再是必需的,使用 Accept 标头应该是首选。

随着时间的推移,文件扩展名的使用已被证明存在各种问题。当与 URI 变量、路径参数和 URI 编码的使用重叠时,它可能会导致歧义。关于基于 URL 的授权和安全性的推理也变得更加困难。

要在 5.3 之前的版本中完全禁用路径扩展,请设置以下内容:

除了通过 Accept 标头请求内容类型之外,还有一种请求内容类型的方法仍然是有用的,例如在浏览器中输入 URL 时。路径扩展的一个安全替代方案是使用查询参数策略。如果必须使用文件扩展名,请考虑通过 ContentNegotiationConfigurermediaTypes 属性将它们限制为显式注册的扩展名列表。

后缀匹配和 RFD

反射文件下载 (RFD) 攻击与 XSS 类似,因为它依赖于响应中反射的请求输入(例如,查询参数和 URI 变量)。然而,RFD 攻击不是将 JavaScript 插入到 HTML 中,而是依赖于浏览器切换来执行下载,并在稍后双击时将响应视为可执行脚本。

在 Spring MVC 中,@ResponseBodyResponseEntity 方法是有风险的,因为它们可以呈现不同的内容类型,客户端可以通过 URL 路径扩展请求这些内容类型。禁用后缀模式匹配并使用路径扩展进行内容协商可降低风险,但不足以防止 RFD 攻击。

为了防止 RFD 攻击,在呈现响应正文之前,Spring MVC 添加了一个 Content-Disposition:inline;filename=f.txt 标头来建议一个固定且安全的下载文件。仅当 URL 路径包含既不安全也不显式注册用于内容协商的文件扩展名时,才会这样做。但是,当 URL 直接输入浏览器时,它可能会产生副作用。

默认情况下,许多常见的路径扩展都是安全的。具有自定义 HttpMessageConverter 实现的应用程序可以为内容协商显式注册文件扩展名,以避免为这些扩展名添加 Content-Disposition 标头。请参阅 内容类型

有关 RFD 的其他建议,请参阅 CVE-2015-5211

指定请求的媒体类型

WebFlux

您可以根据请求的 Content-Type 缩小请求映射范围,如下例所示:

@PostMapping(path = "/pets", consumes = "application/json") 
public void addPet(@RequestBody Pet pet) {
    // ...
}

consumes 属性还支持否定表达式——例如,!text/plain 表示除 text/plain

您可以在类级别声明共享 consumes 属性。当在类级别使用时,方法级别的 consumes 属性会覆盖而不是扩展类级别的声明。

MediaType 为常用媒体类型提供常量,例如 APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

指定响应的媒体类型

WebFlux

您可以根据 Accept 请求标头和控制器方法生成的内容类型列表来缩小请求映射范围,如下例所示:

@GetMapping(path = "/pets/{petId}", produces = "application/json") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

媒体类型可以指定一个字符集。支持否定表达式——例如, !text/plain 表示除 text/plain

您可以在类级别声明共享 produces 属性。当在类级别使用时,方法级别的 produces 属性会覆盖而不是扩展类级别的声明。

MediaType 为常用媒体类型提供常量,例如 APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE

参数(Parameters)、标头

WebFlux

您可以根据请求参数条件缩小请求映射范围。您可以测试是否存在请求参数 ( myParam )、是否存在参数 ( !myParam ) 或特定值 ( myParam=myValue )。以下示例显示了如何测试特定值:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

您还可以对请求标头条件使用相同的内容,如以下示例所示:

@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

您可以使用标头条件匹配 Content-TypeAccept ,但最好使用 consumeproduce

HTTP HEAD, OPTIONS

WebFlux

@GetMapping ( 和 @RequestMapping(method=HttpMethod.GET) ) 支持 HTTP HEAD 请求映射。控制器方法不需要改变。在 javax.servlet.http.HttpServlet 中应用的响应包装器可确保将 Content-Length 标头设置为写入的字节数(而不实际写入响应)。

@GetMapping ( 和 @RequestMapping(method=HttpMethod.GET) ) 隐式映射并支持 HTTP HEAD 。处理 HTTP HEAD 请求就像处理 HTTP GET 一样,不同之处在于,不是写入正文,而是计算字节数并设置 Content-Length 标头。

默认情况下,HTTP OPTIONS 是通过将 Allow 响应标头设置为所有具有匹配 URL 模式的 @RequestMapping 方法中列出的 HTTP 方法列表来处理的。

对于没有 HTTP 方法声明的 @RequestMappingAllow 标头设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS 。控制器方法应始终声明支持的 HTTP 方法(例如,通过使用特定于 HTTP 方法的变体: @GetMapping@PostMapping等)。

您可以将 @RequestMapping 方法显式映射到 HTTP HEAD 和 HTTP OPTIONS,但这在常见情况下不是必需的。

自定义注解

WebFlux

Spring MVC 支持使用 组合注解 进行请求映射。这些注解是 @RequestMapping 元注解的,并被组合起来以重新声明 @RequestMapping 属性的子集(或全部)。

@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping 是组合注解的示例。提供它们是因为,大多数控制器方法应该映射到特定的 HTTP 方法,而不是使用 @RequestMapping ,默认情况下,它与所有 HTTP 方法匹配。如果您需要组合注解的示例,请查看它们是如何声明的。

Spring MVC 还支持具有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要子类化 RequestMappingHandlerMapping 和覆盖 getCustomMethodCondition 方法,您可以在其中检查自定义属性并返回您自己的 RequestCondition

显式注册

WebFlux

您可以以编程方式注册处理器方法,这些方法可用于动态注册或高级用例,例如不同 URL 下同一处理器的不同实例。以下示例注册了一个处理器方法:

@Configuration
public class MyConfig {

    @Autowired
    public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler) 
            throws NoSuchMethodException {

        RequestMappingInfo info = RequestMappingInfo
                .paths("/user/{id}").methods(RequestMethod.GET).build(); 

        Method method = UserHandler.class.getMethod("getUser", Long.class); 

        mapping.registerMapping(info, handler, method); 
    }
}

处理器方法

WebFlux

@RequestMapping 处理器方法具有灵活的签名,可以从一系列支持的控制器方法参数和返回值中进行选择。

方法参数

WebFlux

下表描述了支持的控制器方法参数。任何参数都不支持反应式类型。

JDK 8 的 java.util.Optional 被支持作为方法参数与具有 required 属性的注解组合(例如,@RequestParam@RequestHeader ,和其它属性)且等效于具有 required=false 的注解

控制器方法参数 描述
WebRequestNativeWebRequest 对请求参数、请求和会话属性的通用访问,无需直接使用 Servlet API
javax.servlet.ServletRequestjavax.servlet.ServletResponse 选择任何特定的请求或响应类型,例如 ServletRequestHttpServletRequest 或 Spring 的 MultipartRequestMultipartHttpServletRequest
javax.servlet.http.HttpSession 强制会话的存在。因此,这样的参数永远不会 null 。请注意,会话访问不是线程安全的。如果允许多个请求同时访问一个会话,考虑将 RequestMappingHandlerAdapter 实例的 synchronizeOnSession 标志设置为 true
javax.servlet.http.PushBuilder 用于编程式 HTTP/2 资源推送的 Servlet 4.0 推送构建器 API 。注意,根据 Servlet 规范,如果客户端不支持该 HTTP/2 功能,则注入的实例可以为 null
java.security.Principal 当前经过身份验证的用户,如果已知,可能是特定的 Principal 实现类。
请注意,如果对该参数进行注解是为了允许自定义解析器在通过 HttpServletRequest#getUserPrincipal 回到默认解析之前解析该参数,则不会立即解析该参数。例如,Spring Security Authentication 实现 Principal 并将通过 HttpServletRequest#getUserPrincipal 注入 ,除非它也被 @AuthenticationPrincipal 注解,在这种情况下,它由自定义 Spring Security 解析器通过 Authentication#getPrincipal 进行解析
HttpMethod 请求的 HTTP 方法
java.util.Locale 当前请求的语言环境,由最具体的 LocaleResolver(实际上,是配置的 LocaleResolverLocaleContextResolver )确定
java.util.TimeZone + java.time.ZoneId 与当前请求关联的时区,由 LocaleContextResolver 确定
java.io.InputStreamjava.io.Reader 用于访问由 Servlet API 公开的原始请求主体
java.io.OutputStream , java.io.Writer 用于访问由 Servlet API 公开的原始响应主体
@PathVariable 用于访问 URI 模板变量。请参阅 URI 模式
@MatrixVariable 用于访问 URI 路径段中的键值对。请参阅 矩阵变量
@RequestParam 用于访问 Servlet 请求参数,包括多部分文件。参数值被转换为声明的方法参数类型。见 @RequestParam 以及 多部分
请注意,@RequestParam 对于简单的参数值,是可选的
@RequestHeader 用于访问请求标头。标头值转换为声明的方法参数类型。见 @RequestHeader
@CookieValue 用于访问 Cookies 。Cookies 值被转换为声明的方法参数类型。见 @CookieValue
@RequestBody 用于访问 HTTP 请求主体。主体内容通过使用 HttpMessageConverter 实现转换为声明的方法参数类型。见 @RequestBody
HttpEntity<B> 用于访问请求标头和主体。主体由 HttpMessageConverter 转换。请参阅 HttpEntity
@RequestPart 要访问 multipart/form-data 请求中的部件,使用 HttpMessageConverter 对部件的主体进行转换。请参阅 多部分
java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap 用于访问在 HTML 控制器中使用并作为视图渲染的一部分公开给模板的模型
RedirectAttributes 指定在重定向的情况下使用的属性(即附加到查询字符串)和临时存储的 flash 属性,直到重定向后的请求。请参阅 重定向属性Flash 属性
@ModelAttribute 用于访问模型中的现有属性(如果不存在则实例化)并应用数据绑定和验证。见 @ModelAttribute 以及 ModelDataBinder 。请注意,使用 @ModelAttribute 是可选的(例如,设置其属性)
ErrorsBindingResult 用于访问来自命令对象(即 @ModelAttribute 参数)的验证和数据绑定的错误或来自验证 @RequestBody@RequestPart 参数的错误。您必须在经过验证的方法参数之后立即声明一个 ErrorsBindingResult 参数
SessionStatus + 类级别 @SessionAttributes 用于标记表单处理完成,这会触发对通过类级 @SessionAttributes 注解声明的会话属性的清理。有关更多详细信息,请参阅 @SessionAttributes
UriComponentsBuilder 用于准备相对于当前请求的主机、端口、协议、上下文路径和 Servlet 映射的文字部分的 URL 。请参阅 URI 链接
@SessionAttribute 用于访问任何会话属性,与作为类级 @SessionAttributes 声明结果存储在会话中的模型属性相反。有关更多详细信息,请参阅 @SessionAttribute
@RequestAttribute 用于访问请求属性。有关更多详细信息,请参阅 @RequestAttribute
任何其他参数 如果方法参数与此表中的任何早期值都不匹配,并且它是简单类型(由 BeanUtils#isSimpleProperty 确定 ,则将其解析为 @RequestParam 。否则,将其解析为 @ModelAttribute
返回值

WebFlux

下表描述了支持的控制器方法返回值。所有返回值都支持反应式类型。

控制器方法返回值 描述
@ResponseBody 返回值通过 HttpMessageConverter 实现进行转换并写入响应。见 @ResponseBody
HttpEntity<B>ResponseEntity<B> 指定完整响应(包括 HTTP 标头和主体)的返回值将通过 HttpMessageConverter 实现进行转换并写入响应。请参阅 ResponseEntity
HttpHeaders 用于返回带有标头但没有主体的响应
String 要使用 ViewResolver 实现解析并与隐式模型一起使用的视图名称,通过命令对象和 @ModelAttribute 方法确定。处理器方法还可以通过声明 Model 参数以编程方式丰富模型(请参阅 显式注册
View 用于与隐式模型一起渲染的 View 实例,通过命令对象和 @ModelAttribute 方法确定。处理器方法还可以通过声明 Model 参数以编程方式丰富模型(请参阅 显式注册
java.util.Maporg.springframework.ui.Model 要添加到隐式模型的属性,视图名称通过 RequestToViewNameTranslator 确定
@ModelAttribute 要添加到模型的属性,视图名称通过 RequestToViewNameTranslator 隐式确定。请注意,@ModelAttribute 是可选的
ModelAndView 对象 要使用的视图和模型属性,以及可选的响应状态
void 具有 void 返回类型(或 null 返回值)的方法如果还具有 ServletResponseOutputStream 参数或 @ResponseStatus 注解,则被认为已经完全处理了响应。如果控制器进行了 ETaglastModified 时间戳检查(有关详细信息,请参阅 控制器 ),情况也是如此 。
如果以上都不为真,则 void 返回类型还可以指示 REST 控制器的“无响应主体”或 HTML 控制器的默认视图名称选择
DeferredResult<V> 从任何线程异步生成任何上面的返回值,例如,作为某些事件或回调的结果。请参阅 异步请求DeferredResult
Callable<V> 在 Spring MVC 管理的线程中异步生产上述任何返回值。请参阅 异步请求Callable
ListenableFuture<V>java.util.concurrent.CompletionStage<V>java.util.concurrent.CompletableFuture<V> 替代 DeferredResult ,作为一种便利(例如,当底层服务返回其中之一时)
ResponseBodyEmitterSseEmitter 异步发出一个对象流,以通过 HttpMessageConverter 实现写入响应 。也支持作为 ResponseEntity 主体。请参阅 异步请求HTTP 流
StreamingResponseBody 异步写入 OutputStream 响应。也支持作为 ResponseEntity 主体。 请参阅 异步请求HTTP 流
反应类型——Reactor 、RxJava 或其他类型通过 ReactiveAdapterRegistry 替代 DeferredResult 将多值流(例如 FluxObservable )收集到 List
对于流场景(例如,text/event-streamapplication/json+stream ), 改为使用 SseEmitterResponseBodyEmitter ,其中在 Spring MVC 管理的线程上执行 ServletOutputStream 阻塞 I/O,并且对每次写入的完成施加背压。
请参阅 异步请求反应类型
任何其他返回值 任何返回值不匹配任何此表中上面值的,Stringvoid 被视为视图名称(缺省视图名称选择通过 RequestToViewNameTranslator 适用),只要它不是一个简单的类型,由 BeanUtils#isSimpleProperty 确定。简单类型的值仍未解析。
类型转换

WebFlux

一些带注解的控制器方法的参数(如 @RequestParam@RequestHeader@PathVariable@MatrixVariable ,和 @CookieValue ),基于 String 的请求输入,如果该参数被声明为 String 以外的其他类型,可能需要类型转换。

对于这种情况,将根据配置的转换器自动应用类型转换。默认情况下,支持简单类型( intlongDate 等 )。您可以通过 WebDataBinder(请参见 DataBinder )或通过向 FormattingConversionService 注册 Formatters 来自定义类型转换。请参阅Spring 字段格式

类型转换中的一个实际问题是空字符串源值的处理。如果由于类型转换而变为 null ,则此类值将被视为缺少。对于 LongUUID 和其他目标类型,情况可能就是这样。如果要允许注入 null ,请在参数注解上使用 required 标志,或将参数声明为 @Nullable

从 5.3 开始,即使在类型转换之后也将强制执行非空参数。如果您的处理器方法也打算接受空值,请将您的参数声明为 @Nullable 或将其标记为 required=false 相应的 @RequestParam 注解。这是最佳实践,也是 5.3 升级中遇到的回归的推荐解决方案。

或者,您也可以专门处理例如在需要 @PathVariable 的情况下产生的 MissingPathVariableException 。转换后的 null 值将被视为空的原始值,因此将抛出相应的 Missing…Exception 变体。

矩阵变量

WebFlux

RFC 3986 讨论了路径段中的键值对。在 Spring MVC 中,我们根据 Tim Berners-Lee 的 “old post” 将它们称为“矩阵变量” ,但它们也可以称为 URI 路径参数。

矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如,/cars;color=red,green;year=2012 )。也可以通过重复的变量名称(例如,color=red;color=green;color=blue )指定多个值 。

如果期望 URL 包含矩阵变量,则控制器方法的请求映射必须使用 URI 变量来屏蔽该变量内容,并确保可以成功匹配请求而不受矩阵变量顺序和存在的影响。以下示例使用矩阵变量:

// GET /pets/42;q=11;r=22

@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {

    // petId == 42
    // q == 11
}

鉴于所有路径段都可能包含矩阵变量,您有时可能需要消除矩阵变量应位于哪个路径变量中的歧义。以下示例显示了如何执行此操作:

// GET /owners/42;q=11/pets/21;q=22

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable(name="q", pathVar="ownerId") int q1,
        @MatrixVariable(name="q", pathVar="petId") int q2) {

    // q1 == 11
    // q2 == 22
}

矩阵变量可以定义为可选并指定默认值,如以下示例所示:

// GET /pets/42

@GetMapping("/pets/{petId}")
public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {

    // q == 1
}

要获取所有矩阵变量,您可以使用 MultiValueMap ,如以下示例所示:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
        @MatrixVariable MultiValueMap<String, String> matrixVars,
        @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

    // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
    // petMatrixVars: ["q" : 22, "s" : 23]
}

请注意,您需要启用矩阵变量的使用。在 MVC Java 配置中,需要通过 路径匹配UrlPathHelper 设置为 removeSemicolonContent=false 。在MVC XML名称空间中,可以设置 <mvc:annotation-driven enable-matrix-variables="true"/>

@RequestParam

WebFlux

您可以使用 @RequestParam 注解将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。

以下示例显示了如何执行此操作:

@Controller
@RequestMapping("/pets")
public class EditPetForm {

    // ...

    @GetMapping
    public String setupForm(@RequestParam("petId") int petId, Model model) { 
        Pet pet = this.clinic.loadPet(petId);
        model.addAttribute("pet", pet);
        return "petForm";
    }

    // ...

}

默认情况下,使用 @RequestParam 注解的方法参数是必需的,但您可以通过将 @RequestParam 注解的 required 标志设置为 false 或通过使用 java.util.Optional 包装器声明参数来指定方法参数是可选的。

如果目标方法参数类型不是 String ,则自动应用类型转换。请参阅 类型转换

将参数类型声明为数组或列表允许解析同一参数名称的多个参数值。

@RequestParam 注解被声明为 MapMultiValueMap ,而没有在注解中指定参数名称时,映射将填充每个给定参数名称的请求参数值。

请注意,使用 @RequestParam 是可选的(例如,设置其属性)。默认情况下,任何属于简单值类型(由 BeanUtils#isSimpleProperty 确定 )且未被任何其他参数解析器解析的参数都被视为使用 @RequestParam

@RequestHeader

WebFlux

您可以使用 @RequestHeader 注解将请求标头绑定到控制器中的方法参数。

考虑以下带有标头的请求:

Host                    localhost:8080
Accept                  text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language         fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding         gzip,deflate
Accept-Charset          ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive              300

以下示例获取 Accept-EncodingKeep-Alive 标头的值:

@GetMapping("/demo")
public void handle(
        @RequestHeader("Accept-Encoding") String encoding, 
        @RequestHeader("Keep-Alive") long keepAlive) { 
    //...
}

如果目标方法参数类型不是 String ,则自动应用类型转换。请参阅 类型转换

@RequestHeader 注解上的使用 Map<String, String>MultiValueMap<String, String>HttpHeaders 参数,则 Map 被填充所有标头值。

内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。例如,带有 @RequestHeader("Accept") 注解的方法参数可以是 String 类型,但也可以是 String[]List<String>

@CookieValue

WebFlux

您可以使用 @CookieValue 注解将 HTTP Cookie 的值绑定到控制器中的方法参数。

考虑带有以下 Cookie 的请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

以下示例显示了如何获取 Cookie 值:

@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { 
    //...
}

如果目标方法参数类型不是 String ,则自动应用类型转换。请参阅 类型转换

@ModelAttribute

WebFlux

您可以在方法参数上使用 @ModelAttribute 注解来访问模型中的属性,或者在不存在的情况下对其进行实例化。模型属性还覆盖来自 HTTP Servlet 请求参数的值,其名称与字段名称匹配。这称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段。以下示例显示了如何执行此操作:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
    // method logic...
}

上述 Pet 实例来源于以下方式之一:

  • 从模型中检索,其中它可能已由 @ModelAttribute 方法 添加
  • 如果模型属性列在类级别 @SessionAttributes 注解中,则从 HTTP 会话中检索
  • 通过 Converter 获得,其中模型属性名称匹配请求值的名称(例如路径变量或请求参数)
  • 使用其默认构造函数实例化
  • 通过具有与 Servlet 请求参数匹配的参数的“主构造函数”实例化。参数名称是通过 JavaBean @ConstructorProperties 或字节码中运行时保留的参数名称确定的

使用 @ModelAttribute 方法 来提供该属性或依赖框架来创建模型属性的一种替代方法是使用一个 Converter<String, T> 来提供实例。当模型属性名称与请求值的名称(例如路径变量或请求参数)匹配,并且存在从 String 到模型属性类型的 Converter 时,将应用此选项。

在以下示例中,模型属性名称 account 与 URI 路径变量 account 匹配,并且有一个可以从数据存储加载 Account 的已注册 Converter<String, T>

@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
    // ...
}

获取模型属性实例后,应用数据绑定。WebDataBinder 类将 Servlet 请求参数名称(查询参数和表单字段)与目标 Object 上的字段名称相匹配。必要时,在应用类型转换后填充匹配字段。有关数据绑定(和验证)的更多信息,请参阅 Validation 。有关自定义数据绑定的更多信息,请参阅 DataBinder

数据绑定可能会导致错误。默认情况下,会引发 BindException 。但是,要检查控制器方法中的此类错误,您可以在 @ModelAttribute 旁边添加一个 BindingResult 参数,如下例所示:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

在某些情况下,您可能希望在没有数据绑定的情况下访问模型属性。对于这种情况,您可以将 Model 注入控制器并直接访问它,或者,也可以使用设置 @ModelAttribute(binding=false) ,如下例所示:

@ModelAttribute
public AccountForm setUpForm() {
    return new AccountForm();
}

@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
    return accountRepository.findOne(accountId);
}

@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
        @ModelAttribute(binding=false) Account account) { 
    // ...
}

您可以通过添加 javax.validation.Valid 注解或 Spring 的 @Validated 注解( Bean ValidationSpring validation )在数据绑定后自动应用验证 。以下示例显示了如何执行此操作:

@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { 
    if (result.hasErrors()) {
        return "petForm";
    }
    // ...
}

请注意, 使用 @ModelAttribute 是可选的(例如,设置其属性)。默认情况下,任何不是简单值类型(由 BeanUtils#isSimpleProperty 确定 )并且未被任何其他参数解析器解析的参数都被视为使用 @ModelAttribute

@SessionAttributes

WebFlux

@SessionAttributes 用于在请求之间的 HTTP Servlet 会话中存储模型属性。它是一个类级别的注解,用于声明特定控制器使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,这些名称应该透明地存储在会话中以供后续请求访问。

以下示例使用了 @SessionAttributes 注解:

@Controller
@SessionAttributes("pet") 
public class EditPetForm {
    // ...
}

在第一个请求中,当一个名为 pet 的模型属性被添加到模型中时,它会自动提升并保存在 HTTP Servlet 会话中。它保持在那里,直到另一个控制器方法使用 SessionStatus 方法参数来清除存储,如以下示例所示:

@Controller
@SessionAttributes("pet") 	// 将 Pet 值存储在 Servlet 会话中
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
        	// 清除 Servlet 会话中的 Pet 值
            status.setComplete(); 
            // ...
        }
    }
}
@SessionAttribute

WebFlux

如果您需要访问全局管理的预先存在的会话属性(即,在控制器之外,例如,通过过滤器)并且可能存在或不存在,您可以在方法参数上使用 @SessionAttribute 注解,如下所示示例显示:

@RequestMapping("/")
public String handle(@SessionAttribute User user) { 
    // ...
}

对于需要添加或删除会话属性的用例,请考虑在控制器方法上注入 org.springframework.web.context.request.WebRequestjavax.servlet.http.HttpSession

对于作为控制器工作流一部分的会话中模型属性的临时存储,请考虑按照 @SessionAttributes 中所述使用 @SessionAttributes

@RequestAttribute

WebFlux

@SessionAttribute 类似,您可以使用 @RequestAttribute 注解来访问先前创建的预先存在的请求属性(例如,通过 Servlet FilterHandlerInterceptor ):

@GetMapping("/")
public String handle(@RequestAttribute Client client) { 
    // ...
}
重定向属性(Redirect Attributes)

默认情况下,所有模型属性都被视为在重定向 URL 中公开为 URI 模板变量。在剩余的属性中,原始类型或原始类型的集合或数组会自动附加为查询参数。

如果专门为重定向准备了一个模型实例,那么将原始类型属性作为查询参数附加可能是理想的结果。但是,在带注解的控制器中,模型可以包含为渲染目的添加的附加属性(例如,下拉字段值)。为避免在 URL 中出现此类属性,@RequestMapping 方法可以声明 RedirectAttributes 类型参数并使用它指定可供 RedirectView 使用的确切属性。如果该方法为重定向,则使用 RedirectAttributes 的内容。否则,将使用模型的内容。

RequestMappingHandlerAdapter 提供了一个名为 ignoreDefaultModelOnRedirect 的标志,您可以使用该标志指示如果控制器方法重定向,则不应使用默认 Model 的内容。相反,控制器方法应该声明一个 RedirectAttributes 类型的属性,或者,如果不这样做,则不应将任何属性传递给 RedirectView 。MVC 命名空间和 MVC Java 配置都将此标志设置为 false ,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为 true

请注意,当前请求中的 URI 模板变量在扩展重定向 URL 时自动可用,您无需通过 ModelRedirectAttributes 显式添加它们。以下示例显示了如何定义重定向:

@PostMapping("/files/{path}")
public String upload(...) {
    // ...
    return "redirect:files/{path}";
}

另一种将数据传递到重定向目标的方法是使用 flash 属性。与其他重定向属性不同,flash 属性保存在 HTTP Session 中(因此不会出现在 URL 中)。

Flash 属性(Flash Attributes)

Flash 属性为一个请求提供了一种方法来存储打算在另一个请求中使用的属性。这在重定向时最常需要——例如,Post-Redirect-Get 模式。Flash 属性在重定向之前(通常在会话中)临时保存,以便在重定向之后可用于请求并立即删除。

Spring MVC 有两个主要的抽象来支持 flash 属性。FlashMap 用于保存 flash 属性,FlashMapManager 用于存储、检索和管理 FlashMap 实例。

Flash 属性支持始终处于“开启”状态,无需显式启用。但是,如果不使用,它永远不会导致 HTTP 会话创建。在每个请求中,都有一个“输入” FlashMap ,其中包含从前一个请求传递的属性,以及一个“输出” FlashMap ,其中包含为后续请求保存的属性。这两个 FlashMap 实例都可以通过 RequestContextUtils 中的静态方法从 Spring MVC 中的任何位置访问。

带注解的控制器通常不需要直接使用 FlashMap 。相反, @RequestMapping 方法可以接受 RedirectAttributes 类型参数并使用它为重定向场景添加 flash 属性。通过 RedirectAttributes 添加的 Flash 属性 会自动传播到“输出” FlashMap 。类似地,在重定向之后,来自“输入” FlashMap 的属性会自动添加到为目标 URL 提供服务的控制器的 Model

将请求与 flash 属性匹配

flash 属性的概念存在于许多其他 Web 框架中,并且已被证明有时会面临并发问题。这是因为,根据定义,flash 属性将一直存储到下一个请求。然而,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,闪存属性被过早删除。

为了减少出现此类问题的可能性,RedirectView 使用目标重定向 URL 的路径和查询参数自动“标记” FlashMap 实例。反过来,默认的 FlashMapManager 在查找“输入” FlashMap 时将该信息与传入请求匹配。

这并不能完全消除并发问题的可能性,但可以通过重定向 URL 中已有的信息大大降低并发问题的可能性。因此,我们建议您主要将 flash 属性用于重定向场景。

多部分(Multipart)

WebFlux

启用 MultipartResolver 后,将解析具有 multipart/form-data 的 POST 请求的内容,并将其作为常规请求参数进行访问。以下示例访问一个常规表单字段和一个上传的文件:

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(@RequestParam("name") String name,
            @RequestParam("file") MultipartFile file) {

        if (!file.isEmpty()) {
            byte[] bytes = file.getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

将参数类型声明为 List<MultipartFile> 允许为相同的参数名称解析多个文件。

@RequestParam 注解被声明为 Map<String, MultipartFile>MultiValueMap<String, MultipartFile> ,而没有在注解中指定参数名称时,映射将填充每个给定参数名称的多部分文件。

使用 Servlet 3.0 多部分解析,您还可以声明 javax.servlet.http.Part 代替 Spring 的 MultipartFile ,作为方法参数或集合值类型。

您还可以使用多部分内容作为数据绑定到 命令对象 的一部分。例如,前面示例中的表单字段和文件可以是表单对象上的字段,如下例所示:

class MyForm {

    private String name;

    private MultipartFile file;

    // ...
}

@Controller
public class FileUploadController {

    @PostMapping("/form")
    public String handleFormUpload(MyForm form, BindingResult errors) {
        if (!form.getFile().isEmpty()) {
            byte[] bytes = form.getFile().getBytes();
            // store the bytes somewhere
            return "redirect:uploadSuccess";
        }
        return "redirect:uploadFailure";
    }
}

在 RESTful 服务场景中,也可以从非浏览器客户端提交多部分请求。以下示例显示了一个带有 JSON 的文件:

POST /someUrl
Content-Type: multipart/mixed

--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="meta-data"
Content-Type: application/json; charset=UTF-8
Content-Transfer-Encoding: 8bit

{
    "name": "value"
}
--edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
Content-Disposition: form-data; name="file-data"; filename="file.properties"
Content-Type: text/xml
Content-Transfer-Encoding: 8bit
... File Data ...

您可以使用 @RequestParam 访问“元数据”部分,但您可能希望将其从 JSON 反序列化(类似于 @RequestBody )。使用 HttpMessageConverter 转换后,使用 @RequestPart 注解访问多部分 :

@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
        @RequestPart("file-data") MultipartFile file) {
    // ...
}

您可以结合使用 @RequestPartjavax.validation.Valid 或使用 Spring 的 @Validated 注解,这两者都会导致应用标准 Bean 验证。默认情况下,验证错误会导致 MethodArgumentNotValidException ,它会返回 400 (BAD_REQUEST) 响应。或者,您可以通过 ErrorsBindingResult 参数在控制器内本地处理验证错误,如以下示例所示:

@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
        BindingResult result) {
    // ...
}
@RequestBody

WebFlux

您可以使用 @RequestBody 注解通过 HttpMessageConverter 将请求正文读取并反序列化为 Object 。以下示例使用 @RequestBody 参数:

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
    // ...
}

您可以使用 MVC ConfigMessage Converters 选项来配置或自定义消息转换。

您可以结合使用 @RequestPartjavax.validation.Valid 或使用 Spring 的 @Validated 注解,这两者都会导致应用标准 Bean 验证。默认情况下,验证错误会导致 MethodArgumentNotValidException ,它会返回 400 (BAD_REQUEST) 响应。或者,您可以通过 ErrorsBindingResult 参数在控制器内本地处理验证错误,如以下示例所示:

@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
    // ...
}
HttpEntity

WebFlux

HttpEntity 与使用 @RequestBody 大体相同,但基于公开请求标头和正文的容器对象。以下清单显示了一个示例:

@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
    // ...
}
@ResponseBody

WebFlux

您可以在方法上使用 @ResponseBody 注解,通过 HttpMessageConverter 将返回序列化为响应正文 。以下清单显示了一个示例:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
    // ...
}

@ResponseBody 在类级别也受支持,在这种情况下,所有控制器方法都会继承它。这就是 @RestController 的效果,它用 @Controller@ResponseBody 元注解。

ResponseEntity

WebFlux

ResponseEntity 就像 @ResponseBody ,但是有状态和标头。例如:

@GetMapping("/something")
public ResponseEntity<String> handle() {
    String body = ... ;
    String etag = ... ;
    return ResponseEntity.ok().eTag(etag).build(body);
}

Spring MVC 支持使用单值的 反应式类型 ,以产生异步 ResponseEntity ,或为主体生成单值和多值反应式类型。这允许以下类型的异步响应:

  • ResponseEntity<Mono<T>> 或者 ResponseEntity<Flux<T>> 使响应状态和标头立即为大家所知,同时主体将在稍后异步提供。如果正文由 0..1 值组成,则使用 Mono ,或者如果它可以产生多个值,使用 Flux
  • Mono<ResponseEntity<T>> 提供所有三个内容,响应状态、标头和正文(稍后异步提供)。这允许响应状态和标头根据异步请求处理的结果而变化
Jackson JSON

Spring 提供对 Jackson JSON 库的支持。

JSON 视图

WebFlux

Spring MVC 为 Jackson 的 Serialization Views 提供了内置支持, 它只允许渲染 Object 内的所有属性的子集。要将其与 @ResponseBodyResponseEntity 控制器方法一起使用,您可以使用 Jackson 的 @JsonView 注解来激活序列化视图类,如下例所示:

@RestController
public class UserController {

    @GetMapping("/user")
    @JsonView(User.WithoutPasswordView.class)
    public User getUser() {
        return new User("eric", "7!jd#h23");
    }
}

public class User {

    public interface WithoutPasswordView {};
    public interface WithPasswordView extends WithoutPasswordView {};

    private String username;
    private String password;

    public User() {
    }

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    @JsonView(WithoutPasswordView.class)
    public String getUsername() {
        return this.username;
    }

    @JsonView(WithPasswordView.class)
    public String getPassword() {
        return this.password;
    }
}

@JsonView 允许一组视图类,但您只能为每个控制器方法指定一个。如果需要激活多个视图,可以使用复合接口。

如果您想以编程方式执行上述操作,请不要声明 @JsonView 注解,而是将返回值用 MappingJacksonValue 包装起来并使用它来提供序列化视图:

@RestController
public class UserController {

    @GetMapping("/user")
    public MappingJacksonValue getUser() {
        User user = new User("eric", "7!jd#h23");
        MappingJacksonValue value = new MappingJacksonValue(user);
        value.setSerializationView(User.WithoutPasswordView.class);
        return value;
    }
}

对于依赖视图解析的控制器,您可以将序列化视图类添加到模型中,如下例所示:

@Controller
public class UserController extends AbstractController {

    @GetMapping("/user")
    public String getUser(Model model) {
        model.addAttribute("user", new User("eric", "7!jd#h23"));
        model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
        return "userView";
    }
}

Model

WebFlux

您可以使用 @ModelAttribute 注解:

  • @RequestMapping 方法中的 方法参数 上创建或访问来自模型的 Object 并通过 WebDataBinder 将其绑定到请求
  • 作为 @Controller@ControllerAdvice 类中的方法级注解,有助于在任何 @RequestMapping 方法调用之前初始化模型
  • @RequestMapping 方法上标记其返回值是一个模型属性

本节讨论 @ModelAttribute 方法——前面列表中的第二项。

一个控制器可以有任意数量的 @ModelAttribute 方法。所有这些方法都在同一控制器中的 @RequestMapping 方法之前调用。@ModelAttribute 方法也可以跨控制器通过 @ControllerAdvice 共享。有关更多详细信息,请参阅 控制器通知 部分 。

@ModelAttribute 方法具有灵活的方法签名。它们支持许多与 @RequestMapping 方法相同的参数,除了 @ModelAttribute 本身或与请求主体相关的任何内容。

以下示例显示了 @ModelAttribute 方法:

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
    model.addAttribute(accountRepository.findAccount(number));
    // add more ...
}

以下示例仅添加一个属性:

@ModelAttribute
public Account addAccount(@RequestParam String number) {
    return accountRepository.findAccount(number);
}

如果未明确指定名称,则会根据 Object 类型选择默认名称。您始终可以通过使用重载 addAttribute 方法或通过 @ModelAttribute 上的 name 属性 (用于返回值)分配显式名称。

您还可以在 @RequestMapping 方法上用 @ModelAttribute 作为方法级注解,在这种情况下,@RequestMapping 方法的返回值被解释为模型属性。这通常不是必需的,因为它是 HTML 控制器中的默认行为,除非返回值是 String ,否则会被解释为视图名称。 @ModelAttribute 还可以自定义模型属性名称,如下例所示:

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}
  • org.springframework.web.bind.annotation.ModelAttribute

DataBinder

WebFlux

@Controller@ControllerAdvice 类可以有 @InitBinder 方法来初始化 WebDataBinder 的实例,这些方法可以:

  • 将请求参数(即表单或查询数据)绑定到模型对象
  • 将基于 String 的请求值(例如请求参数、路径变量、标头、 Cookie 和其他)转换为控制器方法参数的目标类型
  • 在呈现 HTML 表单时,将模型对象值格式化为 String 值

@InitBinder 方法可以注册特定于控制器的 java.beans.PropertyEditor 或 Spring ConverterFormatter 组件。此外,您可以使用 MVC 配置 在一个全局共享的 FormattingConversionService 中注册 ConverterFormatter 类型。

@InitBinder 方法支持许多与 @RequestMapping 方法相同的参数,除了 @ModelAttribute (命令对象)参数。通常,它们使用 WebDataBinder 参数(用于注册)和 void 返回值声明。下面的清单显示了一个例子:

@Controller
public class FormController {

    @InitBinder 
    public void initBinder(WebDataBinder binder) {
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
        dateFormat.setLenient(false);
        binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
    }

    // ...
}

或者,当您通过共享 FormattingConversionService 使用基于 Formatter 的设置时,您可以重复使用相同的方法和注册控制器特定的 Formatter 实现,如下面的示例所示:

@Controller
public class FormController {

    @InitBinder 
    protected void initBinder(WebDataBinder binder) {
        binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
    }

    // ...
}

异常

WebFlux

@Controller@ControllerAdvice 类可以使用 @ExceptionHandler 方法来处理来自控制器方法的异常,如下面的示例所示:

@Controller
public class SimpleController {

    // ...

    @ExceptionHandler
    public ResponseEntity<String> handle(IOException ex) {
        // ...
    }
}

异常可能与传播的顶级异常(例如直接抛出的 IOException )匹配,或者与包装器异常中的嵌套原因匹配(例如包装在 IllegalStateException 中的 IOException )。从 5.3 开始,可以在任意的原因水平上匹配,而以前只考虑直接原因。

或者,注解声明可以缩小要匹配的异常类型,如下面的示例所示:

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
    // ...
}

你甚至可以用一个非常通用的参数签名来使用特定异常类型的列表,如下面的例子所示:

@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
    // ...
}

根和原因异常匹配之间的区别可能令人惊讶。

在前面所示的 IOException 变量中,通常使用实际的 FileSystemException RemoteException 实例作为参数来调用该方法,因为它们都是从 IOException 扩展而来的。但是,如果在本身为 IOException 的包装异常中传播了任何此类匹配异常,则传入的异常实例就是该包装异常。

这种行为在 handle(Exception) 变体中更为简单。在包装场景中,总是使用包装异常调用这个异常,在这种情况下,通过 ex.getCause() 找到实际匹配的异常。只有在作为顶级异常引发时,传入的异常才是实际的 FileSystemException RemoteException 实例

我们通常建议您在参数签名中尽可能具体,以减少根类型和导致异常类型之间不匹配的可能性。考虑将多重匹配方法拆分为单个@ExceptionHandler 方法,每个方法通过其签名匹配一个特定的异常类型。

在多个 @ControllerAdvice 安排中,我们建议在 @ControllerAdvice 上声明主要的根异常映射,并按相应顺序排列。虽然根异常匹配优于原因匹配,但这是在给定控制器或 @ControllerAdvice 类的方法中定义的。这意味着较高优先级 @ControllerAdvice bean 上的原因匹配优先于较低优先级 @ControllerAdvice bean 上的任何匹配(例如,root )

最后,@ExceptionHandler 方法实现可以选择通过以原始形式重新引发给定的异常实例来退出处理。在只对根级别匹配感兴趣的场景中,或者在无法静态确定的特定上下文中的匹配中,这种方法很有用。重新抛出的异常会通过剩余的解析链传播,就好像给定的@ExceptionHandler 方法一开始就不匹配一样。

Spring MVC 中对 @ExceptionHandler 方法的支持建立在 DispatcherServlet 级别, HandlerExceptionResolver 机制之上。

方法参数

@ExceptionHandler 方法支持以下参数:

方法参数 描述
异常类型 用于访问被引发的异常
HandlerMethod 用于访问引发异常的控制器方法
WebRequest
NativeWebRequest
通用访问请求参数、请求和会话属性,而不直接使用 Servlet API
javax.servlet.ServletRequest
javax.servlet.ServletResponse
选择任何特定的请求或响应类型(例如,ServletRequestHttpServletRequest 或 Spring 的 MultipartRequestMultipartHttpServletRequest
javax.servlet.http.HttpSession 强制会话的存在。因此,这样的参数永远不会是 null 。注意,会话访问不是线程安全的。如果允许多个请求并发访问一个会话,那么可以考虑将 RequestMappingHandlerAdapter 实例的 synchronizeOnSession 标志设置为 true
java.security.Principal 当前已验证的用户——如果已知,可能是特定的 Principal 实现类
HttpMethod 请求的 HTTP 方法
java.util.Locale 当前语言环境,由可用的最特定的 LocaleResolver 确定,实际上是已配置的 LocaleResolver LocaleContextResolver
java.util.TimeZone
java.time.ZoneId
与当前请求关联的时区,由 LocaleContextResolver 确定
java.io.OutputStream
java.io.Writer
用于访问由 Servlet API 公开的原始响应主体
java.util.Map
org.springframework.ui.Model
org.springframework.ui.ModelMap
用于访问错误响应的模型,总是空的
RedirectAttributes 指定在重定向(即将附加到查询字符串)时使用的属性和闪存属性,暂时存储到重定向之后的请求为止。参见 重定向属性Flash 属性
@SessionAttribute 用于访问任何会话属性,与类级别 @SessionAttributes 声明导致的会话中存储的模型属性形成对比。参见 @SessionAttribute
@RequestAttribute 参见 @RequestAttribute
返回值

@ExceptionHandler 方法支持以下返回值:

返回值 描述
@ResponseBody 返回值通过 HttpMessageConverter 实例转换并写入响应
HttpEntity<B>
ResponseEntity<B>
返回值指定通过 HttpMessageConverter 实例转换完整响应(包括 HTTP 标头和主体)并将其写入响应。参见 ResponseEntity
String 视图名称将与 ViewResolver 实现一起解析,并与隐式模型一起使用,通过命令对象和@ModelAttribute 方法确定。处理器方法还可以通过声明一个 Model 参数 ,以编程方式丰富模型
View 用于与隐式模型一起渲染的 View 实例,通过命令对象和 @ModelAttribute 方法确定。处理器方法还可以通过声明一个 Model 参数以编程方式丰富模型
java.util.Map
org.springframework.ui.Model
要添加到隐式模型的属性,其视图名称通过 RequestToViewNameTranslator 隐式确定
@ModelAttribute 将通过 RequestToViewNameTranslator 隐式确定的视图名称添加到模型的属性
注意,@ModelAttribute 是可选的
ModelAndView 对象 要使用的视图和模型属性,以及响应状态(可选)
void 如果具有 void 返回类型(或 null 返回值)的方法还具有 ServletResponseOutputStream 参数或 @ResponseStatus 注解,则认为该方法已经完全处理了响应。如果控制器进行了正向 ETaglastModified 时间戳检查(参见 Controllers ),也是如此
如果以上都没有,则 void 返回类型还可以指示 REST 控制器的 no response body 或 HTML 控制器的默认视图名称选择
任何其他返回值 如果返回值与上面的任何一个都不匹配,并且不是简单类型(由 BeanUtils#isSimpleProperty 确定),那么默认情况下,它被视为要添加到模型中的模型属性。如果它是一个简单类型,那么它仍然没有解析
REST API 异常

REST 服务的一个常见需求是在响应体中包含错误详细信息。Spring Framework 不会自动执行此操作,因为响应体中错误细节的表示是特定于应用程序的。但是,@RestController 可以使用带有 ResponseEntity 返回值的 @ExceptionHandler 方法来设置响应的状态和主体。这些方法也可以在 @ControllerAdvice 类中声明,以全局应用它们。

在响应主体中实现带有错误细节的全局异常处理的应用程序应该考虑扩展 ResponseEntityExceptionHandler ,该处理器为 Spring MVC 引发的异常提供处理,并提供用于定制响应主体的挂钩。为了利用这一点,创建一个 ResponseEntityExceptionHandler 的子类,用 @ControllerAdvice 注解它,重写必要的方法,并将其声明为 Spring bean 。

控制器通知(Controller Advice)

WebFlux

通常,@ExceptionHandler@InitBinder@ModelAttribute 方法应用于声明它们的 @Controller 类(或类层次结构)中。如果希望这些方法应用更多的全局(跨控制器),可以在带有 @ControllerAdvice@RestControllerAdvice 注解的类中声明它们

@ControllerAdvice 带有 @Component 注解,这意味着这些类可以通过组件扫描注册为 Spring bean 。 @RestControllerAdvice 是由 @ControllerAdvice@ResponseBody 元注解的复合注解,这实际上意味着 @ExceptionHandler 方法通过消息转换(相对视图解析或模板渲染)被渲染给响应主体。

在启动时,用于 @RequestMapping @ExceptionHandler 方法的基础设施类会检测带有 @ControllerAdvice 注解的 Spring bean ,然后在运行时应用它们的方法。全局 @ExceptionHandler 方法(来自 @ControllerAdvice )应用于本地方法之后(来自 @Controller )。相比之下,全局的 @ModelAttribute@InitBinder 方法应用在本地方法之前。

默认情况下, @ControllerAdvice 方法应用于每个请求(即所有控制器) ,但是您可以通过使用注解上的属性将其缩小到控制器的一个子集,如下面的示例所示:

// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// Target all Controllers within specific packages
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// Target all Controllers assignable to specific classes
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

前面示例中的选择器在运行时进行计算,如果广泛使用,可能会对性能产生负面影响。

函数式端点(Functional Endpoints)

WebFlux

Spring Web MVC 包括 WebMvc.fn ,这是一个轻量级的函数式编程模型,其中的函数用于路由和处理请求,而契约是为不变性而设计的。它是基于注解的编程模型的一种替代方法,但是在相同的 DispatcherServlet 上运行。

概述

WebFlux

在 WebMvc.fn 中,HTTP 请求通过 HandlerFunction 处理:这个函数接收 ServerRequest 并返回 ServerResponse 。请求和响应对象都有不可变的契约,这些契约为 JDK 8 友好提供了对 HTTP 请求和响应的访问。HandlerFunction 相当于基于注解的编程模型中的@RequestMapping 方法的主体。

传入的请求被路由到一个具有 RouterFunction 的处理函数:这个函数接受 ServerRequest 并返回一个可选的 HandlerFunction ( Optional<HandlerFunction> ) 。当路由器函数匹配时,返回一个处理器函数;否则返回一个空的 OptionalRouterFunction 相当于 @RequestMapping 注解,但主要区别在于路由器函数不仅提供数据,还提供行为。

RouterFunctions.route() 提供了一个路由器构建器,可以方便地创建路由器,如下面的示例所示:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople)
    .POST("/person", handler::createPerson)
    .build();


public class PersonHandler {

    // ...

    public ServerResponse listPeople(ServerRequest request) {
        // ...
    }

    public ServerResponse createPerson(ServerRequest request) {
        // ...
    }

    public ServerResponse getPerson(ServerRequest request) {
        // ...
    }
}

如果将 RouterFunction 注册为 bean,例如在 @Configuration 类中公开它,servlet 将自动检测它。

HandlerFunction

WebFlux

ServerRequestServerResponse 是不可变的接口,它们为 JDK 8 提供了对 HTTP 请求和响应的友好访问,包括标头、主体、方法和状态码。

ServerRequest

ServerRequest 提供对 HTTP 方法、 URI 、 headers 和查询参数的访问,而对 body 的访问是通过 body 方法提供的。

将请求主体提取为 String

String string = request.body(String.class);

将主体提取到 List<Person> ,其中 Person 对象从序列化的表单(如 JSON 或 XML)中解码:

List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});

访问参数:

MultiValueMap<String, String> params = request.params();
ServerResponse

ServerResponse 提供对 HTTP 响应的访问,而且由于它是不可变的,因此可以使用 build 方法来创建它。您可以使用构建器来设置响应状态、添加响应标头或提供主体。下面的示例使用 JSON 内容创建了一个200 (OK) 响应:

Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);

构建一个带有 Location 头而没有主体的 201(CREATED) 响应:

URI location = ...
ServerResponse.created(location).build();

您还可以使用一个异步结果作为主体,其形式为 CompletableFuturePublisherReactiveAdapterRegistry 支持的任何其他类型。例如:

Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);

如果不仅仅是主体,还有状态或标头都基于异步类型,那么您可以在 ServerResponse 上使用静态异步方法,该方法接受 CompletableFuture<ServerResponse>Publisher<ServerResponse>ReactiveAdapterRegistry 支持的任何其他异步类型。例如:

Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
  .map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);

Server-Sent Events 可以通过 ServerResponse 上的静态 sse 方法提供。该方法提供的构建器允许您将 String 或其他对象作为 JSON 发送。例如:

public RouterFunction<ServerResponse> sse() {
    return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
                // Save the sseBuilder object somewhere..
            }));
}

// In some other thread, sending a String
sseBuilder.send("Hello world");

// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);

// Customize the event by using the other methods
sseBuilder.id("42")
        .event("sse event")
        .data(person);

// and done at some point
sseBuilder.complete();
处理器类

可以写一个 lambda 处理函数:

HandlerFunction<ServerResponse> helloWorld =
  request -> ServerResponse.ok().body("Hello World");

这很方便,但是在应用程序中我们需要多个函数,而多个内联 lambda 函数可能会变得杂乱无章。因此,将相关的处理程序函数组合到一个处理器类中非常有用,该处理器类在基于注解的应用程序中具有类似于 @Controller 的角色。例如,下面的类公开了一个 反应式 Person 存储库:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;

public class PersonHandler {

    private final PersonRepository repository;

    public PersonHandler(PersonRepository repository) {
        this.repository = repository;
    }

    // listPeople 是一个处理器函数,它以 JSON 的形式返回存储库中找到的所有 Person 对象
    public ServerResponse listPeople(ServerRequest request) { 
        List<Person> people = repository.allPeople();
        return ok().contentType(APPLICATION_JSON).body(people);
    }

    // createPerson 是一个处理器函数,它存储包含在请求主体中的新 Person
    public ServerResponse createPerson(ServerRequest request) throws Exception { 
        Person person = request.body(Person.class);
        repository.savePerson(person);
        return ok().build();
    }

    // getPerson 是一个返回单个 Person 的处理器函数,由 id path 变量标识。我们从存储库中检索这个 Person 并创建一个 JSON 响应(如果是的话),如果没有找到,我们将返回一个404 Not Found 响应
    public ServerResponse getPerson(ServerRequest request) { 
        int personId = Integer.parseInt(request.pathVariable("id"));
        Person person = repository.getPerson(personId);
        if (person != null) {
            return ok().contentType(APPLICATION_JSON).body(person);
        }
        else {
            return ServerResponse.notFound().build();
        }
    }

}
验证

功能端点可以使用 Spring 的验证工具对请求体应用验证。例如,给定一个自定义的 Spring Validator 实现:

public class PersonHandler {

    // 创建 Validator 实例
    private final Validator validator = new PersonValidator(); 

    // ...

    public ServerResponse createPerson(ServerRequest request) {
        Person person = request.body(Person.class);
        // 应用验证
        validate(person); 
        repository.savePerson(person);
        return ok().build();
    }

    private void validate(Person person) {
        Errors errors = new BeanPropertyBindingResult(person, "person");
        validator.validate(person, errors);
        if (errors.hasErrors()) {
            // 响应 400 ,抛出异常
            throw new ServerWebInputException(errors.toString()); 
        }
    }
}

处理器还可以通过创建和注入基于 LocalValidatorFactoryBean 的全局 Validator 实例来使用标准 bean 验证 API ( JSR-303 ) 。参见 Spring Validation

RouterFunction

WebFlux

路由器函数用于将请求路由到相应的 HandlerFunction 。通常,您不自己编写路由器函数,而是使用 RouterFunctions 工具类上的方法来创建。RouterFunctions.route() 为创建路由器函数提供了一个链式的构建器,而 RouterFunctions.route(RequestPredicate, HandlerFunction) 提供了创建路由器的直接方法。

通常,建议使用 route() 构建器,因为它为典型的映射场景提供了方便的捷径,而不需要难以发现的静态导入。例如,路由器函数构建器提供了方法 GET(String, HandlerFunction) 为 GET 请求创建映射, POST(String, HandlerFunction) 为 POST 请求创建映射。

除了基于 HTTP 方法的映射之外,路由生成器还提供了一种在映射到请求时引入附加断言(predicates)的方法。对于每个 HTTP 方法,都有一个重载的变体,它将 RequestPredicate 作为参数,可以表达附加约束。

断言(Predicates)

您可以编写自己的 RequestPredicate ,但 RequestPredicates 工具类提供了常用的实现,基于请求路径、 HTTP 方法、内容类型等等。下面的示例使用请求断言创建基于 Accept 标头的约束:

RouterFunction<ServerResponse> route = RouterFunctions.route()
    .GET("/hello-world", accept(MediaType.TEXT_PLAIN),
        request -> ServerResponse.ok().body("Hello World")).build();

可以使用以下命令将多个请求断言组合在一起:

  • RequestPredicate.and(RequestPredicate) :两者必须匹配
  • RequestPredicate.or(RequestPredicate) :任何一个都可以匹配

来自 RequestPredicates 的许多断言都是组合而成的。例如,RequestPredicates.GET(String)RequestPredicates.method(HttpMethod)RequestPredicates.path(String) 组成。上面显示的示例还使用了两个请求断言,因为构建器内部使用 RequestPredicates.GET ,并将其与 accept 断言组合在一起。

路由(Routes)

路由器函数按顺序进行计算:如果第一条路由不匹配,则计算第二条路由,依此类推。因此,在一般路线之前声明更具体的路线是有意义的。在将路由器功能注册为 Spring bean 时,这一点也很重要。注意,这种行为不同于基于注解的编程模型,后者自动选择“最特定的”控制器方法。

当使用路由器函数构建器时,所有已定义的路由都被组合成一个 RouterFunction ,该 RouterFunctionbuild() 返回。还有其他方法可以将多个路由器功能组合在一起:

  • RouterFunctions.route() 构建器上 add(RouterFunction)
  • RouterFunction.and(RouterFunction)
  • RouterFunction.andRoute(RequestPredicate, HandlerFunction) :带有嵌套 RouterFunctions.route()RouterFunction.and() 的快捷方式

四个路由的组成:

import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;

PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);

RouterFunction<ServerResponse> otherRoute = ...

RouterFunction<ServerResponse> route = route()
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) 
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
    .POST("/person", handler::createPerson) 
    .add(otherRoute) 
    .build();
嵌套路由

一组路由器函数有一个共享断言是很常见的,例如一个共享路径。在上面的示例中,共享断言将是匹配 /person 的路径断言,由三个路由使用。在使用注解时,可以通过使用映射到 /person 的类级别 @RequestMapping 注解来删除这种重复。在 WebMvc.fn 中,路径断言可以通过路由器函数构建器上的 path 方法共享。例如,上面例子的最后几行可以通过使用嵌套路改进:

RouterFunction<ServerResponse> route = route()
    .path("/person", builder -> builder 
        .GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
        .GET(accept(APPLICATION_JSON), handler::listPeople)
        .POST("/person", handler::createPerson))
    .build();

尽管基于路径的嵌套是最常见的,但是您可以使用构建器上的 nest 方法在任何类型的断言上进行嵌套。上面的代码仍然以共享 Accept- 标头断言的形式包含一些重复。我们可以通过使用 nest 方法和 accept 来进一步改进:

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople))
        .POST("/person", handler::createPerson))
    .build();

运行服务器

WebFlux

通常可以通过 MVC Config 在基于 DispatcherHandler 的设置中运行路由器函数,MVC Config 使用 Spring 配置声明处理请求所需的组件。配置声明了以下基础设施组件来支持函数式端点:

  • RouterFunctionMapping :检测一个或多个 RouterFunction<?> bean ,通过 RouterFunction.andOther 将它们组合在一起,并将请求路由到生成的组合 RouterFunction
  • HandlerFunctionAdapter :简单的适配器,允许 DispatcherHandler 调用映射到请求的 HandlerFunction

前面的组件让函数式端点适合 DispatcherServlet 请求处理生命周期,并且与注解控制器并行运行。这也是 Spring Boot Web starter 如何启用函数式端点的方法。

WebFlux Java 的配置:

@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public RouterFunction<?> routerFunctionA() {
        // ...
    }

    @Bean
    public RouterFunction<?> routerFunctionB() {
        // ...
    }

    // ...

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        // configure message conversion...
    }

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        // configure CORS...
    }

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        // configure view resolution for HTML rendering...
    }
}

过滤处理函数

WebFlux

您可以使用路由函数生成器上的 beforeafterfilter 方法来筛选处理器函数。使用注解,您可以通过使用 @ControllerAdviceServletFilter 或者两者都使用来实现类似的功能。该过滤器将应用于由构建器构建的所有路由。这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。例如,考虑下面的例子:

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople)
            // 添加自定义请求标头的 before 过滤器仅应用于两个 GET 路由
            .before(request -> ServerRequest.from(request) 
                .header("X-RequestHeader", "Value")
                .build()))
        .POST("/person", handler::createPerson))
    // 记录响应的 after 过滤器应用于所有路由,包括嵌套路由
    .after((request, response) -> logResponse(response)) 
    .build();

路由器构建器上的 filter 方法接受 HandlerFilterFunction :接受 ServerRequestHandlerFunction 并返回 ServerResponse 的函数。处理器函数参数表示链中的下一个元素。这通常是路由到的处理器,但如果应用了多个,它也可以是另一个过滤器。

现在我们可以向路由添加一个简单的安全过滤器,假设我们有一个 SecurityManager,它可以确定是否允许某个特定的路径。

SecurityManager securityManager = ...

RouterFunction<ServerResponse> route = route()
    .path("/person", b1 -> b1
        .nest(accept(APPLICATION_JSON), b2 -> b2
            .GET("/{id}", handler::getPerson)
            .GET(handler::listPeople))
        .POST("/person", handler::createPerson))
    .filter((request, next) -> {
        if (securityManager.allowAccessTo(request.path())) {
            return next.handle(request);
        }
        else {
            return ServerResponse.status(UNAUTHORIZED).build();
        }
    })
    .build();

前面的示例演示了调用 next.handle(ServerRequest) 是可选的。我们只在允许访问时运行处理器函数。

除了在路由器函数构建器上使用 filter 方法之外,还可以通过 RouterFunction.filter(HandlerFilterFunction) 对现有的路由器函数应用过滤器。

CORS 对函数式端点的支持是通过专用 CorsFilter 提供的。

URI 链接

WebFlux

UriComponents

Spring MVC 和 Spring WebFlux

UriComponentsBuilder 有助于使用变量从 URI 模板构建 URI

UriComponents uriComponents = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")  
        .queryParam("q", "{q}")  
        .encode() 
        .build(); 

URI uri = uriComponents.expand("Westin", "123").toUri(); 

上面的例子可以合并成一个链,并通过 buildAndExpand 缩短

URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("Westin", "123")
        .toUri();

可以通过直接访问 URI (这意味着编码)进一步缩短

URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");

可以使用完整的 URI 模板进一步缩短它:

URI uri = UriComponentsBuilder
        .fromUriString("https://example.com/hotels/{hotel}?q={q}")
        .build("Westin", "123");

UriBuilder

Spring MVC 和 Spring WebFlux

UriComponentsBuilder 实现 UriBuilder 。您可以使用 UriBuilderFactory 创建一个 UriBuilderUriBuilderFactoryUriBuilder 共同提供了一种可插拔机制,可以基于共享配置(如基本 URL 、编码首选项和其他细节)从 URI 模板构建 URI 。

您可以使用 UriBuilderFactory 配置 RestTemplateWebClient 来自定义 URI 的准备。DefaultUriBuilderFactoryUriBuilderFactory 的默认实现,它在内部使用 UriComponentsBuilder 并公开共享配置选项。

配置 RestTemplate

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

配置 WebClient

// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;

String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

还可以直接使用 DefaultUriBuilderFactory 。它类似于使用 UriComponentsBuilder ,但是它不是静态工厂方法,而是一个包含配置和首选项的实际实例,如下面的示例所示:

String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);

URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
        .queryParam("q", "{q}")
        .build("Westin", "123");

URI 编码

Spring MVC 和 Spring WebFlux

UriComponentsBuilder 在两个级别公开编码选项:

这两个选项都用转义八进制代替非 ASCII 字符和非法字符。但是,第一个选项还将替换出现在 URI 变量中的具有保留含义的字符。

考虑 ; ,它在路径上是合法的,但具有保留含义。第一个选项在 URI 变量中将 ; 替换为 %3B ,但在 URI 模板中不是这样。相比之下,第二种选择永远不会取代 ; ,因为它是路径中的一个合法字符

在大多数情况下,第一个选项可能会给出预期的结果,因为它将 URI 变量视为完全编码的不透明数据,而第二个选项在 URI 变量故意包含保留字符时非常有用。当根本不展开 URI 变量时,第二个选项也很有用,因为这也会编码任何看起来像 URI 变量的内容。

使用第一个选项:

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .encode()
        .buildAndExpand("New York", "foo+bar")
        .toUri();

// Result is "/hotel%20list/New%20York?q=foo%2Bbar"

可以通过直接进入 URI (这意味着编码) 来缩短前面的例子

URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
        .queryParam("q", "{q}")
        .build("New York", "foo+bar");

可以使用完整的 URI 模板进一步缩短它

URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
        .build("New York", "foo+bar");

WebClientRestTemplate 通过 UriBuilderFactory 策略在内部扩展和编码 URI 模板。两者都可以用自定义策略进行配置

String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);

// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);

// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();

DefaultUriBuilderFactory 实现在内部使用 UriComponentsBuilder 来扩展和编码 URI 模板。作为一个工厂,它提供了一个单独的地方来配置编码方法,基于以下编码模式之一 :

  • TEMPLATE_AND_VALUES :使用 UriComponentsBuilder#encode() ,对应于前面列表中的第一个选项,对 URI 模板进行预编码并在展开时对 URI 变量进行严格编码
  • VALUES_ONLY :不对 URI 模板进行编码,而是在将 URI 变量扩展到模板之前,通过 UriUtils#encodeUriVariables 对 URI 变量应用严格的编码
  • URI_COMPONENT :在展开 URI 变量之后,使用 UriComponents#encode() 对应于前面列表中的第二个选项,对 URI 组件值进行编码
  • NONE :没有应用任何编码

出于历史原因和向后兼容性的考虑,将 RestTemplate 设置为 EncodingMode.URI_COMPONENTWebClient 依赖于 DefaultUriBuilderFactory 中的默认值,它在 5.0.x 中从 EncodingMode.URI_COMPONENT 更改为 5.1 中的 EncodingMode.TEMPLATE_AND_VALUES

反应式 Servlet 请求

可以使用 ServletUriComponentsBuilder 创建相对于当前请求的 URI

HttpServletRequest request = ...

// Re-uses host, scheme, port, path and query string...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
        .replaceQueryParam("accountId", "{id}").build()
        .expand("123")
        .encode();

可以相对于上下文路径创建 URI

// Re-uses host, port and context path...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
        .path("/accounts").build()

可以创建相对于 Servlet 的 URI ( 例如, ``/main/*` )

// Re-uses host, port, context path, and Servlet prefix...

ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
        .path("/accounts").build()

从 5.1 开始,ServletUriComponentsBuilder 忽略 Forwarded X-Forwarded-* 标头的信息,这两个标头指定客户端来源的地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃这些标头。

Spring MVC 提供了一种机制来准备指向控制器方法的链接。例如,下面的 MVC 控制器允许链接创建:

@Controller
@RequestMapping("/hotels/{hotel}")
public class BookingController {

    @GetMapping("/bookings/{booking}")
    public ModelAndView getBooking(@PathVariable Long booking) {
        // ...
    }
}

可以通过引用方法的名称来准备一个链接

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

在前面的示例中,我们提供了实际的方法参数值(在本例中为 long 值: 21 ),用作路径变量并插入到 URL 中。此外,我们提供值 42 来填充任何剩余的 URI 变量,例如从类级别请求映射继承的 hotel 变量。如果方法有更多的参数,我们可以为 URL 不需要的参数提供 null 。一般来说,只有 @PathVariable@RequestParam 参数与构造 URL 相关。

还有其他使用 MvcUriComponentsBuilder 的方法。例如,您可以使用类似于通过代理进行模拟测试的技术,以避免按名称引用控制器方法,如下面的示例所示(该示例假定静态导入 MvcUriComponentsBuilder.on ):

UriComponents uriComponents = MvcUriComponentsBuilder
    .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

当控制器方法签名被认为可用于使用 fromMethodCall 创建链接时,它们在设计中受到限制。除了需要适当的参数签名之外,返回类型还有一个技术上的限制(即为链接构建器调用生成一个运行时代理),因此返回类型不能是 final 。特别是,视图名称的通用 String 返回类型在这里不起作用。您应该使用 ModelAndView 或者甚至普通的 Object (带有 String 返回值) 来代替。

前面的示例使用 MvcUriComponentsBuilder 中的静态方法。在内部,它们依靠 ServletUriComponentsBuilder 从当前请求的协议、主机、端口、上下文路径和 servlet 路径准备一个基本 URL。这在大多数情况下都很有效。然而,有时候,它可能是不够的。例如,您可能在请求的上下文之外 (例如准备链接的批处理) ,或者您可能需要插入路径前缀 (例如从请求路径中删除并需要重新插入链接的语言环境前缀) 。

对于这种情况,可以使用接受 UriComponentsBuilder static fromXxx 重载方法来使用基本 URL 。或者,您可以使用基本 URL 创建 MvcUriComponentsBuilder 的实例,然后使用基于实例的 withXxx 方法。例如,下面的清单使用了 withMethodCall

UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);

URI uri = uriComponents.encode().toUri();

从 5.1 开始,MvcUriComponentsBuilder 忽略 Forwarded X-Forwarded-* 标头的信息,这两个标头指定客户端来源的地址。考虑使用 ForwardedHeaderFilter 来提取和使用或丢弃这些标头。

视图中的链接

在 Thymeleaf、 FreeMarker 或 JSP 等视图中,您可以通过引用每个请求映射的隐式或显式分配的名称来构建到带注解的控制器的链接

@RequestMapping("/people/{id}/addresses")
public class PersonAddressController {

    @RequestMapping("/{country}")
    public HttpEntity<PersonAddress> getAddress(@PathVariable String country) { ... }
}

给定前面的控制器,您可以从 JSP 准备一个链接

<%@ taglib uri="http://www.springframework.org/tags" prefix="s" %>
...
<a href="${s:mvcUrl('PAC#getAddress').arg(0,'US').buildAndExpand('123')}">Get Address</a>

前面的示例依赖于 Spring 标记库中声明的 mvcUrl 函数 (即 META-INF/spring.tld ) ,但是定义自己的函数或者为其他模板技术准备类似的函数很容易。

下面是这个方法的原理。在启动时,通过 HandlerMethodMappingNamingStrategy 为每个 @RequestMapping 分配一个默认名称,HandlerMethodMappingNamingStrategy 的默认实现使用类的大写字母和方法名称(例如,ThingController 中的 getThing 方法变为 TC#getThing )。如果存在名称冲突,可以使用 @RequestMapping(name="..") 指定一个明确的名称或实现您自己的 HandlerMethodMappingNamingStrategy

异步请求

Compared to WebFlux

Spring MVC 与 Servlet 3.0 异步请求处理有着广泛的集成:

  • DeferredResultCallable 返回控制器方法中的值,并为单个异步返回值提供基本支持
  • 控制器可以 流化 多个值,包括 SSE原始数据
  • 控制器可以使用反应式客户端并返回反应式类型进行响应处理

DeferredResult

Compared to WebFlux

一旦在 Servlet 容器中 启用 了异步请求处理特性,控制器方法可以用 DeferredResult 包装任何支持的控制器方法返回值,如下面的示例所示:

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
    DeferredResult<String> deferredResult = new DeferredResult<String>();
    // Save the deferredResult somewhere..
    return deferredResult;
}

// From some other thread...
deferredResult.setResult(result);

控制器可以从不同的线程异步生成返回值,例如,响应外部事件( JMS 消息)、调度任务或其他事件。

Callable

Compared to WebFlux

控制器可以用 java.util.concurrent.Callable 包装任何受支持的返回值

@PostMapping
public Callable<String> processUpload(final MultipartFile file) {

    return new Callable<String>() {
        public String call() throws Exception {
            // ...
            return "someView";
        }
    };
}

然后可以通过 配置的 TaskExecutor 运行给定的任务来获得返回值

处理(Processing)

Compared to WebFlux

下面是 Servlet 异步请求处理的一个非常简洁的概述:

  • 可以通过调用 request.startAsync()ServletRequest 置于异步模式。这样做的主要效果是 Servlet (以及任何过滤器) 可以退出,但响应仍然是开放的,以便稍后处理完成
  • request.startAsync() 的调用返回 AsyncContext ,您可以使用它对异步处理进行进一步控制。例如,它提供了 dispatch 方法,这类似于 Servlet API 的转发,只是它允许应用程序在 Servlet 容器线程上继续处理请求
  • ServletRequest 提供对当前 DispatcherType 的访问,您可以使用它来区分处理初始请求、异步分派、转发和其他分派器类型

DeferredResult 处理工作如下:

  • 控制器返回一个 DeferredResult 并将其保存在一些可以访问它的内存队列或列表中
  • Spring MVC 调用 request.startAsync()
  • 同时,DispatcherServlet 和所有配置的过滤器退出请求处理线程,但是响应仍然是打开的
  • 应用程序从某个线程设置 DeferredResult ,Spring MVC 将请求分派回 Servlet 容器
  • 再次调用 DispatcherServlet ,处理将使用异步生成的返回值继续进行

Callable 处理工作如下:

  • 控制器返回一个 Callable
  • Spring MVC 调用 request.startAsync() ,并将 Callable 提交给 TaskExecutor ,以便在单独的线程中处理
  • 同时,DispatcherServlet 和所有过滤器退出 Servlet 容器线程,但是响应仍然是打开的
  • 最终 Callable 生成结果,Spring MVC 将请求发送回 Servlet 容器以完成处理
  • 再次调用 DispatcherServlet ,处理继续,从 Callable 异步生成返回值
异常处理

当您使用 DeferredResult 时,您可以选择是调用 setResult 还是带有异常的 setErrorResult 。在这两种情况下,Spring MVC 都将请求分派回 Servlet 容器以完成处理。然后将其视为控制器方法返回给定的值,或者视为它产生了给定的异常。然后,异常通过常规的异常处理机制(例如,调用 @ExceptionHandler 方法)

当您使用 Callable 时,也会出现类似的处理逻辑,主要的区别是从 Callable 返回结果或由它引发异常

拦截

HandlerInterceptor 实例可以是 AsyncHandlerInterceptor 类型的,用于接收启动异步处理的初始请求的 afterConcurrentHandlingStarted 回调(而不是 postHandleafterCompletion

HandlerInterceptor 实现还可以注册 CallableProcessingInterceptorDeferredResultProcessingInterceptor ,以便更深入地与异步请求的生命周期集成(例如,处理超时事件)。有关更多细节,请参见 AsyncHandlerInterceptor

DeferredResult 提供 onTimeout(Runnable)onCompletion(Runnable) 回调。有关详细信息,请参阅 DeferredResult 的 javadocCallable 可以替代 WebAsyncTask ,后者公开其他方法以进行超时和完成回调。

与 WebFlux 相比

最初构建 Servlet API 是为了通过 Filter-Servlet 链进行单次传递。在 Servlet 3.0 中添加的异步请求处理允许应用程序退出 Filter-Servlet 链,但将响应保持开放状态以便进一步处理。Spring MVC 异步支持就是围绕这种机制构建的。当控制器返回 DeferredResult 时,Filter-Servlet 链被退出,Servlet 容器线程被释放。稍后,当设置 DeferredResult 时,将执行 ASYNC 分派 (对同一 URL) ,在此期间将再次映射控制器,但不调用它,而是使用 DeferredResult 值 (就像控制器返回它一样) 继续处理。

相比之下,Spring WebFlux 既不是构建在 Servlet API 之上,也不需要这样的异步请求处理特性,因为从设计上讲它是异步的。异步处理构建在所有框架契约中,并且在请求处理的所有阶段都得到内在支持。

从编程模型的角度来看,Spring MVC 和 Spring WebFlux 都支持异步和反应式类型作为控制器方法的返回值。Spring MVC 甚至支持流操作,包括反应式背压。但是,单个对响应的写操作仍然是阻塞的(并且是在单独的线程上执行的) ,这与 WebFlux 不同,WebFlux 依赖于非阻塞 I/O ,每次写操作不需要额外的线程。

另一个根本区别是,Spring MVC 不支持控制器方法参数中的异步或反应式类型(例如,@RequestBody@RequestPart 等),也不支持将异步和反应式类型作为模型属性。支持所有这些功能。

HTTP 流(HTTP Streaming)

WebFlux

可以对单个异步返回值使用 DeferredResultCallable 。如果您希望生成多个异步值并将这些值写入响应,那么该怎么办?

对象(Objects )

可以使用 ResponseBodyEmitter 返回值来生成一个对象流,其中每个对象都用 HttpMessageConverter 序列化并写入到响应中

@GetMapping("/events")
public ResponseBodyEmitter handle() {
    ResponseBodyEmitter emitter = new ResponseBodyEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

可以使用 ResponseBodyEmitter 作为 ResponseEntity 的主体,让您自定义响应的状态和标头

emitter 抛出 IOException (例如,如果远程客户端离开)时,应用程序不负责清除连接,也不应调用 emitter.completeemitter.completeWithError 。相反,servlet 容器会自动发起一个 AsyncListener 错误通知,在这个通知中,Spring MVC 进行 completeWithError 调用。这个调用依次对应用程序执行最后一次 ASYNC 分派,在此期间,Spring MVC 调用已配置的异常解析器并完成请求。

SSE

Ssemitter ( ResponseBodyEmitter 的一个子类) 提供对 Server-Sent Events 的支持,其中从服务器发送的事件根据 W3C SSE 规范进行格式化。若要从控制器生成 SSE 流,请返回 SseEmitter

@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {
    SseEmitter emitter = new SseEmitter();
    // Save the emitter somewhere..
    return emitter;
}

// In some other thread
emitter.send("Hello once");

// and again later on
emitter.send("Hello again");

// and done at some point
emitter.complete();

尽管 SSE 是流入浏览器的主要选项,但请注意,Internet Explorer 不支持 Server-Sent Events 。考虑将 Spring 的 WebSocket 消息传递SockJS 备用传输协议(包括 SSE)一起使用,这些传输协议针对广泛的浏览器。

原始数据(Raw Data)

有时,绕过消息转换并直接向响应 OutputStream 流(例如,用于文件下载)是很有用的。你可以使用 StreamingResponseBody 返回值类型来实现

@GetMapping("/download")
public StreamingResponseBody handle() {
    return new StreamingResponseBody() {
        @Override
        public void writeTo(OutputStream outputStream) throws IOException {
            // write...
        }
    };
}

您可以使用 StreamingResponseBody 作为 ResponseEntity 中的主体来自定义响应的状态和标头。

反应式类型

WebFlux

Spring MVC 支持在控制器中使用反应式客户端库(也可以阅读 WebFlux 部分中的 反应式库 )。这包括 spring-webflux 中的 WebClient 和其他一些数据库,比如 Spring Data 反应式数据存储库。在这种情况下,可以方便地从控制器方法返回反应式类型。

反应式返回值处理如下:

  • 单值约定(promise)适用于,类似于使用 DeferredResult 。例如 Mono (Reactor) 或 Single (RxJava)
  • 具有流媒体类型( 例如 application/x-ndjsontext/event-stream )的多值流适用于类似于使用 ResponseBodyEmitterSseEmitter 。例如 Flux (Reactor) 或 Observable (RxJava) 。应用程序也可以返回 Flux<ServerSentEvent>Observable<ServerSentEvent>
  • 使用任何其他媒体类型 (如 application/json ) 的多值流也适用于,与使用 DeferredResult<List<?>> 类似

Spring MVC 通过 spring-core 中的 ReactiveAdapterRegistry 支持 Reactor 和 RxJava ,这使得它可以从多个反应式库中进行适配

对于响应流,支持反应式背压,但是对响应的写操作仍然是阻塞的,并且通过 配置的 TaskExecutor 在单独的线程上运行,以避免阻塞上游源(例如从 WebClient 返回的 Flux )。默认情况下,SimpleAsyncTaskExecutor 用于阻塞写操作,但在加载情况下不适用。如果您计划使用反应式类型流,则应该使用 MVC 配置 来配置任务执行器

断开连接(Disconnects)

WebFlux

当远程客户端离开时,Servlet API 不提供任何通知。因此,在向响应传输数据时,无论是通过 SseEmitter 还是 反应式类型 ,定期发送数据都很重要,因为如果客户端断开连接,写操作就会失败。发送可以采取空的(仅限注释) SSE 事件的形式,也可以采取另一方必须将其解释为心跳并忽略的任何其他数据的形式。

或者,可以考虑使用具有内置心跳机制的 web 消息传递解决方案 ( 例如 通过 WebSocket 的 STOMP 或带 SockJS 的 WebSocket )

配置

Compared to WebFlux

必须在 Servlet 容器级别启用异步请求处理特性。MVC 配置还公开了几个异步请求选项

Servlet 容器

Filter 和 Servlet 声明有一个 asyncSupported 标志,需要将其设置为 true 以启用异步请求处理。另外,应该声明 Filter 映射来处理 ASYNC javax.servlet.DispatchType

在 Java 配置中,当您使用 AbstractAnnotationConfigDispatcherServletInitializer 初始化 Servlet 容器时,这是自动完成的

web.xml 配置中,您可以向 DispatcherServletFilter 声明中添加 <async-supported>true</async-supported> ,并向 Filter 映射中添加 <dispatcher>ASYNC</dispatcher>

Spring MVC

MVC 配置公开了以下与异步请求处理相关的选项:

  • Java 配置:在 WebMvcConfigurer 上使用 configureAsyncSupport 回调
  • XML 命名空间:使用 <mvc:annotation-driven> 下的 <async-support> 标签

您可以配置以下内容:

  • 异步请求的默认超时值(如果没有设置,则取决于底层 Servlet 容器)
  • AsyncTaskExecutor ,用于在使用反应式类型进行流式传输时进行阻塞写操作,并用于执行从控制器方法返回的 Callable 实例。如果您使用反应式类型流或具有返回 Callable 的控制器方法,我们强烈建议配置此属性,因为默认情况下,它是一个 SimpleAsyncTaskExecutor
  • DeferredResultProcessingInterceptor 实现和 CallableProcessingInterceptor 实现

注意,您还可以在 DeferredResultResponseBodyEmitterSseEmitter 上设置默认超时值。对于 Callable ,可以使用 WebAsyncTask 提供超时值

CORS

WebFlux

引言

WebFlux

出于安全原因,浏览器禁止 AJAX 调用当前源之外的资源。例如,你可以把你的银行账户放在一个标签里,把 evil.com 放在另一个标签里。应该禁止来自 evil.com 的脚本使用您的凭证向您的银行 API 发出 AJAX 请求ーー例如从您的帐户中提款!

CORS (Cross-Origin Resource Sharing)是一个由 大多数浏览器 实现的 W3C 规范 ,它允许您指定哪种跨域请求被授权,而不是使用不那么安全和功能强大的基于 IFRAME 或 JSONP 的方式

处理

WebFlux

CORS 规范区分了飞行前(preflight)请求、简单请求和实际请求

Spring MVC HandlerMapping 实现为 CORS 提供内置支持。在成功地将请求映射到处理程序之后,HandlerMapping 实现将检查给定请求和处理程序的 CORS 配置,并采取进一步的操作。飞行前请求被直接处理,而简单和实际的 CORS 请求被拦截、验证,并且拥有所需的 CORS 响应头集。

为了启用跨域请求(也就是说,出现了 Origin 头并且与请求的主机不同),您需要有一些明确声明的 CORS 配置。如果没有找到匹配的 CORS 配置,则拒绝飞行前请求。在简单和实际的 CORS 请求的响应中没有添加 CORS 标头,因此浏览器会拒绝它们。

每个 HandlerMapping 都可以使用基于 URL 模式的 CorsConfiguration 映射单独配置。在大多数情况下,应用程序使用 MVC Java 配置或 XML 名称空间来声明这样的映射,从而将一个全局映射传递给所有 HandlerMappping 实例。

您可以将 HandlerMapping 级别的全局 CORS 配置与更细粒度、处理程序级别的 CORS 配置结合起来。例如,带注解的控制器可以使用类或方法级别的 @CrossOrigin 注解(其他处理器可以实现 CorsConfigurationSource

结合全局和局部配置的规则通常是相加的,例如,所有全局和所有域(origin)。对于那些只能接受单个值的属性,例如 allowCredentialsmaxAge ,局部覆盖全局值。参见 CorsConfiguration#combine(CorsConfiguration)

要想从源代码中了解更多信息或者进行高级定制,请查看以下代码:

  • CorsConfiguration
  • CorsProcessor , DefaultCorsProcessor
  • AbstractHandlerMapping

@CrossOrigin

WebFlux

@CrossOrigin 注解允许带注解的控制器方法上的跨域请求

@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

默认情况下,@CrossOrigin 允许:

  • 所有域
  • 所有标头
  • 控制器方法映射到的所有 HTTP 方法

allowCredentials 在默认情况下是不启用的,因为它建立了一个信任级别,该级别公开敏感的用户特定信息(如 cookies 和 CSRF 标记) ,并且只应在适当的地方使用。如果启用了 allowOrigins ,则必须将其设置为一个或多个特定域(但不是特殊值 * ) ,或者使用 allowOriginPatterns 属性来匹配一组动态域。

maxAge 设置为 30 分钟

@CrossOrigin 在类级别也受到支持,并被所有方法继承:

@CrossOrigin(origins = "https://domain2.com", maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

可以在类级别和方法级别使用 @CrossOrigin

@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {

    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        // ...
    }

    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        // ...
    }
}

全局配置

WebFlux

除了细粒度的控制器方法级别配置之外,您可能还需要定义一些全局 CORS 配置。您可以在任何 HandlerMapping 上单独设置基于 URL 的 CorsConfiguration 映射。但是,大多数应用程序都使用 MVC Java 配置或 MVC XML 名称空间来完成这项工作

默认情况下,全局配置启用以下功能:

  • 所有域
  • 所有标头
  • GETHEADPOST 方法

allowCredentials 在默认情况下是不启用的,因为它建立了一个信任级别,该级别公开敏感的用户特定信息(如 cookies 和 CSRF 标记),并且只应在适当的地方使用。如果启用了 allowOrigins ,则必须将其设置为一个或多个特定域(但不是特殊值 * ),或者使用 allowOriginPatterns 属性来匹配一组动态域。

maxAge 设置为 30 分钟

Java 配置

WebFlux

要在 MVC Java 配置中启用 CORS ,你可以使用 CorsRegistry 回调:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {

        registry.addMapping("/api/**")
            .allowedOrigins("https://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true).maxAge(3600);

        // Add more mappings...
    }
}
XML 配置

要在 XML 名称空间中启用 CORS ,可以使用 <mvc:cors> 标签

<mvc:cors>

    <mvc:mapping path="/api/**"
        allowed-origins="https://domain1.com, https://domain2.com"
        allowed-methods="GET, PUT"
        allowed-headers="header1, header2, header3"
        exposed-headers="header1, header2" allow-credentials="true"
        max-age="123" />

    <mvc:mapping path="/resources/**"
        allowed-origins="https://domain1.com" />

</mvc:cors>

CORS 过滤器

WebFlux

可以通过内置的 CorsFilter 应用 CORS 支持

如果您试图使用带有 Spring Security 的 CorsFilter ,请记住 Spring Security 具有 内置的 CORS 支持

要配置过滤器,请将 CorsConfigurationSource 传递给它的构造函数

CorsConfiguration config = new CorsConfiguration();

// Possibly...
// config.applyPermitDefaultValues()

config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");

UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);

CorsFilter filter = new CorsFilter(source);

Web 安全

WebFlux

Spring Security 项目为保护 web 应用程序免受恶意攻击提供了支持:

HDIV 是另一个与 Spring MVC 集成的 web 安全框架

HTTP 缓存

WebFlux

HTTP 缓存可以显著提高 web 应用程序的性能。HTTP 缓存围绕 Cache-Control 响应标头以及随后的条件请求标头(如 Last-ModifiedETag )进行。Cache-Control 建议私有(例如,浏览器)和公共(例如,代理)缓存如何缓存和重用响应。ETag 标头用于发出条件请求,如果内容没有更改,则可能导致 304(NOT _ modified) 没有主体。ETag 可以被看作是 Last-Modified 标头的更复杂的继承者。

CacheControl

WebFlux

CacheControl 支持配置与 Cache-Control 标头相关的设置,并且在许多地方被接受为参数:

RFC 7234 描述了 Cache-Control 响应标头的所有可能的指令,而 CacheControl 类型采用面向用例的方法,侧重于常见的场景:

// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);

// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();

// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();

WebContentGenerator 还接受一个更简单的 cachePeriod 属性(以秒为单位定义),它的工作方式如下:

  • -1 值不生成 Cache-Control 响应头
  • 0 值通过使用 'Cache-Control: no-store' 指令防止缓存
  • n > 0 值使用 'Cache-Control: max-age=n' 指令将给定响应缓存 n

控制器

WebFlux

控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为资源的 lastModifiedETag 值需要先计算,然后才能与条件请求标头进行比较。控制器可以向 ResponseEntity 添加 ETag 标头和 Cache-Control 设置,如下面的示例所示:

@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {

    Book book = findBook(id);
    String version = book.getVersion();

    return ResponseEntity
            .ok()
            .cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
            .eTag(version) // lastModified is also available
            .body(book);
}

如果与条件请求标头的比较表明内容没有更改,则前面的示例将发送一个包含空的主体的 304(NOT_MODIFIED) 响应。否则,ETagCache-Control 标头将添加到响应中。

也可以在控制器中对条件请求头进行检查:

@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {

    // 特定于应用的计算
    long eTag = ... 

    // 响应被设置为 304(NOT_MODIFIED) ,没有进一步处理
    if (request.checkNotModified(eTag)) {
        return null; 
    }

    // 继续处理请求
    model.addAttribute(...); 
    return "myViewName";
}

有三种变体用于根据 eTag 值、 lastModified 值或两者同时检查条件请求。对于有条件的 GETHEAD 请求,可以将响应设置为 304(NOT_MODIFIED) 。对于条件 POSTPUTDELETE ,您可以改为将响应设置为 412 (PRECONDITION_FAILED) ,以防止并发修改

静态资源

WebFlux

为了获得最佳性能,您应该使用 Cache-Control 和条件响应标头来提供静态资源。请参阅关于配置 静态资源 的部分。

ETag 过滤器

您可以使用 ShallowEtagHeaderFilter 添加根据响应内容计算的 “浅” eTag 值,从而节省带宽而不是 CPU 时间。参见 浅 ETag

视图技术(View)

WebFlux

在 Spring MVC 中使用视图技术是可插拔的。是否决定使用 Thymeleaf、 Groovy Markup Templates、 JSP 或其他技术主要取决于配置更改。

Spring MVC 应用程序的视图位于该应用程序的内部信任边界内。视图可以访问应用上下文中的所有 bean 。因此,不建议在可以通过外部源编辑模板的应用程序中使用 Spring MVC 的模板支持,因为这可能会产生安全问题。

Thymeleaf

WebFlux

FreeMarker

WebFlux

Groovy Markup

Script Views

WebFlux

JSP 和 JSTL

Tiles

RSS 和 Atom

PDF 和 Excel

Jackson

WebFlux

Spring 提供对 Jackson JSON 库的支持。

基于 Jackson 的 JSON MVC 视图

WebFlux

MappingJackson2JsonView 使用 Jackson 库的 ObjectMapper 将响应内容呈现为 JSON。默认情况下,模型映射的所有内容 (框架特定的类除外) 都被编码为 JSON 。对于需要过滤映射内容的情况,可以使用 modelKeys 属性指定要编码的特定模型属性集。您还可以使用 extractValueFromSingleKeyModel 属性将单键模型中的值直接提取和序列化,而不是作为模型属性映射。

您可以根据需要使用 Jackson 提供的注解来定制 JSON 映射。如果需要进一步的控制,可以通过 ObjectMapper 属性注入自定义的 ObjectMapper ,以便为特定类型提供自定义 JSON 序列化器和反序列化器。

基于 Jackson 的 XML 视图

WebFlux

MappingJackson2XmlView 使用 Jackson XML 扩展的 XmlMapper 将响应内容呈现为 XML。如果模型包含多个条目,则应使用 modelKey bean 属性显式设置要序列化的对象。如果模型包含单个条目,则自动对其进行序列化。

您可以根据需要使用 JAXB 或 Jackson 提供的注解来定制 XML 映射。如果需要进一步的控制,可以通过 ObjectMapper 属性注入自定义 XmlMapper ,用于需要为特定类型提供序列化器和反序列化器的自定义 XML。

XML 编组

XSLT Views

MVC 配置

WebFlux

MVC Java 配置和 MVC XML 命名空间提供了适合大多数应用程序的默认配置,并提供了一个配置 API 来对其进行定制。

有关在配置 API 中不可用的更高级自定义,请参见 高级 Java 配置高级 XML 配置

您不需要理解由 MVC Java 配置和 MVC 命名空间创建的底层 bean。如果您想了解更多信息,请参阅 特殊 Bean 类型Web MVC Config

启用 MVC 配置

WebFlux

在 Java 配置中,你可以使用 @EnableWebMvc 注解来启用 MVC 配置

@Configuration
@EnableWebMvc
public class WebConfig {
}

在 XML 配置中,可以使用 <mvc:annotation-driven> 标签来启用 MVC 配置,如下面的示例所示:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven/>

</beans>

前面的示例注册了许多 Spring MVC 基础设施 bean ,并适应了类路径上可用的依赖关系 (例如,JSON、 XML 等的转换器) 。

MVC 配置 API

WebFlux

在 Java 配置中,你可以实现 WebMvcConfigurer 接口

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // Implement configuration methods...
}

在 XML 中,可以检查 <mvc:annotation-driven/> 的属性和子标签。您可以查看 Spring MVC XML 模式 ,或者使用 IDE 的代码提示特性来发现哪些属性和子标签是可用的。

类型转换

WebFlux

默认情况下,将注册各种数字和日期类型的格式器,并支持通过在字段上注解 @NumberFormat@DateTimeFormat 进行自定义

在 Java 配置中注册自定义格式化器和转换器:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        // ...
    }
}

等效的 XML 配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven conversion-service="conversionService"/>

    <bean id="conversionService"
            class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="org.example.MyConverter"/>
            </set>
        </property>
        <property name="formatters">
            <set>
                <bean class="org.example.MyFormatter"/>
                <bean class="org.example.MyAnnotationFormatterFactory"/>
            </set>
        </property>
        <property name="formatterRegistrars">
            <set>
                <bean class="org.example.MyFormatterRegistrar"/>
            </set>
        </property>
    </bean>

</beans>

默认情况下, Spring MVC 在解析和格式化日期值时考虑语言环境。这适用于将日期表示为带有 input 表单字段的字符串的表单。然而,对于“日期”和“时间”表单字段,浏览器使用 HTML 规范中定义的固定格式。对于这种情况,日期和时间格式可以定制如下:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
        registrar.setUseIsoFormat(true);
        registrar.registerFormatters(registry);
    }
}

有关何时使用 FormatterRegistrar 实现的更多信息,请参见 FormatterRegistrar SPIFormattingConversionServiceFactoryBean

验证

WebFlux

默认情况下,如果类路径上存在 Bean 验证 (例如 Hibernate Validator) ,LocalValidatorFactoryBean 将被注册为一个全局验证器,以便与控制器方法参数上的 @ValidValidated 一起使用。

在 Java 配置中,您可以自定义全局 Validator 实例:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public Validator getValidator() {
        // ...
    }
}

XML 中实现相同的配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:mvc="http://www.springframework.org/schema/mvc"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/mvc
        https://www.springframework.org/schema/mvc/spring-mvc.xsd">

    <mvc:annotation-driven validator="globalValidator"/>

</beans>

请注意,您还可以在本地注册 Validator 实现:

@Controller
public class MyController {

    @InitBinder
    protected void initBinder(WebDataBinder binder) {
        binder.addValidators(new FooValidator());
    }
}

如果您需要在某个地方注入 LocalValidatorFactoryBean ,那么创建一个 bean 并将其标记为 @Primary ,以避免与 MVC 配置中声明的 bean 发生冲突。

拦截器(Interceptors)

在 Java 配置中,你可以注册拦截器来应用到传入的请求

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new LocaleChangeInterceptor());
        registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**");
        registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*");
    }
}

在 XML 中实现相同的配置:

<mvc:interceptors>
    <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <mvc:exclude-mapping path="/admin/**"/>
        <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/>
    </mvc:interceptor>
    <mvc:interceptor>
        <mvc:mapping path="/secure/*"/>
        <bean class="org.example.SecurityInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

内容类型(Content Types)

WebFlux

您可以配置 Spring MVC 如何从请求中确定请求的媒体类型 (例如,Accept 标头、 URL 路径扩展、查询参数等) 。

默认情况下,URL 路径扩展首先被检查,用 jsonxmlrssatom 注册为已知扩展 (取决于类路径依赖) 。然后检查 Accept 标头。

考虑将这些默认值更改为只 Accept 标头,如果必须使用基于 URL 的内容类型解析,则考虑使用路径扩展的查询参数策略。有关详细信息,请参阅 后缀匹配后缀匹配以及 RFD

在 Java 配置中,您可以自定义请求的内容类型协商

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
        configurer.mediaType("json", MediaType.APPLICATION_JSON);
        configurer.mediaType("xml", MediaType.APPLICATION_XML);
    }
}

XML 中实现相同的配置

<mvc:annotation-driven content-negotiation-manager="contentNegotiationManager"/>

<bean id="contentNegotiationManager" class="org.springframework.web.accept.ContentNegotiationManagerFactoryBean">
    <property name="mediaTypes">
        <value>
            json=application/json
            xml=application/xml
        </value>
    </property>
</bean>

消息转换器(Message Converters)

WebFlux

可以通过重写 configureMessageConverters() (替换 Spring MVC 创建的默认转换器) 或重写 extendMessageConverters() (定制默认转换器或为默认转换器添加其他转换器) ,在 Java 配置中自定义 HttpMessageConverter

下面的示例使用定制的 ObjectMapper 代替默认的 ObjectMapper 添加 XML 和 Jackson JSON 转换器

@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {

    @Override
    public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
        Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
                .indentOutput(true)
                .dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
                .modulesToInstall(new ParameterNamesModule());
        converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
        converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
    }
}

在前面的示例中,使用 Jackson2ObjectMapperBuilderMappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter 创建一个公共配置,并启用缩进、定制日期格式和 jackson-module-parameter-names 注册,这增加了对访问参数名称的支持 (在 Java 8中添加的一个特性)

这个构建器自定义 Jackson 的默认属性如下:

如果在类路径中检测到以下知名模块,它也会自动注册这些模块:

除了 jackson-dataformat-xml 之外,使用 Jackson XML 支持的缩进还需要 [woodstox-core-asl](https://search.maven.org/#search|gav|1|g%3A"org.codehaus.woodstox" AND a%3A"woodstox-core-asl") 依赖项。

其他有趣的 Jackson 模块也可以使用:

XML 中实现相同的配置:

<mvc:annotation-driven>
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper" ref="objectMapper"/>
        </bean>
        <bean class="org.springframework.http.converter.xml.MappingJackson2XmlHttpMessageConverter">
            <property name="objectMapper" ref="xmlMapper"/>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

<bean id="objectMapper" class="org.springframework.http.converter.json.Jackson2ObjectMapperFactoryBean"
      p:indentOutput="true"
      p:simpleDateFormat="yyyy-MM-dd"
      p:modulesToInstall="com.fasterxml.jackson.module.paramnames.ParameterNamesModule"/>

<bean id="xmlMapper" parent="objectMapper" p:createXmlMapper="true"/>

视图控制器(View Controllers)

WebFlux

这是定义 ParameterizableViewController 的快捷方式,该控制器在调用时立即转发到视图。如果在视图生成响应之前没有 Java 控制器逻辑可以运行,那么可以在静态情况下使用它。

下面的 Java 配置示例将请求 / 转发到一个名为 home 的视图:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addViewControllers(ViewControllerRegistry registry) {
        registry.addViewController("/").setViewName("home");
    }
}

等效的 XML 配置:

<mvc:view-controller path="/" view-name="home"/>

如果将 @RequestMapping 方法映射到任何 HTTP 方法的 URL ,则不能使用视图控制器来处理相同的 URL 。这是因为通过 URL 与带注解的控制器的匹配被认为是端点所有权的足够有力的指示,因此可以向客户端发送 405 (METHOD_NOT_ALLOWED) 、415 (UNSUPPORTED_MEDIA_TYPE) 或类似的响应来帮助调试。因此,建议避免在带注解的控制器和视图控制器之间拆分 URL 处理

视图解析器(View Resolvers)

WebFlux

MVC 配置简化了视图解析器的注册

下面的 Java 配置示例通过使用 JSP 和 Jackson 作为 JSON 渲染的默认视图来配置内容协商视图解析:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.jsp();
    }
}

下面的示例演示如何在 XML 中实现相同的配置:

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:jsp/>
</mvc:view-resolvers>

但是请注意,FreeMarker、 Tiles、 Groovy Markup 和脚本模板也需要配置底层视图技术。

MVC 名称空间提供了专门的标签,以下示例适用于 FreeMarker :

<mvc:view-resolvers>
    <mvc:content-negotiation>
        <mvc:default-views>
            <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
        </mvc:default-views>
    </mvc:content-negotiation>
    <mvc:freemarker cache="false"/>
</mvc:view-resolvers>

<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>

在 Java 配置中,您可以添加相应的 Configurer bean

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.enableContentNegotiation(new MappingJackson2JsonView());
        registry.freeMarker().cache(false);
    }

    @Bean
    public FreeMarkerConfigurer freeMarkerConfigurer() {
        FreeMarkerConfigurer configurer = new FreeMarkerConfigurer();
        configurer.setTemplateLoaderPath("/freemarker");
        return configurer;
    }
}

静态资源(Static Resources)

WebFlux

此选项提供了一种便捷方式来为基于资源的位置列表中的静态资源提供服务。

在下一个示例中,给定一个以 /resources 开头的请求,相对路径用于查找和服务 web 应用程序根目录下或 /static 目录下的类路径中相对于 /public 的静态资源。这些资源将在一年后到期,以确保最大限度地使用浏览器缓存,并减少浏览器发出的 HTTP 请求。Last-Modified 信息是从 Resource#lastModified 中推导出来的,这样 HTTP 条件请求就可以使用 "Last-Modified" 标头来支持。

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
            .addResourceLocations("/public", "classpath:/static/")
            .setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
    }
}

在 XML 中实现相同的配置:

<mvc:resources mapping="/resources/**"
    location="/public, classpath:/static/"
    cache-period="31556926" />

参阅 静态资源的 HTTP 缓存支持

资源处理器还支持一系列 ResourceResolver 实现和 ResourceTransformer 实现,您可以使用它们创建一个工具链来处理优化的资源。

对于基于从内容、固定应用程序版本或其他内容计算的 MD5 哈希的版本资源 URL ,可以使用 VersionResourceResolverContentVersionStrategy (MD5 哈希) 是一个很好的选择ーー对于一些明显的例外,例如与模块加载程序一起使用的 JavaScript 资源。

在 Java 配置中使用 VersionResourceResolver :

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
                .addResourceLocations("/public/")
                .resourceChain(true)
                .addResolver(new VersionResourceResolver().addContentVersionStrategy("/**"));
    }
}

在 XML 中实现相同的配置:

<mvc:resources mapping="/resources/**" location="/public/">
    <mvc:resource-chain resource-cache="true">
        <mvc:resolvers>
            <mvc:version-resolver>
                <mvc:content-version-strategy patterns="/**"/>
            </mvc:version-resolver>
        </mvc:resolvers>
    </mvc:resource-chain>
</mvc:resources>

然后,您可以使用 ResourceUrlProvider 重写 URL ,并应用解析器和转换器的完整链ーー例如,插入版本。MVC 配置提供了一个 ResourceUrlProvider bean,因此可以将它注入到其他服务器中。您还可以使用 ResourceUrlEncodingFilter 为 Thymeleaf、 JSP、 FreeMarker 和其他使用 URL 标记 (依赖于 HttpServletResponse#encodeURL ) 的组件实现重写透明化。

注意,当同时使用 EncodedResourceResolver (例如,用于提供 gzipped 或 brotli 编码的资源) 和 VersionResourceResolver 时,必须按照以下顺序注册它们。这确保基于内容的版本始终基于未编码的文件可靠地计算。

WebJarsResourceResolver 也支持 WebJars ,它在 rg.webjars:webjars-locator-core 库出现在类路径上时自动注册。解析器可以重写 URL 以包含 jar 的版本,也可以匹配没有版本的传入 URL ,例如,从 /jquery/jquery.min.js/jquery/1.2.0/jquery.min.js

默认 Servlet (Default Servlet)

Spring MVC 允许将 DispatcherServlet 映射到 / (从而重写容器的默认 Servlet 的映射) ,同时仍然允许静态资源请求由容器的默认 Servlet 处理。它使用 /** 的 URL 映射到 DefaultServletHttpRequestHandler ,相对于其他 URL 映射,优先级最低。

此处理器将所有请求转发到默认 Servlet 。因此,它必须按照所有其他 URL HandlerMappings 的顺序保持最后一个。如果使用 <mvc:annotation-driven> ,就会出现这种情况。或者,如果您设置了自己的定制 HandlerMapping 实例,请确保将其 order 属性设置为比 DefaultServletHttpRequestHandler 更低的值,后者是 Integer.MAX_VALUE

使用默认设置启用该功能:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable();
    }
}

XML 中实现相同的配置:

<mvc:default-servlet-handler/>

重写 / 的 Servlet 映射需要注意的是,必须按名称而不是路径检索默认 Servlet 的 RequestDispatcherDefaultServletHttpRequestHandler 试图在启动时使用大多数主要 Servlet 容器 (包括 Tomcat、 Jetty、 GlassFish、 JBoss、 Resin、 WebLogic 和 WebSphere) 的已知名称列表自动检测容器的默认 Servlet 。如果默认的 Servlet 已经用不同的名称自定义配置,或者如果在默认的 Servlet 名称未知的地方使用了不同的 Servlet 容器,那么您必须显式地提供默认的 Servlet 名称,如下面的示例所示:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
        configurer.enable("myCustomDefaultServlet");
    }
}

在 XML 中实现相同的配置:

<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>

路径匹配(Path Matching)

WebFlux

您可以自定义与 URL 的路径匹配和处理相关的选项。有关各个选项的详细信息,请参阅 PathMatchConfigurer javadoc

在 Java 配置中自定义路径匹配:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setPatternParser(new PathPatternParser())
            .addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
    }

    private PathPatternParser patternParser() {
        // ...
    }
}

在 XML 中实现相同的配置:

<mvc:annotation-driven>
    <mvc:path-matching
        trailing-slash="false"
        path-helper="pathHelper"
        path-matcher="pathMatcher"/>
</mvc:annotation-driven>

<bean id="pathHelper" class="org.example.app.MyPathHelper"/>
<bean id="pathMatcher" class="org.example.app.MyPathMatcher"/>

高级 Java 配置

WebFlux

@EnableWebMvc 导入 DelegatingWebMvcConfiguration :

  • 为 Spring MVC 应用程序提供缺省的 Spring 配置
  • 检测并委托 WebMvcConfigurer 实现来自定义该配置

对于高级模式,您可以删除 @EnableWebMvc 并直接从 DelegatingWebMvcConfiguration 进行扩展,而不是实现 WebMvcConfigurer ,如下面的示例所示:

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    // ...
}

您可以在 WebConfig 中保留现有的方法,但是现在也可以覆盖来自基类的 bean 声明,并且您仍然可以在类路径上拥有任意数量的其他 WebMvcConfigurer 实现

高级 XML 配置

MVC 名称空间没有高级模式。如果你需要在一个 bean 上定制一个你不能改变的属性,你可以使用 Spring ApplicationContextBeanPostProcessor 生命周期钩子,如下面的例子所示:

@Component
public class MyPostProcessor implements BeanPostProcessor {

    public Object postProcessBeforeInitialization(Object bean, String name) throws BeansException {
        // ...
    }
}

注意,您需要将 MyPostProcessor 声明为 bean,可以在 XML 中显式地声明,也可以通过 <component-scan/> 声明来检测它。

HTTP/2

WebFlux

支持 HTTP/2 需要 Servlet 4 容器,Spring Framework 5 与 Servlet API 4 兼容。从编程模型的角度来看,应用程序不需要做任何特定的事情。但是,还有一些与服务器配置相关的注意事项。有关详细信息,请参阅 HTTP/2 wiki 页面

Servlet API 确实公开了一个与 HTTP/2 相关的构造。你可以使用 javax.servlet.http.PushBuilder 主动地将资源推送到客户端,并且它作为 @RequestMapping 方法的方法参数得到支持。

posted @ 2022-06-09 21:27  流星<。)#)))≦  阅读(155)  评论(0编辑  收藏  举报