20200319 Spring Web MVC 1

1. Spring Web MVC

Spring Web MVC是基于Servlet API构建的原始Web框架,并且从一开始就已包含在Spring框架中。正式名称“Spring Web MVC”来自其源模块(spring-webmvc)的名称,但它通常被称为“Spring MVC”。

与Spring Web MVC并行,Spring Framework 5.0引入了一个响应式Web框架,其名称“Spring WebFlux”也基于其源模块(spring-webflux)。本节介绍Spring Web MVC。在下一节 介绍Spring WebFlux。

有关基线信息以及与Servlet容器和Java EE版本范围的兼容性,请参见Spring Framework Wiki

1.1。DispatcherServlet

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

DispatcherServlet与任何Servlet一样,都需要使用 Java 配置或在 web.xml 中根据Servlet规范声明和映射。 相反,DispatcherServlet使用Spring配置发现请求映射,视图解析,异常处理等等所需的委托组件。

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

public class MyWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletCxt) {

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

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

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

org.springframework.web.SpringServletContainerInitializer

以下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容器。FilterServlet 声明在Spring配置中检测到并注册到Servlet容器中。有关更多详细信息,请参见 Spring Boot文档

1.1.1。上下文层次

DispatcherServlet 需要 WebApplicationContextApplicationContext的扩展)为其自身的配置。 WebApplicationContext具有指向ServletContext和与其关联的Servlet的链接。 它还绑定到ServletContext,以便应用程序可以在RequestContextUtils上使用静态方法来查找WebApplicationContext(如果需要获取它们)。

对于许多应用来说,拥有单个WebApplicationContext简单且足够。也可能有一个上下文层次结构,其中一个根WebApplicationContext 在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有其自己的子WebApplicationContext配置。有关上下文层次结构功能的更多信息,请参见的 ApplicationContext其他功能

WebApplicationContext通常包含需要在多个Servlet实例之间共享的基础结构Bean,例如数据存储库和业务服务。 这些Bean是有效继承的,并且可以在Servlet特定的子WebApplicationContext中重写(即重新声明),该子WebApplicationContext通常包含给定Servlet本地的Bean。 下图显示了这种关系:

mvc context hierarchy

org.springframework.web.servlet.support.AbstractDispatcherServletInitializer#onStartup

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

public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

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

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

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

如果不需要应用程序上下文层次结构,则应用程序可以使 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>

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

1.1.2。特殊 Bean 类型

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

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

Bean 类型 说明
HandlerMapping 将请求与拦截器列表一起映射到处理程序,以进行预处理和后期处理。映射基于某些标准,具体标准因HandlerMapping 实现而异。两个主要HandlerMapping实现是RequestMappingHandlerMapping (支持带@RequestMapping注释的方法)和SimpleUrlHandlerMapping (将URI路径模式显式注册到处理程序)。
HandlerAdapter 帮助DispatcherServlet调用映射到请求的处理程序,而不管实际如何调用该处理程序。例如,调用带注释的控制器需要解析注释。HandlerAdapter 的主要目的是保护DispatcherServlet这些细节。
HandlerExceptionResolver 解决异常的策略,可能将它们映射到处理程序,HTML错误视图或其他目标。请参阅例外
ViewResolver 解析从处理程序返回的实际基于字符串的基于逻辑的视图名称,以实际的视图呈现给响应。 请参阅 View ResolutionView Technologies
LocaleResolverLocaleContextResolver 解决一个客户正在使用的Locale并且可能是其时区的问题,以便能够提供国际化的视图。请参阅语言环境
ThemeResolver 解决Web应用程序可以使用的主题,例如,提供个性化的布局。请参阅主题
MultipartResolver 借助一些多部分解析库来解析 multi-part 请求的抽象(例如,浏览器表单文件上传)。请参见Multipart Resolver
FlashMapManager 存储和检索“输入”和“输出” FlashMap,它们通常用于通过重定向将属性从一个请求传递到另一个请求。请参见Flash属性

默认配置在 DispatcherServlet.properties

调用:

org.springframework.web.servlet.DispatcherServlet#doDispatch

1.1.3。Web MVC配置

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

C:/Users/80953/.ideaLibSources/spring-webmvc-5.2.3.RELEASE-sources.jar!/org/springframework/web/servlet/DispatcherServlet.java:283

在大多数情况下,MVC Config 是最佳起点。它使用Java或XML声明所需的bean,并提供更高级别的配置回调API对其进行自定义。

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

1.1.4。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

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

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

1.1.5. 请求处理过程

DispatcherServlet 处理请求过程如下:

  • 搜索WebApplicationContext并将其绑定为请求中的属性,控制器和流程中的其他元素可以使用该属性。 默认情况下,它绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE键下。
  • 语言环境解析器绑定到请求,以使流程中的元素解析在处理请求(呈现视图,准备数据等)时要使用的语言环境。 如果不需要语言环境解析,则不需要语言环境解析器。
  • 主题解析器绑定到请求,以使诸如视图之类的元素确定要使用的主题。如果不使用主题,则可以将其忽略。
  • 如果指定多部分文件解析器,则将检查请求中是否有多部分。如果找到多部分,则将请求包装在MultipartHttpServletRequest中,以供流程中的其他元素进一步处理。有关多部分处理的更多信息,请参见Multipart Resolver
  • 搜索适当的处理程序。 如果找到处理程序,则执行与处理程序(预处理器,后处理器和控制器)关联的执行链,以准备模型或渲染。 另外,对于带注释的控制器,可以呈现响应(在HandlerAdapter中),而不是返回视图。
  • 如果返回模型,则呈现视图。如果没有返回任何模型(可能是由于预处理器或后处理器拦截了该请求,可能出于安全原因),则不会呈现任何视图,因为该请求可能已经被满足。

org.springframework.web.servlet.DispatcherServlet#doService

WebApplicationContext中声明的HandlerExceptionResolver Bean用于解决在请求处理期间引发的异常。 这些异常解析器允许定制逻辑以解决异常。 有关更多详细信息,请参见 Exceptions

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

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

表1. DispatcherServlet初始化参数

参数 说明
contextClass 实现ConfigurableWebApplicationContext的类,由该Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext
contextConfigLocation 传递给上下文实例的字符串(由contextClass指定),以指示可以在哪里找到上下文。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。对于具有两次定义的bean的多个上下文位置,以最新位置为准。
namespace WebApplicationContext的命名空间。默认为[servlet-name]-servlet
throwExceptionIfNoHandlerFound 在找不到请求的处理程序时是否抛出NoHandlerFoundException。然后可以使用HandlerExceptionResolver(例如,使用 @ExceptionHandler控制器方法)捕获该异常并将其作为其他任何异常进行处理。默认情况下,它设置为false,在这种情况下,DispatcherServlet将响应状态设置为404NOT_FOUND),而不会引发异常。请注意,如果还配置了默认servlet处理,则始终将未解决的请求转发到默认servlet,并且永远不会引发404

org.springframework.web.servlet.HttpServletBean#init

1.1.6。拦截

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

  • preHandle(..):在执行实际的处理程序之前
  • postHandle(..):执行处理程序后
  • afterCompletion(..):完成完整的请求后

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

有关如何配置拦截器的示例,请参见MVC配置部分中的拦截器。 您还可以通过使用各个HandlerMapping实现上的设置器直接注册它们。

请注意,对于@ResponseBodyResponseEntity方法,postHandle的用处不大,在HandlerAdapter内和postHandle之前,将其写入并提交响应。 这意味着对响应进行任何更改为时已晚,例如添加额外的标头。 对于这种情况,您可以实现ResponseBodyAdvice并将其声明为Controller Advice Bean或直接在RequestMappingHandlerAdapter上对其进行配置。

1.1.7。异常

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

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

HandlerExceptionResolver 描述
SimpleMappingExceptionResolver 异常类名称和错误视图名称之间的映射。对于在浏览器应用程序中呈现错误页面很有用。
DefaultHandlerExceptionResolver 解决Spring MVC引发的异常,并将其映射到HTTP状态代码。另请参见替代ResponseEntityExceptionHandlerREST API异常
ResponseStatusExceptionResolver 使用@ResponseStatus注释解决异常,并根据注释中的值将其映射到HTTP状态代码。
ExceptionHandlerExceptionResolver 通过调用@Controller@ControllerAdvice类中的@ExceptionHandler方法来解决异常。 请参见@ExceptionHandler方法
解析器链

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

org.springframework.web.servlet.DispatcherServlet#initHandlerExceptionResolvers

如果没有自定义 HandlerExceptionResolver bean,则异常解析器链中是在 DispatcherServlet.properties 中定义的:

org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
   org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
   org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver

HandlerExceptionResolver的约定指定它可以返回:

  • 指向错误视图的ModelAndView

  • 如果在解析程序中处理了异常,则为空的ModelAndView

  • 如果该异常仍未解决,则为null,以供后续解析器尝试;如果该异常仍在末尾,则允许将其冒泡到Servlet容器。

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

容器错误页面

如果所有HandlerExceptionResolver仍然无法解决异常,该异常可以传播,或者如果响应状态设置为错误状态(即4xx5xx),则Servlet容器可以在HTML中呈现默认错误页面。要自定义容器的默认错误页面,可以在web.xml中声明错误页面映射。以下示例显示了如何执行此操作:

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

给定前面的示例,当异常冒出气泡或响应具有错误状态时,Servlet容器在容器内向配置的URL(例如/error)进行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中创建错误页面映射的方法。但是,您可以同时使用WebApplicationInitializerweb.xml

1.1.8。视图解析

Spring MVC定义了ViewResolverView接口,可让您在浏览器中呈现模型,而无需将您与特定的视图技术联系在一起。ViewResolver 提供视图名称和实际视图之间的映射。View在移交给特定的视图技术之前,先解决数据准备问题。

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

表3. ViewResolver实现

ViewResolver 描述
AbstractCachingViewResolver AbstractCachingViewResolver的子类缓存它们解析的视图实例。 缓存可以提高某些视图技术的性能。 您可以通过将cache属性设置为false来关闭缓存。 此外,如果必须在运行时刷新某个视图(例如,当修改FreeMarker模板时),则可以使用removeFromCache(String viewName,Locale loc)方法。
XmlViewResolver ViewResolver的实现接受一个用XML编写的配置文件,该配置文件的DTD与Spring的XML bean工厂相同。默认配置文件是 /WEB-INF/views.xml
ResourceBundleViewResolver ViewResolver的实现,该实现使用ResourceBundle中的bean定义(由包基本名称指定)。对于应该解析的每个视图,它将属性的值[viewname].(class)用作视图类,并将属性的值[viewname].url用作视图URL。您可以在View Technologies一章中找到示例 。
UrlBasedViewResolver ViewResolver接口的简单实现会影响将逻辑视图名称直接解析为URL而没有显式映射定义。如果您的逻辑名称以直接的方式与视图资源的名称匹配,而无需任意映射,则这是适当的。
InternalResourceViewResolver UrlBasedViewResolver的方便子类,支持InternalResourceView(实际上,Servlet和JSP)和子类,如JstlViewTilesView。您可以使用setViewClass(..)来为此解析器生成的所有视图指定视图类。有关UrlBasedViewResolver 详细信息,请参见javadoc。
FreeMarkerViewResolver UrlBasedViewResolver的方便的子类支持FreeMarkerView和他们的自定义子类。
ContentNegotiatingViewResolver 基于请求文件名或Accept头解析视图的ViewResolver接口的实现。请参阅内容协商

