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提供了一个用于请求处理的共享算法,而实际工作是由可配置的委托组件执行的。该模型灵活,支持多种工作流程。
与任何Servlet一样,DispatcherServlet需要使用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)。
下面的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引导遵循不同的初始化顺序。Spring Boot没有挂接到Servlet容器的生命周期中,而是使用Spring配置来引导自身和嵌入的Servlet容器。过滤器和Servlet声明在Spring配置中检测到,并向Servlet容器注册。有关更多细节,请参阅 Spring Boot documentation。
1.1.1. Context层次结构
DispatcherServlet期望WebApplicationContext(普通ApplicationContext的扩展)用于自己的配置。WebApplicationContext有一个指向ServletContext及其关联的Servlet的链接。它还绑定到ServletContext,以便应用程序可以使用requestcontext上的静态方法来查找WebApplicationContext(如果它们需要访问它)。
对于许多应用程序来说,拥有一个单一的WebApplicationContext就足够了。也可以有一个上下文层次结构,其中一个根WebApplicationContext在多个DispatcherServlet(或其他Servlet)实例之间共享,每个实例都有自己的子WebApplicationContext配置。有关上下文层次结构特性的更多信息,请参见ApplicationContext的附加功能。
根WebApplicationContext通常包含基础设施bean,比如需要在多个Servlet实例之间共享的数据存储库和业务服务。这些bean可以有效地继承,并且可以在特定于Servlet的子WebApplicationContext中重写(即重新声明),该上下文通常包含给定Servlet的本地bean。下图显示了这种关系:
下面的例子配置了一个WebApplicationContext层次结构:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return new Class<?>[] { RootConfig.class };
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { App1Config.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/app1/*" };
}
}
注意:如果不需要应用程序上下文层次结构,则应用程序可以通过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管理对象实例。这些通常带有内置的契约,但是您可以自定义它们的属性并扩展或替换它们。
下表列出了DispatcherServlet检测到的特殊bean:
Bean type | Explanation |
---|---|
| 将请求与用于预处理和后处理的拦截器列表映射到处理程序。映射基于一些标准,其细节因HandlerMapping实现而异。 两个主要的HandlerMapping实现是RequestMappingHandlerMapping(它支持@RequestMapping注释的方法)和SimpleUrlHandlerMapping(它维护URI路径模式到处理程序的显式注册)。 |
| 帮助DispatcherServlet调用映射到请求的处理程序,而不管实际如何调用处理程序。例如,调用带注释的控制器需要解析注释。HandlerAdapter的主要目的是保护DispatcherServlet不受这些细节的影响。 |
解决异常的策略,可能将异常映射到处理程序、HTML错误视图或其他目标。看到异常。 | |
将从处理程序返回的基于逻辑字符串的视图名称解析为要呈现给响应的实际视图。参见 View Resolution和View Technologies。 | |
解析客户端正在使用的语言环境和时区,以便能够提供国际化的视图。看Locale。 | |
解析web应用程序可以使用的主题——例如,提供个性化的布局。看到Themes。 | |
在一些多部分解析库的帮助下解析多部分请求(例如,浏览器表单文件上传)的抽象。看 Multipart Resolver。 | |
存储和检索“输入”和“输出”FlashMap,它们可用于将属性从一个请求传递到另一个请求,通常是通过重定向。看Flash Attributes. |
1.1.3 Web MVC配置
应用程序可以声明在处理请求所需的特殊Bean类型中列出的基础设施Bean。DispatcherServlet检查每个特殊bean的WebApplicationContext。如果没有匹配的bean类型,则返回到DispatcherServlet.properties
中列出的默认类型。
在大多数情况下, 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容器。名为AbstractDispatcherServletInitializer的WebApplicationInitializer的抽象基类实现使注册DispatcherServlet更加容易,方法是覆盖指定servlet映射和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还提供了一种方便的方法来添加过滤器实例,并将它们自动映射到DispatcherServlet,如下面的示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer {
// ...
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
每个过滤器都根据其具体类型添加一个默认名称,并自动映射到DispatcherServlet。
AbstractDispatcherServletInitializer的isAsyncSupported保护方法提供了一个单独的位置来在DispatcherServlet和映射到它的所有过滤器上启用异步支持。默认情况下,此标志设置为true。
最后,如果您需要进一步定制DispatcherServlet本身,您可以覆盖createDispatcherServlet方法。
1.1.5。Processing
DispatcherServlet按如下方式处理请求:
- 在请求中搜索WebApplicationContext并将其绑定为控制器和流程中的其他元素可以使用的属性。默认情况下,它是在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE下绑定的关键。
- 语言环境解析器被绑定到请求,以让流程中的元素解析处理请求时使用的语言环境(呈现视图、准备数据,等等)。如果不需要区域设置解析,则不需要区域设置解析器。
- 主题解析器被绑定到让元素(如视图)决定使用哪个主题的请求。如果不使用主题,可以忽略它。
- 如果指定了多部分文件解析器,将检查请求是否包含多部分。如果找到了多部分,则将请求包装在MultipartHttpServletRequest中,以便进程中的其他元素进行进一步处理。有关多部件处理的详细信息,请参阅多部件解析器。
- 寻找合适的处理程序。如果找到处理程序,则执行与处理程序(前处理器、后处理器和控制器)关联的执行链,以便准备模型或呈现。另外,对于带注释的控制器,可以(在HandlerAdapter中)呈现响应,而不是返回视图。
- 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理程序或后处理程序拦截了请求,也可能是出于安全原因),就不会呈现视图,因为请求可能已经被满足了。
在WebApplicationContext中声明的HandlerExceptionResolver bean用于解决请求处理期间抛出的异常。这些异常解决程序允许定制处理异常的逻辑。有关更多细节,请参见例外。
Spring DispatcherServlet还支持返回Servlet API指定的最后修改日期。确定特定请求的最后修改日期的过程非常简单:DispatcherServlet查找适当的处理程序映射,并测试找到的处理程序是否实现LastModified接口。如果是,则将LastModified接口的long getLastModified(request)方法的值返回给客户机。
您可以通过向web.xml文件中的Servlet声明添加Servlet初始化参数(init-param元素)来定制各个DispatcherServlet实例。下表列出了支持的参数:
Parameter | Explanation |
---|---|
| 实现ConfigurableWebApplicationContext的类,由这个Servlet实例化并在本地配置。默认情况下,使用XmlWebApplicationContext。 |
| 传递给上下文实例(由contextClass指定)的字符串,以指示在何处可以找到上下文。字符串可能由多个字符串(使用逗号作为分隔符)组成,以支持多个上下文。对于定义了两次的多个上下文位置的bean,最新位置优先。 |
| WebApplicationContext的名称空间。默认为servlet-name servlet。 |
| 当没有为请求找到处理程序时,是否抛出NoHandlerFoundException。然后可以使用HandlerExceptionResolver(例如,通过使用@ExceptionHandler控制器方法)捕获异常,并像处理其他任何异常一样处理它。 默认情况下,这个设置为false,在这种情况下,DispatcherServlet将响应状态设置为404 (NOT_FOUND),而不会引发异常。 注意,如果还配置了默认的servlet处理,未解析的请求总是会被转发到默认的servlet,并且不会引发404。 |
1.1.6。拦截
所有HandlerMapping实现都支持处理程序拦截器,当您希望将特定功能应用于特定请求时,这些拦截器非常有用——例如,检查主体。拦截器必须从org.springframe .web实现HandlerInterceptor。servlet包有三种方法,可以提供足够的灵活性来进行各种预处理和后处理:
- preHandle(..):在实际的处理程序执行之前
- postHandle(..):处理程序执行后
- afterCompletion(..):完成请求后
preHandle(..)方法返回一个布尔值。您可以使用此方法中断或继续执行链的处理。当此方法返回true时,处理程序执行链将继续。当它返回false时,DispatcherServlet假设拦截器本身已经处理了请求(例如,呈现了一个适当的视图),并且没有继续执行执行链中的其他拦截器和实际的处理程序。
有关如何配置拦截器的示例,请参阅MVC配置一节中的拦截器。您还可以通过在各个HandlerMapping实现上使用setter来直接注册它们.
注意,postHandle在@ResponseBody和ResponseEntity方法中不太有用,因为响应是在HandlerAdapter中写入并提交的,而且是在postHandle之前。这意味着对响应进行任何更改都太晚了,比如添加额外的标题。对于这样的场景,您可以实现ResponseBodyAdvice,或者将它声明为控制器通知bean,或者直接在RequestMappingHandlerAdapter上配置它。
1.1.7。异常
如果在请求映射期间发生异常,或者从请求处理程序(例如@Controller)抛出异常,DispatcherServlet将委托给HandlerExceptionResolver bean链来解决异常并提供替代处理,这通常是一个错误响应。
下表列出了可用的HandlerExceptionResolver实现:
HandlerExceptionResolver | Description |
---|---|
| 异常类名和错误视图名之间的映射。用于在浏览器应用程序中呈现错误页面。 |
解决Spring MVC引发的异常,并将它们映射到HTTP状态码。参见备用的ResponseEntityExceptionHandler和REST API异常REST API exceptions.。 | |
| 使用@ResponseStatus注释解决异常,并根据注释中的值将它们映射到HTTP状态代码。 |
| 通过调用@Controller或@ControllerAdvice类中的@ExceptionHandler方法来解决异常。看@ExceptionHandler methods. |
解析器链
通过在Spring配置中声明多个HandlerExceptionResolver bean并根据需要设置它们的order属性,可以形成一个异常解析器链。order属性越高,异常解析器的位置就越晚。
HandlerExceptionResolver的契约规定它可以返回:
- 指向错误视图的ModelAndView。
- 如果在解析器中处理了异常,则为空的ModelAndView。
- 如果异常未得到解决,则为null,以便后续解析器尝试,如果异常在最后仍然存在,则允许它向上冒泡到Servlet容器。
MVC配置自动为默认Spring MVC异常、@ResponseStatus注释异常和@ExceptionHandler方法支持声明内置解析器。您可以自定义该列表或替换它。
容器错误页面
如果任何HandlerExceptionResolver都无法解决异常,因此只能传播,或者如果响应状态设置为错误状态(即4xx、5xx), Servlet容器可以在HTML中呈现一个默认的错误页面。要自定义容器的默认错误页面,可以在web.xml中声明错误页面映射。下面的例子演示了如何做到这一点:
<error-page>
<location>/error</location>
</error-page>
对于前面的示例,当出现异常或响应出现错误状态时,Servlet容器在容器内将错误分派到配置的URL(例如/error)。然后由DispatcherServlet处理,可能会将其映射到一个@Controller,该控制器可以实现为返回一个带有模型的错误视图名或呈现一个JSON响应,如下面的示例所示:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
注意:Servlet API没有提供在Java中创建错误页面映射的方法。但是,您可以同时使用WebApplicationInitializer和最小的web.xml。
1.1.8。视图解析
Spring MVC定义了ViewResolver和View接口,它们允许您在浏览器中呈现模型,而不必将您绑定到特定的视图技术。ViewResolver提供了视图名称和实际视图之间的映射。视图处理数据在移交给特定视图技术之前的准备工作。
下表提供了更多关于ViewResolver层次结构的细节:
ViewResolver | Description |
---|---|
| AbstractCachingViewResolver的子类缓存它们解析的视图实例。缓存提高了某些视图技术的性能。您可以通过将缓存属性设置为false来关闭缓存。此外,如果必须在运行时刷新某个视图(例如,修改FreeMarker模板时),可以使用removeFromCache(String viewName, Locale loc)方法。 |
| ViewResolver的实现,它接受使用与Spring的XML bean工厂相同的DTD用XML编写的配置文件。默认的配置文件是/WEB-INF/views.xml。 |
| 在ResourceBundle中使用bean定义的ViewResolver的实现,由包的基本名称指定。对于每个要解析的视图,它使用属性[viewname].(class)的值作为视图类,使用属性[viewname]的值。url作为视图url。您可以在有关视图技术 View Technologies的章节中找到示例。 |
| ViewResolver接口的简单实现,它影响逻辑视图名称到url的直接解析,而不需要显式的映射定义。如果您的逻辑名称与视图资源的名称直接匹配,而不需要任意映射,那么这是合适的。 |
| UrlBasedViewResolver的方便子类,它支持内部资源视图(实际上是servlet和jsp)和子类,如JstlView和TilesView。可以使用setViewClass(..)为这个解析器生成的所有视图指定视图类。有关详细信息,请参阅UrlBasedViewResolver javadoc。 |
| UrlBasedViewResolver的方便子类,它支持FreeMarkerView和它们的自定义子类。 |
| ViewResolver接口的实现,它根据请求文件名或Accept头解析视图。看Content Negotiation. |
Handling
您可以通过声明多个冲突解决程序bean来链接视图冲突解决程序,如果需要,还可以通过设置order属性来指定顺序。请记住,order属性越高,视图解析器在链中的位置就越晚。
ViewResolver的契约指定它可以返回null来表示找不到视图。但是,对于JSP和InternalResourceViewResolver,判断JSP是否存在的惟一方法是通过RequestDispatcher执行分派。因此,您必须始终将一个InternalResourceViewResolver配置为在视图解析器的整体顺序中最后一个。
配置视图解析非常简单,只需将ViewResolver bean添加到Spring配置中即可。MVC配置为视图解析器和添加无逻辑视图控制器提供了专用的配置API,这对于没有控制器逻辑的HTML模板呈现非常有用。
Redirecting
视图名称中的特殊重定向:前缀允许执行重定向。UrlBasedViewResolver(及其子类)将其视为需要重定向的指令。视图名称的其余部分是重定向URL。
最终效果与控制器返回RedirectView相同,但是现在控制器本身可以根据逻辑视图名进行操作。逻辑视图名(如redirect:/myapp/some/resource)相对于当前Servlet上下文进行重定向,而名称(如redirect: https://myhost.com/some/仲裁员/path)则重定向到一个绝对URL。
注意,如果用@ResponseStatus注释控制器方法,则注释值优先于RedirectView设置的响应状态。
Forwarding
您还可以使用一个特殊的forward:前缀来表示最终由UrlBasedViewResolver和子类解析的视图名称。这将创建一个InternalResourceView,它执行一个RequestDispatcher.forward()。因此,这个前缀在InternalResourceViewResolver和InternalResourceView(用于JSP)中并不有用,但是如果您使用另一种视图技术,但仍然希望强制Servlet/JSP引擎处理资源的转发,那么它可能会有帮助。请注意,您还可以链接多个视图解析器。
内容协商
contentatingviewresolver本身并不解析视图,而是将视图委托给其他视图解析器,并选择与客户端请求的表示形式相似的视图。可以从Accept头或查询参数(例如,“/path?format=pdf”)确定表示。
contentatingviewresolver通过比较请求媒体类型和与每个viewresolver关联的视图所支持的媒体类型(也称为Content-Type)来选择一个适当的视图来处理请求。具有兼容内容类型的列表中的第一个视图将表示返回给客户机。如果ViewResolver链不能提供兼容的视图,则会参考通过DefaultViews属性指定的视图列表。后一个选项适用于单例视图,它可以呈现当前资源的适当表示,而不管逻辑视图名称如何。Accept标头可以包含通配符(例如text/*),在这种情况下,内容类型为text/xml的视图是兼容的。
有关配置细节,请参阅 MVC Config下的视图解析器。
1.1.9. Locale
Spring架构的大多数部分都支持国际化,就像Spring web MVC框架一样。DispatcherServlet允许您通过使用客户端的区域设置来自动解析消息。这是通过LocaleResolver对象完成的。
当请求传入时,DispatcherServlet将查找语言环境解析器,如果找到的话,它将尝试使用它来设置语言环境。通过使用RequestContext.getLocale()方法,您总是可以检索由语言环境解析器解析的语言环境。
除了自动区域解析之外,您还可以将一个拦截器附加到处理程序映射(有关处理程序映射拦截器的更多信息,请参见拦截),以在特定环境下更改区域设置(例如,基于请求中的参数)。
区域设置解析器和拦截器是在org.springframework.web.servlet中定义的。i18n包,并在应用程序上下文中以常规方式配置。以下区域设置解析器的选择包含在Spring中。
时区
除了获得客户机的地区之外,了解它的时区通常也很有用。LocaleContextResolver接口提供了对LocaleResolver的扩展,该扩展允许解析器提供更丰富的LocaleContext,其中可能包括时区信息。
如果可用,可以使用RequestContext.getTimeZone()方法获得用户的时区。任何在Spring的ConversionService中注册的日期/时间转换器和格式化程序对象都会自动使用时区信息。
Header解析器
此区域设置解析器检查客户机(例如,web浏览器)发送的请求中的accept-language头。通常,这个头字段包含客户端操作系统的区域设置。请注意,此解析器不支持时区信息。
Cookie 解析器
此区域设置解析器检查客户端上可能存在的Cookie,以查看是否指定了区域设置或时区。如果是,则使用指定的详细信息。通过使用此区域设置解析器的属性,可以指定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的属性:
Property | Default | Description |
---|---|---|
| classname + LOCALE | The name of the cookie |
| Servlet container default | cookie在客户端上持续的最长时间。如果指定-1,cookie将不会被持久化。它只在客户端关闭浏览器之前可用。 |
| / | 将cookie的可见性限制在站点的某个部分。当指定cookiePath时,cookie仅对该路径及其下面的路径可见。 |
会话解析器
SessionLocaleResolver允许您从可能与用户请求相关联的会话中检索语言环境和时区。与CookieLocaleResolver不同,此策略将本地选择的地区设置存储在Servlet容器的HttpSession中。因此,这些设置对于每个会话都是临时的,因此,当每个会话终止时,这些设置就会丢失。
注意,它与外部会话管理机制(如Spring会话项目)没有直接关系。此SessionLocaleResolver针对当前HttpServletRequest计算并修改相应的HttpSession属性。
现场拦截器
您可以通过将LocaleChangeInterceptor添加到HandlerMapping定义之一来启用地区更改。它检测请求中的一个参数,并相应地更改地区,在dispatcher的应用程序上下文中调用LocaleResolver上的setLocale方法。下一个示例显示调用all *。包含名为siteLanguage的参数的资源现在将更改区域设置。例如,一个URL请求https://www.sf.net/home.view?站点语言=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。主题
您可以应用Spring Web MVC框架主题来设置应用程序的整体外观,从而增强用户体验。主题是影响应用程序视觉样式的静态资源(通常是样式表和图像)的集合。
定义一个主题
要在web应用程序中使用主题,必须设置org.springframework.ui.context的实现。ThemeSource接口。WebApplicationContext接口扩展了ThemeSource,但将其职责委托给一个专用的实现。默认情况下,委托是一个org.springframe .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使用空的基名称前缀。结果,属性文件从类路径的根目录加载。于是,你就放凉了。类路径(例如,/WEB-INF/classes)根目录中的属性主题定义。ResourceBundleThemeSource使用标准的Java资源包加载机制,允许主题的完全国际化。例如,我们可以有一个/WEB-INF/classes/cool_nl。引用带有荷兰文字的特殊背景图像的属性。
Resolving 主题
在定义主题(如前一节所述)之后,您将决定使用哪个主题。DispatcherServlet查找一个名为themeResolver的bean,以确定使用哪个themeResolver实现。主题解析器的工作方式与LocaleResolver非常相似。它检测用于特定请求的主题,还可以更改请求的主题。下表描述了Spring提供的主题解析器:
Class | Description |
---|---|
| 选择使用defaultThemeName属性设置的固定主题。 |
| 主题在用户的HTTP会话中维护。它只需要为每个会话设置一次,而不是在会话之间持久化。 |
| 所选主题存储在客户端的cookie中。 |
Spring还提供了一个ThemeChangeInterceptor,它允许使用一个简单的请求参数来更改每个请求的主题。
1.1.11。多部分解析器
来自org.springframe .web的MultipartResolver。多部分包是一种解析包括文件上传在内的多部分请求的策略。有一种实现基于Commons FileUpload,另一种实现基于Servlet 3.0多部分请求解析。
要启用多部分处理,您需要在DispatcherServlet Spring配置中声明一个名为MultipartResolver的MultipartResolver bean。DispatcherServlet检测它并将其应用于传入的请求。当接收到内容类型为multipart/form-data的POST时,解析器解析内容并将当前HttpServletRequest包装为MultipartHttpServletRequest,以提供对已解析部分的访问,并将其作为请求参数公开。
Apache Commons FileUpload
要使用Apache Commons FileUpload,可以使用multipartResolver配置CommonsMultipartResolver类型的bean。您还需要将commons-fileupload作为类路径的依赖项。
Servlet 3.0
Servlet 3.0多部分解析需要通过Servlet容器配置来启用。这样做:
- 在Java中,在Servlet注册时设置一个MultipartConfigElement。
- 在网络上。将“<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配置就绪,您就可以添加一个类型为StandardServletMultipartResolver、名称为multipartResolver的bean。
1.1.12。日志记录
Spring MVC中的调试级日志记录被设计为紧凑、最少且对人类友好。它侧重于反复有用的高价值信息,而不是只在调试特定问题时有用的信息。
跟踪级日志通常遵循与调试相同的原则(例如,也不应该是消防软管),但是可以用于调试任何问题。此外,一些日志消息可能在跟踪和调试时显示不同级别的详细信息。
良好的日志记录来自于使用日志的经验。如果你发现任何不符合既定目标的地方,请告诉我们。
敏感数据
调试和跟踪日志记录可能会记录敏感信息。这就是为什么请求参数和报头在默认情况下是隐藏的,并且必须通过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 PUT、PATCH和DELETE。Servlet API需要ServletRequest.getParameter*()方法来仅支持HTTP POST的表单字段访问。
spring web模块提供FormContentFilter拦截HTTP PUT,补丁,和删除请求的内容类型应用程序/ x-www-form-urlencoded,从身体的读取表单数据请求,并将ServletRequest表单数据可以通过ServletRequest.getParameter *()的方法。
1.2.2. Forwarded Headers
当请求通过代理(如负载平衡器)时,主机、端口和模式可能会发生变化,这使得从客户端角度创建指向正确主机、端口和模式的链接成为一项挑战。
RFC 7239定义了转发的HTTP头,代理可以使用它来提供关于原始请求的信息。还有其他非标准头文件,包括X-Forwarded-Host
, X-Forwarded-Port
, X-Forwarded-Proto
, X-Forwarded-Ssl
, and X-Forwarded-Prefix
。
forwarding headerfilter是一个Servlet过滤器,它修改请求以a)根据转发的头更改主机、端口和方案,b)删除这些头以消除进一步的影响。筛选器依赖于包装请求,因此它必须先于其他筛选器(如RequestContextFilter)排序,后者应该处理修改后的请求,而不是原始请求。
由于应用程序无法知道报头是由代理按预期添加的还是由恶意客户端添加的,因此对于转发的报头存在安全考虑。这就是为什么信任边界处的代理应该配置为删除来自外部的不受信任的转发头。还可以使用removeOnly=true配置forwarding headerfilter,在这种情况下,它删除但不使用header。
为了支持异步请求和错误调度,这个过滤器应该使用DispatcherType映射。ASYNC和DispatcherType.ERROR。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer(请参阅Servlet配置),则所有筛选器都将自动注册为所有分派类型。但是,如果通过web.xml注册过滤器,或者在Spring启动时通过FilterRegistrationBean注册过滤器,则一定要包含DispatcherType.ASYNC
和DispatcherType.ERROR。除了DispatcherType.REQUEST之外。
1.2.3。Shallow ETag
ShallowEtagHeaderFilter过滤器通过缓存写入响应的内容并从中计算MD5散列来创建“浅层”ETag。下一次客户端发送时,它也会执行相同的操作,但它也会将计算的值与if - none - match请求标头进行比较,如果两者相等,则返回304 (NOT_MODIFIED)。
这种策略节省了网络带宽,但不节省CPU,因为每个请求都必须计算完整的响应。前面描述的控制器级的其他策略可以避免计算。看HTTP Caching。
这个过滤器有一个writeWeakETag参数,它将过滤器配置为写弱ETags,类似于以下内容:W/“02a2d595e6ed9a0b24f027f2b63b134d6”(如RFC 7232部分2.3中定义的那样)。
为了支持 asynchronous requests,这个过滤器必须使用DispatcherType映射。异步,以便筛选器可以延迟并成功地生成ETag,直到最后一次异步调度结束。如果使用Spring框架的AbstractAnnotationConfigDispatcherServletInitializer(请参阅 Servlet Config),则所有筛选器都将自动注册为所有分派类型。但是,如果通过web.xml注册过滤器,或者在Spring启动时通过FilterRegistrationBean注册过滤器,则一定要包含DispatcherType.ASYNC。
1.2.4。CORS
Spring MVC通过对控制器的注释为CORS配置提供了细粒度的支持。但是,当与Spring Security一起使用时,我们建议使用内置的CorsFilter,它必须在Spring Security的过滤器链之前订购。
有关 CORS和CORS Filter的详细信息,请参阅相关章节。
1.3. Annotated Controllers
Spring MVC提供了一个基于注释的编程模型,其中@Controller和@RestController组件使用注释来表示请求映射、请求输入、异常处理等。带注释的控制器具有灵活的方法签名,不需要扩展基类或实现特定的接口。下面的例子展示了一个由注解定义的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在前面的示例中,该方法接受模型并以字符串的形式返回视图名,但是还有许多其他选项,本章稍后将对此进行解释。
关于spring.io的指南和教程。使用本节中描述的基于注释的编程模型。
1.3.1. Declaration
您可以在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是一个复合注释composed annotation,它本身是由@Controller和@ResponseBody组成的元注释,用于指示一个控制器,该控制器的每个方法都继承类型级别的@ResponseBody注释,因此,它直接写入响应体而不是视图解析,并使用HTML模板进行呈现。
AOP代理
在某些情况下,可能需要在运行时用AOP代理装饰控制器。例如,如果您选择直接在控制器上使用@Transactional注释。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果控制器必须实现不是Spring上下文回调的接口(例如InitializingBean、*Aware等),则可能需要显式地配置基于类的代理。例如,使用<tx: annotated -driven/>可以更改为<tx: annotated -driven proxy-target-class="true"/>,使用@EnableTransactionManagement可以更改为@EnableTransactionManagement(proxyTargetClass = true)。
1.3.2。请求映射
可以使用@RequestMapping注释将请求映射到控制器方法。它有各种属性,可以根据URL、HTTP方法、请求参数、标题和媒体类型进行匹配。您可以在类级使用它来表示共享映射,或者在方法级使用它来缩小到特定端点映射。
也有HTTP方法特定的快捷方式变量@RequestMapping:
-
@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。默认情况下支持简单类型(int、long、Date等),您可以为任何其他数据类型注册支持。参见类型转换和数据绑定。
您可以显式地命名URI变量(例如,@PathVariable(“customId”)),但是如果名称相同,并且您的代码是用调试信息或Java 8上的-parameters编译器标志编译的,那么您可以省略这个细节。
语法{varName:regex}用一个正则表达式声明一个URI变量,该正则表达式的语法为{varName:regex}。例如,给定URL“/spring-web-3.0.5 .jar”,下面的方法提取名称、版本和文件扩展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String version, @PathVariable String ext) {
// ...
}
URI路径模式还可以嵌入${…}占位符,这些占位符在启动时使用PropertyPlaceHolderConfigurer对本地、系统、环境和其他属性源进行解析。例如,可以使用它根据外部配置对基本URL进行参数化。
注意:Spring MVC使用PathMatcher契约和来自spring-core的AntPathMatcher实现实现URI路径匹配。
模式对比
当多个模式匹配一个URL时,必须对它们进行比较才能找到最佳匹配。这是通过使用AntPathMatcher完成的。getPatternComparator(字符串路径),它查找更具体的模式。
如果一个模式的URI变量计数较低(计数为1)、单个通配符(计数为1)和双通配符(计数为2),那么该模式就不那么具体。给定相同的分数和长度,则选择具有比通配符更多的URI变量的模式。
默认的映射模式(/**)被排除在得分之外,并且总是最后排序。此外,前缀模式(如/public/**)被认为比其他没有双通配符的模式更不具体。
有关详细信息,请参阅AntPathMatcher中的AntPatternComparator,并请记住您可以自定义PathMatcher实现。请参阅配置部分中的路径匹配。
后缀匹配
默认情况下,Spring MVC执行.*后缀模式匹配,因此映射到/person的控制器也隐式映射到/person.*。然后,文件扩展名用于解释用于响应的请求内容类型(即,而不是Accept标头)——例如/person.pdf
, /person.xml
等。
当浏览器用于发送难以一致解释的Accept标头时,以这种方式使用文件扩展名是必要的。目前,这不再是必需的,使用Accept标头应该是首选。
随着时间的推移,文件扩展名的使用在许多方面都被证明是有问题的。当与使用URI变量、路径参数和URI编码重叠时,可能会导致歧义。关于基于url的授权和安全性的推理(参见下一节了解更多细节)也变得更加困难。
要完全禁用文件扩展名的使用,您必须设置以下两个:
- useSuffixPatternMatching(false),看到PathMatchConfigurer
- favorPathExtension(false),看到ContentNegotiationConfigurer
基于URL的内容协商仍然很有用(例如,在浏览器中输入URL时)。为了实现这一点,我们建议使用基于查询参数的策略来避免文件扩展所带来的大多数问题。另外,如果必须使用文件扩展名,可以考虑通过contentconsultationconfigurer的mediaTypes属性将其限制为显式注册的扩展名列表。
后缀匹配和RFD
反映文件下载(RFD)攻击与XSS相似,因为它依赖于响应中反映的请求输入(例如,查询参数和URI变量)。然而,RFD攻击不是将JavaScript插入HTML,而是依赖于浏览器切换来执行下载,并在稍后双击时将响应视为可执行脚本。
在Spring MVC中,@ResponseBody和ResponseEntity方法是有风险的,因为它们可以呈现不同的内容类型,客户端可以通过URL路径扩展请求不同的内容类型。禁用后缀模式匹配和使用内容协商的路径扩展可以降低风险,但不足以防止RFD攻击。
为了防止RFD攻击,在呈现响应体之前,Spring MVC添加了一个content - dispose:inline;filename=f.t theader来建议一个固定且安全的下载文件。只有当URL路径包含一个文件扩展名,该文件扩展名既不是白名单,也不是为内容协商显式注册的,才会执行此操作。然而,当url被直接输入到浏览器中时,它可能有潜在的副作用。
许多常见的路径扩展在默认情况下是白名单的。具有自定义HttpMessageConverter实现的应用程序可以显式地注册用于内容协商的文件扩展名,以避免为这些扩展添加内容配置头。看 Content Types.。
有关fd的其他建议见 CVE-2015-5211。
可消费的媒体类型
您可以根据请求的内容类型缩小请求映射,如下面的示例所示:
@PostMapping(path = "/pets", consumes = "application/json") //1
public void addPet(@RequestBody Pet pet) {
// ...
}
//1使用属性根据内容类型缩小映射范围。
属性还支持否定表达式——例如,!text/plain表示除text/plain之外的任何内容类型。
您可以在类级别声明一个shared属性。但是,与大多数其他请求映射属性不同的是,当在类级别使用时,方法级别的属性会覆盖而不是扩展类级别声明。
注意:MediaType为常用的媒体类型(如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE)提供常量。
可延长的媒体类型
您可以根据Accept请求头和控制器方法产生的内容类型列表来缩小请求映射,如下面的示例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json") //1
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
使用produces属性根据内容类型缩小映射范围。
媒体类型可以指定一个字符集。否定表达式是受支持的-例如,!text/plain表示除“text/plain”以外的任何内容类型。
您可以在类级别声明shared produces属性。但是,与大多数其他请求映射属性不同的是,当在类级别使用时,方法级别生成属性覆盖而不是扩展类级别声明。
注意:MediaType为常用的媒体类型(如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE)提供常量。
Parameters, headers
可以根据请求参数条件缩小请求映射。您可以测试请求参数(myParam)是否存在,是否缺少一个参数(!myParam),或者特定的值(myParam=myValue)。下面的例子展示了如何测试一个特定的值:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") //1
public void findPet(@PathVariable String petId) {
// ...
}
//1 测试myParam是否等于myValue
你也可以使用相同的请求头条件,如下面的例子所示:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
/ /……
}
注意:您可以使用header条件来匹配Content-Type和Accept,但是使用和produces更好。
HTTP HEAD, OPTIONS
@GetMapping(和@RequestMapping(method=HttpMethod.GET))对请求映射透明地支持HTTP头。控制器方法不需要更改。响应包装器,应用于javax.servlet.http。HttpServlet,确保将内容长度头设置为写入的字节数(不实际写入响应)。
@GetMapping(和@RequestMapping(method=HttpMethod.GET))被隐式映射到并支持HTTP头。处理HTTP头请求的方式与处理HTTP GET类似,不同之处是不写入主体,而是计算字节数并设置内容长度头。
默认情况下,HTTP选项是通过将Allow响应头设置为所有具有匹配URL模式的@RequestMapping方法中列出的HTTP方法列表来处理的。
对于没有HTTP方法声明的@RequestMapping,允许标头设置为GET、HEAD、POST、PUT、PATCH、DELETE和选项。控制器方法应该总是声明支持的HTTP方法(例如,通过使用HTTP方法特定的变体:@GetMapping、@PostMapping和其他)。
您可以显式地将@RequestMapping方法映射到HTTP HEAD和HTTP选项,但在通常情况下这不是必需的。
自定义注解
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) //1
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build(); //2
Method method = UserHandler.class.getMethod("getUser", Long.class); //3
mapping.registerMapping(info, handler, method); //4
}
}
- 为控制器注入目标处理程序和处理程序映射。
- 准备请求映射元数据。
- 获取处理程序方法。
- 添加注册。
1.3.3。处理程序方法
@RequestMapping处理程序方法有一个灵活的签名,可以从一系列受支持的控制器方法参数和返回值中进行选择。
方法参数
下表描述了支持的控制器方法参数。不支持任何参数的响应类型。
JDK 8 java.util.Optional
可选的方法参数与具有required属性的注释(例如,@RequestParam、@RequestHeader等)相结合,相当于required=false。
Controller method argument | Description |
---|---|
| 对请求参数和请求和会话属性的通用访问,而不直接使用Servlet API。 |
| 选择任何特定的请求或响应类型——例如,ServletRequest、HttpServletRequest或Spring的MultipartRequest、MultipartHttpServletRequest。 |
| 强制会话的存在。因此,这样的参数永远不会为空。注意,会话访问不是线程安全的。如果允许多个请求同时访问一个会话,可以考虑将RequestMappingHandlerAdapter实例的synchronizeOnSession标志设置为true。 |
| Servlet 4.0推送构建器API,用于程序化的HTTP/2资源推送。注意,根据Servlet规范,如果客户端不支持HTTP/2特性,则注入的PushBuilder实例可以为空。 |
| 当前已验证的用户—如果已知,可能是特定的主体实现类。 |
| 请求的HTTP方法。 |
| 当前请求区域设置,由最特定的可用LocaleResolver(实际上是已配置的LocaleResolver或LocaleContextResolver)决定。 |
| 与当前请求相关的时区,由LocaleContextResolver确定。 |
| 用于访问Servlet API公开的原始请求体。 |
| 用于访问Servlet API公开的原始响应体。 |
| 用于访问URI模板变量。看 URI patterns. |
| 用于访问URI路径段中的名称-值对。看 Matrix Variables. |
| 用于访问Servlet请求参数,包括多部分文件。参数值被转换为声明的方法参数类型。参见@RequestParam和Multipart。 注意,对于简单的参数值,@RequestParam是可选的。见表末“任何其他参数”。 |
| 用于访问请求标头。标头值被转换为声明的方法参数类型。看 |
| 获取cookies。cookie值被转换为声明的方法参数类型。看 |
| 用于访问HTTP请求体。通过使用HttpMessageConverter实现,将正文内容转换为声明的方法参数类型。See |
| 用于访问请求头和正文。主体通过HttpMessageConverter进行转换。看HttpEntity. |
| 或者访问多部件/表单数据请求中的部件,使用HttpMessageConverter转换部件的主体。See Multipart. |
| 用于访问HTML控制器中使用的模型,并将其作为视图呈现的一部分公开给模板。 |
| 指定重定向(即附加到查询字符串)时使用的属性,并临时存储flash属性,直到重定向后的请求。See Redirect Attributes and Flash Attributes. |
| 用于访问模型中的现有属性(如果不存在则实例化),并应用数据绑定和验证。参见@ModelAttribute以及Model和DataBinder。 注意,使用@ModelAttribute是可选的(例如,设置它的属性)。见表末的“任何其他参数”。 |
| 用于访问来自命令对象(即@ModelAttribute参数)的验证和数据绑定的错误,或来自@RequestBody或@RequestPart参数的验证的错误。必须在验证方法参数之后立即声明错误或BindingResult参数。 |
| 对于标记表单处理完成,这将触发通过类级别@SessionAttributes注释声明的会话属性的清理。更多细节见 |
| 或者准备一个相对于当前请求的主机、端口、方案、上下文路径和servlet映射的文字部分的URL。 See URI Links. |
| 对于任何会话属性的访问,与作为类级@SessionAttributes声明的结果而存储在会话中的模型属性不同。See |
| 用于访问请求属性. See |
Any other argument | 如果方法参数没有与此表中任何较早的值匹配,并且它是一个简单类型(由BeanUtils#isSimpleProperty确定,它被解析为@RequestParam。否则,它将解析为@ModelAttribute。 |
Return Values
Controller method return value | Description |
---|---|
| 返回值通过HttpMessageConverter实现转换并写入响应。 See |
| 指定完整响应(包括HTTP报头和正文)的返回值将通过HttpMessageConverter实现进行转换并写入响应。See ResponseEntity. |
| 用于返回带有标题但没有正文的响应。 |
| 使用ViewResolver实现解析并与隐式模型一起使用的视图名——通过命令对象和@ModelAttribute方法确定。处理程序方法还可以通过声明模型参数以编程方式丰富模型 (see Explicit Registrations). |
| 一个视图实例,用于与通过命令对象和@ModelAttribute方法确定的隐式模型一起呈现。处理程序方法还可以通过声明模型参数以编程方式丰富模型(see Explicit Registrations). |
| 要添加到隐式模型中的属性,通过RequestToViewNameTranslator隐式确定视图名称。 |
| 要添加到模型中的属性,视图名称通过RequestToViewNameTranslator隐式确定。 注意@ModelAttribute是可选的。请参阅该表末尾的“任何其他返回值”。 |
| 要使用的视图和模型属性,以及(可选的)响应状态. |
| 如果具有void返回类型(或null返回值)的方法还具有ServletResponse、OutputStream参数或@ResponseStatus注释,则认为该方法已经完全处理了响应。如果控制器做了一个积极的ETag或lastModified时间戳检查,也是一样的 (see Controllers for details). 如果以上都不是真的,void返回类型也可以表示REST控制器的“无响应体”,或者HTML控制器的默认视图名选择。 |
| 从任何线程异步生成前面的任何返回值——例如,作为某个事件或回调的结果。 See Asynchronous Requests and |
| 在Spring mvc管理的线程中异步生成上述任何一个返回值See Asynchronous Requests and |
| 作为DeferredResult的替代,以提供便利(例如,当一个底层服务返回其中一个时)。 |
| 使用HttpMessageConverter实现异步发送要写入响应的对象流。也支持作为响应实体的主体。 See Asynchronous Requests and HTTP Streaming. |
| 异步写入响应OutputStream。也支持作为响应实体的主体。See Asynchronous Requests and HTTP Streaming. |
Reactive types — Reactor, RxJava, or others through | 替代使用多值流(例如,通量,可观察到的)收集到列表中的DeferredResult。 See Asynchronous Requests and Reactive Types. |
Any other return value | 任何返回值不匹配任何之前的值在这个表,是一个字符串或空白都被视为一个视图名称(默认视图名称选择通过RequestToViewNameTranslator适用),它不是一个简单的类型,提供由BeanUtils # isSimpleProperty。简单类型的值仍未解析。 |
类型转换
一些表示基于字符串的请求输入的带注释的控制器方法参数(如@RequestParam、@RequestHeader、@PathVariable、@MatrixVariable和@CookieValue)可能需要类型转换,如果参数声明为字符串以外的内容。
对于这种情况,将根据配置的转换器自动应用类型转换。默认情况下,支持简单类型(int、long、Date和其他类型)。您可以通过WebDataBinder(请参阅DataBinder)或通过向FormattingConversionService注册格式化程序来定制类型转换。See Spring Field Formatting
Matrix Variables
RFC 3986讨论路径段中的名称-值对。在Spring MVC中,我们根据Tim Berners-Lee的一篇“旧文章”将它们称为“矩阵变量”,但它们也可以称为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
}
matrix variable(矩阵变量)可以定义为可选的,也可以指定一个默认值,如下例所示:
// 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=false通过路径匹配设置UrlPathHelper。在MVC XML名称空间中,您可以设置< MVC:annotation-driven enable-matrix-variables="true"/>。
@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声明参数来指定方法参数是可选的。可选的包装器。
如果目标方法参数类型不是字符串,则自动应用类型转换。看到类型转换 Type Conversion。
将参数类型声明为数组或列表允许解析相同参数名称的多个参数值。
当@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-Encoding和Keep-Alive头信息的值:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
如果目标方法参数类型不是字符串,则自动应用类型转换。看到类型转换。
当在Map<String、String>、MultiValueMap<String、String>或HttpHeaders参数上使用@RequestHeader注释时,映射将使用所有标头值填充。
注意:内置的支持可用于将逗号分隔的字符串转换为类型转换系统已知的字符串数组或字符串集合或其他类型。例如,@RequestHeader("Accept")注释的方法参数可以是String类型,也可以是String[]或List<String>。
@CookieValue
可以使用@CookieValue注释将HTTP cookie的值绑定到控制器中的方法参数。
考虑使用以下cookie的请求:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面的例子展示了如何获取cookie值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果目标方法参数类型不是字符串,则自动应用类型转换。看到类型转换。
@ModelAttribute
您可以在方法参数上使用@ModelAttribute注释来从模型中访问属性,或者在不存在的情况下实例化它。模型属性还与HTTP Servlet请求参数的值重叠,这些参数的名称与字段名称匹配。这称为数据绑定,它使您不必解析和转换各个查询参数和表单字段。下面的例子演示了如何做到这一点:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { }
上面的Pet实例解析如下:
- 如果已经使用模型添加,则从模型中添加。
- 通过使用@SessionAttributes来自HTTP会话。
- 通过转换器传递的URI路径变量(参见下一个示例)。
- 默认构造函数的调用。
- 使用与Servlet请求参数匹配的参数调用“主构造函数”。参数名通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名确定。
虽然通常使用模型用属性填充模型,但另一种替代方法是依赖转换器<String, T>和URI路径变量约定。在下面的示例中,模型属性名account与URI路径变量account匹配,通过将字符串帐号传递给注册的转换器<String,Account >:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
获取模型属性实例后,应用数据绑定。WebDataBinder类将Servlet请求参数名称(查询参数和表单字段)与目标对象上的字段名称匹配。在需要时,在应用类型转换之后填充匹配字段。有关数据绑定(和验证)的更多信息,请参见验证 Validation。有关自定义数据绑定的详细信息,请参阅DataBinder
。
数据绑定可能导致错误。默认情况下,会引发BindException。但是,要在控制器方法中检查这些错误,您可以在@ModelAttribute旁边添加一个BindingResult参数,如下面的示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情况下,您可能希望在不进行数据绑定的情况下访问模型属性。对于这种情况,您可以将模型注入控制器并直接访问它,或者设置@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,可以在数据绑定之后自动应用验证。有效注释或Spring的@Validated注释( Bean Validation and Spring validation)。下面的例子演示了如何做到这一点:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
注意,使用@ModelAttribute是可选的(例如,设置它的属性)。默认情况下,任何不是简单值类型的参数(由BeanUtils#isSimpleProperty决定),并且不被任何其他参数解析器解析的参数都被视为使用@ModelAttribute注释的。
@SessionAttributes
@SessionAttributes用于在请求之间的HTTP Servlet会话中存储模型属性。它是类型级别的注释,声明特定控制器使用的会话属性。这通常列出模型属性的名称或模型属性的类型,它们应该透明地存储在会话中,以便后续请求访问。
下面的例子使用了@SessionAttributes注释:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
在第一个请求中,当名称为pet的模型属性被添加到模型中时,它会被自动提升到HTTP Servlet会话中并保存。直到另一个控制器方法使用一个SessionStatus方法参数来清除存储,如下面的例子所示:
@Controller
@SessionAttributes("pet") //1
public class EditPetForm {
// ...
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete(); //2
// ...
}
}
}
- 在Servlet会话中存储Pet值。
- 从Servlet会话中清除Pet值。
@SessionAttribute
如果您需要访问全局管理的预先存在的会话属性(即,在控制器之外——例如,通过过滤器),并且可能存在也可能不存在,那么您可以在方法参数上使用@SessionAttribute注释,如下面的示例所示:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
对于需要添加或删除会话属性的用例,可以考虑注入org.springframework.web.context.request.WebRequest
或javax.servlet.http.HttpSession到
控制器方法中。
对于作为控制器工作流一部分的会话中的模型属性的临时存储,可以考虑使用@SessionAttributes,如@SessionAttributes中所述。
@RequestAttribute
与@SessionAttribute类似,您可以使用@RequestAttribute注释来访问先前创建的已存在的请求属性(例如,通过Servlet过滤器或HandlerInterceptor):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向的属性
默认情况下,所有模型属性都被认为是作为重定向URL中的URI模板变量公开的。在其余属性中,那些原始类型或原始类型的集合或数组将自动附加为查询参数。
如果模型实例是专门为重定向准备的,那么将原语类型属性附加为查询参数可能是所需的结果。但是,在带注释的控制器中,模型可以包含为呈现而添加的其他属性(例如,下拉字段值)。为了避免这些属性出现在URL中,@RequestMapping方法可以声明一个RedirectAttributes类型的参数,并使用它来指定RedirectView可用的确切属性。如果方法确实需要重定向,则使用RedirectAttributes的内容。否则,将使用模型的内容。
RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect的标志,您可以使用该标志来指示如果控制器方法重定向,则不应该使用默认模型的内容。相反,控制器方法应该声明RedirectAttributes类型的属性,或者,如果不这样做,就不应该将任何属性传递给RedirectView。MVC名称空间和MVC Java配置都将此标志设置为false,以保持向后兼容性。但是,对于新的应用程序,我们建议将其设置为true。
注意,当展开重定向URL时,当前请求中的URI模板变量将自动可用,您不需要通过模型或重定向属性显式地添加它们。下面的例子演示了如何定义重定向:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
另一种向重定向目标传递数据的方法是使用flash属性。与其他重定向属性不同,flash属性保存在HTTP会话中(因此不会出现在URL中)。有关更多信息,请参见Flash属性。
Flash属性
Flash属性为一个请求提供了一种方法来存储另一个请求将要使用的属性。这是在重定向时最常见的需求—例如,后重定向-获取模式。在重定向(通常在会话中)之前临时保存Flash属性,以便在重定向之后提供给请求,并立即删除这些属性。
Spring MVC有两个主要的抽象来支持flash属性。FlashMap用于保存flash属性,而FlashMapManager用于存储、检索和管理FlashMap实例。
Flash属性支持总是“on”,不需要显式地启用。但是,如果不使用它,就不会导致HTTP会话创建。对于每个请求,都有一个“输入”FlashMap,其中包含前一个请求(如果有的话)传递的属性,以及一个“输出”FlashMap,其中包含为后续请求保存的属性。这两个FlashMap实例都可以通过requestcontext中的静态方法从Spring MVC中的任何地方访问。
带注释的控制器通常不需要直接使用FlashMap。相反,@RequestMapping方法可以接受类型为RedirectAttributes的参数,并使用它为重定向场景添加flash属性。通过RedirectAttributes添加的Flash属性会自动传播到“output”FlashMap中。类似地,在重定向之后,来自“input”FlashMap的属性会自动添加到服务于目标URL的控制器模型中。
将请求匹配到flash属性
flash属性的概念存在于许多其他web框架中,并已被证明有时会出现并发性问题。这是因为,根据定义,flash属性将一直存储到下一个请求。然而,下一个请求可能不是预期的接收方,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,flash属性被过早地删除了。
为了减少这种问题的可能性,RedirectView会自动“标记”FlashMap实例的路径和目标重定向URL的查询参数。然后,默认的FlashMapManager在查找“输入”FlashMap时将该信息与传入的请求匹配。
这并不能完全消除并发性问题的可能性,但是可以通过重定向URL中已有的信息大大减少并发性问题。因此,我们建议您主要在重定向场景中使用flash属性。
Multipart(多部分)
启用MultipartResolver后,将解析带有多部分/表单数据的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";
}
}
将参数类型声明为列表<MultipartFile>允许为相同的参数名称解析多个文件。
当@RequestParam注释声明为Map<String、MultipartFile>或MultiValueMap<String、MultipartFile>,而没有在注释中指定参数名时,将使用每个给定参数名的多部分文件填充映射。
使用Servlet 3.0多部分解析,您还可以声明jjavax.servlet.http.Part作为方法参数或集合值类型,而不是Spring的MultipartFile。
您还可以使用multipart内容作为到命令对象command object的数据绑定的一部分。例如,前面示例中的表单字段和文件可以是表单对象上的字段,如下例所示:
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)。使用@RequestPart注释在使用 HttpMessageConverter进行转换后访问多部分:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
您可以将@RequestPart与javax.validation结合使用。有效或使用Spring的@Validated注解,两者都会应用标准Bean验证。默认情况下,验证错误将导致MethodArgumentNotValidException异常,该异常将转换为400 (BAD_REQUEST)响应。或者,您可以通过error或BindingResult参数在控制器中本地处理验证错误,如下面的示例所示:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
可以使用@RequestBody注释通过HttpMessageConverter将请求体读取并反序列化为对象。下面的例子使用了@RequestBody参数:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
您可以使用MVC配置的消息转换器选项来配置或自定义消息转换。
您可以结合使用@RequestBody和javax.validation。有效的或Spring的@Validated注解,两者都会应用标准Bean验证。默认情况下,验证错误将导致MethodArgumentNotValidException异常,该异常将转换为400 (BAD_REQUEST)响应。或者,您可以通过error或BindingResult参数在控制器中本地处理验证错误,如下面的示例所示:
@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配置的消息转换器选项来配置或自定义消息转换。
您可以将@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库的支持。
Jackson JSON
Spring MVC为Jackson的序列化视图提供了内置的支持,它只允许呈现对象中所有字段的一个子集。要将它与@ResponseBody或ResponseEntity控制器方法一起使用,您可以使用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";
}
}
1.3.4。模型
您可以使用@ModelAttribute注释:
- 在@RequestMapping方法中的方法参数上创建或访问模型中的对象,并通过WebDataBinder将其绑定到请求。
- 作为@Controller或@ControllerAdvice类中的方法级注释,帮助在任何@RequestMapping方法调用之前初始化模型。
- 在@RequestMapping方法上标记它的返回值是一个模型属性。
本节讨论@ModelAttribute方法—前一列表中的第二项。一个控制器可以有任意数量的@ModelAttribute方法。所有这些方法都在同一个控制器中的@RequestMapping方法之前调用。@ModelAttribute方法也可以通过@ControllerAdvice在控制器之间共享。有关更多细节,请参见控制器建议一节。
@ModelAttribute方法具有灵活的方法签名。它们支持许多与@RequestMapping方法相同的参数,除了@ModelAttribute本身或与请求体相关的任何东西。
下面的例子展示了一个@ModelAttribute方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
下面的例子只添加了一个属性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
注意:如果没有显式指定名称,则根据对象类型选择默认名称,如javadoc for约定中所述。您总是可以通过使用重载的addAttribute方法或通过@ModelAttribute上的name属性(用于返回值)来分配显式名称。
您还可以使用@ModelAttribute作为@RequestMapping方法上的方法级注释,在这种情况下,@RequestMapping方法的返回值被解释为一个模型属性。这通常不是必需的,因为这是HTML控制器中的默认行为,除非返回值是一个字符串,否则将被解释为一个视图名。@ModelAttribute也可以自定义模型属性名,如下例所示:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
1.3.5。DataBinder
@Controller或@ControllerAdvice类可以有@InitBinder方法来初始化WebDataBinder的实例,而这些方法又可以:
- 将请求参数(即表单或查询数据)绑定到模型对象。
- 将基于字符串的请求值(如请求参数、路径变量、标头、cookie等)转换为控制器方法参数的目标类型。
- 在呈现HTML表单时,将模型对象值格式化为字符串值。
@InitBinder方法可以注册特定于控制器的java.bean。PropertyEditor或Spring转换器和格式化程序组件。此外,可以使用MVC配置在全局共享的FormattingConversionService中注册转换器和格式化程序类型。
@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使用基于格式化的设置时,您可以重用相同的方法并注册特定于控制器的格式化程序实现,如下面的示例所示:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
1.3.6。异常
@Controller和@ControllerAdvice类可以有@ExceptionHandler方法来处理来自控制器方法的异常,如下面的例子所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
异常可能与传播的顶级异常(即抛出的直接IOException)或顶级包装器异常中的直接原因相匹配(例如,在IllegalStateException中包装的IOException)。
为了匹配异常类型,最好将目标异常声明为方法参数,如上面的示例所示。当多个异常方法匹配时,根异常匹配通常比原因异常匹配更可取。更具体地说,ExceptionDepthComparator用于根据抛出的异常类型的深度对异常进行排序。
另外,注释声明可以缩小异常类型以匹配,如下面的示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
您甚至可以使用带有非常通用的参数签名的特定异常类型列表,如下面的示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
注意:
根异常匹配和原因异常匹配之间的区别可能令人吃惊。
在前面显示的IOException变体中,通常使用实际的FileSystemException或RemoteException实例作为参数来调用方法,因为它们都是从IOException扩展而来的。但是,如果在包装器异常(本身就是IOException)中传播任何此类匹配异常,则传入的异常实例就是该包装器异常。
句柄(异常)变体的行为更简单。在包装场景中,这总是与包装器异常一起调用,在这种情况下,通过ex.getCause()找到实际匹配的异常。传入的异常是实际的FileSystemException或RemoteException实例,只有在作为顶级异常抛出时才抛出这些异常。
在一个multi-@ControllerAdvice安排中,我们建议在一个@ControllerAdvice上声明主根异常映射,并按相应的顺序进行优先级排序。虽然根异常匹配是首选的原因,但这是在给定控制器或@ControllerAdvice类的方法中定义的。这意味着高优先级@ControllerAdvice bean上的原因匹配比低优先级@ControllerAdvice bean上的任何匹配(例如根)更受欢迎。
最后但并非最不重要的一点是,@ExceptionHandler方法实现可以选择通过重新抛出给定的异常实例的原始形式来退出处理。这在您只对根级别的匹配或无法静态确定的特定上下文中的匹配感兴趣的场景中非常有用。重新抛出的异常通过剩余的解析链传播,就好像给定的@ExceptionHandler方法一开始就不匹配一样。
Spring MVC中对@ExceptionHandler方法的支持是建立在DispatcherServlet级别、HandlerExceptionResolver机制之上的。
方法参数
@ExceptionHandler方法支持以下参数:
Method argument | Description |
---|---|
Exception type | For access to the raised exception. |
| For access to the controller method that raised the exception. |
| Generic access to request parameters and request and session attributes without direct use of the Servlet API. |
| Choose any specific request or response type (for example, |
| Enforces the presence of a session. As a consequence, such an argument is never |
| Currently authenticated user — possibly a specific |
| The HTTP method of the request. |
| The current request locale, determined by the most specific |
| The time zone associated with the current request, as determined by a |
| For access to the raw response body, as exposed by the Servlet API. |
| For access to the model for an error response. Always empty. |
| Specify attributes to use in case of a redirect — (that is to be appended to the query string) and flash attributes to be stored temporarily until the request after the redirect. See Redirect Attributes and Flash Attributes. |
| For access to any session attribute, in contrast to model attributes stored in the session as a result of a class-level |
| For access to request attributes. See |
返回值
@ExceptionHandler方法支持以下返回值:
Return value | Description |
---|---|
| The return value is converted through |
| The return value specifies that the full response (including the HTTP headers and the body) be converted through |
| A view name to be resolved with |
| A |
| Attributes to be added to the implicit model with the view name implicitly determined through a |
| An attribute to be added to the model with the view name implicitly determined through a Note that |
| The view and model attributes to use and, optionally, a response status. |
| A method with a If none of the above is true, a |
Any other return value | If a return value is not matched to any of the above and is not a simple type (as determined by BeanUtils#isSimpleProperty), by default, it is treated as a model attribute to be added to the model. If it is a simple type, it remains unresolved. |
REST API的例外
REST服务的一个常见需求是在响应体中包含错误细节。Spring框架不会自动这样做,因为响应体中的错误细节表示是特定于应用程序的。但是,@RestController可以使用@ExceptionHandler方法和ResponseEntity返回值来设置状态和响应体。也可以在@ControllerAdvice类中声明这些方法,以便全局应用它们。
在响应体中实现带有错误细节的全局异常处理的应用程序应该考虑扩展ResponseEntityExceptionHandler,它为Spring MVC引发的异常提供处理,并提供定制响应体的钩子。要使用它,创建ResponseEntityExceptionHandler的子类,用@ControllerAdvice注释它,覆盖必要的方法,并将它声明为Spring bean。
1.3.7。控制器的建议
通常@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。功能性端点
Spring Web MVC包含了WebMvc。fn是一种轻量级的函数式编程模型,其中函数用于路由和处理请求和契约,它是为不变性而设计的。它是基于注释的编程模型的另一种选择,但也可以在相同的DispatcherServlet上运行。
1.4.1。概述
在WebMvc。一个HTTP请求由一个HandlerFunction处理:一个接受ServerRequest并返回一个ServerResponse的函数。作为响应对象的两个请求都具有不可变的契约,这些契约提供对HTTP请求和响应的JDK 8友好访问。HandlerFunction相当于基于注释的编程模型中的@RequestMapping方法体。
传入的请求被路由到一个带有RouterFunction的处理函数:该函数接受ServerRequest并返回一个可选的HandlerFunction(即可选的<HandlerFunction>)。当路由器函数匹配时,返回处理函数;否则为空可选。RouterFunction相当于@RequestMapping注释,但主要区别在于,路由器函数不仅提供数据,还提供行为。
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
ServerRequest和ServerResponse是不可变的接口,提供对HTTP请求和响应的JDK 8友好访问,包括头、正文、方法和状态代码。
ServerRequest
ServerRequest提供对HTTP方法、URI、标头和查询参数的访问,而对主体的访问是通过主体方法提供的。
下面的例子将请求体提取为一个字符串:
String string = request.body(String.class);
下面的示例将body提取到一个列表<Person>,其中Person对象是从序列化形式(如JSON或XML)解码而来的:
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
下面的例子展示了如何访问参数:
MultiValueMap<String, String> params = request.params();
ServerResponse
ServerResponse提供对HTTP响应的访问,因为它是不可变的,所以您可以使用构建方法来创建它。您可以使用生成器设置响应状态、添加响应标头或提供正文。下面的例子用JSON内容创建了一个200 (OK)响应:
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
下面的例子展示了如何构建一个201 (CREATED)响应,它只有一个Location头,没有body:
下面的例子展示了如何构建一个201 (CREATED)响应,它只有一个Location头,没有body:
处理程序类
我们可以把处理函数写成lambda,如下例所示:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
这很方便,但是在一个应用程序中,我们需要多个函数,而多个内联lambda可能会变得混乱。因此,将相关的处理程序函数分组到一个处理程序类中是很有用的,这个处理程序类在基于注释的应用程序中扮演类似于@Controller的角色。例如,下面的类公开了一个反应性的人员存储库:
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;
}
public ServerResponse listPeople(ServerRequest request) { //1
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception { //2
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) { //3
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();
}
}
}
- listPeople是一个处理函数,它以JSON的形式返回存储库中找到的所有Person对象。
- createPerson是一个处理函数,用于存储请求体中包含的新Person。
- getPerson是一个处理函数,它返回一个由id路径变量标识的person。我们从存储库检索那个人,并创建一个JSON响应(如果找到的话)。如果没有找到,则返回一个404 not found响应。
验证
功能端点可以使用Spring的验证功能将验证应用到请求体。例如,给定一个人的自定义Spring验证器实现:
public class PersonHandler {
private final Validator validator = new PersonValidator(); //1
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person); //2
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString()); //3
}
}
}
- 创建验证器实例。
- 应用验证。
- 为400个响应抛出异常。
处理程序还可以使用标准的bean验证API (JSR-303),方法是基于LocalValidatorFactoryBean创建并注入一个全局验证器实例。看到 Spring Validation。
1.4.3 RouterFunction
路由器函数用于将请求路由到相应的HandlerFunction。通常,您不需要自己编写路由器函数,而是使用RouterFunctions实用程序类上的方法来创建路由器函数。route()(没有参数)为您提供了一个流畅的构建器来创建路由器函数,而RouterFunctions。route(RequestPredicate, HandlerFunction)提供了创建路由器的直接方法。
通常,建议使用route()构建器,因为它为典型的映射场景提供了方便的捷径,而不需要很难发现的静态导入。例如,router函数生成器提供GET(String, HandlerFunction)方法来创建GET请求的映射;和POST(String, HandlerFunction)用于POST。
除了基于HTTP方法的映射之外,route builder还提供了在映射到请求时引入额外谓词的方法。对于每个HTTP方法,都有一个重载的变量,它以RequestPredicate作为参数,但是可以表示哪些额外的约束。
Predicates(判断式)
您可以编写自己的RequestPredicate,但RequestPredicates实用程序类提供了常用的实现,基于请求路径、HTTP方法、content-type等。下面的例子使用一个请求谓词来创建一个基于Accept标头的约束:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
您可以使用以下方法组合多个请求谓词:
- RequestPredicate.and(RequestPredicate)——两者必须匹配。
- RequestPredicate.or(RequestPredicate)——两者都可以匹配。
Many of the predicates from RequestPredicates
are composed. For example, RequestPredicates.GET(String)
is composed from RequestPredicates.method(HttpMethod)
and RequestPredicates.path(String)
. The example shown above also uses two request predicates, as the builder uses RequestPredicates.GET
internally, and composes that with the accept
predicate.
Routes
路由器函数按顺序计算:如果第一个路由不匹配,则计算第二个路由,依此类推。因此,在一般路由之前声明更具体的路由是有意义的。注意,这种行为与基于注释的编程模型不同,后者自动选择“最特定”的控制器方法。
当使用router函数生成器时,所有定义的路由都被组合成一个从build()返回的RouterFunction。也有其他方式来组合多个路由器功能在一起:
add(RouterFunction)
on theRouterFunctions.route()
builderRouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)
— shortcut forRouterFunction.and()
with nestedRouterFunctions.route()
.
下面的例子展示了四种路由的组成:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson) //1
.GET("/person", accept(APPLICATION_JSON), handler::listPeople) //2
.POST("/person", handler::createPerson) //3
.add(otherRoute) //3
.build();
- 带有与JSON匹配的Accept头的GET /person/{id}被路由到PersonHandler.getPerson
- 带有与JSON匹配的Accept头的GET /person被路由到PersonHandler.listPeople
- 没有附加谓词的POST /person被映射到PersonHandler。createPerson和otherRoute是在其他地方创建的路由器函数,并添加到构建的路由中。
嵌套的路线
一组路由器函数通常具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是一个匹配/person的路径谓词,由三个路由使用。在使用注释时,您可以通过使用映射到/person的类型级别@RequestMapping注释来删除这种重复。在WebMvc。路径谓词可以通过路由器函数生成器上的路径方法共享。例如,上面例子的最后几行可以通过使用嵌套路由来改进:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder //1
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
//1 注意,path的第二个参数是一个消费者,它接受路由器生成器。
尽管基于路径的嵌套是最常见的,但是您可以通过在构建器上使用nest方法在任何类型的谓词上进行嵌套。上面仍然包含一些以共享Accept-header谓词形式出现的重复。我们可以通过结合使用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配置在基于DispatcherHandle
r的设置中运行路由器函数,该配置使用Spring配置来声明处理请求所需的组件。MVC Java配置声明了以下基础设施组件来支持功能端点:
- RouterFunctionMapping:检测一个或多个RouterFunction<?在Spring配置中的> bean,通过RouterFunction组合它们。然后,将请求路由到最终的合成RouterFunction。
- HandlerFunctionAdapter:简单的适配器,允许DispatcherHandler调用映射到请求的HandlerFunction。
前面的组件使功能端点适合DispatcherServlet请求处理生命周期,并且(可能)与带注释的控制器一起运行(如果声明了控制器)。这也是Spring Boot Web starter启用功能端点的方式。
下面的例子显示了WebFlux的Java配置:
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
1.4.5。过滤处理程序函数
您可以使用routing function builder上的before、after或filter方法来过滤处理函数。使用注释,您可以通过使用@ControllerAdvice、ServletFilter或两者同时使用来实现类似的功能。过滤器将应用于由生成器构建的所有路由。这意味着嵌套路由中定义的过滤器并不适用于“顶级”路由。例如,考虑下面的例子:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople)
.before(request -> ServerRequest.from(request) //1
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response)) //2
.build();
- 添加自定义请求头的before筛选器仅应用于两个GET路由。
- 记录响应的after过滤器应用于所有路由,包括嵌套路由。
路由器生成器上的filter方法接受一个HandlerFilterFunction:一个接受ServerRequest和HandlerFunction并返回一个ServerResponse的函数。处理函数参数表示链中的下一个元素。这通常是被路由到的处理程序,但是如果应用了多个过滤器,它也可以是另一个过滤器。
现在,我们可以向路由添加一个简单的安全过滤器,假设我们有一个SecurityManager来确定是否允许特定的路径。下面的例子演示了如何做到这一点:
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET("", handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
前面的示例演示了调用next.handle(ServerRequest)是可选的。我们只允许在允许访问时执行处理函数。
除了在路由器函数生成器上使用filter方法外,还可以通过RouterFunction.filter(HandlerFilterFunction)将一个过滤器应用到现有的路由器函数上。
注意:功能端点的CORS支持是通过专用的CorsWebFilter提供的。
1.5。URI的链接
本节描述Spring框架中可用于处理URI的各种选项。
1.5.1。UriComponents
Spring MVC和Spring WebFlux
UriComponentsBuilder有助于从带有变量的URI模板构建URI,如下面的示例所示:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}") //1
.queryParam("q", "{q}") //2
.encode() //3
.build(); //4
URI uri = uriComponents.expand("Westin", "123").toUri(); //5
- 带有URI模板的静态工厂方法。
- 添加或替换URI组件。
- 请求对URI模板和URI变量进行编码。
- 建立一个UriComponents。
- 拓展变量并获得URI。
前面的例子可以合并成一个链,并使用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
Spring MVC和Spring WebFlux
UriComponentsBuilder实现UriBuilder。您可以使用UriBuilderFactory创建一个UriBuilder。UriBuilderFactory和UriBuilder一起提供了一种可插拔的机制,可以根据共享配置(如基本URL、编码首选项和其他详细信息)从URI模板构建URI。
您可以使用UriBuilderFactory配置RestTemplate和WebClient来定制uri的准备工作。DefaultUriBuilderFactory是UriBuilderFactory的默认实现,它在内部使用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在两个级别公开编码选项:
- UriComponentsBuilder#encode():先对URI模板进行预编码,然后在展开时严格地对URI变量进行编码。
- UriComponents#encode():在URI变量展开后对URI组件进行编码。
这两个选项都用转义的八位字符替换非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")
WebClient和RestTemplate通过UriBuilderFactory策略在内部扩展和编码URI模板。两者都可以配置自定义策略。如下例所示:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl)
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
// Customize the RestTemplate..
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
// Customize the WebClient..
WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
DefaultUriBuilderFactory实现在内部使用UriComponentsBuilder来扩展和编码URI模板。作为一个工厂,它提供了一个单一的地方来配置编码的方法,基于以下编码模式之一:
- TEMPLATE_AND_VALUES:使用UriComponentsBuilder#encode(),对应于前面列表中的第一个选项,对URI模板进行预编码,并在展开时严格编码URI变量。
- VALUES_ONLY:不对URI模板进行编码,而是在将URI变量扩展到模板之前,通过UriUtils#encodeUriUriVariables对URI变量进行严格的编码。
- URI_COMPONENTS:使用UriComponents#encode(),对应于前面列表中的第二个选项,在URI变量展开后对URI组件值进行编码。
- NONE:不应用任何编码。
将RestTemplate设置为EncodingMode。历史原因和向后兼容性的URI_COMPONENTS。WebClient依赖于DefaultUriBuilderFactory中的默认值,该值已从EncodingMode更改。URI_COMPONENTS在5.0。x EncodingMode。TEMPLATE_AND_VALUES在5.1。
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(例如/main/*)的uri,如下面的示例所示:
// Re-uses host, port, context path, and Servlet prefix...
ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request)
.path("/accounts").build()
注意:从5.1开始,ServletUriComponentsBuilder将忽略来自转发和x转发-*报头的信息,这些报头指定了源自客户端的地址。考虑使用forwarding headerfilter来提取和使用或丢弃这些头。
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创建链接时,它们的设计就受到了限制。除了需要正确的参数签名外,返回类型还有技术限制(即,为link builder调用生成运行时代理),因此返回类型不能是final。特别是,用于视图名称的公共字符串返回类型在这里不起作用。您应该使用ModelAndView甚至普通对象(带有字符串返回值)来代替。
前面的示例使用了MvcUriComponentsBuilder中的静态方法。在内部,它们依靠ServletUriComponentsBuilder从当前请求的方案、主机、端口、上下文路径和servlet路径准备一个基本URL。这在大多数情况下都很有效。然而,有时,它可能是不够的。例如,您可能在请求的上下文之外(例如准备链接的批处理过程),或者可能需要插入路径前缀(例如从请求路径中删除的地区前缀,需要重新插入链接)。
对于这种情况,可以使用接受UriComponentsBuilder的静态fromXxx重载方法来使用基本URL。或者,您可以使用一个基本URL创建MvcUriComponentsBuilder的实例,然后使用基于实例的withXxx方法。例如,下面的清单使用了withMethodCall:
UriComponentsBuilder base = ServletUriComponentsBuilder.fromCurrentContextPath().path("/en");
MvcUriComponentsBuilder builder = MvcUriComponentsBuilder.relativeTo(base);
builder.withMethodCall(on(BookingController.class).getBooking(21)).buildAndExpand(42);
URI uri = uriComponents.encode().toUri();
注意:从5.1版开始,MvcUriComponentsBuilder将忽略来自转发和x转发-*报头的信息,这些报头指定了源自客户机的地址。考虑使用forwarding headerfilter来提取和使用或丢弃这些头。
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),但是很容易定义自己的函数或为其他模板技术准备类似的函数。
这是如何工作的。在启动时,每个@RequestMapping都通过HandlerMethodMappingNamingStrategy分配一个默认名称,其默认实现使用类的大写字母和方法名(例如,ThingController中的getThing方法变成“TC#getThing”)。如果存在名称冲突,可以使用@RequestMapping(name="..")来分配显式名称或实现自己的HandlerMethodMappingNamingStrategy。
1.6。异步请求
Spring MVC与Servlet 3.0异步请求处理有广泛的集成:
- 控制器方法中的
DeferredResult
和Callable
的返回值,并提供对单个异步返回值的基本支持。 - 控制器可以传输多个值,包括SSE和原始数据。
- 控制器可以使用响应客户端并返回响应处理的响应类型。
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(
可调用的)
WebFlux相比
控制器可以用java.util.concurrent包装任何受支持的返回值。可调用,如下例所示:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
然后可以通过配置的TaskExecutor运行给定的任务来获得返回值。
1.6.3。处理
下面是Servlet异步请求处理的简要概述:
- 可以通过调用request.startAsync()将ServletRequest置于异步模式。这样做的主要效果是Servlet(以及任何过滤器)可以退出,但是响应仍然是开放的,以便稍后完成处理。
- 对request.startAsync()的调用将返回AsyncContext,您可以使用它进一步控制异步处理。例如,它提供分派方法,该方法类似于Servlet API的转发,只是它允许应用程序在Servlet容器线程上恢复请求处理。
- ServletRequest提供对当前DispatcherType的访问,您可以使用它来区分处理初始请求、异步分派、转发和其他分派器类型。
延迟处理工作如下:
- 控制器返回一个DeferredResult,并将其保存在某个可以访问它的内存队列或列表中。
- Spring MVC调用request.startAsync()。
- 同时,DispatcherServlet和所有配置的筛选器退出请求处理线程,但是响应保持打开状态。
- 应用程序从某个线程设置DeferredResult, Spring MVC将请求发送回Servlet容器。
- 再次调用DispatcherServlet,然后继续处理异步生成的返回值。
可调用的处理工作如下:
- 控制器返回一个可调用的。
- Spring MVC调用request.startAsync()并将调用提交给TaskExecutor,以便在单独的线程中进行处理。
- 同时,DispatcherServlet和所有筛选器退出Servlet容器线程,但是响应保持打开状态。
- 最后,Callable产生一个结果,Spring MVC将请求发送回Servlet容器以完成处理。
- 再次调用DispatcherServlet,然后处理从Callable异步生成的返回值。
要了解更多的背景和上下文,您还可以阅读Spring MVC 3.2中介绍异步请求处理支持的博客文章。
异常处理
当您使用DeferredResult时,您可以选择调用setResult还是setErrorResult(带有异常)。在这两种情况下,Spring MVC都将请求发送回Servlet容器以完成处理。然后,要么将其视为控制器方法返回给定值,要么将其视为产生给定异常。然后异常通过常规的异常处理机制(例如,调用@ExceptionHandler方法)。
当您使用Callable时,会出现类似的处理逻辑,主要的区别是结果是从Callable返回的,还是由它引发的异常。
拦截
HandlerInterceptor实例可以是AsyncHandlerInterceptor类型,用于在开始异步处理的初始请求上接收afterConcurrentHandlingStarted回调(而不是在完成之后)。
HandlerInterceptor实现还可以注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便更深入地集成异步请求的生命周期(例如,处理超时事件)。有关更多详细信息,请参见AsyncHandlerInterceptor。
DeferredResult提供onTimeout(Runnable)和onCompletion(Runnable)回调。有关详细信息,请参阅DeferredResult的javadoc。可以用Callable替换WebAsyncTask,后者为超时和完成回调提供了额外的方法。
WebFlux相比
Servlet API最初是为通过过滤器-Servlet链进行单次传递而构建的。在Servlet 3.0中添加的异步请求处理允许应用程序退出过滤器-Servlet链,但保留响应以供进一步处理。Spring MVC异步支持就是围绕这种机制构建的。当控制器返回一个DeferredResult时,过滤器-Servlet链被退出,Servlet容器线程被释放。稍后,当设置DeferredResult时,将进行异步分派(到相同的URL),在此期间将再次映射控制器,但不是调用它,而是使用DeferredResult值(就像控制器返回它一样)来恢复处理。
相比之下,Spring WebFlux既不是基于Servlet API构建的,也不需要这样的异步请求处理特性,因为它在设计上就是异步的。异步处理构建在所有框架契约中,并在请求处理的所有阶段得到本质上的支持。
从编程模型的角度来看,Spring MVC和Spring WebFlux都支持异步和响应类型作为控制器方法的返回值。Spring MVC甚至支持流,包括无功回压。但是,对响应的各个写操作仍然是阻塞的(并且是在单独的线程上执行的),这与WebFlux不同,后者依赖于非阻塞I/O,并且每次写操作都不需要额外的线程。
另一个基本区别是,Spring MVC在控制器方法参数中不支持异步或响应类型(例如,@RequestBody、@RequestPart等),也不支持将异步和响应类型作为模型属性。Spring WebFlux确实支持所有这些。
1.6.4 HTTP流媒体
您可以对单个异步返回值使用DeferredResult和Callable。如果希望生成多个异步值并将其写入响应中,该怎么办?本节将介绍如何做到这一点。
对象
您可以使用ResponseBodyEmitter返回值来生成对象流,其中每个对象都使用HttpMessageConverter序列化并写入响应,如下面的示例所示:
@GetMapping("/events")
public ResponseBodyEmitter handle() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
// Save the emitter somewhere..
return emitter;
}
// In some other thread
emitter.send("Hello once");
// and again later on
emitter.send("Hello again");
// and done at some point
emitter.complete();
您还可以使用ResponseBodyEmitter作为ResponseEntity中的主体,让您自定义响应的状态和标题。
当发射器抛出IOException(例如,如果远程客户端消失)时,应用程序不负责清理连接,不应调用exit .complete或exit . completewitherror。相反,servlet容器会自动启动一个AsyncListener错误通知,在这个通知中,Spring MVC会执行一个completeWithError调用。然后,这个调用对应用程序执行最后一次异步分派,在此期间,Spring MVC调用已配置的异常解析器并完成请求。
SSE
SseEmitter (ResponseBodyEmitter的子类)提供对服务器发送事件的支持,从服务器发送的事件根据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 -webflux的WebClient和其他的,比如Spring Data反应性数据仓库。在这样的场景中,可以方便地从控制器方法返回响应类型。
无功返回值处理如下:
- 与使用DeferredResult类似,也适用于单值承诺。例如Mono(反应器)或Single (RxJava)。
- 与使用ResponseBodyEmitter或SseEmitter类似,适应具有流媒体类型(例如应用程序/流+json或文本/事件流)的多值流。例如Flux (Reactor)或Observable (RxJava)。应用程序还可以返回Flux<ServerSentEvent>或Observable<ServerSentEvent>。
- 与使用DeferredResult<List<?>>类似,可以适应任何其他媒体类型(如application/json)的多值流。
注意:Spring MVC通过Spring -core的ReactiveAdapterRegistry支持Reactor和RxJava,这使它能够适应多个反应性库。
对于响应的流,支持反应性回压,但是对响应的写操作仍然是阻塞的,并通过配置的TaskExecutor在单独的线程上执行,以避免阻塞上游的源(例如从WebClient返回的通量)。默认情况下,SimpleAsyncTaskExecutor用于阻塞写,但在负载下不适合。如果您计划使用反应式类型进行流处理,则应该使用MVC配置来配置任务执行程序。
1.6.6。断开连接
Servlet API在远程客户端离开时不提供任何通知。因此,在通过SseEmitter或reactive类型流到响应时,定期发送数据是很重要的,因为如果客户机断开连接,写操作就会失败。发送可以采用空的(仅供引用的)SSE事件或任何其他数据的形式,而另一方必须将这些数据解释为心跳并忽略它们。
或者,考虑使用具有内置心跳机制的web消息传递解决方案(such as STOMP over WebSocket or WebSocket with SockJS)。
1.6.7。配置
异步请求处理特性必须在Servlet容器级别启用。MVC配置还为异步请求提供了几个选项。
Servlet容器
过滤器和Servlet声明有一个asyncSupported标记,需要将其设置为true以启用异步请求处理。此外,应该声明过滤器映射来处理ASYNC javax.servlet.DispatchType。
在Java配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer来初始化Servlet容器时,这是自动完成的。
在web.xml配置中,可以将< ASYNC支持的>true</ ASYNC支持的>添加到DispatcherServlet并过滤声明,将<dispatcher>ASYNC</dispatcher>添加到过滤映射。
Spring MVC
MVC配置公开了以下与异步请求处理相关的选项:
- Java配置:在WebMvcConfigurer上使用configureyncsupport回调。
- XML命名空间:使用<mvc:注释驱动的>下的<async-support>元素。
您可以配置以下内容:
- 异步请求的默认超时值(如果没有设置)取决于底层的Servlet容器。
- AsyncTaskExecutor用于在使用响应类型流时进行写阻塞,以及执行从控制器方法返回的可调用实例。我们强烈建议配置此属性,如果您使用响应类型或具有返回可调用的控制器方法,因为在默认情况下,它是一个SimpleAsyncTaskExecutor。
- DeferredResultProcessingInterceptor实现和CallableProcessingInterceptor实现。
注意,您还可以在DeferredResult、ResponseBodyEmitter和SseEmitter上设置默认的超时值。对于Callable,可以使用WebAsyncTask提供超时值。
1.7。CORS
Spring MVC允许您处理CORS(跨源资源共享)。本节将介绍如何做到这一点。
1.7.1上。介绍
出于安全原因,浏览器禁止AJAX调用当前源之外的资源。例如,你可以在一个标签中显示你的银行账户,在另一个标签中显示evil.com。来自evil.com的脚本不应该能够使用您的凭证向您的银行API发出AJAX请求——例如从您的帐户中取款!
跨源资源共享(Cross-Origin Resource Sharing, CORS)是大多数浏览器实现的W3C规范,它允许您指定授权的跨域请求类型,而不是使用基于IFRAME或JSONP的不太安全、功能不太强大的工作区。
1.7.2。处理
CORS规范区分了飞行前、简单请求和实际请求。要了解CORS是如何工作的,您可以阅读这篇文章以及其他许多文章,或者查看规范了解更多细节。
Spring MVC HandlerMapping实现提供了对CORS的内置支持。成功地将请求映射到处理程序之后,HandlerMapping实现将检查给定请求和处理程序的CORS配置并采取进一步的操作。飞行前请求直接处理,而简单的和实际的CORS请求被拦截、验证,并需要设置CORS响应头。
为了启用跨源请求(即,源标头存在并且与请求的主机不同),您需要一些显式声明的CORS配置。如果没有找到匹配的CORS配置,飞行前请求将被拒绝。没有CORS标头被添加到简单的和实际的CORS请求的响应中,因此,浏览器会拒绝它们。
每个HandlerMapping都可以使用基于URL模式的CorsConfiguration映射单独配置。在大多数情况下,应用程序使用MVC Java配置或XML名称空间来声明这样的映射,这会导致将单个全局映射传递给所有HandlerMappping实例。
您可以将HandlerMapping级别的全局CORS配置与更细粒度的、handler级别的CORS配置组合起来。例如,带注释的控制器可以使用类级或方法级@CrossOrigin注释(其他处理程序可以实现CorsConfigurationSource)。
组合全局和本地配置的规则通常是附加的——例如,所有全局和所有本地源。对于那些只能接受单个值的属性(如allowCredentials和maxAge),本地覆盖全局值。有关更多细节,请参见CorsConfiguration#combine(CorsConfiguration)
。
要了解更多的源代码或作出先进的自定义,检查背后的代码:
- CorsConfiguration
- CorsProcessor, DefaultCorsProcessor
- 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名称空间来实现这一点。
默认情况下,全局配置支持以下功能:
- 所有的起源。
- 所有的标题。
- GET、HEAD和POST方法。
默认情况下不启用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支持。
如果您尝试将CorsFilter与Spring Security一起使用,请记住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 Caching
HTTP缓存可以显著提高web应用程序的性能。HTTP缓存围绕着Cache-Control响应标头,随后是条件请求标头(如Last-Modified和ETag)。Cache-Control建议私有(例如,浏览器)和公共(例如,代理)缓存如何缓存和重用响应。ETag头用于发出一个条件请求,如果内容没有更改,则可能导致304 (NOT_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”指令来防止缓存。
- 通过使用'Cache-Control: max-age=n'指令,一个n > 0值缓存给定的响应n秒。
1.9.2。控制器
控制器可以添加对HTTP缓存的显式支持。我们建议这样做,因为在与条件请求头进行比较之前,需要计算资源的lastModified或ETag值。一个控制器可以添加一个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);
}
如果与条件请求头的比较表明内容没有更改,那么前面的示例将发送一个304 (NOT_MODIFIED)响应,该响应的主体为空。否则,ETag和Cache-Control头将添加到响应中。
你也可以在控制器中检查条件请求头,如下例所示:
@RequestMapping
public String myHandleMethod(WebRequest webRequest, Model model) {
long eTag = ... //1
if (request.checkNotModified(eTag)) {
return null; //2
}
model.addAttribute(...); //3
return "myViewName";
}
- 特定于应用程序的计算。
- 响应被设置为304 (NOT_MODIFIED)—没有进一步处理。
- 继续请求处理。
有三种变体用于根据eTag值、lastModified值或两者同时检查条件请求。对于条件GET和HEAD请求,可以将响应设置为304 (NOT_MODIFIED)。对于有条件的POST、PUT和DELETE,您可以将响应设置为412 (PRECONDITION_FAILED),以防止并发修改。
1.9.3。静态资源
您应该使用缓存控制和条件响应头来提供静态资源,以获得最佳性能。参见 Static Resources.一节。
1.9.4。ETag过滤器
您可以使用ShallowEtagHeaderFilter来添加从响应内容计算出来的“shallow”eTag值,从而节省带宽,但不节省CPU时间。看Shallow ETag.
1.10。视图技术
Spring MVC中视图技术的使用是可插拔的,无论您决定使用Thymeleaf、Groovy标记模板、jsp还是其他技术,这主要是一个配置更改的问题。本章将介绍与Spring MVC集成的视图技术。我们假设您已经熟悉 View Resolution。
1.10.1. Thymeleaf
Thymeleaf是一个现代的服务器端Java模板引擎,强调自然的HTML模板,可以在浏览器中预览双击,这是非常有帮助的独立工作的UI模板(例如,由设计师)不需要运行的服务器。如果您想要替换jsp, Thymeleaf提供了一组最广泛的特性来简化这种转换。Thymeleaf是积极发展和维护。有关更完整的介绍,请参见Thymeleaf项目主页。
与Spring MVC的Thymeleaf集成由Thymeleaf项目管理。配置涉及到一些bean声明,如ServletContextTemplateResolver、SpringTemplateEngine和ThymeleafViewResolver。更多细节见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。属性对象,而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"/>
有关应用于配置对象的设置和变量的详细信息,请参阅FreeMarker文档。
表单处理
Spring提供了一个用于jsp的标记库,其中包含一个< Spring:bind/>元素。这个元素主要让表单显示来自表单支持对象的值,并显示来自web或业务层验证器的失败验证的结果。Spring还支持FreeMarker中的相同功能,并为生成表单输入元素本身提供了额外的方便宏。
结合宏
在spring-webmvc.jar文件中为FreeMarker维护了一组标准的宏,因此它们始终对适当配置的应用程序可用。
Spring模板库中定义的一些宏被认为是内部的(私有的),但是在宏定义中不存在这样的作用域,这使得所有宏对于调用代码和用户模板都是可见的。下面的部分只关注您需要从模板中直接调用的宏。如果希望直接查看宏代码,则该文件称为spring。它位于org.springframework.web.servlet.view.freemarker包中。
简单的绑定
在基于FreeMarker模板(充当Spring MVC控制器的表单视图)的HTML表单中,可以使用类似下一个示例的代码绑定到字段值,并以类似于JSP的方式显示每个输入字段的错误消息。下面的例子显示了一个personForm视图:
<!-- FreeMarker macros have to be imported into a namespace.
We strongly recommend sticking to 'spring'. -->
<#import "/spring.ftl" as spring/>
<html>
...
<form action="" method="POST">
Name:
<@spring.bind "personForm.name"/>
<input type="text"
name="${spring.status.expression}"
value="${spring.status.value?html}"/><br />
<#list spring.status.errorMessages as error> <b>${error}</b> <br /> </#list>
<br />
...
<input type="submit" value="submit"/>
</form>
...
</html>
<@spring.bind>需要一个“path”参数,该参数由您的命令对象的名称(它是“command”,除非您在控制器配置中更改了它)、句点和您希望绑定到的命令对象上的字段名称组成。您还可以使用嵌套字段,比如command.address.street。绑定宏假设默认的HTML转义行为是由ServletContext参数defaultHtmlEscape在web.xml中指定的。
宏的另一种形式称为<@spring.bindEscaped>接受第二个参数,该参数显式地指定是否应该在状态错误消息或值中使用HTML转义。您可以根据需要将其设置为true或false。额外的表单处理宏简化了HTML转义的使用,您应该尽可能地使用这些宏。下一节将对此进行解释。
输入宏
FreeMarker附加的方便宏简化了绑定和表单生成(包括验证错误显示)。从来没有必要使用这些宏来生成表单输入字段,您可以将它们与简单的HTML或直接调用Spring bind宏(我们在前面突出显示的)混合和匹配。
下表的可用宏显示FreeMarker模板(FTL)的定义和参数列表,每个:
macro | FTL definition |
---|---|
| <@spring.message code/> |
| <@spring.messageText code, text/> |
| <@spring.url relativeUrl/> |
| <@spring.formInput path, attributes, fieldType/> |
| <@spring.formHiddenInput path, attributes/> |
| <@spring.formPasswordInput path, attributes/> |
| <@spring.formTextarea path, attributes/> |
| <@spring.formSingleSelect path, options, attributes/> |
| <@spring.formMultiSelect path, options, attributes/> |
| <@spring.formRadioButtons path, options separator, attributes/> |
| <@spring.formCheckboxes path, options, separator, attributes/> |
| <@spring.formCheckbox path, attributes/> |
| <@spring.showErrors separator, classOrStyle/> |
注意:在FreeMarker模板中,formHiddenInput和formPasswordInput实际上不是必需的,因为您可以使用常规的formInput宏,将hidden或password指定为fieldType参数的值。
以上任何一个宏的参数都具有一致的含义:
- path:要绑定到的字段的名称(即“command.name”)。
- options:可以从输入字段中选择的所有可用值的映射。映射的键表示从表单发回并绑定到command对象的值。根据键存储的映射对象是表单上显示给用户的标签,可能与表单返回的相应值不同。通常,这样的映射是由控制器作为参考数据提供的。您可以使用任何映射实现,具体取决于所需的行为。对于严格排序的映射,可以使用SortedMap(例如TreeMap)和适当的比较器,对于应该按插入顺序返回值的任意映射,可以使用LinkedHashMap或公共集合中的LinkedMap。
- separator:当多个选项作为离散元素(单选按钮或复选框)可用时,用于分隔列表中每个元素的字符序列(如<br>)
- attributes:HTML标记本身中包含的任意标记或文本的附加字符串。该字符串由宏回显。例如,在textarea字段中,您可以提供属性(如'rows="5" cols="60"'),或者您可以传递样式信息,如'style="border:1px solid silver"'。
- classOrStyle:对于showErrors宏,包装每个错误的span元素使用的CSS类的名称。如果没有提供任何信息(或者值为空),则错误被包装在标记中。
下面几节概述了宏的示例。
输入字段
formInput宏接受path参数(command.name)和一个附加属性参数(在下面的示例中为空)。该宏与所有其他表单生成宏一起,对path参数执行隐式Spring绑定。在出现新的绑定之前,绑定仍然有效,因此showErrors宏不需要再次传递path参数—它对上一次创建绑定的字段进行操作。
showErrors宏接受一个分隔符参数(用于分隔给定字段上的多个错误的字符),并接受第二个参数——这一次是类名或样式属性。注意,FreeMarker可以为attributes参数指定默认值。下面的例子展示了如何使用formInput和showWErrors宏:
<@spring.formInput "command.name"/>
<@spring.showErrors "<br>"/>
下一个示例显示表单片段的输出,生成name字段并在提交表单后显示验证错误,该字段中没有值。验证通过Spring的验证框架进行。
生成的HTML类似于下面的例子:
Name:
<input type="text" name="name" value="">
<br>
<b>required</b>
<br>
<br>
formTextarea宏的工作方式与formInput宏相同,并且接受相同的参数列表。通常,第二个参数(属性)用于传递文本区域的样式信息或行和cols属性。
选择的字段
您可以使用四个选择字段宏来生成HTML表单中常见的UI值选择输入:
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
这四个宏中的每一个都接受一个选项映射,其中包含表单字段的值和对应于该值的标签。值和标签可以是相同的。
下一个例子是FTL中的单选按钮。form-backing对象为这个字段指定默认值'London',因此不需要验证。在呈现表单时,要选择的整个城市列表将作为参考数据提供给模型中的“cityMap”。下面的清单显示了示例:
...
Town:
<@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
前面的清单显示了一行单选按钮,其中一个代表cityMap中的每个值,并使用分隔符“”。没有提供额外的属性(缺少宏的最后一个参数)。cityMap对映射中的每个键-值对使用相同的字符串。映射的键是表单作为POST请求参数实际提交的内容。映射值是用户看到的标签。在前面的例子中,给定三个著名城市的列表和表单支持对象中的默认值,HTML类似于以下内容:
Town:
<input type="radio" name="address.town" value="London">London</input>
<input type="radio" name="address.town" value="Paris" checked="checked">Paris</input>
<input type="radio" name="address.town" value="New York">New York</input>
如果您的应用程序期望通过内部代码处理城市(例如),您可以创建具有适当键的代码映射,如下面的示例所示:
protected Map<String, ?> referenceData(HttpServletRequest request) throws Exception {
Map<String, String> cityMap = new LinkedHashMap<>();
cityMap.put("LDN", "London");
cityMap.put("PRS", "Paris");
cityMap.put("NYC", "New York");
Map<String, Object> model = new HashMap<>();
model.put("cityMap", cityMap);
return model;
}
代码现在产生的输出中,无线电值是相关的代码,但用户仍然看到更友好的城市名称,如下所示:
Town:
<input type="radio" name="address.town" value="LDN">London</input>
<input type="radio" name="address.town" value="PRS" checked="checked">Paris</input>
<input type="radio" name="address.town" value="NYC">New York</input>
HTML转义
前面描述的表单宏的默认用法导致HTML元素符合HTML 4.01,并且使用web.xml文件中定义的HTML转义的默认值,如Spring的绑定支持所使用的。为了使元素符合XHTML或者覆盖默认的HTML转义值,您可以在模板中指定两个变量(或者在模型中,它们对模板是可见的)。在模板中指定它们的好处是,它们可以在稍后的模板处理中更改为不同的值,从而为表单中的不同字段提供不同的行为。
要切换到符合XHTML的标记,为一个名为xhtmlCompliant的模型或上下文变量指定一个值true,如下面的例子所示:
<#-- for FreeMarker -->
<#assign xhtmlCompliant = true>
在处理这个指令之后,由Spring宏生成的任何元素现在都符合XHTML。
以类似的方式,可以为每个字段指定HTML转义,如下面的示例所示:
<#-- until this point, default HTML escaping is used -->
<#assign htmlEscape = true>
<#-- next field will use HTML escaping -->
<@spring.formInput "command.name"/>
<#assign htmlEscape = false in spring>
<#-- all future fields will be bound with HTML escaping off -->
1.10.3。Groovy的标记
Groovy Markup Template Engine的主要目标是生成类似于XML的标记(XML、XHTML、HTML5等),但是您可以使用它来生成任何基于文本的内容。Spring框架有一个内置的集成,用于将Spring MVC与Groovy标记结合使用。
Groovy标记模板引擎需要Groovy 2.3.1+.
配置
下面的示例展示了如何配置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/"/>
例子
与传统的模板引擎不同,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。脚本视图
Spring框架有一个内置的集成,可以使用Spring MVC和任何可以在JSR-223 Java脚本引擎上运行的模板库。我们已经在不同的脚本引擎上测试了以下模板库:
Scripting Library | Scripting Engine |
---|---|
集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngine和Invocable接口。
Requirements
你需要在你的类路径中有脚本引擎,它的细节因脚本引擎而异:
纳什霍恩JavaScript引擎由Java 8+提供。强烈建议使用可用的最新更新版本。
应该添加JRuby作为Ruby支持的依赖项。
Jython应该作为Python支持的依赖项添加。
org.jetbrains.kotlin:kotlin-script-util依赖关系和元inf /services/javax.script。包含org.jetbrain.kotlin.script.jsr223的ScriptEngineFactory文件。为了支持Kotlin脚本,应该添加KotlinJsr223JvmLocalScriptEngineFactory行。有关更多细节,请参见此示例。
您需要有脚本模板库。一种实现Javascript的方法是通过 WebJars。
脚本模板
您可以声明ScriptTemplateConfigurer bean来指定要使用的脚本引擎、要加载的脚本文件、要调用什么函数来呈现模板,等等。下面的例子使用了Mustache模板和Nashorn JavaScript引擎:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("mustache.js");
configurer.setRenderObject("Mustache");
configurer.setRenderFunction("render");
return configurer;
}
}
下面的例子展示了XML中的相同安排:
<mvc:annotation-driven/>
<mvc:view-resolvers>
<mvc:script-template/>
</mvc:view-resolvers>
<mvc:script-template-configurer engine-name="nashorn" render-object="Mustache" render-function="render">
<mvc:script location="mustache.js"/>
</mvc:script-template-configurer>
对于Java和XML配置,控制器看起来没有什么不同,如下面的例子所示:
@Controller
public class SampleController {
@GetMapping("/sample")
public String test(Model model) {
model.addAttribute("title", "Sample title");
model.addAttribute("body", "Sample body");
return "template";
}
}
下面的例子展示了Mustache模板:
<html>
<head>
<title>{{title}}</title>
</head>
<body>
<p>{{body}}</p>
</body>
</html>
使用以下参数调用render函数:
- String template:模板内容
- Map model:视图模型
- RenderingContext renderingContext: RenderingContext允许访问应用程序上下文、语言环境、模板加载器和URL(从5.0开始)
Mustache.render()与这个签名是天生兼容的,所以您可以直接调用它。
如果您的模板技术需要一些自定义,您可以提供实现自定义呈现函数的脚本。例如,Handlerbars需要在使用模板之前进行编译,并且需要一个polyfill来模拟服务器端脚本引擎中不可用的一些浏览器功能。
下面的例子演示了如何做到这一点:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.scriptTemplate();
}
@Bean
public ScriptTemplateConfigurer configurer() {
ScriptTemplateConfigurer configurer = new ScriptTemplateConfigurer();
configurer.setEngineName("nashorn");
configurer.setScripts("polyfill.js", "handlebars.js", "render.js");
configurer.setRenderFunction("render");
configurer.setSharedEngine(false);
return configurer;
}
}
注意:在使用非线程安全的脚本引擎时,需要将sharedEngine属性设置为false,这些脚本引擎的模板库不是为并发性设计的,比如在Nashorn上运行的把手或React。在这种情况下,由于这个bug,需要Java SE 8 update 60,但是通常建议在任何情况下都使用最近的Java SE补丁版本。
js只定义了车Handlebars 正常运行所需要的window对象,如下:
var window = {};
render.js实现在使用模板之前先编译模板。准备生产的实现还应该存储任何重用的缓存模板或预编译模板。您可以在脚本端这样做(并处理您需要的任何自定义—例如管理模板引擎配置)。下面的例子演示了如何做到这一点:
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
有关更多配置示例,请参阅Spring框架单元测试、Java和参考资料。
1.10.5。JSP和JSTL
Spring框架有一个内置的集成,用于将Spring MVC与JSP和JSTL结合使用。
视图解析器
使用jsp开发时,您可以声明一个InternalResourceViewResolver
或ResourceBundleViewResolver 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>
JSPs versus 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片段,其中某些标签需要进一步的注释。
配置
表单标记库绑定在spring-webmvc.jar中。库描述符称为spring-form.tld。
要使用这个库中的标记,请将以下指令添加到JSP页面的顶部:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中form是要用于来自这个库的标记的标记名称前缀。
表单标签
此标记呈现HTML 'form'元素,并将绑定路径公开给用于绑定的内部标记。它将命令对象放在PageContext中,以便内部标记可以访问命令对象。这个库中的所有其他标记都是表单标记的嵌套标记。
假设我们有一个名为User的域对象。它是一个JavaBean,具有firstName和lastName等属性。我们可以使用它作为表单控制器的表单支持对象,它返回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>
下面的清单显示了生成的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>
输入标签
此标记默认呈现一个具有绑定值和type='text'的HTML输入元素。有关此标记的示例,请参见表单标记。您还可以使用html5特有的类型,如电子邮件、电话、日期等。
标签的复选框
此标记呈现类型设置为复选框的HTML输入标记。
假设我们的用户有偏好,比如订阅时事通讯和爱好列表。下面的例子显示了Preferences类:
public class Preferences {
private boolean receiveNewsletter;
private String[] interests;
private String favouriteWord;
public boolean isReceiveNewsletter() {
return receiveNewsletter;
}
public void setReceiveNewsletter(boolean receiveNewsletter) {
this.receiveNewsletter = receiveNewsletter;
}
public String[] getInterests() {
return interests;
}
public void setInterests(String[] interests) {
this.interests = interests;
}
public String getFavouriteWord() {
return favouriteWord;
}
public void setFavouriteWord(String favouriteWord) {
this.favouriteWord = favouriteWord;
}
}
相应的form.jsp可以类似于以下内容:
<form:form>
<table>
<tr>
<td>Subscribe to newsletter?:</td>
<%-- Approach 1: Property is of type java.lang.Boolean --%>
<td><form:checkbox path="preferences.receiveNewsletter"/></td>
</tr>
<tr>
<td>Interests:</td>
<%-- Approach 2: Property is of an array or of type java.util.Collection --%>
<td>
Quidditch: <form:checkbox path="preferences.interests" value="Quidditch"/>
Herbology: <form:checkbox path="preferences.interests" value="Herbology"/>
Defence Against the Dark Arts: <form:checkbox path="preferences.interests" value="Defence Against the Dark Arts"/>
</td>
</tr>
<tr>
<td>Favourite Word:</td>
<%-- Approach 3: Property is of type java.lang.Object --%>
<td>
Magic: <form:checkbox path="preferences.favouriteWord" value="Magic"/>
</td>
</tr>
</table>
</form:form>
有三种方法可以实现复选框标记,它们应该满足您的所有复选框需求。
- 方法一:当绑定值是java.lang类型时。如果绑定值为true,则输入(复选框)被标记为已选中。value属性对应于setValue(Object)值属性的解析值。
- 方法二:当绑定值是array或java.util类型时。如果已配置的setValue(Object)值出现在绑定集合中,则输入(复选框)被标记为已选中。
- 方法三:对于任何其他绑定值类型,如果配置的setValue(对象)等于绑定值,则输入(复选框)被标记为已选中。
注意,无论采用哪种方法,都会生成相同的HTML结构。下面的HTML片段定义了一些复选框:
<tr>
<td>Interests:</td>
<td>
Quidditch: <input name="preferences.interests" type="checkbox" value="Quidditch"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Herbology: <input name="preferences.interests" type="checkbox" value="Herbology"/>
<input type="hidden" value="1" name="_preferences.interests"/>
Defence Against the Dark Arts: <input name="preferences.interests" type="checkbox" value="Defence Against the Dark Arts"/>
<input type="hidden" value="1" name="_preferences.interests"/>
</td>
</tr>
您可能不希望在每个复选框后看到额外的隐藏字段。当HTML页面中的复选框没有被选中时,表单提交后,它的值不会作为HTTP请求参数的一部分发送到服务器,因此我们需要一个解决方案来解决HTML中的这个问题,以便Spring表单数据绑定能够工作。复选框标记遵循现有的Spring约定,即为每个复选框包含一个以下划线(_)为前缀的隐藏参数。通过这样做,您实际上是在告诉Spring“复选框在表单中是可见的,我希望表单数据绑定到的对象能够反映复选框的状态,无论如何。”
复选框的标签
此标记呈现多个类型设置为复选框的HTML输入标记。
本节以前面复选框标记部分中的示例为基础。有时,您不希望在JSP页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表并将其传递给标记。这就是复选框标记的用途。可以传入一个数组、一个列表或一个包含items属性中可用选项的映射。通常,绑定属性是一个集合,因此它可以包含用户选择的多个值。下面的例子展示了一个使用这个标签的JSP:
<form:form>
<table>
<tr>
<td>Interests:</td>
<td>
<%-- Property is of an array or of type java.util.Collection --%>
<form:checkboxes path="preferences.interests" items="${interestList}"/>
</td>
</tr>
</table>
</form:form>
本例假设兴趣列表是一个可用的模型属性列表,其中包含要从中选择的值的字符串。如果使用映射,则将映射条目键用作值,并将映射条目的值用作要显示的标签。您还可以使用自定义对象,其中可以通过使用itemValue为值提供属性名称,通过使用itemLabel提供标签。
radiobutton标记
此标记呈现类型设置为radio的HTML输入元素。
典型的使用模式是将多个标记实例绑定到相同的属性,但是具有不同的值,如下面的示例所示:
<tr>
<td>Sex:</td>
<td>
Male: <form:radiobutton path="sex" value="M"/> <br/>
Female: <form:radiobutton path="sex" value="F"/>
</td>
</tr>
radiobuttons标签
此标记呈现多个类型设置为radio的HTML输入元素。
与复选框标记一样,您可能希望将可用选项作为运行时变量传递。对于这种用法,可以使用radiobuttons标记。传入一个数组、一个列表或一个包含items属性中可用选项的映射。如果使用映射,则使用映射条目键作为值,并使用映射条目的值作为要显示的标签。您还可以使用自定义对象,其中可以通过使用itemValue提供值的属性名,通过使用itemLabel提供标签,如下例所示:
<tr>
<td>Sex:</td>
<td><form:radiobuttons path="sex" items="${sexOptions}"/></td>
</tr>
密码标记
此标记呈现类型设置为password并带有绑定值的HTML输入标记。
<tr>
<td>Password:</td>
<td>
<form:password path="password"/>
</td>
</tr>
注意,默认情况下不显示密码值。如果希望显示密码值,可以将showPassword属性的值设置为true,如下面的示例所示:
<tr>
<td>Password:</td>
<td>
<form:password path="password" value="^76525bvHGq" showPassword="true"/>
</td>
</tr>
选择标记
此标记呈现HTML“select”元素。它支持对所选选项的数据绑定,以及使用嵌套的选项和选项标签。
假设用户有一个技能列表。相应的HTML可以是:
<tr>
<td>Skills:</td>
<td><form:select path="skills" items="${skills}"/></td>
</tr>
如果用户的技能是草药学的,那么“技能”行的HTML源可以如下:
<tr>
<td>Skills:</td>
<td>
<select name="skills" multiple="true">
<option value="Potions">Potions</option>
<option value="Herbology" selected="selected">Herbology</option>
<option value="Quidditch">Quidditch</option>
</select>
</td>
</tr>
选择标记
此标记呈现HTML选项元素。它根据绑定值设置所选的值。下面的HTML显示了它的典型输出:
<tr>
<td>House:</td>
<td>
<form:select path="house">
<form:option value="Gryffindor"/>
<form:option value="Hufflepuff"/>
<form:option value="Ravenclaw"/>
<form:option value="Slytherin"/>
</form:select>
</td>
</tr>
如果用户的房子在格兰芬多,“房子”行的HTML源代码如下:
<tr>
<td>House:</td>
<td>
<select name="house">
<option value="Gryffindor" selected="selected">Gryffindor</option>
<option value="Hufflepuff">Hufflepuff</option>
<option value="Ravenclaw">Ravenclaw</option>
<option value="Slytherin">Slytherin</option>
</select>
</td>
</tr>
选择标记
此标记呈现HTML选项元素列表。它根据绑定值设置所选属性。下面的HTML显示了它的典型输出:
<tr>
<td>Country:</td>
<td>
<form:select path="country">
<form:option value="-" label="--Please Select"/>
<form:options items="${countryList}" itemValue="code" itemLabel="name"/>
</form:select>
</td>
</tr>
如果用户居住在英国,则“Country”行的HTML源代码如下:
<tr>
<td>Country:</td>
<td>
<select name="country">
<option value="-">--Please Select</option>
<option value="AT">Austria</option>
<option value="UK" selected="selected">United Kingdom</option>
<option value="US">United States</option>
</select>
</td>
</tr>
正如前面的示例所示,选项标记和选项标记的组合使用生成了相同的标准HTML,但是允许您在JSP中显式地指定一个仅用于显示(属于它的地方)的值,例如示例中的默认字符串:“——请选择”。
项属性通常由项对象的集合或数组填充。itemValue和itemLabel指的是那些item对象的bean属性(如果指定的话)。否则,项对象本身将被转换为字符串。或者,您可以指定项目的映射,在这种情况下,映射键被解释为选项值,而映射值对应于选项标签。如果同时指定了itemValue或itemLabel(或两者都指定),则item value属性应用于映射键,而item label属性应用于映射值。
文本区域标记
此标记呈现HTML textarea元素。下面的HTML显示了它的典型输出:
<tr>
<td>Notes:</td>
<td><form:textarea path="notes" rows="3" cols="20"/></td>
<td><form:errors path="notes"/></td>
</tr>
隐藏的标签
此标记呈现类型设置为隐藏绑定值的HTML输入标记。要提交未绑定的隐藏值,请使用类型设置为hidden的HTML输入标记。下面的HTML显示了它的典型输出:
<form:hidden path="house"/>
如果我们选择提交隐藏的房屋价值,HTML将如下:
<input name="house" type="hidden" value="Gryffindor"/>
错误的标签
此标记在HTML span元素中呈现字段错误。它提供对在控制器中创建的错误或与控制器关联的任何验证器创建的错误的访问。
public class UserValidator implements Validator {
public boolean supports(Class candidate) {
return User.class.isAssignableFrom(candidate);
}
public void validate(Object obj, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "required", "Field is required.");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "lastName", "required", "Field is required.");
}
}
The form.jsp
could be as follows:
<form:form>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<%-- Show errors for firstName field --%>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<%-- Show errors for lastName field --%>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
如果我们提交的表单中firstName和lastName是空值,如下所示:
<form method="POST">
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<%-- Associated errors to firstName field displayed --%>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<%-- Associated errors to lastName field displayed --%>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
如果我们想要显示给定页面的整个错误列表,该怎么办?下一个示例显示errors标记还支持一些基本的通配符功能。
-
path="*"
: 展示所有的错误 -
path="lastName"
: 展示与lastName相关的所有错误 -
如果省略path,则只显示对象错误。
下面的例子在页面的顶部显示了一个错误列表,然后在字段旁边显示了字段特定的错误:
<form:form>
<form:errors path="*" cssClass="errorBox"/>
<table>
<tr>
<td>First Name:</td>
<td><form:input path="firstName"/></td>
<td><form:errors path="firstName"/></td>
</tr>
<tr>
<td>Last Name:</td>
<td><form:input path="lastName"/></td>
<td><form:errors path="lastName"/></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form:form>
The HTML would be as follows:
<form method="POST">
<span name="*.errors" class="errorBox">Field is required.<br/>Field is required.</span>
<table>
<tr>
<td>First Name:</td>
<td><input name="firstName" type="text" value=""/></td>
<td><span name="firstName.errors">Field is required.</span></td>
</tr>
<tr>
<td>Last Name:</td>
<td><input name="lastName" type="text" value=""/></td>
<td><span name="lastName.errors">Field is required.</span></td>
</tr>
<tr>
<td colspan="3">
<input type="submit" value="Save Changes"/>
</td>
</tr>
</table>
</form>
spring-form.tld
标记库描述符(tld)包含在spring-webmvc.jar中。有关单个标记的全面参考,请浏览API参考或查看标记库描述。
HTTP方法转换
REST的一个关键原则是使用“统一接口”。这意味着所有资源(url)都可以通过使用相同的四种HTTP方法来操作:GET、PUT、POST和DELETE。对于每个方法,HTTP规范都定义了准确的语义。例如,GET应该总是一个安全的操作,这意味着它没有副作用,而PUT或DELETE应该是幂等的,这意味着您可以一遍又一遍重复这些操作,但是最终结果应该是相同的。HTTP定义了这四个方法,而HTML只支持两个:GET和POST。幸运的是,有两种可能的变通方法:您可以使用JavaScript执行PUT或DELETE操作,也可以使用“real”方法作为附加参数(在HTML表单中建模为隐藏的输入字段)进行POST操作。Spring的HiddenHttpMethodFilter使用了后一种技巧。这个过滤器是一个普通的Servlet过滤器,因此,它可以与任何web框架(不仅仅是Spring MVC)结合使用。将此筛选器添加到您的web。和带有隐藏方法参数的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中定义的HiddenHttpMethodFilter获取web.xml,如下例所示:
<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特定的属性。
表单输入标记支持输入文本以外的类型属性。这是为了允许呈现新的HTML5特定的输入类型,如电子邮件、日期、范围等。注意,不需要输入type='text',因为text是默认类型。
1.10.6。Tiles
您可以在使用Spring的web应用程序中集成Tiles——就像其他任何视图技术一样。本节从广义上描述了如何做到这一点。
本节主要讨论Spring对org.springframe .web.servlet.view中第3版Tiles的支持。tiles3包。
依赖关系
为了能够使用Tiles,您必须在Tiles版本3.0.1或更高版本上添加一个依赖项,并将其传递依赖项添加到您的项目中。
配置
为了能够使用Tiles,您必须使用包含定义的文件来配置它(有关定义和其他Tiles概念的基本信息,请参见https://tiles.apache.org)。在春季,这是通过使用TilesConfigurer完成的。下面的示例ApplicationContext配置展示了如何做到这一点:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
</bean>
前面的示例定义了五个包含定义的文件。这些文件都位于WEB-INF/defs目录中。在WebApplicationContext初始化时,加载文件,并初始化定义工厂。完成之后,定义文件中包含的tile可以用作Spring web应用程序中的视图。为了能够使用视图,您必须有一个ViewResolver,就像Spring使用的任何其他视图技术一样。可以使用UrlBasedViewResolver和ResourceBundleViewResolver这两种实现之一。
您可以通过添加一个下划线和区域设置来指定特定于区域设置的Tiles定义,如下面的示例所示:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/tiles.xml</value>
<value>/WEB-INF/defs/tiles_fr_FR.xml</value>
</list>
</property>
</bean>
对于前面的配置,tiles_fr_FR.xml用于fr_FR地区的请求,而tiles_fr_FR.xml是默认使用的。
注意:由于下划线用于指示区域设置,我们建议不要在tile定义的文件名中使用它们。
UrlBasedViewResolver
UrlBasedViewResolver为它要解析的每个视图实例化给定的viewClass。下面的bean定义了一个UrlBasedViewResolver:
<bean id="viewResolver" class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.tiles3.TilesView"/>
</bean>
ResourceBundleViewResolver
ResourceBundleViewResolver必须提供一个属性文件,其中包含解析器可以使用的视图名称和视图类。下面的例子展示了ResourceBundleViewResolver的bean定义,以及相应的视图名称和视图类(取自Pet诊所示例):
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename" value="views"/>
</bean>
...
welcomeView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
welcomeView.url=welcome (this is the name of a Tiles definition)
vetsView.(class)=org.springframework.web.servlet.view.tiles3.TilesView
vetsView.url=vetsView (again, this is the name of a Tiles definition)
findOwnersForm.(class)=org.springframework.web.servlet.view.JstlView
findOwnersForm.url=/WEB-INF/jsp/findOwners.jsp
...
当您使用ResourceBundleViewResolver时,您可以轻松地混合不同的视图技术。
注意,TilesView类支持JSTL (JSP标准标记库)。
SimpleSpringPreparerFactory和SpringBeanPreparerFactory
作为一个高级特性,Spring还支持两个特殊的Tiles制备工厂实现。有关如何在Tiles定义文件中使用ViewPreparer引用的详细信息,请参阅Tiles文档。
您可以指定simplespringprepareerfactory来基于指定的preparer类自动装配ViewPreparer实例,应用Spring的容器回调,以及应用配置好的Spring beanpostprocessor。如果Spring的上下文范围的注释配置已经激活,ViewPreparer类中的注释将被自动检测和应用。注意,这需要在Tiles定义文件中准备类,就像默认的prepareerfactory所做的那样。
您可以指定springbeanprepareerfactory来操作指定的准备器名称(而不是类),从而从DispatcherServlet的应用程序上下文中获得相应的Spring bean。在本例中,完整的bean创建过程由Spring应用程序上下文控制,允许使用显式依赖项注入配置、作用域bean等。注意,您需要为每个准备器名称定义一个Spring bean定义(正如在tile定义中使用的那样)。下面的例子展示了如何在TilesConfigurer bean上定义springbeanprepareerfactory属性:
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles3.TilesConfigurer">
<property name="definitions">
<list>
<value>/WEB-INF/defs/general.xml</value>
<value>/WEB-INF/defs/widgets.xml</value>
<value>/WEB-INF/defs/administrator.xml</value>
<value>/WEB-INF/defs/customer.xml</value>
<value>/WEB-INF/defs/templates.xml</value>
</list>
</property>
<!-- resolving preparer names as Spring bean definition names -->
<property name="preparerFactoryClass"
value="org.springframework.web.servlet.view.tiles3.SpringBeanPreparerFactory"/>
</bean>
1.10.7。RSS和Atom
AbstractAtomFeedView和AbstractRssFeedView都继承自AbstractFeedView基类,分别用于提供Atom和RSS提要视图。它们基于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
}
}
如果需要访问地区,buildFeedItems()和buildFeedEntries()方法会传递HTTP请求。HTTP响应仅在设置cookie或其他HTTP标头时传递。提要在方法返回后自动写入响应对象。
有关创建Atom视图的示例,请参阅Alef Arendsen的Spring Team博客文章。
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));
}
}
}
控制器可以从外部视图定义(通过名称引用)或从处理程序方法作为视图实例返回这样的视图。
Excel的视图
从Spring Framework 4.2开始,org.springframework.web.servlet.view.document.AbstractXlsView是作为Excel视图的基类提供的。它基于Apache POI,具有专门的子类(AbstractXlsxView和AbstractXlsxStreamingView)来取代过时的AbstractExcelView类。
编程模型类似于AbstractPdfView,使用buildExcelDocument()作为中心模板方法,控制器能够从外部定义(通过名称)或从处理程序方法作为视图实例返回此类视图。
1.10.9。Jackson
Spring提供了对Jackson JSON库的支持。
基于jacksen的JSON MVC视图
MappingJackson2JsonView使用Jackson库的ObjectMapper将响应内容呈现为JSON。默认情况下,模型映射的所有内容(特定于框架的类除外)都被编码为JSON。对于需要过滤映射内容的情况,您可以使用modelKeys属性指定要编码的一组特定的模型属性。您还可以使用extractValueFromSingleKeyModel属性来直接提取和序列化单键模型中的值,而不是作为模型属性的映射。
您可以根据需要使用Jackson提供的注释定制JSON映射。当您需要进一步的控制时,您可以通过ObjectMapper属性注入一个定制的ObjectMapper,用于需要为特定类型提供定制的JSON序列化器和反序列化器的情况。
Jackson-based XML视图
MappingJackson2XmlView使用Jackson XML扩展的XmlMapper将响应内容呈现为XML。如果模型包含多个条目,您应该使用modelKey bean属性显式地设置要序列化的对象。如果模型包含单个条目,它将自动序列化。
您可以根据需要使用JAXB或Jackson提供的注释定制XML映射。当需要进一步控制时,可以通过ObjectMapper属性注入定制的XmlMapper,以便在需要为特定类型提供序列化器和反序列化器的情况下使用定制的XML。
1.10.10。XML编组
MarshallingView使用XML编组器(在org.springframework中定义)。以将响应内容呈现为XML。可以使用MarshallingView实例的modelKey bean属性显式地设置要编组的对象。或者,视图遍历所有模型属性并封送编组器支持的第一个类型。有关org.springframework功能的更多信息。oxm包,参见使用O/X映射器编组XML。
1.10.11。XSLT的视图
XSLT是一种用于XML的转换语言,在web应用程序中作为视图技术而流行。如果您的应用程序自然地处理XML,或者您的模型可以很容易地转换成XML,那么作为一种视图技术,XSLT可能是一个不错的选择。下一节将展示如何生成XML文档作为模型数据,并在Spring Web MVC应用程序中使用XSLT进行转换。
这个例子是一个简单的Spring应用程序,它在控制器中创建一个单词列表,并将它们添加到模型映射中。返回映射和XSLT视图的视图名。有关Spring Web MVC控制器接口的详细信息,请参阅带注释的控制器。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
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文档并将其添加到模型映射中。注意,您还可以将XML文件作为资源加载并使用它而不是自定义DOM文档。
有一些软件包可以自动“domify”对象图,但是在Spring中,您可以完全灵活地从模型中以任何方式创建DOM。这可以防止XML的转换在模型数据结构中扮演太大的角色,这在使用工具管理DOMification过程时是很危险的。
转换
最后,XsltViewResolver解析“主”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中没有的更高级的定制,请参阅高级Java配置和高级XML配置。
您不需要了解MVC Java配置和MVC名称空间创建的底层bean。如果您想了解更多,请参阅特殊的Bean类型和Web MVC配置。
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配置API
在Java配置中,可以实现WebMvcConfigurer接口,如下面的示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
在XML中,您可以检查<mvc:annotation-driven/>的属性和子元素。您可以查看Spring MVC XML模式,或者使用IDE的代码完成特性来发现哪些属性和子元素可用。
1.11.3。类型转换
默认情况下,会安装数字和日期类型的格式化器,包括对@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 SPI和FormattingConversionServiceFactoryBean。
1.11.4。验证
默认情况下,如果类路径上存在Bean验证(例如Hibernate验证器),LocalValidatorFactoryBean将注册为全局验证器,与@Valid一起使用,并在控制器方法参数上验证。
在Java配置中,可以自定义全局验证器实例,如下例所示:
@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>
注意,你也可以在本地注册验证器实现,如下面的例子所示:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
注意:如果您需要在某个地方注入LocalValidatorFactoryBean,那么创建一个bean并用@Primary标记它,以避免与MVC配置中声明的bean发生冲突。
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。内容类型
您可以配置Spring MVC如何从请求中确定所请求的媒体类型(例如,Accept标头、URL路径扩展、查询参数等)。
默认情况下,首先检查URL路径扩展—将json、xml、rss和atom注册为已知的扩展(取决于类路径依赖项)。第二个检查Accept标头。
考虑将这些缺省值更改为仅接受header,如果必须使用基于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。消息转换器
您可以通过覆盖configureMessageConverters()(来替换Spring MVC创建的默认转换器)或覆盖extendMessageConverters()(来定制默认转换器或向默认转换器添加额外的转换器)在Java配置中定制HttpMessageConverter。
下面的示例使用定制的ObjectMapper而不是默认的ObjectMapper添加XML和Jackson JSON转换器:
@Configuration
@EnableWebMvc
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder()
.indentOutput(true)
.dateFormat(new SimpleDateFormat("yyyy-MM-dd"))
.modulesToInstall(new ParameterNamesModule());
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
在前面的例子中,Jackson2ObjectMapperBuilder是用来创建一个通用配置MappingJackson2HttpMessageConverter和MappingJackson2XmlHttpMessageConverter启用了缩进,一个定制的日期格式,和jackson-module-parameter-names登记,这增加了支持访问参数名称(功能添加到Java 8)。
该生成器自定义Jackson的默认属性如下:
- DeserializationFeature。FAIL_ON_UNKNOWN_PROPERTIES是禁用的。
- MapperFeature。DEFAULT_VIEW_INCLUSION是禁用的。
如果在类路径中检测到以下知名模块,它还会自动注册它们:
- jackson-datatype-joda:支持Joda-Time类型。
- jackson-datatype-jsr310:支持Java 8日期和时间API类型。
- jackson-datatype-jdk8:支持其他Java 8类型,比如Optional。
jackson-module-kotlin:
支持Kotlin类和数据类。
注意:需要使用Jackson XML支持启用,需要在jackson-dataformat-xml添加
woodstox-core-asl依赖。
其他有趣的Jackson 模块是可用的:
- jackson-datatype-money:支持javax。货币类型(非官方模块)。
- jackson-datatype-hibernate:支持特定于hibernate的类型和属性(包括延迟加载方面)。
下面的例子展示了如何在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"/>
1.11.8。视图控制器
这是定义一个ParameterizableViewController的快捷方式,它在调用时立即转发到视图。如果在视图生成响应之前没有要执行的Java控制器逻辑,您可以在静态情况下使用它。
下面的Java配置示例将一个对/的请求转发给一个名为home的视图:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
下面的示例实现了与前面的示例相同的功能,但是使用的是XML,使用的是<mvc:view-controller>元素:
<mvc:view-controller path="/" view-name="home"/>
如果@RequestMapping方法映射到任何HTTP方法的URL,那么视图控制器就不能处理相同的URL。这是因为通过URL与带注释的控制器匹配被认为是端点所有权的足够强的指示,因此可以将405 (METHOD_NOT_ALLOWED)、415 (UNSUPPORTED_MEDIA_TYPE)或类似的响应发送到客户端以帮助调试。因此,建议避免在带注释的控制器和视图控制器之间分割URL处理。
1.11.9。视图解析器
MVC配置简化了视图解析器的注册。
下面的Java配置示例使用JSP和Jackson作为JSON呈现的默认视图来配置内容协商视图解析:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
下面的例子展示了如何在XML中实现相同的配置:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:jsp/>
</mvc:view-resolvers>
但是请注意,FreeMarker、Tiles、Groovy标记和脚本模板也需要配置底层视图技术。
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。静态资源
此选项提供了一种方便的方式来从基于资源的位置列表中提供静态资源。
在下一个示例中,给定一个以/resources开始的请求,相对路径用于在web应用程序根目录下或在/static类路径下查找和提供相对于/public的静态资源。这些资源有一年的有效期,以确保最大限度地使用浏览器缓存并减少浏览器发出的HTTP请求。最后修改的标头也会被计算,如果有,则返回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,因此可以将它注入到其他bean中。您还可以使用ResourceUrlEncodingFilter对Thymeleaf、jsp、FreeMarker和其他使用URL标记(依赖于HttpServletResponse#encodeURL)的程序进行透明重写。
请注意,在使用EncodedResourceResolver(例如,用于提供gzip压缩或brotli编码的资源)和VersionResourceResolver时,必须按此顺序注册它们。这确保了基于内容的版本总是基于未编码的文件进行可靠的计算。
WebJars也可以通过WebJarsResourceResolver来支持,当组织被注册时,WebJarsResourceResolver会自动注册。webjars:webjar -locator-core库位于类路径中。解析器可以重写url来包含jar的版本,也可以匹配没有版本的传入url——例如,从/jquery/jquery.min.js到/jquery/1.2.0/jquery.min.js。
1.11.11。默认Servlet
Spring MVC允许将DispatcherServlet映射到/(因此覆盖了容器默认Servlet的映射),同时仍然允许容器默认Servlet处理静态资源请求。它将DefaultServletHttpRequestHandler配置为URL映射为/**,并且相对于其他URL映射具有最低优先级。
此处理程序将所有请求转发到缺省Servlet。因此,它必须保持在所有其他URL处理程序映射的最后。如果您使用<mvc:注释驱动的>,就会出现这种情况。另外,如果您设置了自己的自定义HandlerMapping实例,请确保将其order属性设置为一个比DefaultServletHttpRequestHandler更低的值,该值是Integer.MAX_VALUE。
下面的例子演示了如何使用默认设置启用该功能:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
下面的例子展示了如何在XML中实现相同的配置:
<mvc:default-servlet-handler/>
覆盖/ Servlet映射的警告是,必须根据名称而不是路径检索缺省Servlet的RequestDispatcher。DefaultServletHttpRequestHandler尝试在启动时自动检测容器的默认Servlet,使用大多数主要Servlet容器(包括Tomcat、Jetty、GlassFish、JBoss、Resin、WebLogic和WebSphere)的已知名称列表。如果默认的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。路径匹配
您可以自定义与路径匹配和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导入委托webmvcconfiguration,其中:
为Spring MVC应用程序提供默认的Spring配置
检测并委托WebMvcConfigurer实现来定制配置。
对于高级模式,您可以删除@EnableWebMvc并直接从委托webmvcconfiguration扩展,而不是实现WebMvcConfigurer,如下例所示:
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
// ...
}
您可以在WebConfig中保留现有的方法,但是您现在也可以覆盖基类中的bean声明,并且您仍然可以在类路径上拥有任意数量的其他WebMvcConfigurer实现。
1.11.14。先进的XML配置
MVC命名空间没有高级模式。如果你需要定制一个bean上的属性,你不能改变它,你可以使用Spring ApplicationContext的BeanPostProcessor生命周期钩子,如下面的例子所示:
@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 page。
Servlet API确实公开了一个与HTTP/2相关的构造。您可以使用javax.servlet.http。PushBuilder主动地将资源推给客户端,它作为@RequestMapping方法的method argument受到支持。