[关闭]
@chendushuai 2018-03-04T08:54:06.000000Z 字数 64960 阅读 1427

基于Servlet栈的Web

Spring MVC Servlet


这部分的文档覆盖支持Servlet堆栈,建立在Servlet API上的Web应用程序,以及部署到Servlet容器。个别章节包括Spring MVC视图技术CORS支持,及WebSocket支持。关于reactive栈,web应用程序,前往基于Reactive栈的Web

1. Spring Web MVC

1.1 介绍

Spring Web MVC是建立在Servlet API上的原始Web框架,从一开始就包括在Spring框架中。正式名称“Spring Web MVC”来自于它的源模块的名称spring-webmvc,但是俗称为“Spring MVC”。

与Spring Web MVC平行,Spring框架5.0引入了一个reactive栈,Web框架的名称Spring WebFlux也是基于其来源模块spring-webflux。本章将介绍Spring Web MVC。下一章介绍Spring WebFlux。

1.2 DispatcherServlet

Spring WebFlux中的同样内容

Spring MVC,像其他许多Web框架,是围绕前端控制器模式设计的,称为核心Servlet,也就是DispatcherServlet,提供了一个在实际工作中执行可配置、委托组件的共享请求处理算法。这个模型非常灵活,可以支持不同的工作流。

DispatcherServlet,或者任意Servlet,需要声明和映射Servlet规范或者使用web.xml中的Java配置。反过来,如果DispatcherServlet使用Spring配置去发现委托组件,那么需要请求映射、视图内容、异常处理、以及更多

下面是一个Java配置的例子,用于注册和加载DispatcherServlet。这个类是Servlet容器自动发现的。(见基于代码的Servlet容器初始化)

  1. public class MyWebApplicationInitializer implements WebApplicationInitializer {
  2. @Override
  3. public void onStartup(ServletContext servletCxt) {
  4. // 加载Spring web应用程序配置
  5. AnnotationConfigWebApplicationContext cxt = new AnnotationConfigWebApplicationContext();
  6. cxt.register(AppConfig.class);
  7. cxt.refresh();
  8. // 创建DispatcherServlet
  9. DispatcherServlet servlet = new DispatcherServlet(cxt);
  10. // 注册和映射Servlet
  11. ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
  12. registration.setLoadOnStartup(1);
  13. registration.addMapping("/app/*");
  14. }
  15. }

除了直接使用ServletContext API,你也可以拓展AbstractAnnotationConfigDispatcherServletInitializer,并且重写特定的方法。(见WebApplicationContext下的示例)