初始化:

org.springframework.web.servlet.DispatcherServlet#initViewResolvers

DispatcherServlet.properties 中定义的:

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

调用:

org.springframework.web.servlet.DispatcherServlet#resolveViewName

处理方式(Handling)

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

ViewResolver的协定指定它可以返回null来指示找不到该视图。 但是,对于JSP和InternalResourceViewResolver,找出JSP是否存在的唯一方法是通过RequestDispatcher进行调度。 因此,您必须始终将InternalResourceViewResolver配置为在视图解析器的总体顺序中排在最后。

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

重定向

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

org.springframework.web.servlet.view.UrlBasedViewResolver#createView

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

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

转发

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

内容协商

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

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

有关配置详细信息,请参见MVC Config下的查看解析器

1.1.9. Locale

正如Spring Web MVC框架所做的那样,Spring体系结构的大多数部分都支持国际化。DispatcherServlet使您可以使用客户端的语言环境自动解析消息。这是通过LocaleResolver对象完成的。

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

除了自动的语言环境解析之外,您还可以在处理程序映射上附加一个拦截器(有关处理程序映射拦截器的更多信息,请参见拦截),以在特定情况下(例如,基于请求中的参数)更改语言环境。

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

org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
时区

除了获取客户的语言环境外,了解其时区通常也很有用。 LocaleContextResolver接口提供了LocaleResolver的扩展,该扩展使解析程序可以提供更丰富的LocaleContext,其中可能包含时区信息。

如果可用,则可以使用RequestContext.getTimeZone()方法获取用户的TimeZone。 通过Spring的ConversionService注册的任何日期/时间转换器和格式器对象都会自动使用时区信息。

标头解析器

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

Cookie解析器

该语言环境解析器检查客户端上可能存在的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的属性:

表4. CookieLocaleResolver属性

属性 默认 描述
cookieName 类名+ LOCALE Cookie的名称
cookieMaxAge Servlet容器默认 Cookie在客户端上保留的最长时间。如果指定-1,则cookie将不会保留。它仅在客户端关闭浏览器之前可用。
cookiePath / 将Cookie的可见性限制在您网站的特定部分。当cookiePath被指定,cookie是仅对于该路径和它下面的路径可见。
会话解析器

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

请注意,与外部会话管理机制(例如Spring Session项目)没有直接关系。 该SessionLocaleResolver针对当前HttpServletRequest评估并修改相应的HttpSession属性。

区域拦截器

您可以通过将LocaleChangeInterceptor添加到HandlerMapping定义之一来启用语言环境更改。 它在请求中检测到一个参数,并相应地更改语言环境,在调度程序的应用程序上下文中在LocaleResolver上调用setLocale方法。 下一个示例显示,对所有包含名为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>

1.1.10. Themes

您可以应用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:theme定制标记来执行此操作,该标记与spring:message标记非常相似。 以下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寻找一个名为themeResolver的bean,以找出要使用的ThemeResolver实现。 主题解析器的工作方式与LocaleResolver几乎相同。 它可以检测用于特定请求的主题,还可以更改请求的主题。 下表描述了Spring提供的主题解析器:

表5. ThemeResolver实现

描述
FixedThemeResolver 选择通过使用defaultThemeName属性设置的固定主题。
SessionThemeResolver 主题在用户的HTTP会话中维护。每个会话只需设置一次,但在会话之间不会保留。
CookieThemeResolver 所选主题存储在客户端的cookie中。

Spring还提供了一个ThemeChangeInterceptor允许使用简单的请求参数对每个请求进行主题更改的功能。

org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver

1.1.11。Multipart 解析器

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

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

Apache Commons FileUpload

要使用Apache Commons FileUpload,可以配置名称为multipartResolverCommonsMultipartResolver类型的Bean。 您还需要commons-fileupload作为对类路径的依赖。

Servlet 3.0

需要通过Servlet容器配置启用Servlet 3.0 multipart 解析。 为此:

  • 在Java中,在Servlet注册上设置MultipartConfigElement

  • web.xml中,将 <multipart-config> 部分添加到Servlet声明中。

以下示例显示如何在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配置到位后,您可以添加名称为multipartResolverStandardServletMultipartResolver类型的Bean。

1.1.12. Logging

Spring MVC中的DEBUG级别的日志被设计为紧凑,最少且人性化的。 它侧重于一遍又一遍有用的高价值信息,而其他信息仅在调试特定问题时才有用。

TRACE级别的日志记录通常遵循与DEBUG相同的原理(例如,也不应是消防水带),但可用于调试任何问题。 此外,某些日志消息在TRACEDEBUG上可能显示不同级别的详细信息。

良好的日志记录来自使用日志的经验。如果您发现任何不符合既定目标的东西,请告诉我们。

敏感数据

调试和跟踪日志记录可能会记录敏感信息。 这就是默认情况下屏蔽请求参数和标头,并且必须通过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");
    }

}

1.2. Filters

spring-web模块提供了一些有用的过滤器:

1.2.1。表单数据

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

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

1.2.2。转发的表头

当请求通过代理(例如负载平衡器)进行处理时,主机,端口和 scheme 可能会更改,这使得从客户端角度创建指向正确的主机,端口和 scheme 的链接带来了挑战。

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

ForwardedHeaderFilter是一个Servlet过滤器,它根据Forwarded标头修改请求的主机,端口和方案,然后删除这些标头。

对于转发的标头,出于安全方面的考虑,因为应用程序无法知道标头是由代理添加的,还是由恶意客户端添加的。这就是为什么应配置位于信任边界的代理以删除来自外部的不受信任的Forwarded标头的原因。您也可以用removeOnly=true配置ForwardedHeaderFilter,在这种情况下,它会删除但不使用标题。

1.2.3. Shallow ETag

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

此策略可节省网络带宽,但不会节省CPU,因为必须为每个请求计算完整响应。 如前所述,控制器级别的其他策略可以避免计算。 请参阅HTTP缓存

该过滤器具有writeWeakETag参数,该参数将过滤器配置为写入弱ETag,类似于以下内容:W/"02a2d595e6ed9a0b24f027f2b63b134d6"(在RFC 7232第2.3节中定义)。

1.2.4。CORS

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

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

1.3。带注释的控制器

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

@Controller
public class HelloController {

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

在前面的示例中,该方法接受Model并以String的形式返回视图名称,但是还存在许多其他选项,本章稍后将对其进行说明。

spring.io 上的指南和教程使用本节中描述的基于注释的编程模型。

1.3.1。声明

您可以使用Servlet的WebApplicationContext中的标准Spring bean定义来定义控制器bean。 @Controller构造型允许自动检测,与Spring对在类路径中检测@Component类并为其自动注册Bean定义的常规支持保持一致。 它还充当带注释类的构造型,表明其作为Web组件的作用。

要启用对此类@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 Context回调的接口(例如InitializingBean*Aware等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />可以更改为<tx:annotation-driven proxy-target-class="true"/>,使用@EnableTransactionManagement可以更改为@EnableTransactionManagement(proxyTargetClass = true)

1.3.2。请求映射

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

@RequestMapping还有HTTP方法特定的快捷方式:

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

快捷方式是提供的“自定义注释”,因为可以说,大多数控制器方法应该映射到特定的HTTP方法,而不是使用@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) {
        // ...
    }
}
URI模式

您可以使用以下全局模式和通配符来映射请求:

  • ? 匹配一个字符
  • * 匹配路径段中的零个或多个字符
  • ** 匹配零个或多个路径段

您还可以声明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")),但是如果名称相同并且您的代码是通过调试信息或-parameters Java 8上的编译器标志进行编译的,则可以省去该细节。

语法{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 version, @PathVariable String ext) {
    // ...
}

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

Spring MVC使用spring-core中的PathMatcher协定和AntPathMatcher实现来进行URI路径匹配。

模式比较

当多个模式与URL匹配时,必须将它们进行比较以找到最佳匹配。这是通过使用AntPathMatcher.getPatternComparator(String path)来完成的,该工具查找更具体的模式。

如果模式的URI变量(计数为1),单通配符(计数为1)和双通配符(计数为2)的数量较少,则模式的含义不太明确。 给定相等的分数,则选择更长的模式。 给定相同的分数和长度,将选择URI变量比通配符更多的模式。

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

有关完整的详细信息,请参见AntPatternComparatorAntPathMatcher,也请记住,你可以自定义PathMatcher的实现。请参阅配置部分中的路径匹配

后缀匹配

默认情况下,Spring MVC执行.*后缀模式匹配,以便映射到/person的控制器也隐式映射到/person.*。 然后,文件扩展名用于解释请求的内容类型以用于响应(即,代替Accept标头),例如/person.pdf/person.xml等。

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

随着时间的流逝,文件扩展名的使用已经以各种方式证明是有问题的。 当使用URI变量,路径参数和URI编码进行覆盖时,可能会引起歧义。 关于基于URL的授权和安全性的推理(请参阅下一部分以了解更多详细信息)也变得更加困难。

若要完全禁用文件扩展名,必须设置以下两项:

基于URL的内容协商仍然有用(例如,在浏览器中键入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

Consumable Media Types

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

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

使用consumes属性按内容类型缩小映射。

consumes属性还支持否定表达式-例如,!text/plain表示text/plain以外的任何内容类型。

您可以在类级别声明共享consumes属性。但是,与大多数其他请求映射属性不同,在类级别使用时,方法级别的consumes属性将覆盖而不是扩展类级别的声明。

MediaType提供常用媒体类型(例如APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE)的常量 。

Producible Media Types

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

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

使用produces属性按内容类型缩小映射。

媒体类型可以指定字符集。支持否定表达式-例如, !text/plain表示除 text/plain 之外的任何内容类型。

您可以在类级别声明共享produces属性。但是,与大多数其他请求映射属性不同,在类级别使用时,方法级别的produces属性将覆盖而不是扩展类级别的声明。

MediaType提供常用媒体类型(例如APPLICATION_JSON_VALUEAPPLICATION_XML_VALUE)的常量 。

Parameters, headers

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

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

测试myParam是否相等myValue

您还可以将其与请求标头条件一起使用,如以下示例所示:

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

您可以匹配Content-TypeAccept与标头条件匹配,但最好使用 消耗生产

HTTP HEAD, OPTIONS

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

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

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

对于@RequestMapping不使用HTTP方法声明的情况,Allow标头设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS。控制器方法应该总是声明支持HTTP方法(例如,通过使用HTTP方法具体变体: @GetMapping@PostMapping,及其他)。

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

自定义注释

Spring MVC支持将组合注释用于请求映射。 这些注解本身使用@RequestMapping进行元注解,并且旨在以更狭窄,更具体的用途重新声明@RequestMapping属性的子集(或全部)。

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

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

明确注册

您可以以编程方式注册处理程序方法,这些方法可用于动态注册或高级案例,例如同一处理程序在不同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); 
    }
}

1.3.3。处理程序方法(Handler Methods)

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

方法参数

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

支持JDK 8的java.util.Optional作为方法参数,并与具有必需属性(例如,@RequestParam@RequestHeader等)的注释结合在一起,等效于required = false

控制器方法参数 描述
WebRequestNativeWebRequest 通用访问请求参数以及请求和会话属性,而无需直接使用Servlet API。
javax.servlet.ServletRequestjavax.servlet.ServletResponse 选择任何特定的请求或响应类型-例如ServletRequestHttpServletRequest或Spring的MultipartRequestMultipartHttpServletRequest
javax.servlet.http.HttpSession 强制会话的存在。 结果,这样的参数永远不会为空。 请注意,会话访问不是线程安全的。 如果允许多个请求并发访问会话,请考虑将RequestMappingHandlerAdapter实例的syncnizeOnSession标志设置为true
javax.servlet.http.PushBuilder 用于程序化 HTTP/2 资源推送的Servlet 4.0推送构建器API。请注意,根据Servlet规范,如果客户端不支持HTTP/2功能,则注入的PushBuilder实例可以为null
java.security.Principal 当前经过身份验证的用户-可能是特定的Principal实现类(如果已知)。
HttpMethod 请求的HTTP方法。
java.util.Locale 当前的请求语言环境,由最可用的特定LocaleResolver(实际上是配置的LocaleResolverLocaleContextResolver)确定。
java.util.TimeZone + java.time.ZoneId 与当前请求关联的时区,由LocaleContextResolver决定。
java.io.InputStreamjava.io.Reader 用于访问Servlet API公开的原始请求正文。
java.io.OutputStreamjava.io.Writer 用于访问Servlet API公开的原始响应正文。
@PathVariable 用于访问URI模板变量。请参阅URI模式
@MatrixVariable 用于访问URI路径段中的名称/值对。请参阅矩阵变量
@RequestParam 用于访问Servlet请求参数,包括多部分文件。参数值将转换为声明的方法参数类型。参见@RequestParam以及Multipart
请注意,对于简单参数值,使用@RequestParam是可选的。请参阅此表末尾的“其他任何参数”。
@RequestHeader 用于访问请求标头。标头值将转换为声明的方法参数类型。请参阅@RequestHeader
@CookieValue 用于访问cookie。Cookies值将转换为声明的方法参数类型。请参阅@CookieValue
@RequestBody 用于访问HTTP请求正文。正文内容通过使用HttpMessageConverter实现转换为声明的方法参数类型。请参阅@RequestBody
HttpEntity 用于访问请求标头和正文。正文用HttpMessageConverter转换。参见HttpEntity
@RequestPart 要访问multipart/form-data请求中的文件,使用HttpMessageConverter来转换文件的主体。参见多部分
java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap 用于访问HTML控制器中使用的模型,并作为视图渲染的一部分公开给模板。
RedirectAttributes 指定在重定向的情况下使用的属性(即追加到查询字符串中),并指定要临时存储的属性,直到重定向后的请求为止。请参阅重定向属性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

为控制器上的参数赋值

org.springframework.web.method.support.InvocableHandlerMethod#invokeForRequest

默认支持的参数类型解析器:

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#getDefaultArgumentResolvers

返回值

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

控制器方法返回值 描述
@ResponseBody 返回值通过HttpMessageConverter实现进行转换并写入响应。请参阅@ResponseBody
HttpEntity<B>ResponseEntity<B> 指定完整响应(包括HTTP标头和正文)的返回值将通过HttpMessageConverter实现进行转换,并写入响应中。参见ResponseEntity
HttpHeaders 用于返回带标头没有主体内容的响应。
String 一个视图名称,将通过ViewResolver实现来解析,并与隐式模型一起使用-通过命令对象和@ModelAttribute方法确定。处理程序方法还可以通过声明Model参数来以编程方式丰富模型(请参见Explicit Registrations)。
View 用于与隐式模型一起渲染的View实例,该隐式模型是通过命令对象和@ModelAttribute方法确定的。 处理程序方法还可以通过声明Model参数来以编程方式丰富模型(请参阅Explicit Registrations)。
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的替代方法,其中包含收集到List的多值流(例如FluxObservable)。

对于流场景(例如,text/event-stream, application/json+stream),使用SseEmitterResponseBodyEmitter代替,其中在Spring MVC管理的线程上执行ServletOutputStream阻塞I/O,并在完成时施加反压每个写入。

请参阅异步请求响应类型
任何其他返回值 如果返回值不是由BeanUtils#isSimpleProperty确定的简单类型,且它与该表中的任何较早值都不匹配且为Stringvoid的任何返回值均被视为视图名称(通过RequestToViewNameTranslator选择默认视图名称) 。 简单类型的值仍然无法解析。
类型转换

如果参数声明为String以外的形式,则某些表示基于String的请求输入的带注释的控制器方法参数(例如@RequestParam@RequestHeader@PathVariable@MatrixVariable@CookieValue)可能需要类型转换。

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

矩阵变量

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配置中,您需要通过 路径匹配 设置具有removeSemicolonContent = falseUrlPathHelper。 在MVC XML名称空间中,可以设置<mvc:annotation-driven enable-matrix-variables = "true" />

代码示例:

@Configuration
@EnableWebMvc
public class MyWebMvcConfigurer implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer.setUrlPathHelper(urlPathHelper());
    }

    @Bean
    public UrlPathHelper urlPathHelper() {
        UrlPathHelper urlPathHelper = new UrlPathHelper();
        urlPathHelper.setRemoveSemicolonContent(false);
        return urlPathHelper;
    }
}
@RequestParam

您可以使用@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注解的required标志设置为 false或通过使用java.util.Optional包装器声明参数来指定方法参数是可选的。

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

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

如果将@RequestParam注释声明为Map<String, String>MultiValueMap<String, String>,而未在注释中指定参数名称,则将使用每个给定参数名称的请求参数值填充映射。

请注意,@RequestParam的使用是可选的(例如,设置其属性)。 默认情况下,任何简单值类型的参数(由BeanUtils#isSimpleProperty确定)并且没有被任何其他参数解析器解析,就如同使用@RequestParam进行了注释一样。

@RequestHeader

您可以使用@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注解上的使用MapMultiValueMapHttpHeaders参数,则Map被填充有所有标头值。

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

@CookieValue

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

考虑带有以下cookie的请求:

JSESSIONID = 415A4AC178C59DACE0B2C9CA727CDD84

以下示例显示如何获取cookie值:

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

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

@ModelAttribute

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

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

上面的Pet实例解析如下:

  • 来自模型(如果已使用Model添加)。
  • 来自HTTP会话,通过使用@SessionAttributes
  • 来自URI路径变量,通过Converter传递(请参见下一个示例)。
  • 来自默认构造函数的调用。
  • 通过调用具有与Servlet请求参数匹配的参数的“主要构造函数”。 参数名称是通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名称确定的。

尽管通常使用模型来用属性填充模型,但另一种替代方法是依赖于Converter <String,T>与URI路径变量约定结合使用。 在以下示例中,模型属性名称account与URI路径变量account匹配,并且通过将String帐号传递给已注册的Converter <String,Account>来加载Account:

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

获取模型属性实例后,将应用数据绑定。 WebDataBinder类将Servlet请求参数名称(查询参数和表单字段)与目标Object上的字段名称进行匹配。 必要时在应用类型转换后填充匹配字段。 有关数据绑定(和验证)的更多信息,请参见验证。 有关自定义数据绑定的更多信息,请参见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验证Spring验证)在数据绑定之后自动应用验证 。以下示例显示了如何执行此操作:

@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

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

以下示例使用@SessionAttributes注释:

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

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

@Controller
@SessionAttributes("pet") 
public class EditPetForm {

    // ...

    @PostMapping("/pets/{id}")
    public String handle(Pet pet, BindingResult errors, SessionStatus status) {
        if (errors.hasErrors) {
            // ...
        }
        
        status.setComplete(); 
        // ...
        
    }
}

代码示例:

@SessionAttributes("myIdx")
@RestController
public class MyController {

    @ModelAttribute("myIdx")
    public String myId() {
        return UUID.randomUUID().toString();
    }

    @GetMapping("myId2")
    public void myId2(HttpSession session, SessionStatus status) {
        System.out.println("1:" + session.getAttribute("myIdx"));
    }
}
@SessionAttribute

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

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

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

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

@RequestAttribute

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

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

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

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

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

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

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

将数据传递到重定向目标的另一种方法是使用 flash 属性。与其他重定向属性不同,flash 属性保存在HTTP会话中(因此不会出现在URL中)。有关更多信息,请参见Flash属性

代码示例

@Controller
public class MyController {

    @PostMapping("/files/{path}")
    public String upload(@PathVariable("path")String path) {
        // ...
        System.out.println("study.hwj.myspringmvc.MyController.upload");
        return "redirect:/files/my/{path}";
    }

    @RequestMapping("/files/my/{path}")
    public void mupload(@PathVariable("path") String path) {
        // ...
        System.out.println("study.hwj.myspringmvc.MyController.mupload");

        System.out.println(path);
        return ;
    }
}
Flash属性

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

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

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

带注释的控制器通常不需要直接使用FlashMap。而是, @RequestMapping方法可以接受RedirectAttributes类型的参数,并使用它为重定向方案添加Flash属性。通过添加的Flash属性将 RedirectAttributes自动传播到“输出” FlashMap。同样,重定向后,来自“输入”FlashMap的属性会自动添加到 服务于目标URL的控制器的Model

将请求与Flash属性匹配

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

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

这不能完全消除并发问题的可能性,但是可以通过重定向URL中已经可用的信息大大减少并发问题。因此,我们建议您主要将Flash属性用于重定向方案。

Multipart

启用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>,但未在注解中指定参数名称,则将使用每个给定参数名称的多部分文件来填充Map

使用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,该异常将转换为400BAD_REQUEST)响应。 或者,您可以通过ErrorsBindingResult参数在控制器内本地处理验证错误,如以下示例所示:

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

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

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

您可以使用MVC Config的“ 消息转换器”选项来配置或自定义消息转换。