下面的示例使用web.xml配置去注册和初始化DispatcherServlet

  1. <web-app>
  2. <listener>
  3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5. <context-param>
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>/WEB-INF/app-context.xml</param-value>
  8. </context-param>
  9. <servlet>
  10. <servlet-name>app</servlet-name>
  11. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  12. <init-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value></param-value>
  15. </init-param>
  16. <load-on-startup>1</load-on-startup>
  17. </servlet>
  18. <servlet-mapping>
  19. <servlet-name>app</servlet-name>
  20. <url-pattern>/app/*</url-pattern>
  21. </servlet-mapping>
  22. </web-app>

Spring Boot遵照不同的初始化序列。而不是跟Servlet容器的生命周期相连接,Spring Boot使用Spring配置去引导自身嵌入Servlet容器。在Spring配置中检测到FilterServlet声明后,在Servlet容器中注册。更多内容查看Spring Boot文档

1.2.1 WebApplicationContext层次结构

DispatcherServlet使用一个WebApplicationContext(一个普通ApplicationContext的扩展),作为自己的配置。WebApplicationContext有一个链接指向ServletContext,并同Servlet相关。它同样可以绑定到ServletContext,这样,如果需要访问它的话,应用程序可以在RequestContextUtils上使用静态方法去查找WebApplicationContext

许多引用程序有一个单独的WebApplicationContext就足够了。也可以有一个上下文层次结构,也就是一个根WebApplicationContext用于跨多个DispatcherServlet(或者其他Servlet)实例共享,每一个实例都有自己的子WebApplicationContext配置。查看ApplicationContext的额外功能去了解更多上下文层次特征。

WebApplicationContext通常包含数据存储库和商业服务等基础设施bean,这需要跨多个Servlet实例共享。这些bean有效继承,并且在Servlet-specific(具体的Servlet)可以覆盖(也就是重新声明),子WebApplicationContext,通常包含bean指向给定的Servlet

下面是WebApplicationContext层次结构的配置示例

  1. public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  2. @Override
  3. protected Class<?>[] getRootConfigClasses() {
  4. return new Class[] { RootConfig.class };
  5. }
  6. @Override
  7. protected Class<?>[] getServletConfigClasses() {
  8. return new Class[] { App1Config.class };
  9. }
  10. @Override
  11. protected String[] getServletMappings() {
  12. return new String[] { "/app1/*" };
  13. }
  14. }

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

相当于下面的web.xml

  1. <web-app>
  2. <listener>
  3. <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  4. </listener>
  5. <context-param>
  6. <param-name>contextConfigLocation</param-name>
  7. <param-value>/WEB-INF/root-context.xml</param-value>
  8. </context-param>
  9. <servlet>
  10. <servlet-name>app1</servlet-name>
  11. <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
  12. <init-param>
  13. <param-name>contextConfigLocation</param-name>
  14. <param-value>/WEB-INF/app1-context.xml</param-value>
  15. </init-param>
  16. <load-on-startup>1</load-on-startup>
  17. </servlet>
  18. <servlet-mapping>
  19. <servlet-name>app1</servlet-name>
  20. <url-pattern>/app1/*</url-pattern>
  21. </servlet-mapping>
  22. </web-app>

如果不需要应用程序上下文层次结构,那么应用程序可能只配置一个“root”上下文,并将contextConfigLocationServlet参数留空。

1.2.2 特殊Bean类型

Spring WebFlux中的同样内容

DispatcherServlet代表特殊bean去处理请求,并给予合适的响应。通过“特殊bean”,我们指的是实现下表中列出的框架类型之一的Spring管理对象实例。Spring MVC提供了这些类型的内置实现,但是你仍然可以自定义、拓展、或者替换他们。

Bean类型 解释
HandlerMapping 将请求映射到处理程序及拦截器列表,进行预处理和后处理。映射有一些标准,其中的细节因HandlerMapping实现而异。

两个主要HandlerMapping实现RequestMappingHandlerMapping支持@RequestMapping注解的方法和SimpleUrlHandlerMapping保持明确的URI路径模式处理程序的注册。
HandlerAdapter 帮助DispatcherServlet调用映射到请求的处理程序,而不管处理程序如何世纪调用。例如,调用一个带注释的控制器需要解析各种注释。HandlerAdapter的主要目的是保护DispatcherServlet不受这些细节的影响。
HandlerExceptionResolver 策略来解析异常,可能将它们映射到处理程序,或HTML错误视图。请参阅异常处理
ViewResolver 将从处理程序返回的基于逻辑的字符串名称解析为实际视图View,以呈现响应。查看视图解析视图技术
LocaleResolver & LocaleContextResolver 解析客户端正在使用的语言环境Locale,可能是他们的时区,以便能够提供国际化的视图View。请参阅区域设置
ThemeResolver 解析你的Web应用程序可以使用的主题,例如,提供个性化的布局。请参阅主题
MultipartResolver 在一些多部分解析库的帮助下,对一个多部分请求(例如:浏览器表单文件上传)进行解析。请参阅多部分请求
FlashMapManager 存储和检索“input输入”和“output输出”FlashMap,可以用来将属性从一个请求传递到另外一个请求,通常是重定向。请参阅Flash属性

1.2.3 框架配置

对于每种特殊的bean,DispatcherServlet首先检查WebApplicationContext。如果没有匹配的bean类型,它将会使用DispatcherServlet.properties列出的默认类型。

应用程序可以声明他们希望拥有的特殊bean,然而,大多数的应用程序将会在MVC Java配置中或者MVC XML命名空间中找到一个更好的启动点,该启动点提供一个更高级别的配置API,从而反过来生成必要的bean声明。更多细节,查看MVC Java配置、XML命名空间

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

1.2.4 容器配置

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

  1. import org.springframework.web.WebApplicationInitializer;
  2. public class MyWebApplicationInitializer implements WebApplicationInitializer {
  3. @Override
  4. public void onStartup(ServletContext container) {
  5. XmlWebApplicationContext appContext = new XmlWebApplicationContext();
  6. appContext.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
  7. ServletRegistration.Dynamic registration = container.addServlet("dispatcher", new DispatcherServlet(appContext));
  8. registration.setLoadOnStartup(1);
  9. registration.addMapping("/");
  10. }
  11. }

WebApplicationInitializer是Spring MVC提供的一个接口,确保您的实现被检测到并自动用于初始化任何Servlet 3容器。WebApplicationInitializer的一个抽象的基类的实现命名为AbstractDispatcherServletInitializer,使它更容易注册DispatcherServlet,只需重写方法指定servlet映射和DispatcherServlet配置的位置。

建议使用基于Java的Spring配置的应用程序:

  1. public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
  2. @Override
  3. protected Class<?>[] getRootConfigClasses() {
  4. return null;
  5. }
  6. @Override
  7. protected Class<?>[] getServletConfigClasses() {
  8. return new Class[] { MyWebConfig.class };
  9. }
  10. @Override
  11. protected String[] getServletMappings() {
  12. return new String[] { "/" };
  13. }
  14. }

如果使用基于XML的Spring配置,则应该直接从AbstractDispatcherServletInitializer进行拓展。

  1. public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
  2. @Override
  3. protected WebApplicationContext createRootApplicationContext() {
  4. return null;
  5. }
  6. @Override
  7. protected WebApplicationContext createServletApplicationContext() {
  8. XmlWebApplicationContext cxt = new XmlWebApplicationContext();
  9. cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml");
  10. return cxt;
  11. }
  12. @Override
  13. protected String[] getServletMappings() {
  14. return new String[] { "/" };
  15. }
  16. }

AbstractDispatcherServletInitializer还提供了一种方便的方法来添加Filter实例,让他们自动映射到DispatcherServlet:

  1. public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
  2. // ...
  3. @Override
  4. protected Filter[] getServletFilters() {
  5. return new Filter[] {
  6. new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
  7. }
  8. }

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

AbstractDispatcherServletInitializer的受保护的方法isAsyncSupported提供了一个单独的位置来启用DispatcherServlet和映射到它的所有过滤器的异步支持。默认情况下,这个标志设置为true。

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

1.2.5 处理

Spring WebFlux中的同样内容

DispatcherServlet处理流程要求如下:

WebApplicationContext中声明的HandlerExceptionResolver bean用于解析请求处理期间抛出的异常。这些异常解析器允许自定于逻辑来定位处理异常。更多细节,请参见处理异常

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

你可以通过向web.xml文件中的Servlet声明添加Servlet初始化参数(init-param元素)来定制单个DispatcherServlet实例。下面的列表列出了支持的参数列表。

表1. DispatcherServlet初始化参数

参数 解释
contextClass 实现WebApplicationContext的类,它实例化这个Servlet使用的上下文。默认情况下,使用XmlWebApplicationContext
contextConfigLocation 传递给上下文实例的字符串(通过contextClass指定)来指示可以找到上下文(等)。字符串可能包含多个字符串(使用逗号作为分隔符)来支持多个上下文。如果使用定义了两次的bean的多个上下文位置,则以最新的位置优先。
namespace WebApplicationContext的命名空间,默认指向[servlet-name]-servlet.

1.2.6 拦截

所有HandlerMapping实现都支持处理程序拦截器,当您想要将特定功能应用于某些请求时(例如,检查一个主体),该拦截器是有效的。拦截器必须从org.springframework.web.servlet包上实现HandlerInterceptor的三个方法,提供足够的灵活性来完成各种预处理和后处理:

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

有关如何配置拦截器的示例,请参阅MVC配置部分中的拦截器。您还可以通过单独的HandlerMapping实现直接在setter中注册它们。

请注意,在@ResponseBodyResponseEntity方法中,postHandle对响应在HandlerAdapterpostHandle之前所编写和提交的响应是不可用的。这意味着它已经来不及对响应做出任何更改,例如添加额外的标题。因此,这样的场景你可以实现ResponseBodyAdvice并且声明他作为一个控制器建议bean或者直接在RequestMappingHandlerAdapter配置它。

1.2.7 异常解决

如果异常发生在映射或请求的调用处理程序时(例如一个@Controller),那么DispatcherServlet会委托一系列HandlerExceptionResolver bean去试图解决异常,并提供替代处理,这通常意味着需要准备一个对错误的响应,这可以是一个HTML错误页面,一个错误状态,或两者都有。

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

表2. HandlerExceptionResolver实现

HandlerExceptionResolver 描述
SimpleMappingExceptionResolver 异常类名称和错误页面名称之间的映射。用于在浏览器应用程序中呈现错误页面。
DefaultHandlerExceptionResolver 解析Spring MVC提出的异常,并将其映射到HTTP状态码。

同样可以查看替换ResponseEntityExceptionHandlerREST API异常
ResponseStatusExceptionResolver 使用@ResponseStatus注解解析异常,并基于注解内的值将其映射到HTTP状态码。
ExceptionHandlerExceptionResolver 通过在@Controller或者@ControllerAdvice类中请求@ExceptionHandler方法解析异常。查看异常方法

处理

通过声明多个异常解析器Bean,并且在必要时设置order属性来指定排序,你可以使用链式异常解析器。记住,order属性值越高,则异常解析器在链中的位置越靠后。

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

配置异常处理就像是将HandlerExceptionResolverbean添加到Spring配置一样简单。MVC配置自动给默认的Spring MVC异常声明内置的解析器,用于@ResponseStatus注解的异常,以及支持@ExceptionHandler方法,你可以自定义这个列表或者替换它。

容器错误页面

如果异常通过任何HandlerExceptionResolver都仍未解决,那么这个异常将会传播,或者如果响应状态设置为一个错误状态(例如:4xx,5xx),那么Servlet容器可能会在HTML中呈现一个默认的错误页面。想要自定义容器的默认错误页面,你可以在web.xml中声明一个错误页面映射。

  1. <error-page>
  2. <location>/error</location>
  3. </error-page>

在上述情况下,当一个异常冒泡向上时,或者响应一个错误状态时,Servlet容器会将ERROR分派到容器已配置的URL上(例如:"/error")。然后由DispatcherServlet处理,可能映射到一个@Controller,它可以被实现为返回一个带有模型的错误页面名称或者呈现一个JSON响应,如下所示:

  1. @RestController
  2. public class ErrorController {
  3. @RequestMapping(path = "/error")
  4. public Map<String, Object> handle(HttpServletRequest request) {
  5. Map<String, Object> map = new HashMap<String, Object>();
  6. map.put("status", request.getAttribute("javax.servlet.error.status_code"));
  7. map.put("reason", request.getAttribute("javax.servlet.error.message"));
  8. return map;
  9. }
  10. }

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

1.2.8 ViewResolver 视图解析

Spring MVC定义了ViewResolverView接口,使你能够在浏览器中呈现模型,而不将其绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。View处理数据的准备工作,然后移交给特定的视图技术。

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

表2 ViewResolver实现

ViewResolver 说明
AbstractCachingViewResolver AbstractCachingViewResolver的子类缓存视图实例的解析。缓存提高了某些视图技术的性能。可以通过将cache属性设置为false来关闭缓存。此外,如果您必须在运行时刷新特定的视图(例如在修改FreeMarker模板时),您可以使用removeFromCache(String viewName, Locale loc)方法。
XmlViewResolver ViewResolver的实现,接受使用XML编写的同Spring的XMLbean工厂相同的DTD配置文件。默认的配置文件是/WEB-INF/views.xml
ResourceBundleViewResolver ResourceBundle中使用bean定义的ViewResolver的实现,通过bundle基本名称指定,对于它应该解决的每个视图,它使用属性[viewname].(class)作为视图类和[viewname].url属性的值作为视图url。可以在视图技术一章中查看示例。
UrlBasedViewResolver 在没有显式映射定义的情况下,简单地实现ViewResolver接口,它直接将逻辑视图名称解析为URL。如果您的逻辑名称以一种简单的方式匹配视图资源的名称,而不需要任意的映射,那么这是适合的。
InternalResourceViewResolver UrlBasedViewResolver的方便子类支持InternalResourceView(实际指的是Servlet和jsp)和其子类,例如JstlViewTilesView。您可以使用setViewClass(..)为这个解析器生成的所有视图指定视图类。有关UrlBasedViewResolver的详细信息,请参阅java文档。
FreeMarkerViewResolver UrlBasedViewResolver的方便子类支持FreeMarkerView和其自定义子类。
ContentNegotiatingViewResolver ViewResolver接口的实现基于请求文件名称或者Accept头解析一个视图。详情参见内容协商

你可以通过配置实现链视图解析器,如果有必要,通过设置order属性来指定排序。记住,order属性值越高,视图解析器就在链中更靠后。

视图解析器的协议指定一个视图解析器可以返回null,以表示无法找到视图。然而在JSP中,InternalResourceViewResolver找出JSP是否存在的唯一方法是执行一次调度RequestDispatcher。这时InternalResourceViewResolver必须配置在排序的最后。

有关如何配置视图解析器的详细信息,请参见MVC配置下的视图控制器。有关支持的视图技术的更多详细信息,另请参阅视图技术

处理

你可以通过晟敏过多个解析器bean来链接视图解析器,如果必要的话,可以通过设置order属性值来指定排序。记住,order属性值越高,视图解析器在链中就越靠后。

ViewResolver的约定指定他可以返回null来表示无法找到视图。然而在JSP中,InternalResourceViewResolver判断JSP存在唯一途径是通过执行RequestDispatcher分派。因此,InternalResourceViewResolver必须始终将其配置为视图解析器链的整体顺序的最后一个。

配置视图解析器就像是将ViewResolver添加到你的Spring配置中一样简单。MVC配置提供了针对视图解析器提供了专属配置API,并且还可以用于逻辑较少的视图控制器,有助于在没有视图逻辑的情况下呈现HTML模板。

重定向

指定redirect:视图名称中的前缀允许您执行重定向。UrlBasedViewResolver(和子类)将识别这是需要重定向的特殊指示。视图名称的其余部分将被视为重定向URL。

网络效果与控制器返回一个RedirectView的效果相同,但现在控制器本身可以简单地按逻辑视图名称操作。逻辑视图名称如重定向:redirect:/myapp/some/resource将相对于当前Servlet上下文重定向,而一个名称,如:redirect:http://myhost.com/some/arbitrary/path将重定向到一个绝对URL。

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

前缀

还可以使用一个特殊的forward:用于查看最终由UrlBasedViewResolver和子类解析的视图名称的前缀。执行一个RequestDispatcher.forward()创建一个InternalResourceView。因此,这个前缀对于InternalResourceViewResolverInternalResourceView(针对JSP)是无效的,但是如果使用其他视图技术,forward前缀还是有帮助的,但是仍然要求资源的前缀交由Servlet/JSP引擎处理。注意,这样你可能还会链多视图解析器。

内容协商

ContentNegotiatingViewResolver不会自己解析视图,而是委托给其他视图解析器,并选择类似于客户端请求的视图。这种表示可以从Accept头部或查询参数确定,例如"/path?format=pdf"

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

更多配置细节请查看在MVC配置下的视图解析器

1.2.9 本地化

Spring的大部分架构支持国际化,就像Spring web MVC框架所做的那样。DispatcherServlet让你能够使用客户机的语言环境自动解析消息。这是使用LocaleResolver对象完成的。

当一个请求传入时,DispatcherServlet将寻找一个本地语言解析器,如果找到一个,则将尝试使用其设置为本地语言。使用RequestContext.getLocale()方法,您可以始终检索本地语言解析器解析的语言设置。

除了自动区域语言设置之外,还可以将拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,详见拦截),以在特定的环境下更改本地化语言,例如,基于请求中的一个参数。

本地化语言解析器和拦截器在org.springframework.web.servlet.i18n包中定义,并以正常方式在应用程序上下文中配置。这里是在Spring中包含的本地化解析器的选项。

TimeZone时区

除了获取客户端的语言环境之外,了解他们的时区也是很有用的。LocaleContextResolver接口为LocaleResolver提供了一个扩展,允许解析器提供一个更详细的LocaleContext,其中可能包括时区信息。

当时区可用时,使用RequestContext.getTimeZone()方法可以获得用户的时区。使用Spring的ConversionService日期/时间ConverterFormatter对象将自动使用时区信息注册。

头解析器

这个区域语言解析器检查客户端发送的请求中的accept-language头(例如,一个web浏览器)。通常,这个头字段包含客户端操作系统的区域设置。注意,这个解析器不支持时区信息。

Cookie解析器

该区域语言解析器检查客户机上可能存在的Cookie,以查看是否指定了LocaleTimeZone。如果是,它使用指定的内容。使用这个区域解析器的属性,您可以指定cookie的名称和最大存活时间。下面是一个定义CookieLocaleResolver的例子。

  1. <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver">
  2. <property name="cookieName" value="clientlanguage"/>
  3. <!-- in seconds. If set to -1, the cookie is not persisted (deleted when browser shuts down) -->
  4. <property name="cookieMaxAge" value="100000"/>
  5. </bean>

表3 CookieLocaleResolver属性

属性 默认值 说明
cookieName classname + LOCALE cookie的名称
cookieMaxAge Servlet容器默认 cookie在客户端上停留的最长时间。如果指定-1,cookie将不会永久存在;只有在客户端关闭浏览器之前才可用。
cookiePath / 将cookie的可见性限制在站点的某个部分。当指定cookiePath时,cookie只会在这个路径和这个路径下可见。

Session解析器

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

注意,这与Spring会话项目等外部会话管理机制没有直接关系。这个SessionLocaleResolver将针对当前的HttpServletRequest简单地评估和修改相应的HttpSession属性。

本地化拦截器

您可以通过将LocaleChangeInterceptor添加到一个处理程序映射(参见[mvc - handlermapping])来启用本地化区域的修改。它将检测请求中的参数并更改区域设置。它调用在上下文中也存在的LocaleResolver上的setLocale()。下面的示例显示了对所有*.view资源的调用,包含名为siteLanguage的参数现在将更改区域设置。例如,以下的请求URL,http://www.sf.net/home.view?siteLanguage=nl将把网站语言改为荷兰语。

  1. <bean id="localeChangeInterceptor"
  2. class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
  3. <property name="paramName" value="siteLanguage"/>
  4. </bean>
  5. <bean id="localeResolver"
  6. class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/>
  7. <bean id="urlMapping"
  8. class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
  9. <property name="interceptors">
  10. <list>
  11. <ref bean="localeChangeInterceptor"/>
  12. </list>
  13. </property>
  14. <property name="mappings">
  15. <value>/**/*.view=someController</value>
  16. </property>
  17. </bean>