您可以将@RequestBodyjavax.validation.Valid或Spring的@Validated注释结合使用,这两种注释都会导致应用标准Bean验证。 默认情况下,验证错误会导致MethodArgumentNotValidException,该异常将转换为400BAD_REQUEST)响应。 或者,您可以通过ErrorsBindingResult参数在控制器内本地处理验证错误,如以下示例所示:

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

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

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

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

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

@ResponseBody在类级别也受支持,在这种情况下,它由所有控制器方法继承。这就是@RestController的效果,无非就是带有@Controller@ResponseBody标记的元注释。

您可以配合@ResponseBody使用反应类型。有关更多详细信息,请参见异步请求响应类型

您可以使用MVC Config的“消息转换器”选项来配置或自定义消息转换。

您可以将@ResponseBody方法与JSON序列化视图结合使用。有关详细信息,请参见Jackson JSON

ResponseEntity

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

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

Spring MVC支持使用单值反应类型 来为主体生成异步ResponseEntity和/或单值和多值反应类型。

Jackson JSON

Spring提供了对Jackson JSON库的支持。

JSON视图

Spring MVC为Jackson的序列化视图提供了内置支持 ,该视图仅可呈现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允许一组视图类,但是每个控制器方法只能指定一个。如果需要激活多个视图,则可以使用复合接口。

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

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

参考:

@JsonView的使用

1.3.4. Model

您可以使用@ModelAttribute注释:

  • @RequestMapping方法中的方法参数上,可从模型创建或访问对象,并将其通过WebDataBinder绑定到请求。
  • 作为@Controller@ControllerAdvice类中的方法级注释,可在任何@RequestMapping方法调用之前帮助初始化模型。
  • @RequestMapping方法上标记其返回值的是模型属性。

本节讨论@ModelAttribute方法-前面列表中的第二项。 控制器可以具有任意数量的@ModelAttribute方法。 所有此类方法均在同一控制器中的@RequestMapping方法之前调用。 @ModelAttribute方法也可以通过@ControllerAdvice在控制器之间共享。 有关更多详细信息,请参见 Controller Advice 部分。

@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 类型选择默认名称,如javadoc中针对Conventions的解释。您始终可以使用重载addAttribute方法或通过nameon 的属性@ModelAttribute(用于返回值)来分配显式名称。

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

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

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
    // ...
    return account;
}

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#invokeHandlerMethod
先调用			modelFactory.initModel(webRequest, mavContainer, invocableMethod);
后调用			invocableMethod.invokeAndHandle(webRequest, mavContainer);


@ModelAttribute方法的调用栈:

pet:16, MyController (study.hwj.mymvc)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
doInvoke:190, InvocableHandlerMethod (org.springframework.web.method.support)
invokeForRequest:138, InvocableHandlerMethod (org.springframework.web.method.support)
invokeModelAttributeMethods:137, ModelFactory (org.springframework.web.method.annotation)
initModel:106, ModelFactory (org.springframework.web.method.annotation)
invokeHandlerMethod:865, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handleInternal:793, RequestMappingHandlerAdapter (org.springframework.web.servlet.mvc.method.annotation)
handle:87, AbstractHandlerMethodAdapter (org.springframework.web.servlet.mvc.method)
doDispatch:1040, DispatcherServlet (org.springframework.web.servlet)
doService:943, DispatcherServlet (org.springframework.web.servlet)
processRequest:1006, FrameworkServlet (org.springframework.web.servlet)
doGet:898, FrameworkServlet (org.springframework.web.servlet)
service:634, HttpServlet (javax.servlet.http)
service:883, FrameworkServlet (org.springframework.web.servlet)
service:741, HttpServlet (javax.servlet.http)
internalDoFilter:231, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
doFilter:53, WsFilter (org.apache.tomcat.websocket.server)
internalDoFilter:193, ApplicationFilterChain (org.apache.catalina.core)
doFilter:166, ApplicationFilterChain (org.apache.catalina.core)
invoke:202, StandardWrapperValve (org.apache.catalina.core)
invoke:96, StandardContextValve (org.apache.catalina.core)
invoke:541, AuthenticatorBase (org.apache.catalina.authenticator)
invoke:139, StandardHostValve (org.apache.catalina.core)
invoke:92, ErrorReportValve (org.apache.catalina.valves)
invoke:688, AbstractAccessLogValve (org.apache.catalina.valves)
invoke:74, StandardEngineValve (org.apache.catalina.core)
service:343, CoyoteAdapter (org.apache.catalina.connector)
service:367, Http11Processor (org.apache.coyote.http11)
process:65, AbstractProcessorLight (org.apache.coyote)
process:868, AbstractProtocol$ConnectionHandler (org.apache.coyote)
doRun:1639, NioEndpoint$SocketProcessor (org.apache.tomcat.util.net)
run:49, SocketProcessorBase (org.apache.tomcat.util.net)
runWorker:1149, ThreadPoolExecutor (java.util.concurrent)
run:624, ThreadPoolExecutor$Worker (java.util.concurrent)
run:61, TaskThread$WrappingRunnable (org.apache.tomcat.util.threads)
run:748, Thread (java.lang)

1.3.5。 DataBinder

@Controller@ControllerAdvice类可以具有用于初始化WebDataBinder实例的@InitBinder方法,而这些方法又可以:

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

@InitBinder方法可以注册特定于控制器的java.bean.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"));
    }

    // ...
}

1.3.6. Exceptions

@Controller@ControllerAdvice类可以具有处理控制器方法异常的@ExceptionHandler方法,如以下示例所示:

@Controller
public class SimpleController {

    // ...

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

该异常可以与正在传播的顶级异常(即,直接抛出的IOException异常)匹配,也可以与顶级包装异常(例如,IOException包装在IllegalStateException内)的直接原因匹配 。

对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。当多个异常方法匹配时,根源异常匹配通常比原因异常匹配更可取。更具体地,ExceptionDepthComparator用于根据异常从引发的异常类型的深度对异常进行排序。

另外,注释声明可以缩小异常类型以使其匹配,如以下示例所示:

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

您甚至可以使用带有非常通用的参数签名的特定异常类型的列表,如以下示例所示:

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

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

在前面显示的IOException变体中,通常使用实际的FileSystemExceptionRemoteException实例作为参数来调用该方法,因为这两个实例均从IOException扩展。 但是,如果任何此类匹配异常都在本身是IOException的包装器异常中传播,则传入的异常实例就是该包装器异常。

handle(Exception)变体中,行为甚至更简单。 在包装方案中,总是使用包装程序异常来调用此方法,在这种情况下,实际匹配的异常可以通过ex.getCause()找到。 仅当将它们作为顶级异常抛出时,传入的异常才是实际的FileSystemExceptionRemoteException实例。

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

在多@ControllerAdvice安排中,我们建议在以相应顺序优先的@ControllerAdvice上声明您的主根异常映射。 尽管根异常匹配优先于原因,但它是在给定控制器或@ControllerAdvice类的方法之间定义的。 这意味着,优先级较高的@ControllerAdvice Bean上的原因匹配优先于优先级较低的@ControllerAdvice Bean上的任何匹配(例如,根)。

最后但并非最不重要的一点是,@ExceptionHandler方法实现可以选择以不处理给定异常实例的方式将其以原始形式重新抛出。在仅对根级别匹配或无法静态确定的特定上下文中的匹配感兴趣的情况下,这很有用。重新抛出的异常会在其余的解决方案链中传播,就像给定的@ExceptionHandler方法最初不会匹配一样。

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

方法参数

@ExceptionHandler 方法支持以下参数:

方法参数 描述
异常类型 用于访问引发的异常。
HandlerMethod 用于访问引发异常的控制器方法。
WebRequestNativeWebRequest 对请求参数以及请求和会话属性的常规访问,而无需直接使用Servlet API。
javax.servlet.ServletRequestjavax.servlet.ServletResponse 选择任何特定的请求或响应类型(例如ServletRequestHttpServletRequest或Spring的MultipartRequestMultipartHttpServletRequest)。
javax.servlet.http.HttpSession 强制会话的存在。结果,这个参数永远不会为null
请注意,会话访问不是线程安全的。 如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter实例的syncnizeOnSession标志设置为true
java.security.Principal 当前经过身份验证的用户-可能是特定的Principal实现类(如果已知)。
HttpMethod 请求的HTTP方法。
java.util.Locale 当前的请求语言设置,由最具体的可用LocaleResolver确定,实际上是配置的LocaleResolverLocaleContextResolver
java.util.TimeZonejava.time.ZoneId 与当前请求关联的时区,由LocaleContextResolver决定。
java.io.OutputStreamjava.io.Writer 用于访问原始响应主体(由Servlet API公开)。
java.util.Maporg.springframework.ui.Modelorg.springframework.ui.ModelMap 用于访问模型以进行错误响应。永远是空的。
RedirectAttributes 指定在重定向的情况下要使用的属性(要附加到查询字符串中),并指定要临时存储的属性,直到重定向后的请求为止。请参阅重定向属性Flash属性
@SessionAttribute 对于访问任何会话属性,与通过类级别@SessionAttributes声明存储在会话中的模型属性不同。 有关更多详细信息,请参见@SessionAttribute
@RequestAttribute 用于访问请求属性。请参阅@RequestAttribute以获取更多详细信息。
返回值

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

返回值 描述
@ResponseBody 返回值通过HttpMessageConverter实例转换并写入响应。请参阅@ResponseBody
HttpEntityResponseEntity 返回值指定完整的响应(包括HTTP标头和正文)将通过HttpMessageConverter实例转换并写入响应。参见ResponseEntity
String 一个视图名称,将通过实现来解析,并与隐式模型一起使用-通过命令对象和@ModelAttribute方法确定。该处理程序方法还可以通过声明一个Model 变量(如前所述)以编程方式丰富模型。
View 用于与隐式模型一起渲染的View实例,该隐式模型是通过命令对象和@ModelAttribute方法确定的。 该处理程序方法还可以通过声明模型变量(如前所述)以编程方式丰富模型。
java.util.Maporg.springframework.ui.Model 要添加到隐式模型的属性,其视图名称是通过RequestToViewNameTranslator隐式确定的。
@ModelAttribute 要添加到模型的属性,其视图名称通过RequestToViewNameTranslator隐式确定。请注意,@ModelAttribute是可选的。请参见表末尾的“其他任何返回值”。
ModelAndView 对象 要使用的视图和模型属性,以及响应状态(可选)。
void 如果返回类型为void(或者返回值为null)的方法还具有ServletResponseOutputStream参数或@ResponseStatus批注,则认为该方法已完全处理了响应。 如果控制器进行了肯定的ETaglastModified时间戳检查,也是如此(请参阅 Controllers 以获取详细信息)。

如果以上所有条件都不成立,则对于REST控制器,void 返回类型也可以指示“无响应正文”,对于HTML控制器,则默认选择视图名称。
任何其他返回值 如果返回值与上述任何一个都不匹配并且不是简单类型(由BeanUtils#isSimpleProperty确定 ),则默认情况下会将其视为要添加到模型的模型属性。如果它是简单类型,则仍无法解决。
REST API 异常

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

在响应主体中实现具有错误详细信息的全局异常处理的应用应考虑继承 ResponseEntityExceptionHandler,它提供对Spring MVC引发的异常的处理,并提供用于自定义响应主体的钩子。要使用此功能,请创建ResponseEntityExceptionHandler的子类 ,用@ControllerAdvice进行注释,覆盖必要的方法,然后将其声明为Spring bean。

代码示例

@ControllerAdvice
public class MyResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
    @ExceptionHandler(Exception.class)
    public ResponseEntity<Object> handleMyException(Exception ex, WebRequest request) throws Exception {
        System.out.println("study.hwj.mymvc.MyResponseEntityExceptionHandler.handleMyException");
        return new ResponseEntity<>(ex.getMessage(), HttpStatus.OK);
    }
}

继承ResponseEntityExceptionHandler时,异常类型如果是org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler#handleException上指定的具体类型时,调用父类的方法;如果不继承,则都调用实现类方法。

1.3.7. Controller Advice

通常,@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 {}

前面示例中的选择器在运行时进行评估,如果广泛使用,可能会对性能产生负面影响。 有关更多详细信息,请参见@ControllerAdvice javadoc。

1.4. Functional Endpoints

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

1.4.1。总览

在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将自动检测到它,如运行服务器中所述。

1.4.2. HandlerFunction

ServerRequestServerResponse是不可变的接口,它们提供JDK 8友好的HTTP请求和响应访问,包括标头,正文,方法和状态代码。

ServerRequest

ServerRequest提供对HTTP方法,URI,标头和查询参数的访问,而通过body方法提供对主体的访问。

下面的示例将请求正文提取为String:

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

以下示例将主体提取到List,其中Person对象从序列化形式(例如JSON或XML)解码:

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

以下示例显示如何访问参数:

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

ServerResponse提供对HTTP响应的访问,并且由于它是不可变的,因此可以使用一种build方法来创建它。您可以使用构建器来设置响应状态,添加响应标题或提供正文。以下示例使用JSON内容创建200OK)响应:

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

下面的示例演示如何构建一个具有Location标头且没有正文的201CREATED)响应:

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

我们可以将处理程序函数编写为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是一个处理程序函数,该函数返回由path变量id标识的一个人。我们从存储库中检索到该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();
        }
    }

}
Validation

函数式端点(functional endpoint)可以使用Spring的验证工具将验证应用于请求主体。例如,给定Person的定制的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

1.4.3。 RouterFunction

RouterFunction用于将请求路由到相应的HandlerFunction。通常,您不是自己编写路由器功能,而是在RouterFunctions实用工具类上使用一种方法来创建一个。 RouterFunctions.route()(无参数)为您提供了流畅的生成器来创建路由器功能,而RouterFunctions.route(RequestPredicate, HandlerFunction)提供了直接的方式来创建路由器。

通常,建议使用route()构建器,因为它为典型的映射方案提供了便捷的捷径,而无需发现静态导入。例如,路由器功能构建器提供了GET(String, HandlerFunction)为GET请求创建映射的方法,POST请求对应POST(String, HandlerFunction)

除了基于HTTP方法的映射外,路由构建器还提供了一种在映射到请求时引入其他谓词的方法。 对于每个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"));

您可以使用以下命令组合多个请求谓词:

  • RequestPredicate.and(RequestPredicate) -两者都必须匹配。
  • RequestPredicate.or(RequestPredicate) -两者任一匹配即可。

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

路由

路由器功能按顺序评估:如果第一个路由不匹配,则评估第二个路由,依此类推。因此,在通用路由之前声明更具体的路由是有意义的。请注意,此行为不同于基于注释的编程模型,在该模型中,将自动选择“最特定”的控制器方法。

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

  • 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()
	// 带有与JSON匹配的Accept标头的 GET /person/{id} 被路由到PersonHandler.getPerson
    .GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) 
    // 具有与JSON匹配的Accept标头的 GET /person 路由到PersonHandler.listPeople
    .GET("/person", accept(APPLICATION_JSON), handler::listPeople) 
    // 没有其他断言的 POST /person 映射到PersonHandler.createPerson
    .POST("/person", handler::createPerson) 
    // otherRoute 是在其他位置创建的路由器功能,并将其添加到构建的路由中。
    .add(otherRoute) 
    .build();
嵌套路由

一组路由器功能通常具有共享断言,例如共享路径。 在上面的示例中,共享断言将是与/person匹配的路径断言,其中三个路径使用该断言。 使用注释时,您可以通过使用映射到/person的类型级别的@RequestMapping注释来删除此重复项。 在WebMvc.fn中,可以通过路由器功能构建器上的path方法共享路径谓词。 例如,可以通过以下方式使用嵌套路由来改进上面示例的最后几行:

RouterFunction<ServerResponse> route = route()
	// 	请注意,path的第二个参数是使用路由器构建器的消费者
    .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();

1.4.4。运行服务器

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

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

前面的组件使函数式端点适合于DispatcherServlet请求处理生命周期,并且(可能)与带注释的控制器(如果已声明)并排运行。 这也是Spring Boot Web启动程序如何启用函数式端点的方式。

以下示例显示了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...
    }
}

1.4.5。过滤处理程序功能(Filtering Handler Functions)

您可以通过使用路由功能构建器上的beforeafterfilter方法来过滤处理程序函数。 使用注释,可以通过使用@ControllerAdvice和/或ServletFilter来实现类似的功能。 该过滤器将应用于构建器构建的所有路由。 这意味着在嵌套路由中定义的过滤器不适用于“顶级”路由。 例如,考虑以下示例:

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。 handler函数参数代表链中的下一个元素。 这通常是路由到的处理程序,但是如果应用了多个,它也可以是另一个过滤器。

现在,我们可以为路由添加一个简单的安全过滤器,假设我们有一个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)将过滤器应用于现有路由器功能。

通过专用的CorsWebFilter提供对功能端点的CORS支持。

1.5。URI链接

本节描述了Spring框架中可用于URI的各种选项。

1.5.1。UriComponents

UriComponentsBuilder 有助于从带有变量的URI模板构建URI,如以下示例所示:

UriComponents uriComponents = UriComponentsBuilder
		// 带有URI模板的静态工厂方法。
        .fromUriString("https://example.com/hotels/{hotel}")  
        // 添加或替换URI组件。
        .queryParam("q", "{q}")  
        // 请求对URI模板和URI变量进行编码。
        .encode() 
        // 建立一个UriComponents。
        .build(); 

// 展开变量并获得URI。
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");

1.5.2。UriBuilder

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");

1.5.3。URI编码

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

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

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

在大多数情况下,第一个选项可能会产生预期的结果,因为它将URI变量视为要完全编码的不透明数据,而选项2仅在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.fromPath("/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模板进行编码,而是在将其扩展到模板之前通过UriUtils#encodeUriUriVariables对URI变量进行严格编码。
  • URI_COMPONENTS:使用UriComponents#encode(),对应于先前列表中的第二个选项,在扩展URI变量后使用 URI 组件值进行编码。
  • NONE:未应用编码。

由于历史原因和向后兼容性,将RestTemplate设置为EncodingMode.URI_COMPONENTSWebClient依赖于DefaultUriBuilderFactory中的默认值,该默认值已从5.0.x中的EncodingMode.URI_COMPONENTS更改为5.1中的EncodingMode.TEMPLATE_AND_VALUES

1.5.4。响应式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忽略来自ForwardedX-Forwarded-*标头的信息,该标头指定了客户端起源的地址。考虑使用 ForwardedHeaderFilter来提取和使用或丢弃此类标头。

1.5.5。链接到控制器

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的链接创建时,其设计受到限制。 除了需要适当的参数签名外,返回类型还存在技术限制(即,为链接生成器调用生成运行时代理),因此返回类型不得为最终值。 特别是,视图名称的通用String返回类型在这里不起作用。 您应该改用ModelAndView甚至普通对象(具有String返回值)。

前面的示例在MvcUriComponentsBuilder中使用静态方法。 在内部,它们依靠ServletUriComponentsBuilder从当前请求的方案,主机,端口,上下文路径和Servlet路径准备基本URL。 在大多数情况下,此方法效果很好。 但是,有时可能不足。 例如,您可能不在请求的上下文之内(例如,准备链接的批处理过程),或者您可能需要插入路径前缀(例如从请求路径中删除并需要重新插入链接的语言环境前缀)。

在这种情况下,可以使用静态的fromXxx重载方法,这些方法接受UriComponentsBuilder以使用基本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忽略来自ForwardedX-Forwarded-*标头的信息,该标头指定了客户端起源的地址。考虑使用 ForwardedHeaderFilter提取和使用或丢弃此类标头。

1.5.6。视图中的链接

在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分配一个默认名称,该名称的默认实现使用该类的大写字母和方法名称(例如,ThingController中的getThing方法变为“ TC#getThing”)。 如果存在名称冲突,则可以使用@RequestMapping(name="..")分配一个明确的名称,或实现自己的HandlerMethodMappingNamingStrategy

1.6。异步请求

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

1.6.1. DeferredResult

一旦 在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消息),计划任务或其他事件。

1.6.2。 Callable

控制器可以使用java.util.concurrent.Callable来包装任何受支持的返回值,如以下示例所示:

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

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

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

1.6.3. Processing

这是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中异步产生的返回值恢复处理。

有关更多背景知识,您还可以阅读在Spring MVC 3.2中引入了异步请求处理支持的博客文章

异常处理

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

当您使用时Callable,会发生类似的处理逻辑,主要区别是从Callable中返回了结果,或者引发了异常。

拦截

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

HandlerInterceptor实现也可以注册CallableProcessingInterceptorDeferredResultProcessingInterceptor,以与异步请求的生命周期更深入地集成(例如,处理超时事件)。请参阅 AsyncHandlerInterceptor 以获取更多详细信息。

DeferredResult提供onTimeout(Runnable)onCompletion(Runnable)回调。 有关更多详细信息,请参见DeferredResultjavadoc。 可以用Callable代替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等),也没有对异步和响应类型作为模型属性的任何显式支持。 Spring WebFlux确实支持所有这些。

1.6.4. HTTP Streaming

您可以将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();

您还可以在ResponseEntity中将ResponseBodyEmitter用作主体,以自定义响应的状态和标头。

emitter引发IOException异常时(例如,如果远程客户端消失了),应用程序不负责清理连接,因此不应调用emitter.completeemitter.completeWithError。取而代之的是,Servlet容器自动启动 AsyncListener错误通知,Spring MVC在其中进行completeWithError调用。反过来,此调用对应用程序执行最后一次ASYNC调度,在此期间,Spring MVC调用已配置的异常解析器并完成请求。