1.2.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时,主题是在一个简单的属性文件中定义的。属性文件列出了构成主题的资源。这里有一个例子:

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

属性的键是指从视图代码中引用主题元素的名称。对于JSP来说,通常使用spring:theme定制标记,它与spring:message标记非常相似。下面的JSP片段使用上一个示例中定义的主题来定制外观:

  1. <%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
  2. <html>
  3. <head>
  4. <link rel="stylesheet" href="<spring:theme code='styleSheet'/>" type="text/css"/>
  5. </head>
  6. <body style="background=<spring:theme code='background'/>">
  7. ...
  8. </body>
  9. </html>

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

解析主题

在定义主题之后,如前一节中,您决定使用哪个主题。DispatcherServlet将寻找一个名为themeResolver的bean,来找出使用哪个主题。主题解析器与LocaleResolver的工作方式非常相似。它检测主题以用于特定的请求,也可以更改请求的主题。以下的主题解析器是Spring提供的:

表4 ThemeResolver实现

说明
FixedThemeResolver 选择使用defaultThemeName属性设置的固定主题。
SessionThemeResolver 主题在用户的HTTP会话中维护。它只需要为每个会话设置一次,但不会在会话之间一直有效。
CookieThemeResolver 选定的主题存储在客户端的cookie中。

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

1.2.11 多部分请求

Spring内置了对包括文件上传在内的多部分请求的支持。您可以使用可插入的MultipartResolver对象(在org.springframework.web.multipart包中定义的)来启用这个多部分支持。Spring提供了一个MultipartResolver实现,用于使用Commons FileUpload,以及其他用于Servlet 3.0多部分请求解析。

在默认情况下,Spring不做多部分处理,因为一些开发人员想自己处理多部分内容。你可以通过在web应用程序的上下文中添加多部分解析器来启用Spring的多部分处理。每个请求都被检查是否包含多个部分。如果没有找到多部分,请求将按照预期继续。如果在请求中发现了多部分,那么在您的上下文中声明的MultipartResolver将会被调用。在此之后,请求中的多部分属性就像其他属性一样对待。

Commons FileUpload

要使用Apache Commons FileUpload,配置一个名为multipartResolverCommonsMultipartResolver类型的bean。当然,您还需要在您的类路径中添加commons-fileupload的依赖项。

当Spring DispatcherServlet检测到一个多部分请求时,它将激活在您的上下文中声明的解析器,并对请求进行处理。然后解析器包装当前HttpServletRequestMultipartHttpServletRequest中用于支持多部分文件上传。使用MultipartHttpServletRequest,你可以在你的控制器中通过这个请求多部分包含的信息实际获得多部分文件本身。

Servlet 3.0

为了使用Servlet 3.0的多部分解析,您需要在web.xml中使用"multipart-config"部分标记DispatcherServlet,或使用javax.servlet.MultipartConfigElement编写Servlet注册,或在您的Servlet类上通过javax.servlet.annotation.MultipartConfig注解自定义一个Servlet类。由于Servlet 3.0不允许从MultipartResolver中完成这些设置,所以需要在Servlet注册级别应用最大大小或存储位置等配置设置。

一旦在上述方法之一启用Servlet 3.0多部分解析,您可以添加一个StandardServletMultipartResolver类型的bean,使用名称multipartResolver添加到你的Spring配置。

1.3 过滤器

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

1.3.1 HTTP PUT表单

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

spring web模块提供HttpPutFormContentFilter拦截具有内容类型为application/x-www-form-urlencoded的HTTP PUT和PATCH请求,从请求的正文中读取表单数据,并且封装ServletRequest以便可以通过ServletRequest.getParameter*()方法家族让表单数据可用。

1.3.2 转发头

当请求通过诸如负载平衡器、主机、端口和scheme等代理时,可能会改变对需要创建链接的应用程序的挑战,因为链接应该从客户端角度反映原始请求的主机、端口和方案。

RFC 7239定义了“Forwarded”的HTTP头,用于代理提供关于原始请求的信息。还有其他非标准头部,使用如“X-Forwarded-Host”、“X-Forwarded-Port”和“X-Forwarded-Proto”。

ForwardedHeaderFilter检测,提取,并使用来自“Forwarded”头部的信息,或者从“X-Forwarded-Host”,“X-Forwarded-Port”,和“X-Forwarded-Proto”的非标准头部信息。它包装请求以覆盖它的主机、端口和scheme,并“隐藏”forwarded标题以供后续处理。

请注意,在RFC 7239的第8部分中提到,在解释使用的转发标题,存在安全考虑。在应用程序级别,很难确定是否可以信任被转发的头部。这就是为什么应该正确配置网络上游来过滤掉不受信任的外部消息头。

没有代理的应用程序和不需要使用转发头的应用程序可以配置ForwardedHeaderFilter来删除和忽略这些头信息。

1.3.3 浅ETag

有一个ShallowEtagHeaderFilter。它被称为浅层,因为它对内容一无所知。相反,它依赖于将实际内容写入响应,并最终计算ETag值。

更多详细内容,请查看ETag过滤器

1.3.4 CORS

Spring MVC通过控制器上的注解为CORS配置提供了细粒度的支持。然而,当使用Spring Security时,最好依赖内置的CorsFilter,它必须在Spring Security的过滤器链前面设定。

有关更多详细信息,请参阅CORSCorsFilter部分。

1.4 注解控制器

Spring WebFlux中的同样内容

Spring MVC提供了一个基于注解的编程模型,其中@Controller@RestController组件使用注释来表达请求映射、请求输入、异常处理等。带注解的控制器具有灵活的方法签名,不必扩展基类,也不需要实现特定的接口。

  1. @Controller
  2. public class HelloController {
  3. @GetMapping("/hello")
  4. public String handle(Model model) {
  5. model.addAttribute("message", "Hello World!");
  6. return "index";
  7. }
  8. }

在这个特殊的例子中,该方法接受一个模型并返回一个视图名称作为字符串,但是还有许多其他选项存在,并在本章后面进一步解释。

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

1.4.1 声明

Spring WebFlux中的同样内容

你可以在ServletWebApplicationContext中使用标准的Spring bean定义来定义控制器bean。@Controller原型允许自动检测,与Spring一样支持在类路径中检测@Component类,并为它们自动注册bean定义。它还作为注释类的原型,指示其作为web组件的角色。

要启用这种@Controller bean的自动检测,你可以在你的Java配置中增加组件扫描:

  1. @Configuration
  2. @ComponentScan("org.example.web")
  3. public class WebConfig {
  4. // ...
  5. }

相当于XML配置:

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xmlns:p="http://www.springframework.org/schema/p"
  5. xmlns:context="http://www.springframework.org/schema/context"
  6. xsi:schemaLocation="
  7. http://www.springframework.org/schema/beans
  8. http://www.springframework.org/schema/beans/spring-beans.xsd
  9. http://www.springframework.org/schema/context
  10. http://www.springframework.org/schema/context/spring-context.xsd">
  11. <context:component-scan base-package="org.example.web"/>
  12. <!-- ... -->
  13. </beans>

@RestController是由@Controller@ResponseBody注释组合而来的组合注释,它知识一个控制器,它的每个方法都继承了类型级别(type-level)的@ResponseBody注释,因此写入响应主体(同模型和视图呈现相比)。

AOP代理

在一些案例中,一个控制器可能需要在运行时使用AOP代理进行修饰。一个例子是如果你选择@Transactional直接在控制器上注释。这种情况下,对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果一个控制器必须实现一个接口,这不是一个Spring Context回调(例如:InitializingBean*Aware等),你可能需要去显式配置基于类的代理。例如将<tx:annotation-driven/>变更为<tx:annotation-driven proxy-target-class="true"/>

1.4.2 请求映射

Spring WebFlux中的同样内容

@RequestMapping注释用于将请求映射到控制器方法。它有不同的属性,可以通过URL、HTTP方法、请求参数、头部和媒体类型匹配。可以在累计别使用它来表示共享映射或在方法级别上缩小到特定的端点映射。

这里也有@RequestMapping的HTTP方法特定简写:

快捷方式的变体是组合注解——它们自己使用@RequestMapping注解。它们通常在方法级别使用。在类级别上,@RequestMapping对于表达共享映射更有用。

  1. @RestController
  2. @RequestMapping("/persons")
  3. class PersonController {
  4. @GetMapping("/{id}")
  5. public Person getPerson(@PathVariable Long id) {
  6. // ...
  7. }
  8. @PostMapping
  9. @ResponseStatus(HttpStatus.CREATED)
  10. public void add(@RequestBody Person person) {
  11. // ...
  12. }
  13. }

URI路径模式

Spring WebFlux中的相同内容

你可以使用路径模式和通配符来映射请求:

你还可以声明URI变量并使用@PathVariable访问它们的值。

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

可以在类和方法级别声明URI变量:

  1. @Controller
  2. @RequestMapping("/owners/{ownerId}")
  3. public class OwnerController {
  4. @GetMapping("/pets/{petId}")
  5. public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
  6. // ...
  7. }
  8. }

URI变量被自动转换为适当的类型或TypeMismatchException。简单类型——默认情况下支持intlongDate,你可以为其他数据类型注册支持。参加类型转换[mvc-ann-webdatabinder]

URI变量可以显示命名——例如@PathVariable("customId"),但是如果名称是相同的,你可以将该详细信息保留下来,并且您的代码是用调试信息或Java 8的-parameters编译器标记编译的。