SSE(Server Sent Events)

SseEmitterResponseBodyEmitter的子类)提供对服务器发送事件的支持 ,其中从服务器发送的事件根据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不支持服务器发送事件。考虑将Spring的 WebSocket消息与针对各种浏览器的SockJS后备传输(包括SSE)一起使用。

另请参阅上一节以获取有关异常处理的注释。

原始数据

有时,绕过消息转换并直接流式传输到响应OutputStream(例如,用于文件下载)很有用 。您可以使用StreamingResponseBody 返回值类型来执行此操作,如以下示例所示:

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

您可以将StreamingResponseBody用作ResponseEntity的主体,以自定义响应的状态和标头。

1.6.5。反应类型

Spring MVC支持在控制器中使用反应式客户端库(另请参阅WebFlux部分中的反应式库)。 这包括来自spring-webfluxWebClient和其他资源,例如Spring Data反应数据存储库。 在这种情况下,能够从控制器方法返回反应类型是很方便的。

反应性返回值的处理方式如下:

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

Spring MVC通过spring-coreReactiveAdapterRegistry支持Reactor和RxJava,这使其可以适应多个反应式库。

为了流式传输到响应,支持响应式背压,但响应的写仍处于阻塞状态,并通过配置TaskExecutor在单独的线程上执行,以避免阻塞上游源(例如,从WebClient返回的Flux)。 默认情况下,SimpleAsyncTaskExecutor用于阻止写操作,但是在负载下不适合。 如果计划使用响应类型进行流传输,则应使用MVC配置来配置任务执行程序。

1.6.6。断开连接

当远程客户端离开时,Servlet API不提供任何通知。因此,在通过SseEmitter反应性类型流式传输到响应时,定期发送数据非常重要,因为如果客户端断开连接,写入将失败。发送可以采取空(comment-only)SSE事件或另一端必须将其解释为心跳和忽略的任何其他数据的形式。

或者,考虑使用具有内置心跳机制的Web消息传递解决方案(例如,基于 WebSocket的STOMP或具有SockJS的 WebSocket )。

1.6.7. Configuration

必须在Servlet容器级别启用异步请求处理功能。MVC配置还为异步请求提供了多个选项。

Servlet容器

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

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

web.xml配置中,可以将<async-supported>true</async-supported>添加到DispatcherServletFilter声明中,并添加<dispatcher>ASYNC</dispatcher>来过滤映射。

Spring MVC

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

  • Java配置:在WebMvcConfigurer上使用configureAsyncSupport回调。
  • XML名称空间:使用<mvc:annotation-driven>下的<async-support>元素。

您可以配置以下内容:

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

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

1.7。CORS

Spring MVC使您可以处理CORS(跨域资源共享)。本节介绍如何执行此操作。

1.7.1。介绍

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

跨域资源共享(CORS)是由大多数浏览器实现的W3C规范,可让您指定授权哪种类型的跨域请求,而不是使用基于IFRAMEJSONP的安全性较低且功能较弱的变通办法。

1.7.2. Processing

CORS规范区分飞行前(preflight),简单和实际请求。要了解CORS的工作原理,您可以阅读本文以及其他内容,或者参阅规范以获取更多详细信息。

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)。

组合全局和本地配置的规则通常是相加的,例如,所有全局和所有本地来源。对于只能接受单个值的那些属性(例如allowCredentialsmaxAge),局部属性将覆盖全局值。请参阅 CorsConfiguration#combine(CorsConfiguration) 以获取更多详细信息。

要从源中了解更多信息或进行高级自定义,请查看后面的代码:

  • CorsConfiguration
  • CorsProcessorDefaultCorsProcessor
  • AbstractHandlerMapping

1.7.3。 @CrossOrigin

@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方法。

默认情况下不会启用allowedCredentials ,因为这会建立一个信任级别,该级别公开敏感的用户特定信息(例如cookie和CSRF令牌),应该仅在适当的地方使用。

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) {
        // ...
    }
}

1.7.4。全局配置

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

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

  • 所有来源。
  • 所有标题。
  • GETHEADPOST方法。

默认情况下不会启用allowedCredentials ,因为这会建立一个信任级别,该级别公开敏感的用户特定信息(例如cookie和CSRF令牌),并且仅在适当的地方使用。

maxAge 设置为30分钟。

Java配置

要在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>

1.7.5。CORS过滤器

您可以通过内置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);

1.8。网络安全

Spring Security的项目提供了保护Web应用程序免受恶意攻击的支持。请参阅Spring Security参考文档,包括:

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

1.9。HTTP缓存

HTTP缓存可以显着提高Web应用程序的性能。HTTP缓存围绕Cache-Control响应标头和随后的条件请求标头(例如Last-ModifiedETag)展开。Cache-Control为私有(例如浏览器)和公共(例如代理)缓存提供有关如何缓存和重用响应的建议。ETag标头用于发出条件请求,如果内容未更改,则可能导致没有主体的304NOT_MODIFIED)。ETag可以看作是Last-Modified标头的更复杂的后继。

本节描述了Spring Web MVC中与HTTP缓存相关的选项。

1.9.1. CacheControl

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秒。

1.9.2. Controllers

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

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

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

您还可以在控制器中针对条件请求标头进行检查,如以下示例所示:

@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
	// 特定于应用程序的计算。
    long eTag = ... 

    if (request.checkNotModified(eTag)) {
	    // 响应已设置为304(NOT_MODIFIED)-无需进一步处理。
        return null; 
    }

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

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

1.9.3。静态资源

您应使用Cache-Control和条件响应标头来提供静态资源,以实现最佳性能。请参阅“配置静态资源 ”部分。

1.9.4。ETag 过滤器

您可以使用ShallowEtagHeaderFilter来添加根据响应内容计算出的“shallow” eTag值,从而节省带宽,但不节省CPU时间。请参阅Shallow ETag

1.10. View Technologies

Spring MVC中视图技术的使用是可插入的,无论您决定使用Thymeleaf,Groovy标记模板,JSP还是其他技术,主要取决于配置更改。本章介绍与Spring MVC集成的视图技术。我们假设您已经熟悉View Resolution

1.10.1. Thymeleaf

Thymeleaf是一种现代的服务器端Java模板引擎,它强调可以通过双击在浏览器中预览的自然HTML模板,这对于独立处理UI模板(例如,由设计人员)而无需使用非常有用。正在运行的服务器。如果要替换JSP,Thymeleaf提供了最广泛的功能集之一,以使这种过渡更加容易。Thymeleaf是积极开发和维护的。有关更完整的介绍,请参见 Thymeleaf项目主页。

Thymeleaf与Spring MVC的集成由Thymeleaf项目管理。配置涉及几个bean声明,如 ServletContextTemplateResolverSpringTemplateEngineThymeleafViewResolver。有关更多详细信息,请参见Thymeleaf + Spring

1.10.2。FreeMarker

Apache FreeMarker是一个模板引擎,用于生成从HTML到电子邮件等的任何类型的文本输出。Spring框架具有内置的集成,可以将Spring MVC与FreeMarker模板一起使用。

查看配置

以下示例显示了如何将FreeMarker配置为视图技术:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.freeMarker();
    }

    // Configure FreeMarker...

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

以下示例显示了如何在XML中进行配置:

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:freemarker/>
</mvc:view-resolvers>

<!-- Configure FreeMarker... -->
<mvc:freemarker-configurer>
    <mvc:template-loader-path location="/WEB-INF/freemarker"/>
</mvc:freemarker-configurer>

另外,您也可以声明FreeMarkerConfigurer Bean以完全控制所有属性,如以下示例所示:

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
</bean>

您的模板需要存储在上例所示的FreeMarkerConfigurer指定的目录中。 给定上述配置,如果您的控制器返回视图名称welcome,则解析器将查找/WEB-INF/freemarker/welcome.ftl模板。

FreeMarker配置

您可以通过在FreeMarkerConfigurer bean上设置适当的bean属性,将FreeMarker的“Settings”和“SharedVariables”直接传递给FreeMarker Configuration 对象(由Spring管理)。 freemarkerSettings属性需要一个java.util.Properties对象,而freemarkerVariables属性需要一个java.util.Map。 以下示例显示了如何使用FreeMarkerConfigurer

<bean id="freemarkerConfig" class="org.springframework.web.servlet.view.freemarker.FreeMarkerConfigurer">
    <property name="templateLoaderPath" value="/WEB-INF/freemarker/"/>
    <property name="freemarkerVariables">
        <map>
            <entry key="xml_escape" value-ref="fmXmlEscape"/>
        </map>
    </property>
</bean>

<bean id="fmXmlEscape" class="freemarker.template.utility.XmlEscape"/>

有关设置和变量应用于Configuration对象的详细信息,请参见FreeMarker文档。

表单处理

Spring提供了一个在JSP中使用的标签库,其中包含一个<spring:bind />元素。 该元素主要允许表单显示来自表单支持对象的值,并显示来自Web或业务层中Validator的验证失败的结果。 Spring还支持FreeMarker中的相同功能,并带有用于生成表单输入元素本身的附加便利宏。

绑定宏
简单绑定
输入宏
HTML转义

1.10.3。Groovy标记

Groovy的标记模板引擎的主要目的是生成XML类标记(XML,XHTML,HTML5等),但你可以用它来生成任何基于文本的内容。Spring框架具有内置的集成,可以将Spring MVC与Groovy标记一起使用。

Groovy标记模板引擎需要Groovy 2.3.1+。

Configuration

以下示例显示了如何配置Groovy标记模板引擎:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configureViewResolvers(ViewResolverRegistry registry) {
        registry.groovy();
    }

    // Configure the Groovy Markup Template Engine...

    @Bean
    public GroovyMarkupConfigurer groovyMarkupConfigurer() {
        GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer();
        configurer.setResourceLoaderPath("/WEB-INF/");
        return configurer;
    }
}

以下示例显示了如何在XML中进行配置:

<mvc:annotation-driven/>

<mvc:view-resolvers>
    <mvc:groovy/>
</mvc:view-resolvers>

<!-- Configure the Groovy Markup Template Engine... -->
<mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
Example

与传统的模板引擎不同,Groovy标记依赖于使用构建器语法的DSL。以下示例显示了HTML页面的示例模板:

yieldUnescaped '<!DOCTYPE html>'
html(lang:'en') {
    head {
        meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"')
        title('My page')
    }
    body {
        p('This is an example of HTML contents')
    }
}

1.10.4. Script Views

Spring框架具有一个内置的集成,可以将Spring MVC与可以在JSR-223 Java脚本引擎之上运行的任何模板库一起使用 。我们已经在不同的脚本引擎上测试了以下模板库:

脚本库 脚本引擎
Handlebars Nashorn
Mustache Nashorn
React Nashorn
EJS Nashorn
ERB JRuby
String templates Jython
Kotlin Script templating Kotlin

集成任何其他脚本引擎的基本规则是,它必须实现 ScriptEngineInvocable接口。

要求
脚本模板

1.10.5。JSP和JSTL

Spring框架具有内置的集成,可以将Spring MVC与JSP和JSTL一起使用。

视图解析器

使用JSP开发时,可以声明 InternalResourceViewResolverResourceBundleViewResolver bean。

ResourceBundleViewResolver依靠属性文件来定义映射到类和URL的视图名称。使用ResourceBundleViewResolver,您可以仅使用一个解析器来混合不同类型的视图,如以下示例所示:

<!-- the ResourceBundleViewResolver -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
    <property name="basename" value="views"/>
</bean>

# And a sample properties file is used (views.properties in WEB-INF/classes):
welcome.(class)=org.springframework.web.servlet.view.JstlView
welcome.url=/WEB-INF/jsp/welcome.jsp

productList.(class)=org.springframework.web.servlet.view.JstlView
productList.url=/WEB-INF/jsp/productlist.jsp

InternalResourceViewResolver也可以用于JSP。作为最佳实践,我们强烈建议您将JSP文件放在该目录下的'WEB-INF'目录中,以便客户端无法直接访问。

<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
JSP与JSTL

使用JSP标准标记库(JSTL)时,必须使用特殊的视图类 JstlView,因为JSTL需要一些准备工作才能使用I18N功能。

Spring的JSP标签库

如前几章所述,Spring提供了将请求参数与命令对象的数据绑定。为了促进结合这些数据绑定功能的JSP页面的开发,Spring提供了一些使事情变得更加容易的标记。所有Spring标记都具有HTML转义功能,以启用或禁用字符转义。

spring.tld标签库描述符(TLD)包含在spring-webmvc.jar。有关单个标签的全面参考,请浏览 API参考 或查看标签库说明。

Spring的表单标签库

从2.0版开始,Spring使用JSP和Spring Web MVC时,提供了一组全面的数据绑定感知标记,用于处理表单元素。每个标签都支持与其对应的HTML标签对应物的属性集,从而使标签变得熟悉且易于使用。标记生成的HTML符合HTML 4.01 / XHTML 1.0

与其他表单/输入标签库不同,Spring的表单标签库与Spring Web MVC集成在一起,使标签可以访问命令对象和控制器处理的参考数据。正如我们在以下示例中所示,表单标签使JSP易于开发,读取和维护。

我们浏览一下表单标签,并查看一个如何使用每个标签的示例。我们包含了生成的HTML代码段,其中某些标记需要进一步的注释。

Configuration

表单标签库捆绑在spring-webmvc.jar中。库描述符称为spring-form.tld

要使用该库中的标记,请在JSP页面顶部添加以下指令:

<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>

其中form是要用于此库中标签的标签名称前缀。

表单标签

该标签呈现HTML 'form' 元素,并向内部标签公开绑定路径以进行绑定。它将命令对象放入PageContext中,以便内部标签可以访问该命令对象。该库中的所有其他标签都是form标签的嵌套标签 。

假设我们有一个名为User的域对象。这是一个JavaBean,具有诸如firstNamelastName的属性。我们可以将其用作表单控制器的表单支持对象,该对象返回form.jsp。以下示例显示了form.jsp可能的样子:

<form:form>
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>

页面控制器从放置在PageContext中的命令对象中检索firstName和lastName值。 继续阅读以了解如何将内部标签与表单标签一起使用的更复杂的示例。

下面的清单显示了生成的HTML,它看起来像标准格式:

<form method="POST">
    <table>
        <tr>
            <td>First Name:</td>
            <td><input name="firstName" type="text" value="Harry"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><input name="lastName" type="text" value="Potter"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form>

前面的JSP假定表单支持对象的变量名称为 command。如果已将表单支持对象以另一个名称(肯定是最佳实践)放入模型中,则可以将表单绑定到命名变量,如以下示例所示:

<form:form modelAttribute="user">
    <table>
        <tr>
            <td>First Name:</td>
            <td><form:input path="firstName"/></td>
        </tr>
        <tr>
            <td>Last Name:</td>
            <td><form:input path="lastName"/></td>
        </tr>
        <tr>
            <td colspan="2">
                <input type="submit" value="Save Changes"/>
            </td>
        </tr>
    </table>
</form:form>
input标签

默认情况下,此标记呈现具有绑定值的HTML input type='text'元素。有关此标签的示例,请参见Form标签。您还可以使用特定的HTML5类型,如emailteldate,等。

checkbox标签
checkboxes标签
radiobutton标签
radiobuttons标签
password标签
select标签
option标签
options标签
textarea标签
hidden标签
errors标签

spring-form.tld标签库描述符(TLD)包含在spring-webmvc.jar。有关单个标签的全面参考,请浏览 API参考 或查看标签库说明。

HTTP方法转换

REST的一个关键原则是使用“统一接口”。这意味着可以使用相同的四种HTTP方法(GET,PUT,POST和DELETE)来操纵所有资源(URL)。对于每种方法,HTTP规范都定义了确切的语义。例如,GET应该始终是安全的操作,这意味着它没有副作用,而PUT或DELETE应该是幂等的,这意味着您可以一遍又一遍地重复这些操作,但是最终结果应该相同。虽然HTTP定义了这四种方法,但是HTML仅支持两种:GET和POST。幸运的是,有两种可能的解决方法:您可以使用JavaScript进行PUT或DELETE,或者可以使用“real”方法作为附加参数(在HTML表单中建模为隐藏的输入字段)进行POST。 Spring的HiddenHttpMethodFilter使用了后一种技巧。该过滤器是一个普通的Servlet过滤器,因此,它可以与任何Web框架(不仅仅是Spring MVC)结合使用。将此过滤器添加到web.xml,然后将带有隐藏method参数的POST转换为相应的HTTP方法请求。

为了支持HTTP方法转换,Spring MVC表单标签已更新为支持设置HTTP方法。例如,以下代码片段来自“宠物诊所”样本:

<form:form method="delete">
    <p class="submit"><input type="submit" value="Delete Pet"/></p>
</form:form>

前面的示例执行HTTP POST,并将“real” DELETE方法隐藏在请求参数后面。它由web.xml中定义的HiddenHttpMethodFilter拾取,如以下示例所示:

<filter>
    <filter-name>httpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>

<filter-mapping>
    <filter-name>httpMethodFilter</filter-name>
    <servlet-name>petclinic</servlet-name>
</filter-mapping>

以下示例显示了相应的@Controller方法:

@RequestMapping(method = RequestMethod.DELETE)
public String deletePet(@PathVariable int ownerId, @PathVariable int petId) {
    this.clinic.deletePet(petId);
    return "redirect:/owners/" + ownerId;
}
HTML5标签

Spring表单标签库允许输入动态属性,这意味着您可以输入任何HTML5特定的属性。

表单input标签支持输入text以外的类型属性。这是为了让渲染新的HTML5特定的输入类型,如emaildaterange等。注意type='text',由于text 是默认类型,因此不需要输入。

1.10.6. Tiles

您可以像使用其他视图技术一样,将Tiles集成到使用Spring的Web应用程序中。本节将广泛介绍如何执行此操作。

本节重点介绍Spring对org.springframework.web.servlet.view.tiles3软件包中Tiles版本3的支持 。

Dependencies
Configuration

1.10.7。RSS和Atom

AbstractAtomFeedViewAbstractRssFeedView都继承自AbstractFeedView基类,分别用于提供Atom和RSS Feed视图。 它们基于ROME项目,位于包org.springframework.web.servlet.view.feed中。

AbstractAtomFeedView需要您实现buildFeedEntries()方法并可以选择覆盖buildFeedMetadata()方法(默认实现为空)。以下示例显示了如何执行此操作:

public class SampleContentAtomView extends AbstractAtomFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Feed feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Entry> buildFeedEntries(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}

类似的要求也适用于实现AbstractRssFeedView,如以下示例所示:

public class SampleContentRssView extends AbstractRssFeedView {

    @Override
    protected void buildFeedMetadata(Map<String, Object> model,
            Channel feed, HttpServletRequest request) {
        // implementation omitted
    }

    @Override
    protected List<Item> buildFeedItems(Map<String, Object> model,
            HttpServletRequest request, HttpServletResponse response) throws Exception {
        // implementation omitted
    }
}

如果需要访问Locale,则buildFeedItems()buildFeedEntries()方法会传递HTTP请求。 仅针对Cookie或其他HTTP标头的设置传入HTTP响应。 方法返回后,feed 将自动写入响应对象。

有关创建Atom视图的示例,请参见Alef Arendsen的Spring Team Blog条目

1.10.8。PDF和Excel

Spring提供了返回HTML以外的输出的方法,包括PDF和Excel电子表格。本节介绍如何使用这些功能。

文档视图简介

HTML页面并非始终是用户查看模型输出的最佳方式,而Spring使从模型数据动态生成PDF文档或Excel电子表格变得简单。该文档是视图,并从服务器以正确的内容类型进行流传输,以(希望)使客户端PC能够运行其电子表格或PDF查看器应用程序作为响应。

为了使用Excel视图,您需要将Apache POI库添加到类路径中。为了生成PDF,您需要添加(最好是)OpenPDF库。

如果可能,您应该使用基础文档生成库的最新版本。特别是,我们强烈建议使用OpenPDF(例如,OpenPDF 1.2.12),而不是过时的原始iText 2.1.7,因为OpenPDF会得到积极维护并修复了不可信任PDF内容的一个重要漏洞。

PDF视图

单词列表的简单PDF视图可以扩展 org.springframework.web.servlet.view.document.AbstractPdfView 并实现 buildPdfDocument()方法,如以下示例所示:

public class PdfWordList extends AbstractPdfView {

    protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer,
            HttpServletRequest request, HttpServletResponse response) throws Exception {

        List<String> words = (List<String>) model.get("wordList");
        for (String word : words) {
            doc.add(new Paragraph(word));
        }
    }
}

控制器可以从外部视图定义(按名称引用)或作为处理程序方法的View实例返回这种视图。

Excel视图

从Spring Framework 4.2开始, org.springframework.web.servlet.view.document.AbstractXlsView 作为Excel视图的基类提供。它基于Apache POI,具有取代过时类AbstractExcelView的专用子类(AbstractXlsxViewAbstractXlsxStreamingView)。

编程模型类似于AbstractPdfViewbuildExcelDocument() 作为核心模板方法,控制器能够从外部定义(按名称)或从处理程序方法作为View实例返回这种视图。

代码示例
@Slf4j
@Controller
public class MyController {

    @GetMapping("/pdf")
    public MyPdfView pdf() {
        return new MyPdfView();
    }