语法{varName:regex}声明了一个URI变量,使用语法{varName:regex}来使用正则表达式——例如,给定URL"/spring-web-3.0.5 .jar",使用下面的方法提取名称、版本和文件拓展名:

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

URI路径模式可以解决嵌入的${…​}占位符,在启动时通过PropertyPlaceHolderConfigurer对本地、系统、环境和其他属性来源。这可以用于以一些外部位置为基础来参数化一个基本URL。

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

模式比较

Spring WebFlux中的同样内容

在多个模式匹配一个URL时,它们必须使用比较来找到最佳匹配。这通过AntPathMatcher.getPatternComparator(String path)来查找更具体的形式。

如果一个模式的URI变量数较低,并且单个通配符数为1,而双通配符数为2,那么该模式就不那么具体了。给定一个相等的分数,选择较长的形式。给定相同的分数和长度,选择比通配符多的URI变量的模式。

默认的映射模式/**被排除在评分之外,并且总是排在最后。另外,诸如/public/**之类的前缀模式被认为比没有双通配符的其他模式更具体。

对于完整的细节,请查看AntPathMatcher中的AntPatternComparator,同时也要注意使用的PathMatcher实现可以定制,参加配置部分中的路径匹配

后缀匹配

默认的Spring MVC执行".*"后缀模式匹配以便将控制器映射到/person,也隐式映射到/person.*。这是用于基于URL的内容协商,例如/person.pdf/person.xml等。

当浏览器用于发送难以持续的接收头时,后缀模式匹配非常有用。在当前和REST服务中,Accept标头应该是首选项。

后缀模式可以在与路径参数、编码字符和URI变量的组合中造成歧义和复杂性。这也使得基于URL授权规则和安全性变得更加困难(参见后缀模式匹配和RFD)。

后缀模式匹配可以完全关闭或限制为一组显示注册的路径扩展。我们强烈建议使用这些选项。参见路径匹配请求的内容类型。如果需要基于URL的内容协商,可以考虑使用查询参数。

后缀模式匹配和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头。详见请求内容类型

参阅CVE-2015-5211,了解与RFD相关的其他建议。

矩阵变量

URI规范RFC 3986定义了在路径段中包含名称-值对的可能性。在规范中没有使用具体的术语。可以应用一般的“URI路径参数”,尽管更独特的“矩阵URI”,源自Tim Berners-Lee的一篇旧文章,也经常被使用并且相当有名。在Spring MVC中,这些被称为矩阵变量。

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

如果希望URL包含矩阵变量,则请求映射模式必须使用URI模板表示它们。这确保了请求可以正确匹配,不管矩阵变量是否存在,以及以什么顺序提供。

下面是一个提取矩阵变量"q"的例子:

  1. // GET /pets/42;q=11;r=22
  2. @GetMapping("/pets/{petId}")
  3. public void findPet(@PathVariable String petId, @MatrixVariable int q) {
  4. // petId == 42
  5. // q == 11
  6. }

由于所有路径段可能包含矩阵变量,在某些情况下,你需要更具体的确定该变量的位置:

  1. // GET /owners/42;q=11/pets/21;q=22
  2. @GetMapping("/owners/{ownerId}/pets/{petId}")
  3. public void findPet(
  4. @MatrixVariable(name="q", pathVar="ownerId") int q1,
  5. @MatrixVariable(name="q", pathVar="petId") int q2) {
  6. // q1 == 11
  7. // q2 == 22
  8. }

矩阵变量可以定义为可选的,包含默认值的:

  1. // GET /pets/42
  2. @GetMapping("/pets/{petId}")
  3. public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) {
  4. // q == 1
  5. }

所有矩阵变量均可在Map中得到:

  1. // GET /owners/42;q=11;r=12/pets/21;q=22;s=23
  2. @GetMapping("/owners/{ownerId}/pets/{petId}")
  3. public void findPet(
  4. @MatrixVariable MultiValueMap<String, String> matrixVars,
  5. @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {
  6. // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
  7. // petMatrixVars: ["q" : 22, "s" : 23]
  8. }

注意:启用使用矩阵变量,你必须设置RequestMappingHandlerMappingremoveSemicolonContent属性设置为false。默认设置为true

MVC Java配置和MVC命名空间都提供了允许使用矩阵变量的选项。

如果你使用的是Java配置,使用MVC Java配置进行高级自定义章节描述RequestMappingHandlerMapping如何自定义。

在MVC命名空间中,<mvc:annotation-driven>元素有一个enable-matrix-variables属性需要设置为ture。默认为false。

  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <beans xmlns="http://www.springframework.org/schema/beans"
  3. xmlns:mvc="http://www.springframework.org/schema/mvc"
  4. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  5. xsi:schemaLocation="
  6. http://www.springframework.org/schema/beans
  7. http://www.springframework.org/schema/beans/spring-beans.xsd
  8. http://www.springframework.org/schema/mvc
  9. http://www.springframework.org/schema/mvc/spring-mvc.xsd">
  10. <mvc:annotation-driven enable-matrix-variables="true"/>
  11. </beans>

可消费的用户类型

Spring WebFlux中的同样内容

你可以根据请求的Content-Type来缩小请求映射:

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

消费属性也支持否定表达式——例如:!text/plain指的是除"text/plain"以外的任何内容类型。

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

MediaType为常用的媒体类型提供常量,例如:APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

可延长的媒体类型

Spring WebFlux中的同样内容

你可以根据Accept请求头和控制器方法产生的内容类型列表缩小请求映射。

  1. @GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8")
  2. @ResponseBody
  3. public Pet getPet(@PathVariable String petId) {
  4. // ...
  5. }

媒体类型可以指定一个字符集。支持否定表达式——例如:!text/plain指的是"text/plain"以外的任何内容类型

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

MediaType提供了常用的介质类型的常量——例如:APPLICATION_JSON_VALUEAPPLICATION_JSON_UTF8_VALUE

参数,标题

Spring WebFlux中的同样内容

你可以根据请求参数条件缩小请求映射。你可以测试是否存在请求参数("myParam"),判断是否没有参数("!myParam"),或者特定值("myParam=myValue"):

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

你也可以使用与请求头条件相同的条件:

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

你可以匹配Content-TypeAccept标题条件,但是最好是使用消费生产

HTTP HEAD,OPTIONS

Spring WebFlux中的同样内容

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

@GetMapping——而且还要@RequestMapping(method=HttpMethod.GET)隐式的映射到并且还支持HTTP HEAD。HTTP HEAD请求被处理,就像HTTP GET一样,但是除了编写正文之外,还要计数字节数,并设置"Content-Length"头。

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

对于@RequestMapping没有HTTP方法声明,"Allow"允许标题设置为"GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS"。控制器方法应该总是使用HTTP方法的具体变体声明所支持的HTTP方法,例如:@GetMapping@PostMapping等。

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

自定义注解

Spring WebFlux中的同样内容

Spring MVC支持使用组合注解来请求映射。这些注解本身就是带有@RequestMapping的元注解,并组合成以更窄、更具体的目的重新声明@RequestMapping属性的子集(或全部)。
@GetMapping@PostMapping@PutMapping@DeleteMapping@PatchMapping都是组合注解的例子。他们都是开箱即用的,因为大多数的控制器方法应该映射到特定的HTTP方法,而使用@RequestMapping默认情况下与所有HTTP方法相匹配。如果你需要组合注解的示例,可以查看他们是怎么声明的。

Spring MVC同样支持自定义请求映射属性和自定义请求匹配逻辑。这是一个更为高级的选项,需要生成子类RequestMappingHandlerMapping并重写getCustomMethodCondition方法,你可以检查自定义属性并且返回你自己的RequestCondition

1.4.3 处理程序方法

Spring WebFlux中的同样内容

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

方法参数

Spring WebFlux中的同样内容

下表显示控制的控制器方法参数。任何参数都不支持反应类型。

JDK 1.8的java.util.Optional支持作为组合的方法参数与有注释的required属性——例如@RequestParam@RequestHeader等,并等价于required=false

控制器方法参数 描述
WebRequest, NativeWebRequest 通用访问请求参数,请求&会话参数,而不直接使用Servlet API。
javax.servlet.ServletRequest, javax.servlet.ServletResponse 选择任何特定的请求或响应类型——例如:ServletRequestHttpServletRequest, 或者Spring的MultipartRequestMultipartHttpServletRequest
javax.servlet.http.HttpSession 执行会话的存在。因此,这样的论证从来不是null。
注意:会话访问不是线程安全的。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter"synchronizeOnSession"标志设置为"true"
javax.servlet.http.PushBuilder 用于编程HTTP/2资源推送的Servlet 4.0推送构建器API。
java.security.Principal 当前认证用户;如果已知的话,可能是一个特定的Principal实现类。
HttpMethod 请求的HTTP方法。
java.util.Locale 当前的请求区域设置有最具体的LocaleResolver可用性确定,实际上由配置的LocaleResolver/LocaleContextResolver确定。
Java 6+: java.util.TimeZone
Java 8+: java.time.ZoneId
与当前请求相关联的时区,由一个LocaleContextResolver决定。
java.io.InputStream, java.io.Reader 访问由Servlet API公开的原始请求正文。
java.io.OutputStream, java.io.Writer 访问由Servlet API公开的原始响应体。
@PathVariable 访问URI末班变量,请参阅URI路径模式
@MatrixVariable 访问URI路径段中的名称/值对。参见矩阵变量
@RequestParam 访问Servlet请求参数。参数值将转换为声明的方法参数类型。参阅使用@RequestParam将请求参数绑定到方法参数
@RequestHeader 访问请求头。标题值将转换为声明的方法参数类型。参阅使用@RequestHeader注释映射请求标头属性
@RequestBody 访问HTTP请求体。正文内容使用HttpMessageConverter转换为声明的方法参数类型。参阅使用@RequestBody注释映射请求正文
HttpEntity<B> 访问请求标头和正文,正文使用HttpMessageConverter进行转换。参阅使用HttpEntity
@RequestPart 访问"multipart/form-data"请求中的一部分。参阅从程序化客户端处理文件上传请求多部分(文件上传)支持
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap 用于访问和更新暴露于Web视图的隐式模型。
RedirectAttributes 指定在重定向的情况下使用的属性——即附加到查询字符串and/orflash属性,以便临时存储,直到重定向后的请求。参阅将数据传递到重定向目标使用闪存属性
命令或表单对象 (带可选@ModelAttribute) 根据@InitBinder方法and/or``HandlerAdapter配置(参见RequestMappingHandlerAdapterwebBindingInitializer属性_,可以将属性绑定到请求参数的命令对象(通过setter或直接到字段)进行可定制的类型转换。

命令对象及其验证结果将作为模型属性公开,默认情况下使用命令类名——例如:"some.package.OrderAddress"类型的命令对象的model属性"orderAddress"
@ModelAttribute可用于自定义模型属性名称。
Errors, BindingResult 命令/表单对象数据绑定的验证结果;必须在控制器方法签名后的命令/表单对象之后立即声明此参数。
SessionStatus 用于标记表单处理完成,它触发清除通过类级别@SessionAttributes注释声明的会话属性。
UriComponentsBuilder 为了准备相对于当前请求的主机、端口、计划、上下文路径和Servlet映射的文字部分的URL也要考虑ForwardedX-Forwarded-*标题。
@SessionAttribute 访问任何会话属性;与通过类级别@SessionAttributes声明的结果存储在会话中的模型属性相反。
@RequestAttribute 访问请求属性

返回值

Spring WebFlux中的同样内容

下表显示支持的控制器方法返回值。支持所有返回值的响应类型,详细信息,参见下表。

控制器方法返回值 描述
@ResponseBody 返回值通过HttpMessageConverter转换并写入响应。参阅使用@ResponseBody注释映射响应正文
HttpEntity<B>, ResponseEntity<B> 返回值指定完整的响应,包括HTTP头和主体通过HttpMessageConverter转换并写入响应。参阅使用HttpEntity
HttpHeaders 返回一个带有标题的响应,没有主体。
String 视图名称可以用ViewResolver解决,并与隐式模型一起使用——通过命令对象和@ModelAttribute方法确定。处理程序方法还可以通过声明模型参数以编程方式丰富模型(见上文)。
View 用于呈现与隐式模型的View实例——通过命令对象和@ModelAttribute方法确定。处理程序方法也可以通过声明一个Model参数以编程方式丰富模型(见上文)。
java.util.Map, org.springframework.ui.Model 要添加到隐式模型中的属性,通常通过一个RequestToViewNameTranslator隐式确定视图名称。
ModelAndView对象 要使用的视图和模型属性,以及可选的响应状态。
void 用于声明ServletResponseOutputStreamFor参数并写入响应体的方法;或者可以使用RequestToViewNameTranslator隐式确定视图名称。
Callable<V> 在Spring MVC管理的线程中异步生成上述任何返回值。
DeferredResult<V> 从任何现成异步生成上述返回值——例如可能是由于某些事件或回调。
ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V> 替代方法DeferredResult,例如当底层服务返回其中之一时。
ResponseBodyEmitter, SseEmitter 以异步方式发出一个对象流,使用HttpMessageConverter写入响应;也支持ResponseEntity的正文。
StreamingResponseBody OutputStream异步写入响应;也支持ResponseEntity
Reactive类型——Reactor, RxJava, 或者其他通过ReactiveAdapterRegistry 替代`DeferredResult与多值流(例如Flux, Observable)收集List

对于流媒体场景——例如:text/event-stream, application/json+stream, SseEmitterResponseBodyEmitter都被使用,其中ServletOutputStream阻塞I/O是在Spring MVC管理的线程和支持每个写完成的压力下执行的。

参阅具有反应类型的异步请求
任何其他返回类型 单个模型属性添加到隐式模型与视图,通过RequestToViewNameTranslator隐式确定名称;属性名称可以通过方法级别的@ModelAttribute指定,否则根据返回类型的类名选择名称。

@RequestParam

使用@RequestParam注释将请求参数绑定到控制器中的方法参数。

下面的代码片段显示了用法

  1. @Controller
  2. @RequestMapping("/pets")
  3. @SessionAttributes("pet")
  4. public class EditPetForm {
  5. // ...
  6. @GetMapping
  7. public String setupForm(@RequestParam("petId") int petId, ModelMap model) {
  8. Pet pet = this.clinic.loadPet(petId);
  9. model.addAttribute("pet", pet);
  10. return "petForm";
  11. }
  12. // ...
  13. }

使用这个注解的参数默认情况下是必须的,但你可以通过设置@RequestParamrequired属性值为false(例如:@RequestParam(name="id", required=false))来指定一个参数可选。

如果目标方法参数类型不是String,则会自动进行应用类型转换。参见类型转换

当一个@RequestParam注释用在一个Map<String, String>MultiValueMap<String, String>参数上时,映射将会填充所有请求参数。

类型转换

从请求中提取的基于字符串的值包括请求参数、路径变量、请求头和cookie值可能需要转换为它们绑定到的方法参数或字段的目标类型(例如,将请求参数绑定到@ModelAttribute参数中的字段)。如果目标类型不是String,则Spring会自动转换为适当的类型。所有的简单类型,例如int,long,Date等都支持。你可以通过WebDataBinder进一步自定义转换过程,查看绑定方法,或通过注册FormattersFormattingConversionService,查看Spring字段转换

@RequestHeader

@RequestHeader注解允许一个方法参数绑定到一个请求头

这里是一个请求头的示例:

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

下面的代码示例显示了如何获取Accept-EncodingKeep-Alive头的值。

  1. @RequestMapping("/displayHeaderInfo.do")
  2. public void displayHeaderInfo(@RequestHeader("Accept-Encoding") String encoding,
  3. @RequestHeader("Keep-Alive") long keepAlive) {
  4. //...
  5. }

如果方法参数不是String,那么类型转换将自动应用,将参数转换为String。查看类型转换

当在Map<String, String>MultiValueMap<String, String>或者HttpHeaders参数上使用@RequestHeader注解时,map将会被填充所有头部值。

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

@CookieValue

@CookieValue注解允许一个方法参数绑定到一个HTTP cookie的值上。

让我们考虑下面这个cookie已经收到了一个HTTP请求。

  1. JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84

下面的代码演示了如何获取JSESSIONID cookie的值:

  1. @RequestMapping("/displayHeaderInfo.do")
  2. public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
  3. //...
  4. }

如果方法参数不是String,那么类型转换将自动应用,将参数转换为String。查看类型转换

@ModelAttribute

如上一节所述,@ModelAttribute可以在方法或者方法参数上使用。本节介绍了其在方法参数中的使用。

一个方法参数上的@ModelAttribute指示这个参数应该从模型中检索。如果模型不存在,则首先将该参数实例化,然后添加到模型中。一旦出现在模型中,参数的字段应该从具有匹配名称的所有请求参数中填充。这被称为Spring MVC中的数据绑定,这是一种非常有用的机制,可以节省你逐个解析每个表单字段。

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

上述实例中,Pet实例可以从哪里来?有几个选择:

@ModelAttribute方法是从数据库中检索属性的常用方法,可以通过使用@SessionAttributes在请求之间进行存储。在某些情况下,通过使用URI模板变量和类型转换器来检索属性可能更方便。这里有一个例子:

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

在这个例子中,模型属性(即"account")的名称与URI模板变量的名称相匹配。如果你注册Converter<String, Account>可以将String account值转换为一个Account实例,则上述实例将无需使用@ModelAttribute方法。

下一步是数据绑定。WebDataBinder类匹配请求参数名称包括查询字符串参数化表单字段——使用名称转换为模型属性列。在必要时已经应用了类型转换(从String到目标字段类型)之后填充匹配字段。数据绑定和验证都属于验证。在自定义WebDataBinder初始化过程中为控制器级别定制数据绑定过程。

由于数据绑定的结果可能会出现错误,例如缺少必填字段或类型转换错误。要检查这样的错误,请在@ModelAttribute参数之后立即添加一个BindingResult参数:

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

使用BindingResult,你可以检查是否发现错误,在这种情况下,在Spring的<errors>表单标记的帮助下,可以显示错误的相同形式。

注意,在某些情况下,在没有数据绑定的情况下,可以访问模型中的属性。对于这种情况,你可以将其Model注入控制器,或者使用注释上的binding标记:

  1. @ModelAttribute
  2. public AccountForm setUpForm() {
  3. return new AccountForm();
  4. }
  5. @ModelAttribute
  6. public Account findAccount(@PathVariable String accountId) {
  7. return accountRepository.findOne(accountId);
  8. }
  9. @PostMapping("update")
  10. public String update(@Valid AccountUpdateForm form, BindingResult result,
  11. @ModelAttribute(binding=false) Account account) {
  12. // ...
  13. }

除了数据绑定之外,你还可以使用给自己的自定义验证器调用验证,传递与BindingResult一样的用于记录数据绑定错误的验证器。这允许在一个地方累计数据绑定和验证错误,然后向用户报告:

  1. @PostMapping("/owners/{ownerId}/pets/{petId}/edit")
  2. public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
  3. new PetValidator().validate(pet, result);
  4. if (result.hasErrors()) {
  5. return "petForm";
  6. }
  7. // ...
  8. }

或者你可以通过添加JSR-303 @Valid注释自动调用验证:

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

有关如何配置和使用验证的详细信息,请参阅Bean验证和[Spring验证]
(https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation)

文件上传

MultipartResolver完成它的工作后,请求就像其他的一样处理。首先,创建一个带有文件输入的表单,允许用户上传表单。编码属性(enctype="multipart/form-data")让浏览器知道如何将表单编码为多部分请求:

  1. <html>
  2. <head>
  3. <title>Upload a file please</title>
  4. </head>
  5. <body>
  6. <h1>Please upload a file</h1>
  7. <form method="post" action="/form" enctype="multipart/form-data">
  8. <input type="text" name="name"/>
  9. <input type="file" name="file"/>
  10. <input type="submit"/>
  11. </form>
  12. </body>
  13. </html>

下一步是创建一个控制器处理文件上传。这个控制器非常类似于一个正常的注解@Controller,除了我们在方法参数中使用MultipartHttpServletRequestMultipartFile

  1. @Controller
  2. public class FileUploadController {
  3. @PostMapping("/form")
  4. public String handleFormUpload(@RequestParam("name") String name,
  5. @RequestParam("file") MultipartFile file) {
  6. if (!file.isEmpty()) {
  7. byte[] bytes = file.getBytes();
  8. // 在一些地方存储字节
  9. return "redirect:uploadSuccess";
  10. }
  11. return "redirect:uploadFailure";
  12. }
  13. }

注意@RequestParam方法参数如何映射到表单中声明的input元素,在这个例子中,没有对byte[]做任何事情,但是在实践中,你可以保存在数据库中,存储在文件系统等等。

当使用Servlet 3.0多部分解析时,你也可以在方法参数中使用javax.servlet.http.Part

  1. @Controller
  2. public class FileUploadController {
  3. @PostMapping("/form")
  4. public String handleFormUpload(@RequestParam("name") String name,
  5. @RequestParam("file") Part file) {
  6. InputStream inputStream = file.getInputStream();
  7. // store bytes from uploaded file somewhere
  8. return "redirect:uploadSuccess";
  9. }
  10. }

@SessionAttributes

@SessionAttributes用于在请求之间的HTTP会话中存储模型属性。这是用于使用特定处理程序声明会话属性一个类型级注解。这通常将列出模型属性的名称或模型属性的类型,这些属性或类型应该透明的存储在会话或者一些对话存储中,在后续请求之间提供表单支持bean。

以下代码片段显示了这个注释的用法,指定模型属性名称:

  1. @Controller
  2. @RequestMapping("/editPet.do")
  3. @SessionAttributes("pet")
  4. public class EditPetForm {
  5. // ...
  6. }

@SessionAttribute

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

  1. @RequestMapping("/")
  2. public String handle(@SessionAttribute User user) {
  3. // ...
  4. }

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

作为控制器工作流的一部分,在会话中临时存储模型属性,可以考虑在[mvc-ann-sessionattrib]中说明的声明使用SessionAttributes

@RequestAttribute

类似于@SessionAttribute@RequestAttribute注释可以用来访问由筛选器或拦截器创建的预先存在的请求属性:

  1. @RequestMapping("/")
  2. public String handle(@RequestAttribute Client client) {
  3. // ...
  4. }

重定向属性

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

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

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

请注意,在扩展重定向URL时自动提供了来自当前请求的URI模板变量,并且不需要通过模型和RedirectAttributes显式添加。例如:

  1. @PostMapping("/files/{path}")
  2. public String upload(...) {
  3. // ...
  4. return "redirect:files/{path}";
  5. }

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

Flash属性

Flash属性提供了一种方法,可以将一个请求用于存储的属性存储在另一个请求中。这在重定向时最常见——例如,Post/Redirect/Get模式。在重定向并立即删除重定向之后,在重定向(通常是在会话中)之前暂时保存Flash属性。

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

Flash属性支持始终处于“打开”状态,不需要显式启用,尽管如果不使用,它也不会导致HTTP会话的创建。在每个请求上,都有一个其属性从先前的请求(如果有的话)的中传递的“输入”的FlashMap,和一个“输出”FlashMap,去将其属性保存为后续请求。在Spring MVC的任何地方,都可以通过RequestContextUtils中的静态方法访问FlashMap实例。

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

将请求与flash属性匹配

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

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

这并没有完全消除并发性问题的可能性,但却大大减少了在重定向URL中已经可用的信息。因此,建议使用flash属性主要用于重定向场景。

@RequestPart

还可以在RESTful服务场景中从非浏览器客户端提交多部分请求。上面的所有示例和配置也适用于这里。但是,与通常提交文件和简单表单字段的浏览器不同,编程客户机还可以发送更复杂的特定内容类型的数据——例如,一个包含文件的多部分请求和JSON格式的数据的第二部分:

  1. POST /someUrl
  2. Content-Type: multipart/mixed
  3. --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
  4. Content-Disposition: form-data; name="meta-data"
  5. Content-Type: application/json; charset=UTF-8
  6. Content-Transfer-Encoding: 8bit
  7. {
  8. "name": "value"
  9. }
  10. --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp
  11. Content-Disposition: form-data; name="file-data"; filename="file.properties"
  12. Content-Type: text/xml
  13. Content-Transfer-Encoding: 8bit
  14. ... 文件数据 ...

您可以使用@RequestParam("meta-data")字符串元数据控制器方法参数来访问名为"meta-data"的部分。然而,您可能更愿意接受由请求部分的JSON格式的数据初始化的强类型对象,这与@RequestBodyHttpMessageConverter的帮助下将非多部分请求的主体转换成目标对象的方式非常类似。

为此,可以使用@RequestPart注解而不是@RequestParam注解。它允许您通过HttpMessageConverter获得一个特定的多部分内容,并考虑到多部分的'Content-Type'头:

  1. @PostMapping("/someUrl")
  2. public String onSubmit(@RequestPart("meta-data") MetaData metadata,
  3. @RequestPart("file-data") MultipartFile file) {
  4. // ...
  5. }

请注意,可以使用@RequestParam@RequestPart交换访问多段文件方法参数。然而,@RequestPart("meta-data") MetaData方法参数在本例中为JSON内容根据其'Content-Type'的标题和MappingJackson2HttpMessageConverter的帮助下转换。

@RequestBody

@RequestBody方法参数注释表明方法参数应该与HTTP请求主体的值绑定。例如:

  1. @PutMapping("/something")
  2. public void handle(@RequestBody String body, Writer writer) throws IOException {
  3. writer.write(body);
  4. }

你可以使用HttpMessageConverter将请求正文转换为方法参数。HttpMessageConverter负责将HTTP请求消息转换为对象,并且从对象转换为HTTP响应正文。RequestMappingHandlerAdapter支持@RequestBody使用以下默认HttpMessageConverter注释

有关这些转换器的更多信息,请参阅消息转换器。另请注意,如果使用MVC命名空间或MVC Java配置,默认情况下注册更广泛的消息转换器。有关详细信息,请参阅启用MVC配置

对于自定义示例,如果你打算使用spring-oxm模块读取和写入XML,则需要从org.springframework.oxm包的特定Marshaller实现中配置MarshallingHttpMessageConverter。下面的示例显示了如何在配置中直接执行此操作,但如果你的应用程序通过MVC命名空间或MVC Java配置进行配置,请参阅启用MVC配置

  1. <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
  2. <property name="messageConverters">
  3. <util:list id="beanList">
  4. <ref bean="stringHttpMessageConverter"/>
  5. <ref bean="marshallingHttpMessageConverter"/>
  6. </util:list>
  7. </property>
  8. </bean>
  9. <bean id="stringHttpMessageConverter"
  10. class="org.springframework.http.converter.StringHttpMessageConverter"/>
  11. <bean id="marshallingHttpMessageConverter"
  12. class="org.springframework.http.converter.xml.MarshallingHttpMessageConverter">
  13. <property name="marshaller" ref="castorMarshaller"/>
  14. <property name="unmarshaller" ref="castorMarshaller"/>
  15. </bean>
  16. <bean id="castorMarshaller" class="org.springframework.oxm.castor.CastorMarshaller"/>

@RequestBody方法参数可以使用@Valid进行注释,在这种情况下,它将使用配置的验证Validator实例进行验证。当使用MVC命名空间或MVC Java配置时,会自动配置一个JSR-303验证器,假设在类路径上可用JSR-303实现。

就像@ModelAttribute参数一样,Errors可以使用参数来检查错误。如果没有声明这样的参数,MethodArgumentNotValidException将会声明一个。该异常在处理DefaultHandlerExceptionResolver时,会发送404错误回到客户端。

另请参阅启用MVC配置以获取有关通过MVC命名空间或MVC Java配置配置消息转换器和验证器的信息。

HttpEntity

HttpEntity@RequestBody@ResponseBody相似。但是HttpEntity还允许访问请求和响应头,如下所示:

  1. @RequestMapping("/something")
  2. public ResponseEntity<String> handle(HttpEntity<byte[]> requestEntity) throws UnsupportedEncodingException {
  3. String requestHeader = requestEntity.getHeaders().getFirst("MyRequestHeader");
  4. byte[] requestBody = requestEntity.getBody();
  5. // ...
  6. }

上述示例获取MyRequestHeader请求头的值,并将其作为字节数组读取。至于@ResponseBody,Spring使用HttpMessageConverter从请求和响应流中转换。有关这些转换器的更多信息,请参阅上一部分和消息转换器

@ResponseBody

@ResponseBody注释类似于@RequestBody。该注释可以放在一个方法上,并指示返回类型应该直接写入HTTP响应体(而不是放在模型中,或者解释为视图名称)。例如:

  1. @GetMapping("/something")
  2. @ResponseBody
  3. public String helloWorld() {
  4. return "Hello World";
  5. }

上述示例将导致文本Hello World被写入HTTP响应流。

@RequestBody一样,Spring通过使用一个转换HttpMessageConverter将返回的对象转换为响应体。有关这些转换器的更多信息,请参阅上一部分和消息转换器

ResponseEntity

类似于@ResponseBody提供响应体,ResponseEntity也允许设置响应头:

  1. @PostMapping("/something")
  2. public ResponseEntity<String> handle() {
  3. // ...
  4. URI location = ... ;
  5. return new ResponseEntity.created(location).build();
  6. }

@ResponseBody一样,Spring使用HttpMessageConverter来转换 从/到 请求和响应流。想要了解更多这个转换器的信息,请查看上一节和消息转换

Jackson JSON

Jackson序列化视图

有时候,过滤将被序列化到HTTP响应主体上的对象是有用的。为了提供这样的功能,Spring MVC内置了对Jackson序列化视图的支持。

要使用@ResponseBody控制器方法或返回ResponseEntity的控制器方法,只需添加一个类参数的@JsonView注解,指定要使用的视图类或接口:

  1. @RestController
  2. public class UserController {
  3. @GetMapping("/user")
  4. @JsonView(User.WithoutPasswordView.class)
  5. public User getUser() {
  6. return new User("eric", "7!jd#h23");
  7. }
  8. }
  9. public class User {
  10. public interface WithoutPasswordView {};
  11. public interface WithPasswordView extends WithoutPasswordView {};
  12. private String username;
  13. private String password;
  14. public User() {
  15. }
  16. public User(String username, String password) {
  17. this.username = username;
  18. this.password = password;
  19. }
  20. @JsonView(WithoutPasswordView.class)
  21. public String getUsername() {
  22. return this.username;
  23. }
  24. @JsonView(WithPasswordView.class)
  25. public String getPassword() {
  26. return this.password;
  27. }
  28. }

注意,尽管@JsonView允许指定多个类,但是控制器方法的使用只支持一个类参数。如果需要启用多个视图,请考虑使用复合接口。

对于依赖于视图解析的控制器,只需将序列化视图类添加到模型中:

  1. @Controller
  2. public class UserController extends AbstractController {
  3. @GetMapping("/user")
  4. public String getUser(Model model) {
  5. model.addAttribute("user", new User("eric", "7!jd#h23"));
  6. model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class);
  7. return "userView";
  8. }
  9. }

Jackson JSONP

为了使JSONP支持@ResponseBodyResponseEntity方法,声明一个@ControllerAdvice bean,拓展AbstractJsonpResponseBodyAdvice如下所示的构造函数参数表明JSONP查询参数名称(s):

  1. @ControllerAdvice
  2. public class JsonpAdvice extends AbstractJsonpResponseBodyAdvice {
  3. public JsonpAdvice() {
  4. super("callback");
  5. }
  6. }

对于依赖于视图解析的控制器,当请求有一个名为jsonpcallback的查询参数时,JSONP自动启用。这些名称可以通过jsonpParameterNames属性自定义。

1.4.4 模型方法

可以在方法或方法参数上使用@ModelAttribute注解。本节解释了它在方法上的用法,下一节解释了它在方法参数上的用法。

方法的@ModelAttribute注解表明该方法的目的是添加一个或多个模型属性。这些方法支持与@RequestMapping方法相同的参数类型,但不能直接映射到请求上。在同一个控制器内,在@RequestMapping方法之前调用控制器中的@ModelAttribute方法。几个例子:

  1. // 添加一个属性
  2. // 方法的返回值被添加到名为"account"下的模型中
  3. // 你可以通过@ModelAttribute("myAccount")自定义名称
  4. @ModelAttribute
  5. public Account addAccount(@RequestParam String number) {
  6. return accountManager.findAccount(number);
  7. }
  8. // 添加多个属性
  9. @ModelAttribute
  10. public void populateModel(@RequestParam String number, Model model) {
  11. model.addAttribute(accountManager.findAccount(number));
  12. // 添加更多 ...
  13. }

@ModelAttribute方法用于填充模型通常需要的属性,例如用状态或pet类型填充一个下拉列表,或者检索一个类似于Account这样的命令对象,以便用它来表示HTML表单上的数据。下一节将进一步讨论后一种情况。

注意这两种类型的@ModelAttribute方法。在第一个方法中,方法通过返回属性隐式添加一个属性。在第二个方法中,该方法接受一个模型并添加任意数量的模型属性。您可以根据您的需要选择这两种样式。

控制器可以有任意数量的@ModelAttribute方法。在同一控制器的@RequestMapping方法之前调用所有这些方法。

@ModelAttribute方法也可以在@ControllerAdvice注解类中定义,这种方法适用于许多控制器。有关更多细节,请参见Controller Advice章节

当没有显式指定模型属性名称时,会发生什么情况?在这种情况下,根据其类型将分配给模型属性一个默认名称。例如,如果该方法返回类型Account的对象,则使用的默认名称为"account"。您可以通过@ModelAttribute注解的值来更改它。如果直接向模型添加属性,则可以适当的重载addAttribute(..)方法。即,带有或没有属性名。

@ModelAttribute注解也可以在@RequestMapping方法中使用。在这种情况下,@RequestMapping方法的返回值被解释为模型属性,而不是视图名。然后根据视图名约定来派生视图名称,这很像返回void的方法——参见默认视图名称

1.4.5 绑定方法

要在Spring的WebDataBinder中定制与PropertyEditors的请求参数绑定,可以在控制器中使用@InitBinder注解方法,@ControllerAdvice类中的@InitBinder方法,或者提供自定义的WebBindingInitializer。有关更多细节,请参见Controller Advice部分

带有@InitBinder的注解控制器方法允许您直接在控制器类中配置web数据绑定。@InitBinder标识了初始化WebDataBinder的方法,它将用于填充命令,并形成带注解的处理程序方法的表单对象参数。

这种init-binder方法支持所有的参数@RequestMapping方法支持,除了命令/表单对象和相应的验证结果对象。Init-binder方法不能有返回值。因此,它们通常被声明为void。典型的参数包括WebDataBinderWebRequestjava.util.Locale相结合,允许代码注册上下文特定的编辑器。

下面的示例演示了如何使用@InitBinder为所有java.util.Date表单属性配置一个CustomDateEditor

  1. @Controller
  2. public class MyFormController {
  3. @InitBinder
  4. protected void initBinder(WebDataBinder binder) {
  5. SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
  6. dateFormat.setLenient(false);
  7. binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
  8. }
  9. // ...
  10. }

或者,在Spring 4.2中,考虑使用addCustomFormatter来指定Formatter实现而不是PropertyEditor实例。如果你碰巧有一个基于Formatter的程序在一个FormattingConversionService共享,用同样的方法来重用特定于控制器调整的绑定规则。

  1. @Controller
  2. public class MyFormController {
  3. @InitBinder
  4. protected void initBinder(WebDataBinder binder) {
  5. binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
  6. }
  7. // ...
  8. }

1.4.6 异常方法

1.4.7 Controller Advice

@ControllerAdvice注解是一个组件注解,允许通过类路径扫描自动检测实现类。当使用MVC命名空间或MVC Java配置时,它会自动启用。

带有@ControllerAdvice的类可以包含@ExceptionHandler@InitBinder@ModelAttribute注解方法,这些方法将适用于所有控制器层次上的@RequestMapping方法,而不是声明它们的控制器层次结构。

@RestControllerAdvice是一个替代方法,在默认情况下@ExceptionHandler方法假设@ResponseBody语义。

@ControllerAdvice@RestControllerAdvice都可以针对控制器的一个子集:

  1. // 针对所有@RestController注解的控制器
  2. @ControllerAdvice(annotations = RestController.class)
  3. public class AnnotationAdvice {}
  4. // 针对所有指定包的控制器
  5. @ControllerAdvice("org.example.controllers")
  6. public class BasePackageAdvice {}
  7. // 指定所有指定类的拓展类型的控制器
  8. @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
  9. public class AssignableTypesAdvice {}

更多内容请查看@ControllerAdvice javadoc。

1.5 URI链接

本节介绍Spring框架中可用于准备URI的各种选项

1.5.1 UriComponents

Spring MVC和Spring WebFlux

UriComponents可同java.net.URI相比。但它带有一个专用的UriComponentsBuilder并支持URI模板变量:

  1. String uriTemplate = "http://example.com/hotels/{hotel}";
  2. UriComponents uriComponents = UriComponentsBuilder.fromUriString(uriTemplate) //1.
  3. .queryParam("q", "{q}") //2.
  4. .build(); //3.
  5. URI uri = uriComponents.expand("Westin", "123").encode().toUri(); //4.
  1. 带有URI模板的静态工厂方法
  2. 添加或替换URI组件。
  3. 建立UriComponents
  4. 展开URI变量,编码并获取URI。

以上可以作为一条单链,并有一条捷径。

  1. String uriTemplate = "http://example.com/hotels/{hotel}";
  2. URI uri = UriComponentsBuilder.fromUriString(uriTemplate)
  3. .queryParam("q", "{q}")
  4. .buildAndExpand("Westin", "123")
  5. .encode()
  6. .toUri();

1.5.2 UriBuilder

Spring MVC和Spring WebFlux

UriComponentsBuilder是UriBuilder的一个实现。UriBuilderFactoryUriBuilder一起提供一个可插拔的机制用于从URI模板创建URI,以及分享共同属性的方法,例如一个基础URI,编码策略,及其他。

RestTemplateWebClient都可以配置UriBuilderFactory,以便于自定义从URI模板创建URI的方式。默认实现依赖于内部的UriComponentsBuilder,并提供了配置公共基础URI的选项,另一种编码策略等等。

下面是一个配置RestTemplate的例子:

  1. String baseUrl = "http://example.com";
  2. DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
  3. RestTemplate restTemplate = new RestTemplate();
  4. restTemplate.setUriTemplateHandler(factory);

配置WebClient的例子:

  1. String baseUrl = "http://example.com";
  2. DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
  3. // 配置UriBuilderFactory..
  4. WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
  5. // 或者使用builder上的快捷方式..
  6. WebClient client = WebClient.builder().baseUrl(baseUrl).build();
  7. // 或者创建一个快捷方式...
  8. WebClient client = WebClient.create(baseUrl);

你也可以直接使用DefaultUriBuilderFactory,就像使用UriComponentsBuilder。主要不同是DefaultUriBuilderFactory是有状态的,并能重新用于准备许多URL,分享公共配置,例如基本URL,而UriComponentsBuilder是无状态的,每个URI。

使用DefaultUriBuilderFactory的例子:

  1. String baseUrl = "http://example.com";
  2. DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
  3. URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
  4. .queryParam("q", "{q}")
  5. .build("Westin", "123"); // encoding strategy applied..

1.5.3 URI编码

Spring MVC和Spring WebFlux

UriComponents内编码URI的默认方式如下所示:

编码规则如下:在URI组件中,按照RFC 3986中的定义,对所有非法字符,包括非US-ASCII字符,以及URI组件中非法的所有其他字符。

UriComponents内的编码与java.net.URI的多参数构造函数相似,如其类级别的Javadoc中的"Escaped octets, quotation, encoding, and decoding"部分所述。

上述默认编码策略不会对所有具有保留含义的字符进行编码,而只会对给定URI组件中的非法字符尽心改变吗。如果这不符合你的预期,你可以使用下面提供的替代策略。

当使用DefaultUriBuilderFactory——插入WebClientRestTemplate或者直接使用,你可以切换到一个替换策略编码,如下所示:

  1. String baseUrl = "http://example.com";
  2. DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
  3. factory.setEncodingMode(EncodingMode.VALUES_ONLY);
  4. // ...

这种替代编码策略应用了UriUtils.encode(String, Charset)在扩展之前对每个URI变量进行编码,有效地编码所有非US-ASCII字符,以及在URI中具有保留意义的所有字符,这确保了扩展的URI变量不会对URI的结构或含义产生任何影响。

1.5.4 Servlet请求相对

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

  1. HttpServletRequest request = ...
  2. // Re-uses host, scheme, port, path and query string...
  3. ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request)
  4. .replaceQueryParam("accountId", "{id}").build()
  5. .expand("123")
  6. .encode();

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

  1. // Re-uses host, port and context path...
  2. ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request)
  3. .path("/accounts").build()

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

  1. // Re-uses host, port, context path, and Servlet prefix...
  2. ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
  3. .path("/accounts").build()

ServletUriComponentsBuilder检测并使用"Forwarded"、"X-Forwarded-Host"、"X-Forwarded-Port"和"X-Forwarded-Proto"头部的信息,以便生成的链接返回原始的请求。你需要确保你的应用程序位于可信代理的后面,该代理可以过滤来自外部的头部。还要考虑使用ForwardedHeaderFilter,它可以再每个请求中处理这样的头部,并且还提供给了一个删除和忽略这些头部的选项。

1.5.5 链接到Controller

Spring MVC也提供了用于构建链接到控制器的机制。例如,给出:

  1. @Controller
  2. @RequestMapping("/hotels/{hotel}")
  3. public class BookingController {
  4. @GetMapping("/bookings/{booking}")
  5. public String getBooking(@PathVariable Long booking) {
  6. // ...
  7. }
  8. }

你可以通过名称引用方法来准备链接:

  1. UriComponents uriComponents = MvcUriComponentsBuilder
  2. .fromMethodName(BookingController.class, "getBooking", 21).buildAndExpand(42);
  3. URI uri = uriComponents.encode().toUri();

在上面的例子中,我们提供了实际的方法参数值,在这种情况下,long值21,用作路径变量并插入到URL中。此外,我们提供了值42,以填充任何剩余的URI变量,如从类型级请求映射继承的"hotel"变量。如果该方法有更多的参数,则可以为URL不需要的参数提供空值。一般来说,只有@PathVariable@RequestParam参数与构建URL有关。

还有其他使用MvcUriComponentsBuilder的方法。例如你可以使用类似的技术来模拟测试通过代理来避免指向的控制器方法的名字(示例采用MvcUriComponentsBuilder.on的静态导入):

  1. UriComponents uriComponents = MvcUriComponentsBuilder
  2. .fromMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
  3. URI uri = uriComponents.encode().toUri();

上面的例子使用了MvcUriComponentsBuilder中的静态方法。他们依靠内部ServletUriComponentsBuilder从方案,主机、端口、上下文路径和当前请求的servlet路径准备的基本URL。这种方法在大多数情况下都很有效,但有时可能还不够。例如,您可能在请求的上下文之外(例如,准备链接的批处理过程),或者您可能需要插入一个路径前缀(例如,从请求路径中删除的区域设置前缀,需要重新插入到链接中)。

对于这种情况,您可以使用静态的"fromXxx"重载方法,该方法接受一个UriComponentsBuilder以使用基本URL。或者您可以创建一个带有基本URL的MvcUriComponentsBuilder实例,然后使用基于实例的"withXxx"方法。例如:

  1. UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
  2. MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
  3. builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
  4. URI uri = uriComponents.encode().toUri();

1.5.6 视图内的链接

还可以从JSP、Thymeleaf、FreeMarker等视图构建带注释控制器的链接。可以使用MvcUriComponentsBuilder中的fromMappingName方法来完成,该方法引用了名称的映射。

每个@RequestMapping都根据类的大写字母和完整的方法名指定一个默认名称。例如,类FooController中的getFoo方法被指定为"FC#getFoo"。这种策略可以通过创建一个HandlerMethodMappingNamingStrategy实例取代或定制,可以插入到你的RequestMappingHandlerMapping。默认策略实现也查看@RequestMapping中的name属性,并在当前使用该属性。这意味着如果默认的映射名称与另一个(例如重载的方法)发生冲突,您可以在@RequestMapping上显式地指定一个名称。

指定的请求映射名称在启动时在TRACE级别上记录。

Spring JSP标记库提供了一个名为mvcUrl的函数,该函数可用于根据该机制为控制器方法准备链接。

例如给定:

  1. @RequestMapping("/people/{id}/addresses")
  2. public class PersonAddressController {
  3. @RequestMapping("/{country}")
  4. public HttpEntity getAddress(@PathVariable String country) { ... }
  5. }

你可以从JSP准备一个链接,如下:

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

上面的例子依赖于Spring标记库中声明的mvcUrl JSP函数(即META-INF/spring.tld)。对于更高级的情况(如前一节中解释的自定义基本URL),可以很容易定义您自己的函数,或者使用自定义标记文件,以便使用带有自定义基本URL的MvcUriComponentsBuilder的特定实例。

1.6 异步请求

同WebFlux相比

Spring MVC与Servlet 3.0异步请求处理进行了广泛的整合:

1.6.1 DeferredResult

同WebFlux相比

一旦在Servlet容器中启用了异步请求处理功能,控制器方法就可以将任何受支持的控制器方法返回值包装在DeferredResult

  1. @GetMapping("/quotes")
  2. @ResponseBody
  3. public DeferredResult<String> quotes() {
  4. DeferredResult<String> deferredResult = new DeferredResult<String>();
  5. // Save the deferredResult somewhere..
  6. return deferredResult;
  7. }
  8. // From some other thread...
  9. deferredResult.setResult(data);

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

1.6.2 Callable

同WebFlux相比

控制器也可以与java.util.concurrent.Callable包装任意受支持的返回值:

  1. @PostMapping
  2. public Callable<String> processUpload(final MultipartFile file) {
  3. return new Callable<String>() {
  4. public String call() throws Exception {
  5. // ...
  6. return "someView";
  7. }
  8. };
  9. }

然后通过配置TaskExecutor执行给定的任务来获得返回值。

1.6.3 处理

同WebFlux相比

以下是Servlet异步请求处理的简要描述:

DeferredResult处理:

Callable处理:

有关更多背景和上下文,你也可以阅读Spring MVC 3.2中引入异步请求处理支持的博客文章

异常处理

当使用一个递延程序时,您可以选择是否调用setResult或setErrorResult。在这两种情况下,Spring MVC将请求发送回Servlet容器以完成处理。然后,它将被处理,好像控制器方法返回给定值,或者好像它产生了给定的异常。然后,异常会通过常规的异常处理机制,例如调用@ExceptionHandler方法。
当使用可调用时,类似的处理逻辑如下。主要区别在于,结果是从可调用的或异常中返回的。
拦截
HandlerInterceptor的也可以AsyncHandlerInterceptor为了接收afterConcurrentHandlingStarted回调的初始请求异步处理而不是postHandle afterCompletion开始。
HandlerInterceptor也可以注册一个CallableProcessingInterceptor或DeferredResultProcessingInterceptor为了更深入地与异步请求的生命周期集成例如处理超时事件。有关更多细节,请参见AsyncHandlerInterceptor。

When using a DeferredResult you can choose whether to call setResult or setErrorResult with an exception. In both cases Spring MVC dispatches the request back to the Servlet container to complete processing. It is then treated either as if the controller method returned the given value, or as if it produced the given exception. The exception then goes through the regular exception handling mechanism, e.g. invoking @ExceptionHandler methods.

When using Callable, similar processing logic follows. The main difference being that the result is returned from the Callable or an exception is raised by it.

Interception

HandlerInterceptor's can also be AsyncHandlerInterceptor in order to receive the afterConcurrentHandlingStarted callback on the initial request that starts asynchronous processing instead of postHandle and afterCompletion.

HandlerInterceptor's can also register a CallableProcessingInterceptor or a DeferredResultProcessingInterceptor in order to integrate more deeply with the lifecycle of an asynchronous request for example to handle a timeout event. See AsyncHandlerInterceptor for more details.

DeferredResult provides onTimeout(Runnable) and onCompletion(Runnable) callbacks. See the Javadoc of DeferredResult for more details. Callable can be substituted for WebAsyncTask that exposes additional methods for timeout and completion callbacks.

Compared to WebFlux

The Servlet API was originally built for sequential processing, i.e. making a single pass through the Filter-Servlet chain. The asynchronous request processing feature added in Servlet 3.0 allows applications to exit the Filter-Servlet chain but leave the response open, therefore breaking this thread-per-request model.

Spring MVC async support is built around that model. When a controller returns a DeferredResult, the Filter-Servlet chain is exited and the Servlet container thread is released. Later when the DeferredResult is set, an ASYNC dispatch (to the same URL) is made during which the controller is mapped again but not invoked. Instead the DeferredResult value is used to resume processing.

Spring WebFlux is not aware of the Servlet API nor does it such an asynchronous request processing feature because it is asynchronous by design. It processes each request in stages (continuations) rather than making a single pass through the callstack on a single thread. That means asynchronous handling is built into all framework contracts and is therefore intrinsically supported at all stages of request processing.

Essentially both Spring MVC and Spring WebFlux support asynchronous and Reactive types for return values from controller methods. Spring MVC even supports streaming, including reactive back pressure, however individual writes to the response remain blocking (performed in a separate thread) and that is one major difference with WebFlux which relies on non-blocking I/O.

Another fundamental difference is that Spring MVC does not support asynchronous or reactive types in controller method arguments, e.g. @RequestBody, @RequestPart, and others, nor does it have any explicit support for asynchronous and reactive types as model attributes, all of which Spring WebFlux does support.

1.6.4. HTTP Streaming
Compared to WebFlux

DeferredResult and Callable can be used for a single asynchronous return value. What if you want to produce multiple asynchronous values and have those written to the response?

Objects

The ResponseBodyEmitter return value can be used to produce a stream of Objects, where each Object sent is serialized with an HttpMessageConverter and written to the response. For example:

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

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

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

// and done at some point
emitter.complete();
ResponseBodyEmitter can also be used as the body in a ResponseEntity allowing you to customize the status and headers of the response.

SSE

SseEmitter is a sub-class of ResponseBodyEmitter that provides support for Server-Sent Events where events sent from the server are formatted according to the W3C SSE specification. In order to produce an SSE stream from a controller simply return 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();
While SSE is the main option for streaming into browsers, note that Internet Explorer does not support Server-Sent Events. Consider using Spring’s WebSocket messaging with SockJS fallback transports (including SSE) that target a wide range of browsers.

Raw data

Sometimes it is useful to bypass message conversion and stream directly to the response OutputStream for example for a file download. Use the of the StreamingResponseBody return value type to do that:

@GetMapping("/download")
public StreamingResponseBody handle() {
return new StreamingResponseBody() {
@Override
public void writeTo(OutputStream outputStream) throws IOException {
// write...
}
};
}
StreamingResponseBody can be used as the body in a ResponseEntity allowing you to customize the status and headers of the response.

1.6.5. Reactive types
Compared to WebFlux

Spring MVC supports use of reactive client libraries in a controller. This includes the WebClient from spring-webflux and others such as Spring Data reactive data repositories. In such scenarios it is convenient to be able to return reactive types from the controller method .

Reactive return values are handled as follows:

A single-value promise is adapted to, and similar to using DeferredResult. Examples include Mono (Reactor) or Single (RxJava).

A multi-value stream, with a streaming media type such as "application/stream+json" or "text/event-stream", is adapted to, and similar to using ResponseBodyEmitter or SseEmitter. Examples include Flux (Reactor) or Observable (RxJava). Applications can also return Flux or Observable.

A multi-value stream, with any other media type (e.g. "application/json"), is adapted to, and similar to using DeferredResult>.

Spring MVC supports Reactor and RxJava through the ReactiveAdapterRegistry from spring-core which allows it to adapt from multiple reactive libraries.
When streaming to the response with a reactive type, Spring MVC performs (blocking) writes to the response through the through the configured MVC TaskExecutor. By default this is a SyncTaskExecutor and not suitable for production. SPR-16203 will provide better defaults. In the mean time please configure the executor through the MVC config.

1.6.6. Configuration
Compared to WebFlux

The async request processing feature must be enabled at the Servlet container level. The MVC config also exposes several options for asynchronous requests.

Servlet container

Filter and Servlet declarations have an asyncSupported that needs to be set to true in order enable asynchronous request processing. In addition, Filter mappings should be declared to handle the ASYNC javax.servlet.DispatchType.

In Java configuration, when you use AbstractAnnotationConfigDispatcherServletInitializer to initialize the Servlet container, this is done automatically.

In web.xml configuration, add true to the DispatcherServlet and to Filter declarations, and also add ASYNC to filter mappings.

Spring MVC

The MVC config exposes options related to async request processing:

Java config — use the configureAsyncSupport callback on WebMvcConfigurer.

XML namespace — use the element under .

You can configure the following:

Default timeout value for async requests, which if not set, depends on the underlying Servlet container (e.g. 10 seconds on Tomcat).

AsyncTaskExecutor to use for blocking writes when streaming with Reactive types, and also for executing Callable's returned from controller methods. It is highly recommended to configure this property if you’re streaming with reactive types or have controller methods that return Callable since by default it is a SimpleAsyncTaskExecutor.

DeferredResultProcessingInterceptor's and CallableProcessingInterceptor's.

Note that the default timeout value can also be set on a DeferredResult, ResponseBodyEmitter and SseEmitter. For a Callable, use WebAsyncTask to provide a timeout value.

原文地址

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注