    @GetMapping("/excel")
    public ModelAndView excel() {
        return new ModelAndView(new MyExcelView());
    }
}

class MyPdfView extends AbstractPdfView {

    @Autowired
    protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception {
        // 输出中文需要设置字体
        BaseFont bfChinese = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
        Font font = new Font(bfChinese, Font.BOLD);

        doc.add(new Paragraph("11"));
        doc.add(new Paragraph("12"));
        doc.add(new Paragraph("13我", font));
    }
}

class MyExcelView extends AbstractXlsxView {

    @Override
    protected void buildExcelDocument(Map<String, Object> model, Workbook workbook, HttpServletRequest request, HttpServletResponse response) throws Exception {
        Sheet sheet = workbook.createSheet();
        Row row = sheet.createRow(0);
        row.createCell(0).setCellValue("00");
        row.createCell(1).setCellValue("01");
        row.createCell(2).setCellValue("02我");
    }
}

1.10.9. Jackson

Spring提供了对Jackson JSON库的支持。

基于Jackson的JSON MVC视图

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

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

基于Jackson的XML视图

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

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

1.10.10。XML编组(XML Marshalling)

MarshallingView使用XML Marshaller(在org.springframework.oxm包中定义)将响应内容呈现为XML。 您可以使用MarshallingView实例的modelKey bean属性显式设置要编组的对象。 或者,视图遍历所有模型属性,并封送Marshaller支持的第一个类型。 有关org.springframework.oxm包中功能的更多信息,请参见使用O/X映射器编组XML

1.10.11。XSLT视图

XSLT是XML的一种转换语言,在Web应用程序中作为一种视图技术而流行。如果您的应用程序自然处理XML,或者您的模型可以轻松转换为XML,那么XSLT可以作为视图技术的不错选择。下一节说明如何将XML文档生成为模型数据,以及如何在Spring Web MVC应用程序中使用XSLT对其进行转换。

这个示例是一个简单的Spring应用程序,它在Controller中创建一个单词列表并将其添加到模型map。返回该map以及XSLT视图的视图名称。有关Spring Web MVC Controller接口的详细信息, 请参见带注释的控制器。XSLT控制器将单词列表转换为准备转换的简单XML文档。

Beans

配置是简单Spring Web应用程序的标准配置:MVC配置必须定义XsltViewResolver Bean和常规MVC注释配置。以下示例显示了如何执行此操作:

@EnableWebMvc
@ComponentScan
@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Bean
    public XsltViewResolver xsltViewResolver() {
        XsltViewResolver viewResolver = new XsltViewResolver();
        viewResolver.setPrefix("/WEB-INF/xsl/");
        viewResolver.setSuffix(".xslt");
        return viewResolver;
    }
}
Controller

我们还需要一个控制器来封装词生成逻辑。

控制器逻辑封装在一个@Controller类中,其中handler方法的定义如下:

@Controller
public class XsltController {

    @RequestMapping("/")
    public String home(Model model) throws Exception {
        Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
        Element root = document.createElement("wordList");

        List<String> words = Arrays.asList("Hello", "Spring", "Framework");
        for (String word : words) {
            Element wordNode = document.createElement("word");
            Text textNode = document.createTextNode(word);
            wordNode.appendChild(textNode);
            root.appendChild(wordNode);
        }

        model.addAttribute("wordList", root);
        return "home";
    }
}

到目前为止,我们仅创建了一个DOM文档并将其添加到Model映射中。请注意,您还可以将XML文件作为Resource加载并使用它代替自定义DOM文档。

有可用的软件包自动“对象化”对象图,但是在Spring中,您可以完全灵活地以任何你选择的方式从模型中创建DOM。这样可以防止XML转换在模型数据的结构中扮演过重要的角色,这在使用工具管理DOM化流程时是一种危险。

Transformation

最后,XsltViewResolver解析 “home” XSLT模板文件并将DOM文档合并到其中以生成我们的视图。如XsltViewResolver 配置中所示,XSLT模板位于WEB-INF/xsl目录中的war文件中,并以xslt文件扩展名结尾。

以下示例显示了XSLT转换:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

    <xsl:output method="html" omit-xml-declaration="yes"/>

    <xsl:template match="/">
        <html>
            <head><title>Hello!</title></head>
            <body>
                <h1>My First Words</h1>
                <ul>
                    <xsl:apply-templates/>
                </ul>
            </body>
        </html>
    </xsl:template>

    <xsl:template match="word">
        <li><xsl:value-of select="."/></li>
    </xsl:template>

</xsl:stylesheet>

前面的转换呈现为以下HTML:

<html>
    <head>
        <META http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Hello!</title>
    </head>
    <body>
        <h1>My First Words</h1>
        <ul>
            <li>Hello</li>
            <li>Spring</li>
            <li>Framework</li>
        </ul>
    </body>
</html>

1.11。MVC配置

MVC Java配置和MVC XML名称空间提供适用于大多数应用程序的默认配置以及用于自定义它的配置API。

有关配置API中不可用的更多高级定制,请参阅Advanced Java ConfigAdvanced XML Config

您不需要了解由MVC Java配置和MVC名称空间创建的基础bean。如果要了解更多信息,请参见特殊Bean类型Web MVC Config

1.11.1。启用MVC配置

在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等的有效负载转换器)。

1.11.2。MVC Config API

在Java配置中,您可以实现WebMvcConfigurer接口,如以下示例所示:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    // Implement configuration methods...
}

在XML中,您可以检查<mvc:annotation-driven>的属性和子元素。您可以查看Spring MVC XML模式或使用IDE的代码完成功能来发现可用的属性和子元素。

1.11.3。类型转换

默认情况下,将安装NumberDate类型的格式化程序,包括对@NumberFormat@DateTimeFormat注解的支持。 如果类路径中存在Joda-Time,则还将安装对Joda-Time格式库的完全支持。

在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>

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

1.11.4. Validation

默认情况下,如果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实现,如以下示例所示:

如果需要在某个地方注入LocalValidatorFactoryBean,请创建一个bean并用@Primary进行标记,以避免与MVC配置中声明的那个冲突。

1.11.5。拦截器

在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>

1.11.6. Content Types

您可以配置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>

1.11.7. Message Converters

您可以通过覆盖configureMessageConverters()(以替换Spring MVC创建的默认转换器)或覆盖extendMessageConverters()(以自定义默认转换器或向默认转换器添加其他转换器)来以Java配置自定义HttpMessageConverter

以下示例使用自定义的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()));
    }
}

在前面的示例中,使用Jackson2ObjectMapperBuilder为启用了缩进的MappingJackson2HttpMessageConverterMappingJackson2XmlHttpMessageConverter创建通用配置,自定义日期格式以及jackson-module-parameter-names的注册,从而增加了对访问参数名称的支持(在Java 8中增加的一项功能)。

该构建器自定义Jackson的默认属性,如下所示:

如果在类路径中检测到以下知名模块,它还将自动注册以下知名模块:

使用jackson XML支持启用缩进,除了jackson-dataformat-xml一个之外,还需要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"/>

org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport#addDefaultHttpMessageConverters

1.11.8. View Controllers

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

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

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

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

以下示例通过使用<mvc:view-controller>元素,实现了与上一示例相同的操作,但使用XML :

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

1.11.9. View Resolvers

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

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

@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标记和脚本模板也需要配置基础视图技术。

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

1.11.10。静态资源

此选项提供了一种方便的方法来从基于 Resource的位置列表中提供静态资源。

在下一个示例中,给定一个以/resources开头的请求,相对路径用于查找和提供静态资源,在Web应用程序根目录下/public或类路径下/static。 这些资源的使用期限为一年,以确保最大程度地利用浏览器缓存并减少浏览器发出的HTTP请求。 还评估Last-Modified标头,如果存在,则返回304状态码。

以下清单显示了如何使用Java配置进行操作:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/resources/**")
            .addResourceLocations("/public", "classpath:/static/")
            .setCachePeriod(31556926);
    }
}

以下示例显示了如何在XML中实现相同的配置:

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

另请参见 HTTP缓存对静态资源的支持

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

您可以将VersionResourceResolver用于基于资源,固定应用程序版本或其他内容计算出的MD5哈希值的版本化资源URL。 ContentVersionStrategy(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以及其他依赖于HttpServletResponse#encodeURL的URL标签的重写透明化。

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

WebJars还通过WebJarsResourceResolver支持,当org.webjars:webjars-locator-core库存在于类路径中时,WebJars将自动注册。解析器可以重写URL以包括jar的版本,并且还可以与不带版本的传入URL进行匹配,例如,从/jquery/jquery.min.js/jquery/1.2.0/jquery.min.js

1.11.11. Default Servlet

Spring MVC允许映射DispatcherServlet/(从而覆盖了容器默认Servlet的映射),同时仍然允许容器的默认Servlet处理静态资源请求。它配置的 DefaultServletHttpRequestHandler URL映射为/**,相对于其他URL映射具有最低的优先级。

该处理程序将所有请求转发到默认Servlet。 因此,它必须按所有其他URL HandlerMappings的顺序保留在最后。 如果使用<mvc:annotation-driven>,就是这种情况。 另外,如果您设置自己的自定义HandlerMapping实例,请确保将其order属性设置为小于DefaultServletHttpRequestHandlerInteger.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"/>

1.11.12。路径匹配(Path Matching)

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

以下示例显示了如何在Java配置中自定义路径匹配:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        configurer
            .setUseSuffixPatternMatch(true)
            .setUseTrailingSlashMatch(false)
            .setUseRegisteredSuffixPatternMatch(true)
            .setPathMatcher(antPathMatcher())
            .setUrlPathHelper(urlPathHelper())
            .addPathPrefix("/api",
                    HandlerTypePredicate.forAnnotation(RestController.class));
    }

    @Bean
    public UrlPathHelper urlPathHelper() {
        //...
    }

    @Bean
    public PathMatcher antPathMatcher() {
        //...
    }
}

以下示例显示了如何在XML中实现相同的配置:

<mvc:annotation-driven>
    <mvc:path-matching
        suffix-pattern="true"
        trailing-slash="false"
        registered-suffixes-only="true"
        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"/>

1.11.13。高级Java配置

@EnableWebMvc 引入 DelegatingWebMvcConfiguration,其中:

  • 为Spring MVC应用程序提供默认的Spring配置
  • 检测并委托给WebMvcConfigurer实现以自定义配置。

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

@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {

    // ...
}

您可以将现有方法保留在WebConfig中,但是现在您还可以覆盖基类中的bean声明,并且在类路径上仍然可以具有许多其他WebMvcConfigurer实现。

1.11.14。高级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 />声明将其检测出来。

1.12。HTTP / 2

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

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

posted @ 2020-03-19 16:16  流星<。)#)))≦  阅读(351)  评论(0编辑  收藏  举报