Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从一开始就已包含在 Spring Framework 中。
1.1. DispatcherServlet
与其他许多 Web 框架一样,Spring MVC 围绕前端控制器模式进行设计,其中中央Servlet
DispatcherServlet
提供了用于请求处理的共享算法,而实际工作是由可配置的委托组件执行的。该模型非常灵活,并支持多种工作流程。
和其他Servlet
一样,需要根据 Servlet 规范通过使用 Java 配置或在web.xml
中进行声明和映射。反过来,DispatcherServlet
使用 Spring 配置发现请求映射,视图解析,异常处理及更多所需的委托组件。
方式1:以下 Java 配置示例注册并初始化DispatcherServlet
,它由 Servlet 容器自动检测:
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
并覆盖特定方法。
方式2:以下web.xml
配置示例注册并初始化DispatcherServlet
:
<web-app> <listener> <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class> </listener> <context-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/app-context.xml</param-value> </context-param> <servlet> <servlet-name>app</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value></param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>app</servlet-name> <url-pattern>/app/*</url-pattern> </servlet-mapping> </web-app>
Spring Boot 遵循不同的初始化 Sequences。 Spring Boot 并没有陷入 Servlet 容器的生命周期,而是使用 Spring 配置来引导自身和嵌入式 Servlet 容器。在 Spring 配置中检测到Filter
和Servlet
声明,并在 Servlet 容器中注册。
1.1.1. 上下文层次
DispatcherServlet
期望其自己的配置为WebApplicationContext。
WebApplicationContext
具有到ServletContext
和与其关联的Servlet
的链接。它还绑定到ServletContext
,以便应用程序可以在RequestContextUtils
上使用静态方法来查找WebApplicationContext
(如果需要访问它们)。
对于许多应用程序来说,只有一个WebApplicationContext
很简单并且足够。也可能具有上下文层次结构,其中一个根WebApplicationContext
跨多个DispatcherServlet
(或其他Servlet
)实例共享,每个实例都有其自己的子WebApplicationContext
配置。
根WebApplicationContext
通常包含需要在多个Servlet
实例之间共享的基础结构 Bean,例如数据存储库和业务服务。这些 Bean 是有效继承的,可以在 Servlet 特定子WebApplicationContext
中重写(即重新声明),该子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/*" };
}
}
如果不需要应用程序上下文层次结构,则应用程序可以通过getServletConfigClasses()
的getRootConfigClasses()
和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”,是指实现 WebFlux 框架 Contract 的 SpringManagement 的Object
实例。这些通常带有内置 Contract,但是您可以自定义它们的属性并扩展或替换它们。
下表列出了DispatcherHandler
检测到的特殊 bean:
Bean type | Explanation |
---|---|
HandlerMapping |
将请求与interceptors列表一起映射到处理程序,以进行预处理和后期处理。映射基于某些条件,具体取决于HandlerMapping 实现。 |
HandlerMapping
的两个主要实现是RequestMappingHandlerMapping
(支持@RequestMapping
带注解的方法)和SimpleUrlHandlerMapping
(将 URI 路径模式的显式注册维护到处理程序)。
1.SimpleUrlHandlerMapping根据配置文件中的SimpleUrlHandlerMapping的配置,获得一个map集合,map中存储的是{urlString=beanId}。SimpleUrlHandlerMapping调用父类
AbstractUrlHandlerMapping的registerHandler方法。
2.AbstractUrlHandlerMapping的registerHandler方法有SimpleUrlHandlerMapping传入的map中的urlString和beanId,并根据beanId找到对应的bean即controller,将urlString和urlString对应的controller放入AbstractUrlHandlerMapping的handlerMap中。
3.Dispatcher获取IOC容器中已经初始化好的HandlerMapping,再由HandlerMapping调用自己的getHandler方法根据请求返回HandlerExecutionChain对象。AbstractHandlerMapping方法实现了HandlerMapping接口的getHandler方法。AbstractHandlerMapping中的getHandler方法的主要作用是找到controller,并对controller进行封装成HandlerExecutionChain对象,HandlerExecutionChain中除了controller对象外,还有拦截器对象的集合。
4.AbstractHandlerMapping的getHandler方法中又 调用了AbstractHandlerMapping子类的AbstractUrlHandlerMapping getHandlerInternal方法。getHandlerInternal方法就是截取url中对应的controller字段,并以这个字段为key值去AbstractUrlHandlerMapping 的handlerMap中找寻对应的value,即controller。
| HandlerAdapter
|帮助DispatcherServlet
调用映射到请求的处理程序,无论实际如何调用该处理程序。例如,调用带注解的控制器需要解析注解。 HandlerAdapter
的主要目的是使DispatcherServlet
免受此类细节的影响。
| HandlerExceptionResolver |解决异常的策略,可能将它们映射到处理程序,HTML 错误视图或其他目标。参见Exceptions。
| ViewResolver |解析从处理程序返回到实际View
的基于逻辑String
的视图名称,使用该视图名称呈现给响应。参见View Resolution和View Technologies。
| LocaleResolver,LocaleContextResolver |解析 Client 端正在使用的Locale
以及可能的时区,以便能够提供国际化的视图。参见Locale。
| ThemeResolver |解决 Web 应用程序可以使用的主题,例如提供个性化的布局。参见Themes。
| MultipartResolver |用于借助一些 Multipart 解析库来分析 Multipart 请求(例如,浏览器表单文件上载)的抽象。参见Multipart Resolver。
| FlashMapManager |存储和检索“Importing”和“输出” FlashMap
,它们可用于将属性从一个请求传递到另一个请求,通常跨重定向。参见Flash Attributes。
1.1.3. Web MVC 配置
应用程序可以声明处理请求所必需的特殊 bean 类中列出的基础结构 bean。 DispatcherServlet
为每个特殊 bean 检查WebApplicationContext
。如果没有匹配的 Bean 类型,它将使用DispatcherServlet.properties中列出的默认类型。
在大多数情况下,MVC Config是最佳起点。它使用 Java 或 XML 声明所需的 bean,并提供更高级别的配置回调 API 对其进行自定义。
Note
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
的抽象 Base Class 实现通过覆盖方法来指定 servletMap 和DispatcherServlet
配置的位置,从而使DispatcherServlet
的注册更加容易。
对于使用基于 Java 的 Spring 配置的应用程序,建议这样做,如以下示例所示:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { @Override protected Class<?>[] getRootConfigClasses() { return null; } @Override protected Class<?>[] getServletConfigClasses() { return new Class<?>[] { MyWebConfig.class }; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
如果使用基于 XML 的 Spring 配置,则应直接从AbstractDispatcherServletInitializer
扩展,如以下示例所示:
public class MyWebAppInitializer extends AbstractDispatcherServletInitializer { @Override protected WebApplicationContext createRootApplicationContext() { return null; } @Override protected WebApplicationContext createServletApplicationContext() { XmlWebApplicationContext cxt = new XmlWebApplicationContext(); cxt.setConfigLocation("/WEB-INF/spring/dispatcher-config.xml"); return cxt; } @Override protected String[] getServletMappings() { return new String[] { "/" }; } }
AbstractDispatcherServletInitializer
还提供了一种方便的方法来添加Filter
实例,并将它们自动 Map 到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
键下绑定的。 -
语言环境解析器绑定到请求,以使流程中的元素解析在处理请求(呈现视图,准备数据等)时要使用的语言环境。如果不需要语言环境解析,则不需要语言环境解析器。
-
主题解析器绑定到请求,以使诸如视图之类的元素确定要使用的主题。如果不使用主题,则可以忽略它。
-
如果指定 Multipart 文件解析器,则将检查请求中是否有 Multipart。如果找到 Multipart,则将请求包装在
MultipartHttpServletRequest
中,以供流程中的其他元素进一步处理。有关 Multipart 处理的更多信息,请参见Multipart Resolver。 -
搜索适当的处理程序。如果找到处理程序,则执行与处理程序(预处理器,后处理器和控制器)关联的执行链,以准备模型或渲染。或者,对于带注解的控制器,可以呈现响应(在
HandlerAdapter
内),而不返回视图。 -
如果返回模型,则呈现视图。如果没有返回任何模型(可能是由于预处理器或后处理器拦截了该请求,可能出于安全原因),则不会呈现任何视图,因为该请求可能已经被满足。
WebApplicationContext
中声明的HandlerExceptionResolver
bean 用于解决在请求处理期间引发的异常。这些异常解析器允许定制逻辑以解决异常。
Spring DispatcherServlet
还支持 Servlet API 指定的last-modification-date
的返回。确定特定请求的最后修改日期的过程很简单:DispatcherServlet
查找适当的处理程序并测试找到的处理程序是否实现LastModified
接口。如果是这样,则LastModified
接口的long getLastModified(request)
方法的值返回给 Client 端。
您可以通过向web.xml
文件中的 Servlet 声明中添加 Servlet 初始化参数(init-param
元素)来自定义DispatcherServlet
实例。下表列出了受支持的参数:
Parameter | Explanation |
---|---|
contextClass |
实现ConfigurableWebApplicationContext 的类,将由此 Servlet 实例化并在本地配置。默认情况下,使用XmlWebApplicationContext 。 |
contextConfigLocation |
传递给上下文实例的字符串(由contextClass 指定),以指示可以在哪里找到上下文。该字符串可能包含多个字符串(使用逗号作为分隔符)以支持多个上下文。对于具有两次定义的 bean 的多个上下文位置,以最新位置为准。 |
namespace |
WebApplicationContext 的命名空间。默认为[servlet-name]-servlet 。 |
throwExceptionIfNoHandlerFound |
在找不到请求处理程序时是否抛出NoHandlerFoundException 。然后可以使用HandlerExceptionResolver 捕获异常(例如,通过使用@ExceptionHandler 控制器方法),然后将其作为其他任何异常进行处理。 |
默认情况下,它设置为false
,在这种情况下DispatcherServlet
将响应状态设置为 404(NOT_FOUND),而不会引发异常。
请注意,如果还配置了默认 servlet 处理,则始终将未解决的请求转发到默认 servlet,并且永远不会引发 404.
1.1.6. Interception
所有HandlerMapping
实现都支持处理程序拦截器,这些拦截器在您要将特定功能应用于某些请求时非常有用-例如检查主体。拦截器必须使用三种方法从org.springframework.web.servlet
包中实现HandlerInterceptor
,这三种方法应提供足够的灵活性以执行所有类型的预处理和后处理:
-
preHandle(..)
:在执行实际处理程序之前 -
postHandle(..)
:执行处理程序后 -
afterCompletion(..)
:完成完整的请求后
preHandle(..)
方法返回布尔值。您可以使用此方法来中断或 continue 执行链的处理。当此方法返回true
时,处理程序执行链 continue。当它返回 false 时,DispatcherServlet
假定拦截器本身已经处理了请求(例如,提供了适当的视图),并且不会 continue 执行执行链中的其他拦截器和实际处理程序。
有关如何配置拦截器的示例,请参见 MVC 配置部分中的Interceptors。您还可以通过使用各个HandlerMapping
实现上的设置器直接注册它们。
请注意,postHandle
在@ResponseBody
和ResponseEntity
方法中用处不大,因为在HandlerAdapter
内和postHandle
之前将响应写入和提交。这意味着对响应进行任何更改为时已晚,例如添加额外的 Headers。对于此类情况,您可以实现ResponseBodyAdvice
并将其声明为Controller Advice bean 或直接在RequestMappingHandlerAdapter
上对其进行配置。
1.1.7. Exceptions
如果在请求映射期间发生异常或从请求处理程序(例如@Controller
)引发异常,则DispatcherServlet
委托HandlerExceptionResolver
bean 链来解决该异常并提供替代处理,通常是错误响应。
下表列出了可用的HandlerExceptionResolver
实现:
HandlerExceptionResolver | Description |
---|---|
SimpleMappingExceptionResolver |
异常类名称和错误视图名称之间的映射。对于在浏览器应用程序中呈现错误页面很有用。 |
DefaultHandlerExceptionResolver | 解决了 Spring MVC 引发的异常,并将它们映射到 HTTP 状态代码。另请参见ResponseEntityExceptionHandler 和REST APIexception。 |
ResponseStatusExceptionResolver |
使用@ResponseStatus 注释解决异常,并根据注解中的值将其映射到 HTTP 状态代码。 |
ExceptionHandlerExceptionResolver |
通过调用@Controller 或@ControllerAdvice 类中的@ExceptionHandler 方法来解决异常。参见@ExceptionHandler methods。 |
解析器链
您可以通过在 Spring 配置中声明多个HandlerExceptionResolver
bean 并根据需要设置其order
属性来形成异常解析器链。 order 属性越高,异常解析器的定位就越晚。
HandlerExceptionResolver
的 Contract 规定它可以返回:
-
ModelAndView
指向错误视图。 -
如果在解析程序中处理了异常,则为空
ModelAndView
。 -
null
如果仍未解决异常,则供以后的解析器尝试,并且,如果异常仍在末尾,则允许其冒泡到 Servlet 容器。
MVC Config自动为默认的 Spring MVC 异常,@ResponseStatus
带注解的异常以及对@ExceptionHandler
方法的支持声明内置解析器。您可以自定义该列表或替换它。
容器错误页面
如果任何HandlerExceptionResolver
都无法解决异常,因此该异常可以 continue 传播,或者如果响应状态设置为错误状态(即 4xx,5xx),则 Servlet 容器可以在 HTML 中呈现默认错误页面。要自定义容器的默认错误页面,可以在web.xml
中声明一个错误页面 Map。以下示例显示了如何执行此操作:
<error-page> <location>/error</location> </error-page>
给定前面的示例,当异常冒出气泡或响应具有错误状态时,Servlet 容器在容器内向配置的 URL(例如/error
)进行 ERROR 调度。然后由DispatcherServlet
处理,可能将其映射到@Controller
,可以实现该错误以返回带有模型的错误视图名称或呈现 JSON 响应,如以下示例所示:
@RestController public class ErrorController { @RequestMapping(path = "/error") public Map<String, Object> handle(HttpServletRequest request) { Map<String, Object> map = new HashMap<String, Object>(); map.put("status", request.getAttribute("javax.servlet.error.status_code")); map.put("reason", request.getAttribute("javax.servlet.error.message")); return map; } }
Servlet API 没有提供在 Java 中创建错误页面的方法。但是,您可以同时使用WebApplicationInitializer
和最小的web.xml
。
1.1.8. 查看分辨率
Spring MVC 定义了ViewResolver
和View
接口,这些接口使您可以在浏览器中呈现模型,而无需将您绑定到特定的视图技术。 ViewResolver
提供了视图名称和实际视图之间的映射。 View
解决了在移交给特定视图技术之前的数据准备问题。
下表提供了有关ViewResolver
层次结构的更多详细信息:
ViewResolver | Description |
---|---|
AbstractCachingViewResolver |
AbstractCachingViewResolver 缓存视图实例所解析的子类。缓存可以提高某些视图技术的性能。您可以通过将cache 属性设置为false 来关闭缓存。此外,如果必须在运行时刷新某个视图(例如,修改 FreeMarker 模板时),则可以使用removeFromCache(String viewName, Locale loc) 方法。 |
XmlViewResolver |
ViewResolver 的实现,该实现接受用 XML 编写的配置文件,该配置文件的 DTD 与 Spring 的 XML bean 工厂相同。默认配置文件为/WEB-INF/views.xml 。 |
ResourceBundleViewResolver |
ViewResolver 的实现使用ResourceBundle 中的 bean 定义(由包基本名称指定)。对于应该解析的每个视图,它将属性[viewname].(class) 的值用作视图类,并将属性[viewname].url 的值用作视图 URL。您可以在View Technologies的章节中找到示例。 |
UrlBasedViewResolver |
ViewResolver 接口的简单实现会影响逻辑视图名称到 URL 的直接解析,而无需显式映射定义。如果您的逻辑名称以直接的方式与视图资源的名称匹配,而不需要任意映射,则这是适当的。 |
InternalResourceViewResolver |
方便的UrlBasedViewResolver 子类,支持InternalResourceView (实际上是 Servlet 和 JSP)以及JstlView 和TilesView 之类的子类。您可以使用setViewClass(..) 为该解析器生成的所有视图指定视图类。有关详细信息,请参见UrlBasedViewResolver javadoc。 |
FreeMarkerViewResolver |
UrlBasedViewResolver 的便捷子类,支持FreeMarkerView 及其自定义子类。 |
ContentNegotiatingViewResolver |
ViewResolver 接口的实现,该接口根据请求文件名或Accept Headers 解析视图。参见Content Negotiation。 |
解析器链
您可以通过多个解析器 bean 以及必要时通过设置order
属性以指定 Sequences 来链接视图解析器。请记住,order 属性越高,视图解析器在链中的定位就越晚。
ViewResolver
的协定指定它可以返回 null 来指示找不到该视图。但是,对于 JSP 和InternalResourceViewResolver
而言,弄清 JSP 是否存在的唯一方法是通过RequestDispatcher
进行调度。因此,必须始终将InternalResourceViewResolver
配置为在视图解析器的总体 Sequences 中排在最后。
配置视图分辨率就像在 Spring 配置中添加ViewResolver
bean 一样简单。 MVC Config为View Resolvers和添加无逻辑的View Controllers提供了专用的配置 API,这对于不带控制器逻辑的 HTML 模板呈现非常有用。
重定向
视图名称中特殊的redirect:
前缀使您可以执行重定向。 UrlBasedViewResolver
(及其子类)将其识别为需要重定向的指令。视图名称的其余部分是重定向 URL。
最终效果与控制器返回RedirectView
的效果相同,但是现在控制器本身可以根据逻辑视图名称进行操作。逻辑视图名称(例如redirect:/myapp/some/resource
)相对于当前 Servlet 上下文重定向,而名称(例如redirect:http://myhost.com/some/arbitrary/path
)重定向到绝对 URL。
请注意,如果控制器方法用@ResponseStatus
注释,则注解值优先于RedirectView
设置的响应状态。
转发
您还可以为视图名称使用特殊的forward:
前缀,这些名称最终由UrlBasedViewResolver
和子类解析。这将创建一个InternalResourceView
,它执行RequestDispatcher.forward()
。因此,此前缀对于InternalResourceViewResolver
和InternalResourceView(对于jsp)
没有用,但是如果您使用另一种视图技术,但仍然希望强制转发由 Servlet/JSP 引擎处理的资源,则该前缀很有用。请注意,您也可以改为链接多个视图解析器。
内容协商
ContentNegotiatingViewResolver本身不会解析视图,而是委派给其他视图解析器,并选择类似于 Client 端请求的表示形式的视图。可以从Accept
Headers 或查询参数(例如"/path?format=pdf"
)确定表示形式。
ContentNegotiatingViewResolver
通过将请求媒体类型与与每个ViewResolvers
关联的View
支持的媒体类型(也称为Content-Type
)进行比较,从而选择合适的View
来处理请求。列表中具有兼容Content-Type
的第一个View
将表示形式返回给 Client 端。如果ViewResolver
链无法提供兼容的视图,请查阅通过DefaultViews
属性指定的视图列表。后一个选项适用于单例Views
,该单例Views
可以呈现当前资源的适当表示形式,而与逻辑视图名称无关。 Accept
Headers 可以包含通配符(例如text/*
),在这种情况下,Content-Type
为text/xml
的View
是兼容的匹配。
1.1.9. Locale
正如 Spring Web MVC 框架所做的那样,Spring 体系结构的大多数部分都支持国际化。 DispatcherServlet
使您可以使用 Client 端的语言环境自动解析邮件。这是通过LocaleResolver
对象完成的。
收到请求时,DispatcherServlet
查找语言环境解析器,如果找到一个,它将尝试使用它来设置语言环境。通过使用RequestContext.getLocale()
方法,您始终可以检索由语言环境解析器解析的语言环境。
除了自动的语言环境解析之外,您还可以在处理程序映射上附加一个拦截器以在特定情况下(例如,基于请求中的参数)更改语言环境。
语言环境解析器和拦截器在org.springframework.web.servlet.i18n
包中定义,并以常规方式在您的应用程序上下文中配置。 Spring 包含以下选择的语言环境解析器。
Time Zone
除了获取 Client 的语言环境外,了解其时区通常也很有用。 LocaleContextResolver
界面提供了对LocaleResolver
的扩展,该扩展使解析器可以提供更丰富的LocaleContext
,其中可能包含时区信息。
如果可用,可以使用RequestContext.getTimeZone()
方法获取用户的TimeZone
。 Spring 的ConversionService
注册的任何 Date/Time Converter
和Formatter
对象都会自动使用时区信息。
Header Resolver
此语言环境解析器检查 Client 端(例如,Web 浏览器)发送的请求中的accept-language
Headers。通常,此头字段包含 Client 端 os 的语言环境。请注意,此解析器不支持时区信息。
Cookie Resolver
此语言环境解析器检查 Client 端上可能存在的Cookie
,以查看是否指定了Locale
或TimeZone
。如果是这样,它将使用指定的详细信息。通过使用此语言环境解析器的属性,可以指定 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 |
---|---|---|
cookieName |
类别名称 LOCALE | Cookie 的名称 |
cookieMaxAge |
Servlet 容器默认 | Cookie 在 Client 端上保留的最长时间。如果指定了-1 ,则 cookie 将不会保留。它仅在 Client 端关闭浏览器之前可用。 |
cookiePath |
/ | 将 Cookie 的可见性限制在您网站的特定部分。指定cookiePath 时,该 cookie 仅对该路径及其下方的路径可见。 |
Session Resolver
SessionLocaleResolver
可让您从可能与用户请求关联的会话中检索Locale
和TimeZone
。与CookieLocaleResolver
相反,此策略将本地选择的语言环境设置存储在 Servlet 容器的HttpSession
中。结果,这些设置对于每个会话都是临时的,因此在每个会话终止时会丢失。
请注意,与外部会话管理机制(例如 Spring Session 项目)没有直接关系。该SessionLocaleResolver
对当前HttpServletRequest
评估并修改了相应的HttpSession
属性。
Locale Interceptor
您可以通过将LocaleChangeInterceptor
添加到HandlerMapping
定义之一来启用语言环境更改。它在请求中检测到一个参数,并相应地更改语言环境,从而在调度程序的应用程序上下文中在LocaleResolver
上调用setLocale
方法。下一个示例显示对包含参数siteLanguage
的所有*.view
资源的调用现在会更改语言环境。因此,例如,对 URL http://www.sf.net/home.view?siteLanguage=nl
的请求将站点语言更改为荷兰语。以下示例显示如何拦截语言环境:
<bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"> <property name="paramName" value="siteLanguage"/> </bean> <bean id="localeResolver" class="org.springframework.web.servlet.i18n.CookieLocaleResolver"/> <bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping"> <property name="interceptors"> <list> <ref bean="localeChangeInterceptor"/> </list> </property> <property name="mappings"> <value>/**/*.view=someController</value> </property> </bean>
1.1.10. Themes
您可以应用 Spring Web MVC 框架主题来设置应用程序的整体外观,从而增强用户体验。主题是静态资源(通常是样式表和图像)的集合,这些资源会影响应用程序的视觉样式。
定义主题
要在 Web 应用程序中使用主题,必须设置org.springframework.ui.context.ThemeSource
接口的实现。 WebApplicationContext
接口扩展了ThemeSource
,但将其职责委托给专用的实现。默认情况下,委托是org.springframework.ui.context.support.ResourceBundleThemeSource
实现,该实现从 Classpath 的根目录加载属性文件。要使用自定义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
使用空的基本名称前缀。结果,从 Classpath 的根加载属性文件。因此,您可以将cool.properties
主题定义放在 Classpath 的根目录中(例如,在/WEB-INF/classes
中)。 ResourceBundleThemeSource
使用标准的 Java 资源束加载机制,允许主题的完全国际化。例如,我们可能有一个/WEB-INF/classes/cool_nl.properties
,它引用了带有荷兰 Literals 的特殊背景图像。
解析Themes
定义主题后,您可以决定使用哪个主题。 DispatcherServlet
查找名为themeResolver
的 bean,以找出要使用的ThemeResolver
实现。主题解析器的工作方式与LocaleResolver
大致相同。它可以检测用于特定请求的主题,还可以更改请求的主题。下表描述了 Spring 提供的主题解析器:
Class | Description |
---|---|
FixedThemeResolver |
选择通过使用defaultThemeName 属性设置的固定主题。 |
SessionThemeResolver |
主题在用户的 HTTP 会话中维护。每个会话只需设置一次,但在会话之间不会保留。 |
CookieThemeResolver |
所选主题存储在 Client 端的 cookie 中。 |
Spring 还提供了一个ThemeChangeInterceptor
,该主题可以使用简单的 request 参数在每个请求上更改主题。
1.1.11. Multipart 解析器
org.springframework.web.multipart
包中的MultipartResolver
是一种用于解析包括文件上传在内的 Multipart 请求的策略。
有一种基于Commons FileUpload的实现,另一种基于 Servlet 3.0Multipart 请求解析。
要启用 Multipart 处理,您需要在DispatcherServlet
Spring 配置中声明名称为multipartResolver
的MultipartResolver
bean。 DispatcherServlet
检测到它并将其应用于传入的请求。收到 Content Type 为multipart/form-data
的 POST 时,解析程序将解析内容并将当前HttpServletRequest
包装为MultipartHttpServletRequest
以提供对已解析部分的访问权限,除了将其公开为请求参数之外。
Apache Commons FileUpload
要使用 Apache Commons FileUpload
,可以配置名称为multipartResolver
的类型CommonsMultipartResolver
的 bean。您还需要commons-fileupload
作为对 Classpath 的依赖。
Servlet 3.0
需要通过 Servlet 容器配置启用 Servlet 3.0Multipart 解析。为此:
-
在 Java 中,在 Servlet 注册上设置
MultipartConfigElement
。 -
在
web.xml
中,将"<multipart-config>"
部分添加到 Servlet 声明中。
以下示例显示了如何在 Servlet 注册上设置MultipartConfigElement
:
public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer { // ... @Override protected void customizeRegistration(ServletRegistration.Dynamic registration) { // Optionally also set maxFileSize, maxRequestSize, fileSizeThreshold registration.setMultipartConfig(new MultipartConfigElement("/tmp")); } }
Servlet 3.0 配置到位后,您可以添加名称为multipartResolver
的类型为StandardServletMultipartResolver
的 bean。
1.1.12. Logging
Spring MVC 中的 DEBUG 级别的日志被设计为紧凑,最少且人性化的。它侧重于高价值的信息,这些信息一遍又一遍地有用,而其他信息则仅在调试特定问题时才有用。
TRACE 级别的日志记录通常遵循与 DEBUG 相同的原则(例如,也不应是消防水带),但可用于调试任何问题。此外,某些日志消息在 TRACE 和 DEBUG 上可能显示不同级别的详细信息。
良好的日志记录来自使用日志的经验。如果发现任何不符合既定目标的东西,请告诉我们。
敏感数据
调试和跟踪日志记录可能会记录敏感信息。这就是为什么默认情况下屏蔽请求参数和 Headers,并且必须通过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(Dynamic registration) { registration.setInitParameter("enableLoggingRequestDetails", "true"); } }
1.2. Filters
spring-web
模块提供了一些有用的过滤器:
1.2.1. 表格数据
浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器 Client 端也可以使用 HTTP PUT,PATCH 和 DELETE。 Servlet API 需要ServletRequest.getParameter*()
个方法才能仅对 HTTP POST 支持表单字段访问。
spring-web
模块提供FormContentFilter
来拦截 Content Type 为application/x-www-form-urlencoded
的 HTTP PUT,PATCH 和 DELETE 请求,从请求的正文中读取表单数据,并包装ServletRequest
以通过ServletRequest.getParameter*()
系列方法使表单数据可用。
1.2.2. 转发的标题
当请求通过代理(例如负载平衡器)进行处理时,主机,端口和方案可能会更改,这使得从 Client 端角度创建指向正确的主机,端口和方案的链接带来了挑战。
RFC 7239定义Forwarded
HTTPHeaders,代理可用来提供有关原始请求的信息。还有其他非标准 Headers,包括X-Forwarded-Host
,X-Forwarded-Port
,X-Forwarded-Proto
,X-Forwarded-Ssl
和X-Forwarded-Prefix
。
ForwardedHeaderFilter
是一个 Servlet 过滤器,它根据Forwarded
Headers 修改请求的主机,端口和方案,然后删除这些 Headers。
对于转发的 Headers,存在安全方面的考虑,因为应用程序无法知道 Headers 是由代理添加的,还是由恶意 Client 端添加的。这就是为什么应配置信任边界处的代理以删除来自外部的不受信任的Forwarded
Headers 的原因。您还可以使用removeOnly=true
配置ForwardedHeaderFilter
,在这种情况下,它将删除但不使用 Headers。
1.2.3. 浅 ETag
ShallowEtagHeaderFilter
过滤器通过缓存写入响应的内容并从中计算 MD5 哈希值来创建“浅” ETag。Client 端下一次发送时,将执行相同的操作,但还会将计算值与If-None-Match
请求 Headers 进行比较,如果两者相等,则返回 304(NOT_MODIFIED)。
此策略可节省网络带宽,但不能节省 CPU,因为必须为每个请求计算完整响应。如前所述,控制器级别的其他策略可以避免计算。参见HTTP Caching。
该过滤器具有一个writeWeakETag
参数,该参数将过滤器配置为写入弱 ETag,类似于以下代码:W/"02a2d595e6ed9a0b24f027f2b63b134d6"
(在RFC 7232 第 2.3 节中定义)。
1.2.4. CORS
Spring MVC 通过控制器上的注解 为 CORS 配置提供了细粒度的支持。但是,当与 Spring Security 一起使用时,我们建议您依赖内置的CorsFilter
,该CorsFilter
必须在 Spring Security 的过滤器链之前 Order。
1.3. 带注解的控制器
Spring MVC 提供了基于注解 的编程模型,其中@Controller
和@RestController
组件使用注解来表达请求映射,请求 Importing,异常处理等。带注解 的控制器具有灵活的方法签名,无需扩展 Base Class 或实现特定的接口。以下示例显示了由注解 定义的控制器:
@Controller public class HelloController { @GetMapping("/hello") public String handle(Model model) { model.addAttribute("message", "Hello World!"); return "index"; } }
在前面的示例中,该方法接受Model
并以String
的形式返回视图名称,但是还存在许多其他选项,本章稍后将对其进行说明。
1.3.1. 声明
您可以使用 Servlet 的WebApplicationContext
中的标准 Spring bean 定义来定义控制器 bean。 @Controller
原型允许自动检测,与 Spring 对在 Classpath 中检测@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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
<!-- ... -->
</beans>
@RestController
是一个组合注解,它本身会用@Controller
和@ResponseBody
进行元注解,以表示其每个方法都继承了类型级别@ResponseBody
注解的控制器,因此直接将其写入响应主体(与视图分辨率和 HTML 模板渲染相比)。
aop代理
在某些情况下,许多人需要在运行时用 AOP 代理装饰控制器。一个示例是,如果您选择直接在控制器上具有@Transactional
注解。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。但是,如果控制器必须实现不是 Spring Context 回调的接口(例如InitializingBean
,*Aware
等),则可能需要显式配置基于类的代理。例如,使用<tx:annotation-driven/>
,您可以更改为<tx:annotation-driven proxy-target-class="true"/>
。
1.3.2. 请求映射
您可以使用@RequestMapping
注解将请求映射到控制器方法。它具有各种属性,可以通过 URL,HTTP 方法,请求参数,Headers 和媒体类型进行匹配。您可以在类级别使用它来表示共享的映射,也可以在方法级别使用它来缩小到特定的端点映射。
也有@RequestMapping
的 HTTP 方法特定的快捷方式:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
之所以提供快捷方式是Custom Annotations,可以说是因为大多数控制器方法应该映射到特定的HTTP方法,而不是使用@RequestMapping
。同时,在类级别仍需要@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 patterns
浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据,但非浏览器 Client 端也可以使用 HTTP PUT,PATCH 和 DELETE。 Servlet API 需要ServletRequest.getParameter*()
个方法才能仅对 HTTP POST 支持表单字段访问。
您可以使用以下 glob 模式和通配符来映射请求:
-
?
匹配一个字符 -
*
匹配路径段中的零个或多个字符 -
**
匹配零个或多个路径段
您还可以声明 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
等),您可以注册对任何其他数据类型的支持。参见Type Conversion和DataBinder。
您可以显式命名 URI 变量(例如@PathVariable("customId")
),但是如果名称相同并且您的代码是使用调试信息或 Java 8 上的-parameters
编译器标志编译的,则可以省略该详细信息。
语法{varName:regex}
声明带有正则表达式的 URI 变量,语法为{varName:regex}
。例如,给定 URL "/spring-web-3.0.5 .jar"
,以下方法将提取名称,版本和文件 extensions:
@GetMapping("/{name:[a-z-]+}-{version:\d\.\d\.\d}{ext:\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String ext) { // ... }
URI 路径模式还可以嵌入${…}
占位符,这些占位符在启动时可以通过针对本地,系统,环境和其他属性源的PropertyPlaceHolderConfigurer
进行解析。例如,您可以使用它来基于一些外部配置参数化基本 URL。
Note
Spring MVC 使用PathMatcher
协定和spring-core
的AntPathMatcher
实现实现 URI 路径匹配。
模式匹配
当多个模式与 URL 匹配时,必须将它们进行比较以找到最佳匹配。这是通过使用AntPathMatcher.getPatternComparator(String path)
完成的,该AntPathMatcher.getPatternComparator(String path)
查找更具体的模式。
如果模式的 URI 变量数较少,单个通配符计为 1,双通配符计为 2,则模式的特异性较低。给定分数,则选择较长的模式。给定相同的分数和长度,将选择 URI 变量比通配符更多的模式。
默认 Map 模式(/**
)被排除在评分之外,并且始终排在最后。另外,前缀模式(例如/public/**
)被认为比其他没有双通配符的模式更具体。
后缀匹配
默认情况下,Spring MVC 执行.*
后缀模式匹配,以便 Map 到/person
的控制器也隐式 Map 到/person.*
。然后,文件 extensions 用于解释请求的 Content Type 以用于响应(即,代替Accept
Headers)—例如/person.pdf
,/person.xml
等。
当浏览器过去曾经发送难以理解的Accept
Headers 时,以这种方式使用文件 extensions 是必要的。目前,这已不再是必须的,使用Accept
Headers 应该是首选。
随着时间的流逝,文件 extensions 的使用已经以各种方式证明是有问题的。当使用 URI 变量,路径参数和 URI 编码进行覆盖时,可能会引起歧义。关于基于 URL 的授权和安全性的推理(请参阅下一部分以了解更多详细信息)也变得更加困难。
要完全禁用文件 extensions,必须设置以下两项:
-
useSuffixPatternMatching(false)
,请参阅PathMatchConfigurer -
favorPathExtension(false)
,请参阅ContentNegotiationConfigurer
后缀匹配和 RFD
反射文件下载(RFD)攻击与 XSS 相似,它依赖于响应中反映的请求 Importing(例如,查询参数和 URI 变量)。但是,RFD 攻击不是将 JavaScript 插入 HTML,而是依靠浏览器切换来执行下载,并在以后双击时将响应视为可执行脚本。
在 Spring MVC 中,@ResponseBody
和ResponseEntity
方法存在风险,因为它们可以呈现不同的 Content Type,Client 端可以通过 URL 路径扩展请求这些 Content Type。禁用后缀模式匹配并使用路径扩展进行内容协商可以降低风险,但不足以防止 RFD 攻击。
为了防止 RFD 攻击,Spring MVC 在呈现响应主体之前添加了Content-Disposition:inline;filename=f.txt
Headers,以建议一个固定且安全的下载文件。仅当 URL 路径包含既未列入白名单也未明确注册用于内容协商的文件 extensions 时,才执行此操作。但是,当直接在浏览器中键入 URL 时,它可能会产生副作用。
默认情况下,许多常见的路径 extensions 都被列入白名单。具有自定义HttpMessageConverter
实现的应用程序可以显式注册文件 extensions 以进行内容协商,以避免为这些 extensions 添加Content-Disposition
Headers。参见Content Types。
消耗媒体类型
您可以根据请求的Content-Type
缩小请求映射,如下例所示:
@PostMapping(path = "/pets", consumes = "application/json") (1)
public void addPet(@RequestBody Pet pet) {
// ...
}
- (1) 使用
consumes
属性按 Content Type 缩小映射范围。
consumes
属性还支持否定表达式-例如,!text/plain
表示text/plain
以外的任何 Content Type。
您可以在类级别声明共享的consumes
属性。但是,与大多数其他请求映射属性不同,在类级使用时,方法级consumes
属性会覆盖而不是扩展类级声明。
Tip
MediaType
为常用的媒体类型(例如APPLICATION_JSON_VALUE
和APPLICATION_XML_VALUE
)提供常数。
可生产的媒体类型
您可以根据Accept
请求 Headers 和控制器方法生成的 Content Type 列表来缩小请求映射,如下例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8") (1)
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
- (1) 使用
produces
属性按 Content Type 缩小映射范围。
媒体类型可以指定字符集。支持否定的表达式。例如,!text/plain
表示除“文本/纯文本”之外的任何 Content Type。
Note
对于 JSONContent Type,即使RFC7159明确声明“未为此注册定义任何字符集参数”,也应指定 UTF-8 字符集,因为某些浏览器要求它正确解释 UTF-8 特殊字符。
您可以在类级别声明共享的produces
属性。但是,与大多数其他请求映射属性不同,在类级使用时,方法级produces
属性会覆盖而不是扩展类级声明。
Tip
MediaType
为常用的媒体类型(例如APPLICATION_JSON_UTF8_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
。
您还可以将其与请求 Headers 条件一起使用,如以下示例所示:
@GetMapping(path = "/pets", headers = "myHeader=myValue") (1)
public void findPet(@PathVariable String petId) {
// ...
}
- (1) 测试
myHeader
是否等于myValue
。
Tip
您可以将Content-Type
和Accept
与 Headers 条件进行匹配,但最好使用consumes和produces。
HTTP HEAD,选项
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)透明地支持 HTTP HEAD 以进行请求映射。控制器方法不需要更改。 javax.servlet.http.HttpServlet
中应用的响应包装器确保将Content-Length
Headers 设置为写入的字节数(实际上未写入响应)。
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)被隐式映射到并支持HTTP HEAD。像处理 HTTP GET 一样处理 HTTP HEAD 请求,不同的是,不是写入正文,而是计算字节数并设置Content-Length
头。
默认情况下,通过将Allow
响应 Headers 设置为所有具有匹配 URL 模式的@RequestMapping
方法中列出的 HTTP 方法列表来处理 HTTP OPTIONS。
对于没有 HTTP 方法声明的@RequestMapping
,Allow
Headers 设置为GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
。控制器方法应始终声明受支持的 HTTP 方法(例如,通过使用 HTTP 方法特定的变体@GetMapping
,@PostMapping
等)。
您可以将@RequestMapping
方法显式映射到 HTTP HEAD 和 HTTP OPTIONS,但这在通常情况下不是必需的。
自定义注解
Spring MVC 支持使用composed annotations进行请求映射。这些注解本身用@RequestMapping
进行元注解,并组成它们以更狭窄,更具体的用途重新声明@RequestMapping
属性的子集(或全部)。
@GetMapping
,@PostMapping
,@PutMapping
,@DeleteMapping
和@PatchMapping
是组合注解 的示例。之所以提供它们,是因为大多数控制器方法应该映射到特定的 HTTP 方法,而不是使用@RequestMapping
,默认情况下,@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) 注入目标处理程序和控制器的处理程序 Map。
- (2) 准备请求 Map 元数据。
- (3) 获取处理程序方法。
- (4) 添加注册。
1.3.3. 处理程序方法
@RequestMapping
处理程序方法具有灵活的签名,可以从一系列受支持的控制器方法参数和返回值中进行选择。
方法参数
下表描述了受支持的控制器方法参数。任何参数均不支持 Reactive 类型。
支持 JDK 8 的java.util.Optional
作为方法参数,并与具有required
属性(例如@RequestParam
,@RequestHeader
等)的注解结合使用,该注解等效于required=false
。
控制器方法参数 | Description |
---|---|
WebRequest ,NativeWebRequest |
对请求参数以及请求和会话属性的常规访问,而无需直接使用 Servlet API。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定的请求或响应类型,例如ServletRequest ,HttpServletRequest 或 Spring 的MultipartRequest ,MultipartHttpServletRequest 。 |
javax.servlet.http.HttpSession |
强制会话的存在。因此,这样的参数永远不会是null 。请注意,会话访问不是线程安全的。如果允许多个请求并发访问会话,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession 标志设置为true 。 |
javax.servlet.http.PushBuilder |
用于程序化 HTTP/2 资源推送的 Servlet 4.0 推送构建器 API。请注意,根据 Servlet 规范,如果 Client 端不支持 HTTP/2 功能,则注入的PushBuilder 实例可以为 null。 |
java.security.Principal |
当前通过身份验证的用户-可能是特定的Principal 实现类(如果已知)。 |
HttpMethod |
请求的 HTTP 方法。 |
java.util.Locale |
当前的请求语言环境,由最具体的LocaleResolver (实际上是配置的LocaleResolver 或LocaleContextResolver )确定。 |
java.util.TimeZone + java.time.ZoneId |
与当前请求关联的时区,由LocaleContextResolver 确定。 |
java.io.InputStream , java.io.Reader |
用于访问 Servlet API 公开的原始请求正文。 |
java.io.OutputStream , java.io.Writer |
用于访问 Servlet API 公开的原始响应正文。 |
@PathVariable |
用于访问 URI 模板变量。参见URI patterns。 |
@MatrixVariable |
用于访问 URI 路径段中的名称/值对。参见Matrix Variables。 |
@RequestParam |
用于访问 Servlet 请求参数,包括 Multipart 文件。参数值将转换为声明的方法参数类型。参见@RequestParam和Multipart。 |
请注意,对于简单的参数值,使用@RequestParam
是可选的。
| @RequestHeader
|用于访问请求 Headers。Headers 值将转换为声明的方法参数类型。
| @CookieValue
|用于访问 cookie。 Cookies 值将转换为声明的方法参数类型。
| @RequestBody
|用于访问 HTTP 请求正文。通过使用HttpMessageConverter
实现,主体内容将转换为声明的方法参数类型。
| HttpEntity<B>
|用于访问请求 Headers 和正文。主体用HttpMessageConverter
转换。
| @RequestPart
|要访问multipart/form-data
请求中的 Component,请用HttpMessageConverter
转换 Component 的主体。
| java.util.Map
,org.springframework.ui.Model
,org.springframework.ui.ModelMap
|用于访问 HTML 控制器中使用的模型,并作为视图渲染的一部分公开给模板。
| RedirectAttributes
|指定在重定向(即附加到查询字符串)的情况下使用的属性,以及将 Flash 属性临时存储直到重定向后的请求。
| @ModelAttribute
|用于访问应用了数据绑定和验证的模型中的现有属性(如果不存在,则进行实例化)。
| Errors
,BindingResult
|用于访问命令对象的验证和数据绑定(即@ModelAttribute
参数)中的错误或@RequestBody
或@RequestPart
参数的验证中的错误。您必须在经过验证的方法参数后立即声明Errors
或BindingResult
参数。
| SessionStatus
类级@SessionAttributes
|用于标记表单处理完成,将触发清除通过类级@SessionAttributes
Comments 声明的会话属性。
| UriComponentsBuilder
|用于准备相对于当前请求的主机,端口,方案,上下文路径以及 servletMap 的 Literals 部分的 URL。
| @SessionAttribute
|用于访问任何会话属性,与由于类级别@SessionAttributes
声明而存储在会话中的模型属性相反。
| @RequestAttribute
|用于访问请求属性。
|任何其他参数|如果方法参数与该表中的任何较早值都不匹配,并且是简单类型(由BeanUtils#isSimpleProperty确定,则将其解析为@RequestParam
。否则,将其解析为@ModelAttribute
。
返回值
下表描述了受支持的控制器方法返回值。所有返回值都支持 Reactive 类型。
控制器方法返回值 | Description |
---|---|
@ResponseBody |
返回值通过HttpMessageConverter 实现进行转换,并写入响应中。 |
HttpEntity<B> ,ResponseEntity<B> |
指定完整响应(包括 HTTPHeaders 和正文)的返回值将通过HttpMessageConverter 实现进行转换并写入响应中。 |
HttpHeaders |
用于返回不包含标题的响应。 |
String |
用ViewResolver 实现解析的视图名称,并与通过命令对象和@ModelAttribute 方法确定的隐式模型一起使用。处理程序方法还可以pass 语句Model 参数(请参见Explicit Registrations)以编程方式丰富模型。 |
View |
用于与隐式模型一起渲染的View 实例-通过命令对象和@ModelAttribute 方法确定。处理程序方法还可以pass 语句Model 参数(请参见Explicit Registrations)以编程方式丰富模型。 |
java.util.Map ,org.springframework.ui.Model |
要添加到隐式模型的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute |
要添加到模型的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
请注意,@ModelAttribute
是可选的。请参见此表末尾的“其他任何返回值”。
| ModelAndView
object |要使用的视图和模型属性,以及响应状态(可选)。
| void
|具有void
返回类型(或null
返回值)的方法,如果它也具有ServletResponse
,OutputStream
参数或@ResponseStatus
Comments,则认为已完全处理了响应。如果控制器对ETag
或lastModified
时间戳进行了肯定检查,则也是如此(有关详细信息,请参见Controllers)。
如果以上所有条件都不成立,则void
返回类型还可以为 REST 控制器指示“无响应正文”,或者为 HTML 控制器指示默认的视图名称选择。
| DeferredResult<V>
|从任何线程异步生成任何上述返回值,例如由于某些事件或回调的结果。
| Callable<V>
|在 Spring MVC管理的线程中异步产生上述任何返回值。
| ListenableFuture<V>
,java.util.concurrent.CompletionStage<V>
,java.util.concurrent.CompletableFuture<V>
|替代DeferredResult
,以方便使用(例如,当基础服务返回其中之一时)。
| ResponseBodyEmitter
,SseEmitter
|使用HttpMessageConverter
实现异步发出对象流以将其写入响应。也支持作为ResponseEntity
的主体。
| StreamingResponseBody
|异步写入响应OutputStream
。也支持作为ResponseEntity
的主体。
|Reactive 类型-反应器,RxJava 或其他通过ReactiveAdapterRegistry
|替代DeferredResult
的多值流(例如Flux
,Observable
)收集到List
。
对于流方案(例如text/event-stream
,application/json+stream
),将使用SseEmitter
和ResponseBodyEmitter
代替,其中ServletOutputStream
阻塞 I/O 在 Spring MVCManagement 的线程上执行,并且在每次写入完成时施加反压。
|任何其他返回值|与该表中的任何较早值不匹配且为String
或void
的任何返回值均被视为视图名称(适用于通过RequestToViewNameTranslator
进行默认视图名称选择),前提是该操作不简单类型,由BeanUtils#isSimpleProperty确定。简单类型的值仍然无法解析。
类型转换
如果声明的参数不是String
,则表示基于String
的请求 Importing 的某些带注解 的控制器方法参数(例如@RequestParam
,@RequestHeader
,@PathVariable
,@MatrixVariable
和@CookieValue
)可能需要类型转换。
在这种情况下,将根据配置的转换器自动应用类型转换。默认情况下,支持简单类型(int
,long
,Date
等)。您可以通过WebDataBinder
(请参见DataBinder)或通过向FormattingConversionService
注册Formatters
来定制类型转换。
矩阵变量
RFC 3986讨论路径段中的名称/值对。在 Spring MVC 中,基于 Tim Berners-Lee 的"old post",我们将其称为“矩阵变量”,但它们也可以称为 URI 路径参数。
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如/cars;color=red,green;year=2012
)。也可以通过重复的变量名称(例如color=red;color=green;color=blue
)来指定多个值。
如果期望 URL 包含矩阵变量,则控制器方法的请求 Map 必须使用 URI 变量来屏蔽该变量内容,并确保可以成功地匹配请求,而与矩阵变量的 Sequences 和状态无关。以下示例使用矩阵变量:
// GET /pets/42;q=11;r=22 @GetMapping("/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
鉴于所有路径段都可能包含矩阵变量,因此有时您可能需要消除矩阵变量应位于哪个路径变量中的歧义。下面的示例演示如何做到这一点:
// GET /owners/42;q=11/pets/21;q=22 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable(name="q", pathVar="ownerId") int q1, @MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
可以将矩阵变量定义为可选变量,并指定默认值,如以下示例所示:
// GET /pets/42 @GetMapping("/pets/{petId}") public void findPet(@MatrixVariable(required=false, defaultValue="1") int q) { // q == 1 }
要获取所有矩阵变量,可以使用MultiValueMap
,如以下示例所示:
// GET /owners/42;q=11;r=12/pets/21;q=22;s=23 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable MultiValueMap<String, String> matrixVars, @MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) { // matrixVars: ["q" : [11,22], "r" : 12, "s" : 23] // petMatrixVars: ["q" : 22, "s" : 23] }
请注意,您需要启用矩阵变量的使用。在 MVC Java 配置中,您需要使用removeSemicolonContent=false
到Path Matching来设置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) { (1)
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
默认情况下,使用此注解的方法参数是必需的,但是您可以通过将@RequestParam
注解的required
标志设置为false
或通过java.util.Optional
包装器声明参数来指定方法参数是可选的。
如果目标方法参数类型不是String
,则会自动应用类型转换。参见Type Conversion。
将参数类型声明为数组或列表,可以为同一参数名称解析多个参数值。
如果将@RequestParam
注解声明为Map<String, String>
或MultiValueMap<String, String>
,而未在注解中指定参数名,则将使用每个给定参数名的请求参数值填充 Map。
请注意,使用@RequestParam
是可选的(例如,设置其属性)。默认情况下,任何简单值类型(由BeanUtils#isSimpleProperty确定)且未由其他任何参数解析器解析的参数都将被视为带有@RequestParam
注解。
@RequestHeader
您可以使用@RequestHeader
注解将请求 Headers 绑定到控制器中的方法参数。
考虑以下带有 Headers 的请求:
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
Headers 的值:
@GetMapping("/demo") public void handle( @RequestHeader("Accept-Encoding") String encoding, (1) @RequestHeader("Keep-Alive") long keepAlive) { (2) //... }
- (1) 获取
Accept-Encoding
Headers 的值。 - (2) 获取
Keep-Alive
Headers 的值。
如果目标方法的参数类型不是String
,则将自动应用类型转换。参见Type Conversion。
在Map<String, String>
,MultiValueMap<String, String>
或HttpHeaders
参数上使用@RequestHeader
注解时,将使用所有 Headers 值填充 Map。
Tip
内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。例如,带有@RequestHeader("Accept")
注解的方法参数可以是String
类型,也可以是String[]
或List<String>
类型。
@CookieValue
您可以使用@CookieValue
注解将 HTTP cookie 的值绑定到控制器中的方法参数。
考虑带有以下 cookie 的请求:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
以下示例显示如何获取 cookie 值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) { (1)
//...
}
- (1) 获取
JSESSIONID
cookie 的值。
如果目标方法的参数类型不是String
,那么将自动应用类型转换。参见Type Conversion。
@ModelAttribute
您可以在方法参数上使用@ModelAttribute
注解来访问模型中的属性,或者将其实例化(如果不存在)。 model 属性还覆盖了名称与字段名称匹配的 HTTP Servlet 请求参数中的值。这称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段的工作。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) { } (1)
- (1) 绑定
Pet
的实例。
上面的Pet
实例解析如下:
-
从模型(如果已使用Model添加)。
-
通过使用@SessionAttributes在 HTTP 会话中进行。
-
来自通过
Converter
传递的 URI 路径变量(请参见下一个示例)。 -
从默认构造函数的调用开始。
-
从带有与 Servlet 请求参数匹配的参数的“主要构造函数”的调用开始。参数名称是通过 JavaBeans
@ConstructorProperties
或字节码中运行时保留的参数名称确定的。
虽然通常使用Model来为模型填充属性,但另一种替代方法是将Converter<String, T>
与 URI 路径变量约定结合使用。在以下示例中,模型属性名称account
与 URI 路径变量account
匹配,并且通过将String
帐号传递给已注册的Converter<String, Account>
来加载Account
:
@PutMapping("/accounts/{account}") public String save(@ModelAttribute("account") Account account) { // ... }
获取模型属性实例后,将应用数据绑定。 WebDataBinder
类将 Servlet 请求参数名称(查询参数和表单字段)与目标Object
上的字段名称匹配。必要时在应用类型转换后填充匹配字段。有关数据绑定(和验证)的更多信息,请参见Validation。有关自定义数据绑定的更多信息,请参见DataBinder。
数据绑定可能会导致错误。默认情况下,引发BindException
。但是,要检查 controller 方法中的此类错误,可以在@ModelAttribute
旁边紧接着添加BindingResult
参数,如以下示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情况下,您可能希望访问没有数据绑定的模型属性。对于这种情况,您可以将Model
注入控制器并直接访问它,或者设置@ModelAttribute(binding=false)
,如以下示例所示:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountUpdateForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) { (1)
// ...
}
- (1) 设置
@ModelAttribute(binding=false)
。
通过添加javax.validation.Valid
注解或 Spring 的@Validated
注解(ee Bean validation和Spring validation),可以在数据绑定之后自动应用验证。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) { (1)
if (result.hasErrors()) {
return "petForm";
}
// ...
}
- (1) 验证
Pet
实例。
请注意,使用@ModelAttribute
是可选的(例如,设置其属性)。默认情况下,任何不是简单值类型(由BeanUtils#isSimpleProperty确定)且未由任何其他参数解析器解析的参数都将被视为以@ModelAttribute
注解。
@SessionAttributes
@SessionAttributes
用于在请求之间的 HTTP Servlet 会话中存储模型属性。它是类型级别的注解,用于声明特定控制器使用的会话属性。这通常列出应透明地存储在会话中以供后续访问请求的模型属性名称或模型属性类型。
下面的示例使用@SessionAttributes
注解:
@Controller
@SessionAttributes("pet") (1)
public class EditPetForm {
// ...
}
- (1) 使用
@SessionAttributes
注解。
在第一个请求上,将名称为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) // ... } } }
- (1) 将
Pet
值存储在 Servlet 会话中。 - (2) 从 Servlet 会话中清除
Pet
值。
@SessionAttribute
与@SessionAttribute
相似,您可以使用@RequestAttribute
注解来访问先前创建的预先存在的请求属性(例如,由 Servlet Filter
或HandlerInterceptor
创建):
@GetMapping("/")
public String handle(@RequestAttribute Client client) { (1)
// ...
}
(1) 使用@RequestAttribute注解。
Redirect Attributes
默认情况下,所有模型属性均被视为在重定向 URL 中作为 URI 模板变量公开。在其余属性中,那些属于原始类型或原始类型的集合或数组的属性会自动附加为查询参数。
如果专门为重定向准备了模型实例,则将原始类型属性作为查询参数附加可能是理想的结果。但是,在带注解 的控制器中,模型可以包含为渲染目的添加的其他属性(例如,下拉字段值)。为了避免此类属性出现在 URL 中的可能性,@RequestMapping
方法可以声明类型RedirectAttributes
的参数,并使用它来指定确切的属性以供RedirectView
使用。如果该方法确实重定向,则使用RedirectAttributes
的内容。否则,将使用模型的内容。
RequestMappingHandlerAdapter
提供了一个名为ignoreDefaultModelOnRedirect
的标志,您可以使用该标志来表示如果控制器方法重定向,则绝不要使用默认Model
的内容。相反,控制器方法应声明类型为RedirectAttributes
的属性,或者,如果没有声明,则不应将任何属性传递给RedirectView
。 MVC 名称空间和 MVC Java 配置都将此标志设置为false
,以保持向后兼容性。但是,对于新应用程序,建议将其设置为true
。
请注意,展开重定向 URL 时,本请求中的 URI 模板变量将自动变为可用,并且您需要通过Model
或RedirectAttributes
显式添加它们。以下示例显示了如何定义重定向:
@PostMapping("/files/{path}") public String upload(...) { // ... return "redirect:files/{path}"; }
将数据传递到重定向目标的另一种方法是使用闪存属性。与其他重定向属性不同,Flash 属性保存在 HTTP 会话中(因此不会出现在 URL 中)。
Flash Attributes
Flash 属性为一个请求提供了一种存储要在另一个请求中使用的属性的方式。重定向时最常需要此操作,例如 Post-Redirect-Get 模式。 Flash 属性在重定向之前(通常在会话中)被临时保存,以便在重定向之后可供请求使用,并立即被删除。
Spring MVC 有两个主要抽象来支持 Flash 属性。 FlashMap
用于保存 Flash 属性,而FlashMapManager
用于存储,检索和管理FlashMap
实例。
Flash 属性支持始终处于“启用”状态,无需显式启用。但是,如果不使用它,则永远不会导致 HTTP 会话创建。在每个请求上,都有一个具有从上一个请求(如果有)传递的属性的“Importing” FlashMap
,和一个具有为后续请求保存的属性的“输出” FlashMap
。通过RequestContextUtils
中的静态方法,可以从 Spring MVC 中的任何位置访问这两个FlashMap
实例。
带注解 的控制器通常不需要直接与FlashMap
一起使用。相反,@RequestMapping
方法可以接受RedirectAttributes
类型的参数,并使用它为重定向方案添加 Flash 属性。通过RedirectAttributes
添加的 Flash 属性会自动传播到“输出” FlashMap。同样,在重定向之后,来自“Importing” FlashMap
的属性会自动添加到服务目标 URL 的控制器的Model
中。
将请求与 Flash 属性匹配
Flash 属性的概念存在于许多其他 Web 框架中,并已证明有时会遇到并发问题。这是因为根据定义,闪存属性将存储到下一个请求。但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,过早删除了 Flash 属性。
为了减少发生此类问题的可能性,RedirectView
自动使用目标重定向 URL 的路径和查询参数“标记” FlashMap
实例。反过来,默认FlashMapManager
在查找“Importing” FlashMap
时会将信息与传入请求匹配。
这不能完全消除并发问题的可能性,但是可以通过重定向 URL 中已经可用的信息大大减少并发问题。因此,我们建议您主要将 Flash 属性用于重定向方案。
Multipart
在MultipartResolver
成为enabled之后,将解析multipart/form-data
的 POST 请求的内容并将其作为常规请求参数进行访问。以下示例访问一个常规表单字段和一个上载文件:
@Controller
public class FileUploadController {
@PostMapping("/form")
public String handleFormUpload(@RequestParam("name") String name,
@RequestParam("file") MultipartFile file) {
if (!file.isEmpty()) {
byte[] bytes = file.getBytes();
// store the bytes somewhere
return "redirect:uploadSuccess";
}
return "redirect:uploadFailure";
}
}
将参数类型声明为List<MultipartFile>
可以为同一参数名称解析多个文件。
如果将@RequestParam
注解声明为Map<String, MultipartFile>
或MultiValueMap<String, MultipartFile>
,而未在注解中指定参数名称,则将使用每个给定参数名的 Multipart 文件填充 Map。
Note
使用 Servlet 3.0Multipart 解析时,您还可以将javax.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 服务方案中,也可以从非浏览器 Client 端提交 Multipart 请求。以下示例显示了带有 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
作为String
来访问“元数据”部分,但您可能希望将其从 JSON 反序列化(类似于@RequestBody
)。用HttpMessageConverter转换后,使用@RequestPart
注解来访问 Multipart:
@PostMapping("/") public String handle(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) { // ... }
您可以将@RequestPart
与javax.validation.Valid
结合使用,也可以使用 Spring 的@Validated
注解,这两种注解 都会导致应用标准 Bean 验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,它变成 400(BAD_REQUEST)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内部本地处理验证错误,如以下示例所示:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
@RequestBody
您可以使用@RequestBody
注解将请求正文读取并通过HttpMessageConverter反序列化为Object
。以下示例使用@RequestBody
参数:
@PostMapping("/accounts") public void handle(@RequestBody Account account) { // ... }
您可以使用MVC Config的Message Converters选项来配置或自定义消息转换。
您可以将@RequestBody
与javax.validation.Valid
或 Spring 的@Validated
注解结合使用,这两种注解 都会导致应用标准 Bean 验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,它变成 400(BAD_REQUEST)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内部本地处理验证错误,如以下示例所示:
@PostMapping("/accounts") public void handle(@Valid @RequestBody Account account, BindingResult result) { // ... }
HttpEntity
HttpEntity
与使用@RequestBody大致相同,但是它基于一个容器对象,该对象公开了请求 Headers 和正文。以下 Lists 显示了一个示例:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
您可以在方法上使用@ResponseBody
注解,以使返回值通过HttpMessageConverter序列化到响应主体。以下 Lists 显示了一个示例:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody
在类级别上也受支持,在这种情况下,它被所有控制器方法继承。这就是@RestController
的效果,它不过是用@Controller
和@ResponseBody
标记的元注解。
您可以将@ResponseBody
与反应类型一起使用。
您可以使用MVC Config的Message Converters选项来配置或自定义消息转换。
您可以将@ResponseBody
方法与 JSON 序列化视图结合使用。
ResponseEntity
ResponseEntity
类似于@ResponseBody,但具有状态和标题。例如:
@GetMapping("/something") public ResponseEntity<String> handle() { String body = ... ; String etag = ... ; return ResponseEntity.ok().eTag(etag).build(body); }
Spring MVC 支持使用单个值reactive type异步生成ResponseEntity
和/或主体的单值和多值反应类型。
Jackson JSON
Spring 提供了对 Jackson JSON 库的支持。
Spring MVC 为Jackson 的序列化视图提供了内置支持,该支持仅渲染Object
中所有字段的一部分。要将它与@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; } }
Note
@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. Model
您可以使用@ModelAttribute
注解:
-
在
@RequestMapping
方法参数中,从模型创建或访问Object
并将其通过WebDataBinder
绑定到请求。 -
作为
@Controller
或@ControllerAdvice
类中的方法级注解,可在任何@RequestMapping
方法调用之前帮助初始化模型。 -
在
@RequestMapping
方法上标记其返回值的是模型属性。
本节讨论@ModelAttribute
方法-前面列表中的第二项。控制器可以具有任意数量的@ModelAttribute
方法。所有此类方法均在同一控制器中的@RequestMapping
方法之前调用。 @ModelAttribute
方法也可以通过@ControllerAdvice
在控制器之间共享。
@ModelAttribute
方法具有灵活的方法签名。它们支持与@RequestMapping
方法相同的许多参数,但@ModelAttribute
本身与请求正文相关的任何东西除外。
以下示例显示了@ModelAttribute
方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
以下示例仅添加一个属性:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
以下示例仅添加一个属性:
@ModelAttribute public Account addAccount(@RequestParam String number) { return accountRepository.findAccount(number); }
Note
如果未明确指定名称,则根据Object
类型选择默认名称,如Conventions的 javadoc 中所述。您始终可以通过使用重载的addAttribute
方法或通过@ModelAttribute
上的name
属性(用于返回值)来分配显式名称。
您也可以将@ModelAttribute
用作@RequestMapping
方法的方法级注解,在这种情况下@RequestMapping
方法的返回值将解释为模型属性。通常不需要这样做,因为这是 HTML 控制器中的默认行为,除非返回值是String
,否则它将被解释为视图名称。 @ModelAttribute
还可以自定义模型属性名称,如以下示例所示:
@GetMapping("/accounts/{id}") @ModelAttribute("myAccount") public Account handle() { // ... return account; }
1.3.5. DataBinder
@Controller
或@ControllerAdvice
类可以使用@InitBinder
注解来初始化WebDataBinder
的实例,而这些实例又可以:
@Controller
或@ControllerAdvice
类可以具有@InitBinder
个方法来初始化WebDataBinder
的实例,而这些实例又可以:
-
将请求参数(即表单或查询数据)绑定到模型对象。
-
将基于字符串的请求值(例如请求参数,路径变量,Headers,Cookie 等)转换为控制器方法参数的目标类型。
-
呈现 HTML 表单时,将模型对象的值格式化为
String
值。
@InitBinder
个方法可以注册特定于控制器的java.bean.PropertyEditor
或 Spring Converter
和Formatter
组件。此外,您可以使用MVC config在全局共享的FormattingConversionService
中注册Converter
和Formatter
类型。
@InitBinder
方法支持与@RequestMapping
方法相同的许多参数,但@ModelAttribute
(命令对象)参数除外。通常,它们使用WebDataBinder
参数(用于注册)和void
返回值声明。以下 Lists 显示了一个示例:
@Controller
public class FormController {
@InitBinder (1)
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
- (1) 定义
@InitBinder
方法。
另外,当您通过共享FormattingConversionService
使用基于Formatter
的设置时,可以重新使用相同的方法并注册特定于控制器的Formatter
实现,如以下示例所示:
@Controller
public class FormController {
@InitBinder (1)
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
- (1) 在自定义格式化程序上定义
@InitBinder
方法。
1.3.6. Exceptions
@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) { // ... }
Note
根和原因异常匹配之间的区别可能令人惊讶。
在前面显示的IOException
变体中,通常以实际的FileSystemException
或RemoteException
实例作为参数来调用该方法,因为这两个实例都从IOException
扩展。但是,如果任何此类匹配异常都在本身是IOException
的包装器异常中传播,则传入的异常实例就是该包装器异常。
handle(Exception)
变体中的行为甚至更简单。在包装方案中,总是使用包装器异常来调用此方法,在这种情况下,实际匹配的异常将通过ex.getCause()
找到。仅当将它们作为顶级异常抛出时,传入的异常才是实际的FileSystemException
或RemoteException
实例。
通常,我们建议您在参数签名中尽可能具体,以减少根类型和原因异常类型之间不匹配的可能性。考虑将多重匹配方法分解为单独的@ExceptionHandler
方法,每个方法都通过其签名匹配单个特定的异常类型。
在多@ControllerAdvice
的安排中,我们建议在以相应 Sequences 优先的@ControllerAdvice
上声明您的主根异常 Map。尽管根源异常匹配是原因的首选,但这是在给定控制器或@ControllerAdvice
类的方法之间定义的。这意味着优先级较高的@ControllerAdvice
bean 上的原因匹配优于优先级较低的@ControllerAdvice
bean 上的任何匹配(例如,根)。
最后但并非最不重要的一点是,@ExceptionHandler
方法实现可以选择以放弃原始形式重新处理给定异常实例的方法。在仅对根级别匹配或无法静态确定的特定上下文中的匹配感兴趣的情况下,这很有用。重新抛出的异常会在其余的解决方案链中传播,就像给定的@ExceptionHandler
方法最初不会匹配一样。
Spring MVC 中对@ExceptionHandler
方法的支持构建在DispatcherServlet
级别的HandlerExceptionResolver机制上。
Method Arguments
@ExceptionHandler
方法支持以下参数:
Method argument | Description |
---|---|
Exception type | 用于访问引发的异常。 |
HandlerMethod |
用于访问引发异常的控制器方法。 |
WebRequest , NativeWebRequest |
对请求参数以及请求和会话属性的常规访问,而无需直接使用 Servlet API。 |
javax.servlet.ServletRequest , javax.servlet.ServletResponse |
选择任何特定的请求或响应类型(例如ServletRequest 或HttpServletRequest 或 Spring 的MultipartRequest 或MultipartHttpServletRequest )。 |
javax.servlet.http.HttpSession |
强制会话的存在。因此,这样的参数永远不会是null 。 |
请注意,会话访问不是线程安全的。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter
实例的synchronizeOnSession
标志设置为true
。
| java.security.Principal
|当前经过身份验证的用户-可能是特定的Principal
实现类(如果已知)。
| HttpMethod
|请求的 HTTP 方法。
| java.util.Locale
|当前请求的语言环境,由最具体的LocaleResolver
可用决定,实际上是配置的LocaleResolver
或LocaleContextResolver
。
| java.util.TimeZone
,java.time.ZoneId
|与当前请求关联的时区,由LocaleContextResolver
确定。
| java.io.OutputStream
,java.io.Writer
|用于访问原始响应主体,如 Servlet API 所公开。
| java.util.Map
,org.springframework.ui.Model
,org.springframework.ui.ModelMap
|用于访问模型以进行错误响应。总是空的。
| RedirectAttributes
|指定在重定向的情况下使用的属性(要附加到查询字符串中)和 flash 属性,这些属性将临时存储直到重定向后的请求。参见Redirect Attributes和Flash Attributes。
| @SessionAttribute
|用于访问任何会话属性,与由于类级别@SessionAttributes
声明而存储在会话中的模型属性相反。有关更多详细信息,请参见@SessionAttribute。
| @RequestAttribute
|用于访问请求属性。有关更多详细信息,请参见@RequestAttribute。
Return Values
@ExceptionHandler
方法支持以下返回值:
Return value | Description |
---|---|
@ResponseBody |
返回值通过HttpMessageConverter 个实例进行转换并写入响应中。参见@ResponseBody。 |
HttpEntity<B> , ResponseEntity<B> |
返回值指定完整的响应(包括 HTTPHeaders 和正文)通过HttpMessageConverter 实例进行转换并写入响应中。参见ResponseEntity。 |
String |
要通过ViewResolver 实现解析的视图名称,并与隐式模型一起使用-通过命令对象和@ModelAttribute 方法确定。处理程序方法还可以pass 语句Model 参数(如前所述)以编程方式丰富模型。 |
View |
通过命令对象和@ModelAttribute 方法确定的用于与隐式模型一起渲染的View 实例。处理程序方法还可以pass 语句Model 参数(先前描述)来以编程方式丰富模型。 |
java.util.Map , org.springframework.ui.Model |
通过RequestToViewNameTranslator 隐式确定的视图名称将添加到隐式模型的属性。 |
@ModelAttribute |
通过RequestToViewNameTranslator 隐式确定的视图名称将添加到模型的属性。 |
请注意,@ModelAttribute
是可选的。请参见此表末尾的“其他任何返回值”。
| ModelAndView
object |要使用的视图和模型属性,以及响应状态(可选)。
| void
|具有void
返回类型(或null
返回值)的方法,如果它也具有ServletResponse
,OutputStream
自变量或@ResponseStatus
Comments,则认为已完全处理了响应。如果控制器对ETag
或lastModified
时间戳进行了肯定检查,则也是如此(有关详细信息,请参见Controllers)。
如果以上所有条件都不成立,则void
返回类型还可以为 REST 控制器指示“无响应正文”,或者为 HTML 控制器指示默认视图名称选择。
|其他任何返回值|如果返回值与上述任何一个都不匹配并且不是简单类型(由BeanUtils#isSimpleProperty确定),则默认情况下会将其视为要添加到模型的模型属性。如果是简单类型,则仍然无法解决。
REST API 异常
REST 服务的常见要求是在响应正文中包含错误详细信息。 Spring 框架不会自动执行此操作,因为响应主体中错误详细信息的表示是特定于应用程序的。但是,@RestController
可以使用具有ResponseEntity
返回值的@ExceptionHandler
方法来设置响应的状态和主体。也可以在@ControllerAdvice
类中声明此类方法以将其全局应用。
在响应正文中实现具有错误详细信息的全局异常处理的应用程序应考虑扩展ResponseEntityExceptionHandler,该扩展为 Spring MVC 引发的异常提供处理,并提供用于自定义响应正文的钩子。要使用此功能,请创建ResponseEntityExceptionHandler
的子类,并用@ControllerAdvice
对其进行注解,覆盖必要的方法,然后将其声明为 Spring bean。
1.3.7. 控制器建议
通常,@ExceptionHandler
,@InitBinder
和@ModelAttribute
方法适用于声明它们的@Controller
类(或类层次结构)。如果希望此类方法更全局地应用(跨控制器),则可以在标有@ControllerAdvice
或@RestControllerAdvice
的类中声明它们。
@ControllerAdvice
标有@Component
,这意味着可以通过component scanning将此类注册为 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 {}
1.4. URI 链接
1.4.1. UriComponents
UriComponentsBuilder
帮助从具有变量的 URI 模板构建 URI,如以下示例所示:
UriComponents uriComponents = UriComponentsBuilder .fromUriString("http://example.com/hotels/{hotel}") (1) .queryParam("q", "{q}") (2) .encode() (3) .build(); (4) URI uri = uriComponents.expand("Westin", "123").toUri(); (5)
- (1) 带有 URI 模板的静态工厂方法。
- (2) 添加或替换 URI 组件。
- (3) 请求对 URI 模板和 URI 变量进行编码。
- (4) 构建
UriComponents
。 - (5) 展开变量并获得
URI
。
可以将前面的示例合并为一个链,并使用buildAndExpand
进行缩短,如以下示例所示:
URI uri = UriComponentsBuilder .fromUriString("http://example.com/hotels/{hotel}") .queryParam("q", "{q}") .encode() .buildAndExpand("Westin", "123") .toUri();
您可以通过直接转到 URI(这意味着编码)来进一步缩短它,如以下示例所示:
URI uri = UriComponentsBuilder .fromUriString("http://example.com/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123");
您可以使用完整的 URI 模板进一步缩短它,如以下示例所示:
URI uri = UriComponentsBuilder .fromUriString("http://example.com/hotels/{hotel}?q={q}") .build("Westin", "123");
1.4.2. UriBuilder
UriComponentsBuilder实现UriBuilder
。您可以依次创建UriBuilder
和UriBuilderFactory
。 UriBuilderFactory
和UriBuilder
一起提供了一种可插入的机制,用于基于共享配置(例如基本 URL,编码首选项和其他详细信息)从 URI 模板构建 URI。
您可以使用UriBuilderFactory
配置RestTemplate
和WebClient
以自定义 URI 的准备。 DefaultUriBuilderFactory
是UriBuilderFactory
的默认实现,该实现在内部使用UriComponentsBuilder
并公开共享的配置选项。
以下示例显示了如何配置RestTemplate
:
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "http://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VARIABLES); RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory);
以下示例配置WebClient
:
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode; String baseUrl = "http://example.org"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl); factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VARIABLES); WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
此外,您也可以直接使用DefaultUriBuilderFactory
。它类似于使用UriComponentsBuilder
,但不是静态工厂方法,而是一个包含配置和首选项的实际实例,如以下示例所示:
String baseUrl = "http://example.com"; DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl); URI uri = uriBuilderFactory.uriString("/hotels/{hotel}") .queryParam("q", "{q}") .build("Westin", "123");
1.4.3. URI 编码
UriComponentsBuilder
公开了两个级别的编码选项:
-
UriComponentsBuilder#encode():首先对 URI 模板进行预编码,然后在扩展时严格对 URI 变量进行编码。
-
UriComponents#encode():在扩展 URI 变量后*编码 URI 组件。
这两个选项都使用转义的八位字节替换非 ASCII 和非法字符。但是,第一个选项还会替换出现在 URI 变量中的具有保留含义的字符。
Tip
考虑“;”,它在路径上是合法的,但具有保留的含义。第一个选项代替“;” 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 = "http://example.com"; DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl) factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES); // Customize the RestTemplate.. RestTemplate restTemplate = new RestTemplate(); restTemplate.setUriTemplateHandler(factory); // Customize the WebClient.. WebClient client = WebClient.builder().uriBuilderFactory(factory).build();
DefaultUriBuilderFactory
实现内部使用UriComponentsBuilder
来扩展和编码 URI 模板。作为工厂,它提供了一个位置,可以根据以下一种编码模式来配置编码方法:
-
TEMPLATE_AND_VALUES
:使用UriComponentsBuilder#encode()
(对应于较早列表中的第一个选项)来预编码 URI 模板,并在扩展时严格编码 URI 变量。 -
VALUES_ONLY
:不对 URI 模板进行编码,而是在将它们扩展到模板之前通过UriUtils#encodeUriUriVariables
对 URI 变量进行严格编码。 -
URI_COMPONENTS
:在扩展 URI 变量之后*,使用与较早列表中第二个选项相对应的UriComponents#encode()
来编码 URI 组件值。 -
NONE
:未应用编码。
出于历史原因和向后兼容性,RestTemplate
设置为EncodingMode.URI_COMPONENTS
。 WebClient
依赖于DefaultUriBuilderFactory
中的默认值,该默认值已从 5.0.x 中的EncodingMode.URI_COMPONENTS
更改为 5.1 中的EncodingMode.TEMPLATE_AND_VALUES
。
1.4.4. 相对 Servlet 请求
您可以使用ServletUriComponentsBuilder
来创建相对于当前请求的 URI,如以下示例所示:
HttpServletRequest request = ... // Re-uses host, scheme, port, path and query string... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromRequest(request) .replaceQueryParam("accountId", "{id}").build() .expand("123") .encode();
您可以创建相对于上下文路径的 URI,如以下示例所示:
// Re-uses host, port and context path... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromContextPath(request) .path("/accounts").build()
您可以创建相对于 Servlet 的 URI(例如/main/*
),如以下示例所示:
// Re-uses host, port, context path, and Servlet prefix... ServletUriComponentsBuilder ucb = ServletUriComponentsBuilder.fromServletMapping(request) .path("/accounts").build()
Note
从 5.1 开始,ServletUriComponentsBuilder
会忽略Forwarded
和X-Forwarded-*
Headers 中的信息,这些 Headers 指定了 Client 端起源的地址。考虑使用ForwardedHeaderFilter提取和使用或丢弃此类 Headers。
1.4.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 变量,例如从类型级请求 Map 继承的hotel
变量。如果该方法具有更多参数,则可以为 URL 不需要的参数提供 null。通常,只有@PathVariable
和@RequestParam
参数与构造 URL 有关。
还有其他使用MvcUriComponentsBuilder
的方法。例如,您可以使用类似于代理的测试技术来避免按名称引用控制器方法,如以下示例所示(该示例假定MvcUriComponentsBuilder.on
静态导入):
Note
当控制器方法签名可用于fromMethodCall
链接创建时,它们的设计受到限制。除了需要适当的参数签名外,返回类型还存在技术限制(即,为链接生成器调用生成运行时代理),因此返回类型不得为final
。特别是,视图名称的通用String
返回类型在这里不起作用。您应该改用ModelAndView
或什至是普通Object
(返回值为String
)。
较早的示例在MvcUriComponentsBuilder
中使用静态方法。在内部,他们依靠ServletUriComponentsBuilder
从当前请求的方案,主机,端口,上下文路径和 servlet 路径准备基本 URL。在大多数情况下,此方法效果很好。但是,有时可能不足。例如,您可能不在请求的上下文之内(例如,准备链接的批处理过程),或者您可能需要插入路径前缀(例如,从请求路径中删除并需要重新设置的语言环境前缀)。插入链接)。
在这种情况下,您可以使用接受fromXxx
的静态fromXxx
重载方法来使用基本 URL。或者,您可以使用基本 URL 创建MvcUriComponentsBuilder
的实例,然后使用基于实例的withXxx
方法。例如,以下 Lists 使用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();
Note
从 5.1 开始,MvcUriComponentsBuilder
会忽略Forwarded
和X-Forwarded-*
Headers 中的信息,这些 Headers 指定了 Client 端起源的地址。考虑使用ForwardedHeaderFilter提取和使用或丢弃此类 Headers。
1.4.6. 视图中的链接
在 Thymeleaf,FreeMarker 或 JSP 之类的视图中,可以通过引用每个请求 Map 的隐式或显式分配的名称来构建到带注解 的控制器的链接。
考虑以下示例:
@RequestMapping("/people/{id}/addresses") public class PersonAddressController { @RequestMapping("/{country}") public HttpEntity 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.5. 异步请求
Spring MVC 与 Servlet 3.0 异步请求processing进行了广泛的集成:
-
控制器方法中的DeferredResult和Callable返回值,并为单个异步返回值提供基本支持。
-
控制器可以使用反应式 Client 端,并返回reactive types进行响应处理。
1.5.1. DeferredResult
一旦异步请求处理功能在 Servlet 容器中为enabled,控制器方法就可以使用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(data);
控制器可以从另一个线程异步生成返回值,例如响应外部事件(JMS 消息),计划任务或其他事件。
1.5.2. Callable
控制器可以使用java.util.concurrent.Callable
包装任何受支持的返回值,如以下示例所示:
@PostMapping public Callable<String> processUpload(final MultipartFile file) { return new Callable<String>() { public String call() throws Exception { // ... return "someView"; } }; }
然后可以通过configured TaskExecutor
运行给定的任务来获取返回值。
1.5.3. Processing
这是 Servlet 异步请求处理的非常简洁的概述:
-
可以通过调用
request.startAsync()
将ServletRequest
置于异步模式。这样做的主要效果是 Servlet(以及所有过滤器)可以退出,但是响应保持打开状态,以便以后完成处理。 -
对
request.startAsync()
的调用返回AsyncContext
,您可以将其用于对异步处理的进一步控制。例如,它提供了dispatch
方法,与 Servlet API 的转发类似,不同之处在于,它允许应用程序恢复 Servlet 容器线程上的请求处理。 -
ServletRequest
提供对当前DispatcherType
的访问,您可以使用它们区分处理初始请求,异步调度,转发和其他调度程序类型。
DeferredResult
处理如下:
-
控制器返回一个
DeferredResult
并将其保存在一些可以访问它的内存队列或列表中。 -
Spring MVC 调用
request.startAsync()
。 -
同时,
DispatcherServlet
和所有已配置的过滤器退出请求处理线程,但响应保持打开状态。 -
应用程序从某个线程设置
DeferredResult
,Spring MVC 将请求分派回 Servlet 容器。 -
再次调用
DispatcherServlet
,并使用异步产生的返回值恢复处理。
Callable
处理如下:
-
控制器返回
Callable
。 -
Spring MVC 调用
request.startAsync()
并将Callable
提交给TaskExecutor
以便在单独的线程中进行处理。 -
同时,
DispatcherServlet
和所有过滤器退出 Servlet 容器线程,但响应保持打开状态。 -
最终
Callable
产生一个结果,Spring MVC 将请求分派回 Servlet 容器以完成处理。 -
再次调用
DispatcherServlet
,并使用Callable
异步生成的返回值恢复处理。
有关进一步的背景知识,您还可以阅读博客文章,它介绍了 Spring MVC 3.2 中的异步请求处理支持。
Exception Handling
使用DeferredResult
时,可以选择是呼叫setResult
还是setErrorResult
,但有 exception。在这两种情况下,Spring MVC 都将请求分派回 Servlet 容器以完成处理。然后将其视为控制器方法返回了给定值,或者好像它产生了给定的异常。然后,该异常将通过常规的异常处理机制(例如,调用@ExceptionHandler
方法)进行处理。
当您使用Callable
时,会发生类似的处理逻辑,主要区别是从Callable
返回结果或引发异常。
Interception
HandlerInterceptor
实例的类型可以为AsyncHandlerInterceptor
,以在启动异步处理的初始请求(而不是postHandle
和afterCompletion
)上接收afterConcurrentHandlingStarted
回调。
HandlerInterceptor
实现也可以注册CallableProcessingInterceptor
或DeferredResultProcessingInterceptor
,以与异步请求的生命周期更深入地集成(例如,处理超时事件)。有关更多详细信息,请参见AsyncHandlerInterceptor。
DeferredResult
提供onTimeout(Runnable)
和onCompletion(Runnable)
回调。有关更多详细信息,请参见DeferredResult 的 Javadoc。 Callable
可以替代WebAsyncTask
,后者提供了超时和完成回调的其他方法。
1.5.4. HTTP 流
您可以将DeferredResult
和Callable
用作单个异步返回值。如果要产生多个异步值并将那些值写入响应,该怎么办?本节介绍如何执行此操作。
Objects
您可以使用ResponseBodyEmitter
返回值生成对象流,其中每个对象都使用HttpMessageConverter序列化并写入响应,如以下示例所示:
@GetMapping("/events") public ResponseBodyEmitter handle() { ResponseBodyEmitter emitter = new ResponseBodyEmitter(); // Save the emitter somewhere.. return emitter; } // In some other thread emitter.send("Hello once"); // and again later on emitter.send("Hello again"); // and done at some point emitter.complete();
您还可以将ResponseBodyEmitter
用作ResponseEntity
的正文,以自定义响应的状态和标题。
当emitter
抛出IOException
时(例如,如果远程 Client 端离开了),应用程序不负责清理连接,并且不应调用emitter.complete
或emitter.completeWithError
。取而代之的是,Servlet 容器自动启动AsyncListener
错误通知,Spring MVC 在其中发出completeWithError
调用。该调用依次对应用程序执行最后的ASYNC
分派,在此期间 Spring MVC 调用配置的异常解析器并完成请求。
SSE
SseEmitter
(ResponseBodyEmitter
的子类)提供对Server-Sent Events的支持,其中根据 W3C SSE 规范格式化从服务器发送的事件。要从控制器生成 SSE 流,请返回SseEmitter
,如以下示例所示:
@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter handle() { SseEmitter emitter = new SseEmitter(); // Save the emitter somewhere.. return emitter; } // In some other thread emitter.send("Hello once"); // and again later on emitter.send("Hello again"); // and done at some point emitter.complete();
尽管 SSE 是流式传输到浏览器的主要选项,但请注意 Internet Explorer 不支持服务器发送事件。考虑将 Spring 的WebSocket messaging和SockJS fallback传输(包括 SSE)一起使用,这些传输针对广泛的浏览器。
Raw Data
有时,绕过消息转换并直接流式传输到响应OutputStream
很有用(例如,用于文件下载)。您可以使用StreamingResponseBody
返回值类型来执行此操作,如以下示例所示:
@GetMapping("/download") public StreamingResponseBody handle() { return new StreamingResponseBody() { @Override public void writeTo(OutputStream outputStream) throws IOException { // write... } }; }
您可以将StreamingResponseBody
用作ResponseEntity
中的正文,以自定义响应的状态和标题。
1.5.5. 反应类型
Spring MVC 支持在控制器中使用反应式 Client 端库(另请参阅 WebFlux 部分中的Reactive Libraries)。这包括spring-webflux
中的WebClient
以及其他内容,例如 Spring Data 反应数据存储库。在这种情况下,能够从控制器方法返回反应类型是很方便的。
Reactive 返回值的处理方式如下:
-
与使用
DeferredResult
相似,单值承诺也适用于此。示例包括Mono
(Reactor)或Single
(RxJava)。 -
类似于使用
ResponseBodyEmitter
或SseEmitter
,适用于具有流媒体类型(例如application/stream+json
或text/event-stream
)的多值流。示例包括Flux
(Reactor)或Observable
(RxJava)。应用程序还可以返回Flux<ServerSentEvent>
或Observable<ServerSentEvent>
。 -
类似于使用
DeferredResult<List<?>>
,可以适应具有任何其他媒体类型(例如application/json
)的多值流。
Tip
Spring MVC 通过spring-core
的ReactiveAdapterRegistry支持 Reactor 和 RxJava,这使其可以适应多个反应式库。
为了流式传输到响应,支持了反作用背压,但响应的写仍处于阻塞状态,并通过configured TaskExecutor
在单独的线程上执行,以避免阻塞上游源(例如WebClient
返回的Flux
)。默认情况下,SimpleAsyncTaskExecutor
用于阻止写入,但是在负载下不适合。如果您打算使用 Reactive 类型进行流式传输,则应使用MVC configuration来配置任务 Actuator。
1.5.6. Disconnects
当远程 Client 端离开时,Servlet API 不提供任何通知。因此,在通过SseEmitter或 <<mvc-ann-async-reactive-types,reactive types>流式传输到响应时,定期发送数据很重要,因为如果 Client 端断开连接,写入将失败。发送可以采取空(仅 Comment)SSE 事件或另一端必须将其解释为心跳和忽略的任何其他数据的形式。
或者,考虑使用具有内置心跳机制的 Web 消息传递解决方案(例如通过 WebSocket 进行 STOMP或带有SockJS的 WebSocket)。
1.5.7. Configuration
必须在 Servlet 容器级别启用异步请求处理功能。 MVC 配置还为异步请求提供了多个选项。
Servlet Container
过滤器和 Servlet 声明具有asyncSupported
标志,需要将其设置为true
才能启用异步请求处理。另外,应声明过滤器 Map 以处理ASYNC
javax.servlet.DispatchType
。
在 Java 配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer
初始化 Servlet 容器时,这是自动完成的。
在web.xml
配置中,可以将<async-supported>true</async-supported>
添加到DispatcherServlet
和Filter
声明中,并添加<dispatcher>ASYNC</dispatcher>
来过滤 Map。
Spring MVC
MVC 配置公开了以下与异步请求处理相关的选项:
-
Java 配置:在
WebMvcConfigurer
上使用configureAsyncSupport
回调。 -
XML 名称空间:使用
<mvc:annotation-driven>
下的<async-support>
元素。
您可以配置以下内容:
-
异步请求的默认超时值(如果未设置)取决于底层的 Servlet 容器(例如,在 Tomcat 上为 10 秒)。
-
AsyncTaskExecutor
用于在与Reactive Types流式传输时阻止写操作,并用于执行从控制器方法返回的Callable
实例。如果您使用反应式类型进行流式处理或具有返回Callable
的控制器方法,我们强烈建议配置此属性,因为默认情况下,它是SimpleAsyncTaskExecutor
。 -
DeferredResultProcessingInterceptor
实施和CallableProcessingInterceptor
实施。
请注意,您还可以在DeferredResult
,ResponseBodyEmitter
和SseEmitter
上设置默认超时值。对于Callable
,可以使用WebAsyncTask
提供超时值。
1.6. CORS
Spring MVC 使您可以处理 CORS(跨域资源共享)。本节介绍如何执行此操作。
1.6.1. Introduction
出于安全原因,浏览器禁止 AJAX 调用当前来源以外的资源。例如,您可以将您的银行帐户放在一个标签中,将 evil.com 放在另一个标签中。来自 evil.com 的脚本不能使用您的凭据向您的银行 API 发出 AJAX 请求,例如从您的帐户中提取资金!
跨域资源共享(CORS)是由most browsers实现的W3C specification,它使您可以指定授权哪种类型的跨域请求,而不是使用基于 IFRAME 或 JSONP 的安全性较低且功能较弱的变通办法。
1.6.2. Processing
CORS 规范区分飞行前,简单和实际要求。要了解 CORS 的工作原理
Spring MVC HandlerMapping
实现为 CORS 提供内置支持。成功将请求 映射到处理程序后,HandlerMapping
个实现检查给定请求和处理程序的 CORS 配置并采取进一步的措施。飞行前请求直接处理,而简单和实际的 CORS 请求被拦截,验证并设置了必需的 CORS 响应 Headers。
为了启用跨域请求(即Origin
Headers 存在并且与请求的主机不同),您需要具有一些显式声明的 CORS 配置。如果找不到匹配的 CORS 配置,则飞行前请求将被拒绝。没有将 CORSHeaders 添加到简单和实际 CORS 请求的响应中,因此,浏览器拒绝了它们。
每个HandlerMapping
可以分别是configured,并具有基于 URL 模式的CorsConfiguration映射
。在大多数情况下,应用程序使用 MVC Java 配置或 XML 名称空间声明此映射,这导致将单个全局映射传递给所有HandlerMappping
实例。
您可以将HandlerMapping
级别的全局 CORS 配置与更细粒度的处理程序级别的 CORS 配置结合使用。例如,带注解 的控制器可以使用类或方法级别的@CrossOrigin
Comments(其他处理程序可以实现CorsConfigurationSource
)。
整体配置和局部配置的组合规则通常是相加的,例如,所有全局和所有本地来源。对于仅接受单个值的那些属性(例如allowCredentials
和maxAge
),局部变量将覆盖全局值。
Tip
要从源中了解更多信息或进行高级自定义,请查看后面的代码:
-
CorsConfiguration
-
CorsProcessor
,DefaultCorsProcessor
-
AbstractHandlerMapping
1.6.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
允许:
-
All origins.
-
All headers.
-
控制器方法 Map 到的所有 HTTP 方法。
默认情况下未启用allowedCredentials
,因为它构建了一个信任级别,该级别公开了敏感的用户特定信息(例如 cookie 和 CSRF 令牌),仅应在适当的地方使用。
maxAge
设置为 30 分钟。
@CrossOrigin
在类级别也受支持,并且被所有方法继承,如以下示例所示:
@CrossOrigin(origins = "http://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("http://domain2.com")
@GetMapping("/{id}")
public Account retrieve(@PathVariable Long id) {
// ...
}
@DeleteMapping("/{id}")
public void remove(@PathVariable Long id) {
// ...
}
}
1.6.4. 全局配置
除了细粒度的控制器方法级别配置之外,您可能还想定义一些全局 CORS 配置。您可以在任何HandlerMapping
上分别设置基于 URL 的CorsConfiguration
映射。但是,大多数应用程序使用 MVC Java 配置或 MVC XNM 命名空间来执行此操作。
默认情况下,全局配置启用以下功能:
-
All origins.
-
All headers.
-
GET
,HEAD
和POST
方法。
默认情况下未启用allowedCredentials
,因为它构建了一个信任级别,该级别公开了敏感的用户特定信息(例如 cookie 和 CSRF 令牌),仅应在适当的地方使用。
maxAge
设置为 30 分钟。
Java Configuration
要在 MVC Java 配置中启用 CORS,可以使用CorsRegistry
回调,如以下示例所示:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/api/**") .allowedOrigins("http://domain2.com") .allowedMethods("PUT", "DELETE") .allowedHeaders("header1", "header2", "header3") .exposedHeaders("header1", "header2") .allowCredentials(true).maxAge(3600); // Add more mappings... } }
XML Configuration
要在 XML 名称空间中启用 CORS,可以使用<mvc:cors>
元素,如以下示例所示:
<mvc:cors> <mvc:mapping path="/api/**" allowed-origins="http://domain1.com, http://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="http://domain1.com" /> </mvc:cors>
1.6.5. CORS 过滤器
您可以通过内置的CorsFilter来应用 CORS 支持。
Note
如果您尝试将CorsFilter
与 Spring Security 一起使用,请记住 Spring Security 的 CORS 具有built-in support。
要配置过滤器,请将CorsConfigurationSource
传递给其构造函数,如以下示例所示:
CorsConfiguration config = new CorsConfiguration(); // Possibly... // config.applyPermitDefaultValues() config.setAllowCredentials(true); config.addAllowedOrigin("http://domain1.com"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); CorsFilter filter = new CorsFilter(source);
1.7. 网络安全
Spring Security项目为保护 Web 应用程序免受恶意攻击提供支持。请参阅 Spring Security 参考文档,包括:
1.8. HTTP 缓存
HTTP 缓存可以显着提高 Web 应用程序的性能。 HTTP 缓存围绕Cache-Control
响应 Headers 以及随后的条件请求 Headers(例如Last-Modified
和ETag
)展开。 Cache-Control
为私有(例如浏览器)和公共(例如代理)缓存提供有关如何缓存和重用响应的建议。 ETag
Headers 用于发出条件请求,如果内容未更改,则可能导致没有主体的 304(NOT_MODIFIED)。 ETag
可以看作是Last-Modified
Headers 的更复杂的后继者。
本节描述了 Spring Web MVC 中与 HTTP 缓存相关的选项。
1.8.1. CacheControl
CacheControl支持配置与Cache-Control
Headers 相关的设置,并且在许多地方都作为参数接受:
RFC 7234描述了Cache-Control
响应 Headers 的所有可能的指令,而CacheControl
类型采用了面向用例的方法,该方法着重于常见方案:
// Cache for an hour - "Cache-Control: max-age=3600" CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS); // Prevent caching - "Cache-Control: no-store" CacheControl ccNoStore = CacheControl.noStore(); // Cache for ten days in public and private caches, // public caches should not transform the response // "Cache-Control: max-age=864000, public, no-transform" CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
WebContentGenerator
还接受一个更简单的cachePeriod
属性(以秒为单位定义),该属性的工作方式如下:
-
-1
的值不会生成Cache-Control
响应头。 -
0
值可防止使用'Cache-Control: no-store'
指令进行缓存。 -
n > 0
值使用'Cache-Control: max-age=n'
指令将给定响应缓存n
秒。
1.8.2. Controllers
控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为需要先计算资源的lastModified
或ETag
值,然后才能将其与条件请求 Headers 进行比较。控制器可以将ETag
Headers 和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); }
如果与条件请求 Headers 的比较表明内容未更改,则前面的示例发送带有空主体的 304(NOT_MODIFIED)响应。否则,ETag
和Cache-Control
Headers 将添加到响应中。
您还可以在控制器中针对条件请求 Headers 进行检查,如以下示例所示:
@RequestMapping public String myHandleMethod(WebRequest webRequest, Model model) { long eTag = ... (1) if (request.checkNotModified(eTag)) { return null; (2) } model.addAttribute(...); (3) return "myViewName"; }
- (1) 特定于应用程序的计算。
- (2) 响应已设置为 304(NOT_MODIFIED)-没有进一步处理。
- (3) continue 进行请求处理。
可以使用三种变体来检查针对eTag
值和lastModified
值或两者的条件请求。对于有条件的GET
和HEAD
请求,可以将响应设置为 304(NOT_MODIFIED)。对于条件POST
,PUT
和DELETE
,您可以改为将响应设置为 409(PRECONDITION_FAILED),以防止并发修改。
1.8.3. 静态资源
您应该为静态资源提供Cache-Control
和条件响应 Headers,以实现最佳性能。
1.8.4. ETag 过滤器
您可以使用ShallowEtagHeaderFilter
添加根据响应内容计算的“浅” eTag
值,从而节省带宽,但不节省 CPU 时间。
1.9. 查看技术
Spring MVC 中视图技术的使用是可插入的,无论您决定使用 Thymeleaf,Groovy 标记模板,JSP 还是其他技术,主要取决于配置更改。本章介绍与 Spring MVC 集成的视图技术。我们假设您已经熟悉View Resolution。
1.9.1. Thymeleaf
Thymeleaf 是一种现代的服务器端 Java 模板引擎,它强调可以通过双击在浏览器中预览的自然 HTML 模板,这对于独立处理 UI 模板(例如,由设计人员)而无需使用非常有用。正在运行的服务器。如果要替换 JSP,Thymeleaf 提供了最广泛的功能集之一,以使这种过渡更加容易。 Thymeleaf 是积极开发和维护的。
Thymeleaf 与 Spring MVC 的集成由 Thymeleaf 项目 Management。该配置涉及一些 Bean 声明,例如ServletContextTemplateResolver
,SpringTemplateEngine
和ThymeleafViewResolver
。
1.9.2. FreeMarker
Apache FreeMarker是一个模板引擎,用于生成从 HTML 到电子邮件等的任何类型的文本输出。 Spring 框架具有内置的集成,可以将 Spring MVC 与 FreeMarker 模板一起使用。
View Configuration
以下示例显示了如何将 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 Configuration
您可以通过在FreeMarkerConfigurer
bean 上设置适当的 bean 属性,将 FreeMarker 的“设置”和“ SharedVariables”直接传递给 FreeMarker Configuration
对象(由 SpringManagement)。 freemarkerSettings
属性需要一个java.util.Properties
对象,而freemarkerVariables
属性需要一个java.util.Map
。以下示例显示了如何执行此操作:
<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"/>
Form Handling
Spring 提供了一个供 JSP 使用的标签库,其中包含<spring:bind/>
元素。该元素主要允许表单显示来自表单支持对象的值,并显示来自 Web 或业务层中Validator
的验证失败的结果。 Spring 还支持 FreeMarker 中的相同功能,并带有用于生成表单 Importing 元素本身的附加便利宏。
绑定宏
两种语言都在spring-webmvc.jar
文件中维护了一组标准宏,因此它们始终可用于经过适当配置的应用程序。
Spring 库中定义的某些宏被视为内部(私有)宏,但是在宏定义中不存在这种范围,使所有宏对调用代码和用户模板可见。以下各节仅关注您需要从模板内直接调用的宏。如果您希望直接查看宏代码,则该文件名为spring.ftl
,位于org.springframework.web.servlet.view.freemarker
包中。
Simple Binding
在充当 Spring MVC 控制器的表单视图的 HTML 表单(vm 或 ftl 模板)中,您可以使用类似于下一个示例的代码绑定到字段值,并以类似于 JSP 的方式显示每个 Importing 字段的错误消息。当量。以下示例显示了先前配置的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 "myModelObject.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'参数,该参数由命令对象的名称(除非您在FormController
属性中进行了更改,否则为'command'),后跟句点和命令对象上的字段名称希望绑定。您还可以使用嵌套字段,例如command.address.street
。 bind
宏假定web.xml
中的 ServletContext 参数defaultHtmlEscape
指定的默认 HTML 转义行为。
名为<@spring.bindEscaped>
的宏的可选形式带有第二个参数,并明确指定在状态错误消息或值中应使用 HTML 转义。您可以根据需要将其设置为true
或false
。附加的表单处理宏可简化 HTML 转义的使用,并且应尽可能使用这些宏。
输入宏
两种语言的附加便利宏均简化了绑定和表单生成(包括验证错误显示)。从来没有必要使用这些宏来生成表单 Importing 字段,并且您可以将它们与简单的 HTML 混合或匹配,或者直接调用我们前面强调的 spring 绑定宏。
下表可用的宏显示了每个 FTL 定义和参数列表:
macro | FTL definition |
---|---|
message (根据 code 参数从资源包中输出字符串) |
<@spring.message code/> |
messageText (根据 code 参数从资源包中输出一个字符串,回退到默认参数的值) |
<@spring.messageText code, text/> |
url (使用应用程序的上下文根作为相对 URL 的前缀) |
<@spring.url relativeUrl/> |
formInput (用于收集用户 Importing 的标准 Importing 字段) |
<@spring.formInput path, attributes, fieldType/> |
formHiddenInput (用于提交非用户 Importing 的隐藏 Importing 字段) |
<@spring.formHiddenInput path, attributes/> |
formPasswordInput (用于收集密码的标准 Importing 字段.请注意,此类型的字段中不会填充任何值.) |
<@spring.formPasswordInput path, attributes/> |
formTextarea (大文本字段,用于收集长而自由格式的文本 Importing) |
<@spring.formTextarea path, attributes/> |
formSingleSelect (选项的下拉框,用于选择一个必需的值) |
<@spring.formSingleSelect path, options, attributes/> |
formMultiSelect (选项列表框,允许用户选择 0 个或多个值) |
<@spring.formMultiSelect path, options, attributes/> |
formRadioButtons (一组单选按钮,可从可用选项中进行单个选择) |
<@spring.formRadioButtons path, options separator, attributes/> |
formCheckboxes (一组允许选择 0 个或多个值的复选框) |
<@spring.formCheckboxes path, options, separator, attributes/> |
formCheckbox (单个复选框) |
<@spring.formCheckbox path, attributes/> |
showErrors (简化显示绑定字段的验证错误) |
<@spring.showErrors separator, classOrStyle/> |
- 在 FTL(FreeMarker)中,实际上并不需要
formHiddenInput
和formPasswordInput
,因为您可以使用普通的formInput
宏,将hidden
或password
指定为fieldType
参数的值。
以上任何宏的参数都具有一致的含义:
-
path
:要绑定的字段的名称(即“ command.name”) -
options
:可以在 Importing 字段中选择的所有可用值中的Map
。Map 的键表示从表单回发并绑定到命令对象的值。针对键存储的 Map 对象是在表单上显示给用户的标签,并且可能与表单回发的相应值不同。通常,这种 Map 由控制器作为参考数据提供。您可以使用任何Map
实现,具体取决于所需的行为。对于严格排序的 Map,可以将SortedMap
(例如TreeMap
)与合适的Comparator
一起使用,对于要按插入 Sequences 返回值的任意 Map,请使用LinkedHashMap
或commons-collections
的LinkedMap
。 -
separator
:当多个选项可用作离散元素(单选按钮或复选框)时,用于分隔列表中的每个字符的字符序列(例如<br>
)。 -
attributes
:HTML 标记本身中将包含一个附加的任意标记字符串或文本。该字符串实际上是由宏回显的。例如,在textarea
字段中,您可以提供属性(例如'rows =“ 5” cols =“ 60”'),也可以传递样式信息,例如'style =“ border:1px solid silver”'。 -
classOrStyle
:对于showErrors
宏,包装每个错误的span
元素使用的 CSS 类的名称。如果未提供任何信息(或该值为空),则错误将被包装在<b></b>
标记中。
以下各节概述了宏的示例(某些在 FTL 中,有些在 VTL 中)。如果两种语言之间存在用法差异,请在注解 中进行说明。
Input Fields
formInput
宏带有path
参数(command.name
)和一个附加的attributes
参数(在接下来的示例中为空)。该宏与所有其他表单生成宏一起,对 path 参数执行隐式 Spring 绑定。绑定将保持有效,直到发生新的绑定为止,因此showErrors
宏无需再次传递 path 参数。它在最后创建绑定的字段上进行操作。
showErrors
宏带有分隔符参数(用于分隔给定字段上的多个错误的字符),并且还接受第二个参数-这次是类名或样式属性。注意,FreeMarker 可以为 attributes 参数指定默认值。下面的示例演示如何使用formInput
和showWErrors
宏:
<@spring.formInput "command.name"/> <@spring.showErrors "<br>"/>
下一个示例显示表单片段的输出,生成名称字段,并在提交表单后在该字段中没有值的情况下显示验证错误。验证通过 Spring 的 Validation 框架进行。
生成的 HTML 类似于以下示例:
Name: <input type="text" name="name" value=""> <br> <b>required</b> <br> <br>
formTextarea
宏的工作方式与formInput
宏相同,并且接受相同的参数列表。通常,第二个参数(属性)用于传递样式信息或textarea
的rows
和cols
属性。
Selection Fields
您可以使用四个选择字段宏在 HTML 表单中生成常见的 UI 值选择 Importing:
-
formSingleSelect
-
formMultiSelect
-
formRadioButtons
-
formCheckboxes
四个宏中的每个宏都接受一个Map
选项,其中包含表单字段的值和与该值相对应的标签。值和标签可以相同。
下一个示例是 FTL 中的单选按钮。表单支持对象为此字段指定默认值“伦敦”,因此无需验证。呈现表单时,将在模型中以“ cityMap”为名称提供可供选择的整个城市列表作为参考数据。以下 Lists 显示了示例:
... Town: <@spring.formRadioButtons "command.address.town", cityMap, ""/><br><br>
上面的 Lists 呈现了一行单选按钮,其中一个用于cityMap
中的每个值,并使用一个分隔符""
。没有提供其他属性(缺少该宏的最后一个参数)。 cityMap
对 Map 中的每个键值对使用相同的String
。Map 键是表单实际作为 POSTed 请求参数提交的键。Map 值是用户看到的标签。在前面的示例中,给定三个知名城市的列表以及表单支持对象中的默认值,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>
如果您的应用程序希望通过内部代码处理城市(例如),则可以使用适当的键创建代码 Map,如以下示例所示:
protected Map<String, 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, String> 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.9.3. Groovy 标记
Groovy 标记模板引擎主要用于生成类似 XML 的标记(XML,XHTML,HTML5 等),但是您可以使用它来生成任何基于文本的内容。 Spring 框架具有内置的集成,可以将 Spring MVC 与 Groovy 标记一起使用。
Note
Groovy 标记模板引擎需要 Groovy 2.3.1.
Configuration
以下示例显示了如何配置 Groovy 标记模板引擎:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.groovy(); } // Configure the Groovy Markup Template Engine... @Bean public GroovyMarkupConfigurer groovyMarkupConfigurer() { GroovyMarkupConfigurer configurer = new GroovyMarkupConfigurer(); configurer.setResourceLoaderPath("/WEB-INF/"); return configurer; } }
以下示例显示了如何在 XML 中进行配置:
<mvc:annotation-driven/> <mvc:view-resolvers> <mvc:groovy/> </mvc:view-resolvers> <!-- Configure the Groovy Markup Template Engine... --> <mvc:groovy-configurer resource-loader-path="/WEB-INF/"/>
Example
与传统的模板引擎不同,Groovy 标记依赖于使用构建器语法的 DSL。以下示例显示了 HTML 页面的示例模板:
yieldUnescaped '<!DOCTYPE html>' html(lang:'en') { head { meta('http-equiv':'"Content-Type" content="text/html; charset=utf-8"') title('My page') } body { p('This is an example of HTML contents') } }
1.9.4. 脚本视图
Spring 框架具有内置的集成,可以将 Spring MVC 与可以在JSR-223 Java 脚本引擎之上运行的任何模板库一起使用。我们已经在不同的脚本引擎上测试了以下模板库:
Scripting Library | Scripting Engine |
---|---|
Handlebars | Nashorn |
Mustache | Nashorn |
React | Nashorn |
EJS | Nashorn |
ERB | JRuby |
String templates | Jython |
Kotlin 脚本模板 | Kotlin |
Tip
集成任何其他脚本引擎的基本规则是,它必须实现ScriptEngine
和Invocable
接口。
要求
您需要在 Classpath 上具有脚本引擎,其细节因脚本引擎而异:
-
Java 8 随附了Nashorn JavaScript 引擎。强烈建议使用可用的最新更新版本。
-
应该添加JRuby作为 Ruby 支持的依赖项。
-
应该添加Jython作为对 Python 支持的依赖。
-
为了支持 Kotlin 脚本,应添加
org.jetbrains.kotlin:kotlin-script-util
依赖项和包含org.jetbrains.kotlin.script.jsr223.KotlinJsr223JvmLocalScriptEngineFactory
行的META-INF/services/javax.script.ScriptEngineFactory
文件。有关更多详细信息,请参见this example。
您需要具有脚本模板库。一种针对 Javascript 的方法是通过WebJars。
Script 模板
您可以声明一个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 配置,该控制器看起来没有什么不同,如以下示例所示:
public class SampleController { @GetMapping("/sample") public String test(Model model) { model.addObject("title", "Sample title"); model.addObject("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
:用于访问应用程序上下文,语言环境,模板加载器和 URL 的RenderingContext(自 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; } }
Note
当您将非线程安全脚本引擎与不是为并发设计的模板库一起使用时,需要将sharedEngine
属性设置为false
,例如 Nashorn 上运行的 Handlebars 或 React。在这种情况下,由于this bug,因此需要 Java 8u60 或更高版本。
polyfill.js
仅定义 Handlebars 正常运行所需的window
对象,如下所示:
var window = {};
这个基本的render.js
实现在使用模板之前先对其进行编译。生产就绪的实现还应该存储任何重用的缓存模板或预编译的模板。您可以在脚本方面进行操作(并处理所需的任何自定义,例如 Management 模板引擎配置)。以下示例显示了如何执行此操作:
function render(template, model) {
var compiledTemplate = Handlebars.compile(template);
return compiledTemplate(model);
}
查看 Spring Framework 单元测试Java和resources,以获取更多配置示例。
1.9.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 uses (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
InternalResourceBundleViewResolver
也可用于 JSP。作为最佳实践,我们强烈建议您将 JSP 文件放在'WEB-INF'
目录下的目录中,以便 Client 端无法直接访问。
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="viewClass" value="org.springframework.web.servlet.view.JstlView"/>
<property name="prefix" value="/WEB-INF/jsp/"/>
<property name="suffix" value=".jsp"/>
</bean>
JSP 与 JSTL
使用 Java 标准标记库时,必须使用特殊的视图类JstlView
,因为 JSTL 需要一些准备工作,然后 I18N 功能才能正常工作。
Spring 的 JSP 标签库
Spring 提供了将请求参数与命令对象的数据绑定。为了促进结合这些数据绑定功能的 JSP 页面的开发,Spring 提供了一些使事情变得更加容易的标记。所有 Spring 标记都具有 HTML 转义功能,以启用或禁用字符转义。
spring.tld
标签库 Descriptors(TLD)包含在spring-webmvc.jar
中。
Spring 的表单标签库
从 2.0 版开始,Spring 使用 JSP 和 Spring Web MVC 时,提供了一组全面的数据绑定感知标记,用于处理表单元素。每个标签都支持与其对应的 HTML 标签对等物的属性集,从而使标签熟悉且使用直观。标记生成的 HTML 符合 HTML 4.01/XHTML 1.0.
与其他表单/Importing 标签库不同,Spring 的表单标签库与 Spring Web MVC 集成在一起,使标签可以访问命令对象和控制器处理的参考数据。正如我们在以下示例中所示,表单标签使 JSP 易于开发,读取和维护。
我们浏览一下表单标签,并查看有关如何使用每个标签的示例。我们包含了生成的 HTML 代码段,其中某些标记需要进一步的注解。
Configuration
表单标签库 Binding 在spring-webmvc.jar
中。库 Descriptors 称为spring-form.tld
。
要使用此库中的标记,请在 JSP 页面顶部添加以下指令:
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
其中form
是要用于此库中标签的标签名称前缀。
表单标签
此标记呈现 HTML'form'元素,并向内部标记公开绑定路径以进行绑定。它将命令对象放在PageContext
中,以便内部标签可以访问该命令对象。该库中的所有其他标签都是form
标签的嵌套标签。
假设我们有一个名为User
的域对象。它是具有firstName
和lastName
之类的属性的 JavaBean。我们可以将其用作表单控制器的表单支持对象,该表单控制器返回form.jsp
。以下示例显示form.jsp
的外观:
<form:form> <table> <tr> <td>First Name:</td> <td><form:input path="firstName"/></td> </tr> <tr> <td>Last Name:</td> <td><form:input path="lastName"/></td> </tr> <tr> <td colspan="2"> <input type="submit" value="Save Changes"/> </td> </tr> </table> </form:form>
页面控制器从放置在PageContext
中的命令对象中检索firstName
和lastName
值。continue 阅读以查看有关如何将内部标签与form
标签一起使用的更复杂的示例。
下面的 Lists 显示了生成的 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>
Importing 标签
默认情况下,此标记呈现具有绑定值和type='text'
的 HTML input
元素。有关此标记的示例,请参见表单标签。您还可以使用特定于 HTML5 的类型,例如email
,tel
,date
等。
复选框标签
此标签呈现type
设置为checkbox
的 HTML input
标签。
假设我们的User
具有首选项,例如通讯订阅和兴趣爱好列表。以下示例显示了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>
checkbox
标记有三种方法,应该可以满足您所有复选框的需求。
-
方法一:当绑定值为
java.lang.Boolean
时,如果绑定值为true
,则input(checkbox)
被标记为checked
。value
属性对应于setValue(Object)
value 属性的解析值。 -
方法二:当界限值是
array
或java.util.Collection
类型时,如果界限Collection
中存在已配置的setValue(Object)
值,则input(checkbox)
被标记为checked
。 -
方法三:对于任何其他绑定值类型,如果已配置的
setValue(Object)
等于绑定值,则将input(checkbox)
标记为checked
。
请注意,无论采用哪种方法,都会生成相同的 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 表单数据绑定生效。 checkbox
标签遵循现有的 Spring 约定,其中为每个复选框包括一个带有下划线(_
)前缀的隐藏参数。通过这样做,您可以有效地告诉 Spring“复选框在表单中可见,并且我希望与表单数据绑定的对象能够反映复选框的状态,无论如何。”
复选框标签
此标签呈现type
设置为checkbox
的多个 HTML input
标签。
本节以上一个checkbox
标签节的示例为基础。有时,您宁愿不必在 JSP 页面中列出所有可能的爱好。您宁愿在运行时提供可用选项的列表,然后将其传递给标记。这就是checkboxes
标记的目的。您可以传入items
属性中包含可用选项的Array
,List
或Map
。通常,bound 属性是一个集合,因此它可以容纳用户选择的多个值。以下示例显示了使用此标记的 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>
本示例假定interestList
是List
,可以用作模型属性,其中包含要从中选择的值的字符串。如果使用Map
,则将 Map 条目键用作值,并将 Map 条目的值用作要显示的标签。您也可以使用自定义对象,在其中可以使用itemValue
提供值的属性名称,并通过itemLabel
提供标签。
单选按钮标记
此标记呈现type
设置为radio
的 HTML input
元素。
典型的用法模式涉及绑定到相同属性但值不同的多个标记实例,如以下示例所示
<tr> <td>Sex:</td> <td> Male: <form:radiobutton path="sex" value="M"/> Female: <form:radiobutton path="sex" value="F"/> </td> </tr>
单选按钮标记
此标记呈现type
设置为radio
的多个 HTML input
元素。
与checkboxes tag一样,您可能希望将可用选项作为运行时变量传递。为此,您可以使用radiobuttons
标签。您传入Array
,List
或Map
,它们包含items
属性中的可用选项。如果使用Map
,则将 Map 条目键用作值,并将 Map 条目的值用作要显示的标签。您还可以使用一个自定义对象,在其中您可以使用itemValue
提供值的属性名称,并通过itemLabel
提供标签,如以下示例所示:
<tr> <td>Sex:</td> <td><form:radiobuttons path="sex" items="${sexOptions}"/></td> </tr>
密码标签
此标记呈现具有设置为password
且具有绑定值的 HTML input
标记。
<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”元素。它支持将数据绑定到所选选项以及使用嵌套的option
和options
标签。
假设User
拥有技能列表。相应的 HTML 可能如下所示:
<tr> <td>Skills:</td> <td><form:select path="skills" items="${skills}"/></td> </tr>
如果User's
技能是草药学,则“技能”行的 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 option
元素。它基于绑定值设置selected
。以下 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>
如果User's
房屋位于格兰芬多,则“房屋”行的 HTML 源代码如下:
<tr> <td>House:</td> <td> <select name="house"> <option value="Gryffindor" selected="selected">Gryffindor</option> (1) <option value="Hufflepuff">Hufflepuff</option> <option value="Ravenclaw">Ravenclaw</option> <option value="Slytherin">Slytherin</option> </select> </td> </tr>
- (1) 请注意添加了
selected
属性。
选项标签
此标记呈现 HTML option
元素的列表。它根据绑定值设置selected
属性。以下 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>
如果User
居住在英国,则“国家/locale”行的 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> (1) <option value="US">United States</option> </select> </td> </tr>
- (1) 请注意添加了
selected
属性。
如前面的示例所示,option
标记与options
标记的组合用法会生成相同的标准 HTML,但可以让您在 JSP 中显式指定一个仅用于显示(它所属的位置)的值,例如例如:“-请选择”。
items
属性通常由项对象的集合或数组填充。 itemValue
和itemLabel
引用这些项目对象的 bean 属性(如果已指定)。否则,项目对象本身将变成字符串。或者,您可以指定项目的Map
,在这种情况下,Map 键将解释为选项值,并且 Map 值对应于选项标签。如果也恰好指定了itemValue
或itemLabel
(或两者),则 item value 属性应用于 Map 键,item label 属性应用于 Map 值。
textarea 标签
此标记呈现 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 input
标签,其中type
设置为hidden
并具有绑定值。要提交未绑定的隐藏值,请使用type
设置为hidden
的 HTML input
标记。以下 HTML 显示了其典型输出:
<form:hidden path="house"/>
如果我们选择将house
值提交为隐藏值,则 HTML 如下所示:
<input name="house" type="hidden" value="Gryffindor"/>
错误代码
此标记在 HTML span
元素中呈现字段错误。它提供对在控制器中创建的错误或由与控制器关联的任何验证程序创建的错误的访问。
假设一旦提交表单,我们想显示firstName
和lastName
字段的所有错误消息。我们有一个名为UserValidator
的User
类实例的验证器,如以下示例所示:
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."); } }
form.jsp
可能如下:
<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
字段中提交具有空值的表单,则 HTML 将如下所示:
<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>
HTML 将如下所示:
<form method="POST"> <span name="*.errors" class="errorBox">Field is required. 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>
HTTP 方法转换
REST 的一个关键原则是使用“统一接口”。这意味着可以使用相同的四种 HTTP 方法(GET,PUT,POST 和 DELETE)来操纵所有资源(URL)。对于每种方法,HTTP 规范都定义了确切的语义。例如,GET 应该始终是安全的操作,这意味着没有副作用,而 PUT 或 DELETE 应该是幂等的,这意味着您可以一遍又一遍地重复这些操作,但是最终结果应该相同。虽然 HTTP 定义了这四种方法,但 HTML 仅支持两种:GET 和 POST。幸运的是,有两种可能的解决方法:您可以使用 JavaScript 进行 PUT 或 DELETE,或者可以使用“ real”方法作为附加参数(在 HTML 表单中建模为隐藏的 Importing 字段)进行 POST。 Spring 的HiddenHttpMethodFilter
使用了后一种技巧。该过滤器是一个普通的 Servlet 过滤器,因此,它可以与任何 Web 框架(不仅仅是 Spring MVC)结合使用。将此过滤器添加到 web.xml,然后将带有隐藏method
参数的 POST 转换为相应的 HTTP 方法请求。
为了支持 HTTP 方法转换,Spring MVC 表单标签已更新为支持设置 HTTP 方法。例如,以下代码片段来自“宠物诊所”samples:
<form:form method="delete"> <p class="submit"><input type="submit" value="Delete Pet"/></p> </form:form>
前面的示例执行 HTTP POST,并将“真实” DELETE 方法隐藏在请求参数后面。它由 web_xml 中定义的HiddenHttpMethodFilter
拾取,如以下示例所示:
<filter> <filter-name>httpMethodFilter</filter-name> <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class> </filter> <filter-mapping> <filter-name>httpMethodFilter</filter-name> <servlet-name>petclinic</servlet-name> </filter-mapping>
以下示例显示了相应的@Controller
方法:
@RequestMapping(method = RequestMethod.DELETE) public String deletePet(@PathVariable int ownerId, @PathVariable int petId) { this.clinic.deletePet(petId); return "redirect:/owners/" + ownerId; }
HTML5 Tags
Spring 表单标签库允许 Importing 动态属性,这意味着您可以 Importing 任何 HTML5 特定的属性。
input
标签形式支持 Importingtext
以外的 type 属性。这旨在允许呈现新的 HTML5 特定 Importing 类型,例如email
,date
,range
等。请注意,由于text
是默认类型,因此不需要 Importingtype='text'
。
1.9.6. Tiles
您可以像使用其他视图技术一样,将 Tiles 集成到使用 Spring 的 Web 应用程序中。本节将广泛地描述如何执行此操作。
Note
本节重点介绍org.springframework.web.servlet.view.tiles3
软件包中 Spring 对 Tiles 版本 3 的支持。
Configuration
为了能够使用 Tiles,您必须使用包含定义的文件对其进行配置(有关定义和其他 Tiles 概念的基本信息,请参见http://tiles.apache.org)。在 Spring,可以使用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
初始化时,将加载文件,并初始化定义工厂。完成之后,定义文件中包含的 Tiles 可以用作 Spring Web 应用程序中的视图。为了能够使用视图,与 Spring 所使用的任何其他视图技术一样,您必须具有ViewResolver
。您可以使用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.xml
。
Note
由于下划线用于指示语言环境,因此我们建议不要在 Tiles 定义的文件名中使用下划线。
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 Clinic 示例):
<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 PreparerFactory
实现。有关如何在 Tiles 定义文件中使用ViewPreparer
引用的详细信息,请参见 Tiles 文档。
您可以基于指定的准备器类,通过应用 Spring 的容器回调以及应用配置的 Spring BeanPostProcessor,来指定SimpleSpringPreparerFactory
自动连接ViewPreparer
实例。如果已激活 Spring 的上下文范围内的注解 配置,则会自动检测并应用ViewPreparer
类中的注解。请注意,这与默认的PreparerFactory
一样,期望 Tiles 定义文件中的准备程序类。
您可以指定SpringBeanPreparerFactory
来对指定的准备器名称(而不是类)进行操作,从而从 DispatcherServlet 的应用程序上下文中获取相应的 Spring bean。在这种情况下,完整的 bean 创建过程由 Spring 应用程序上下文控制,从而允许使用显式依赖项注入配置,作用域 bean 等。注意,您需要为每个准备器名称定义一个 Spring bean 定义(在 Tiles 定义中使用)。下面的示例显示如何在TilesConfigurer
bean 上定义一个SpringBeanPreparerFactory
属性集:
<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.9.7. RSS 和 Atom
AbstractAtomFeedView
和AbstractRssFeedView
都继承自AbstractFeedView
Base Class,并分别用于提供 Atom 和 RSS Feed 视图。它们基于 java.net 的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 SampleContentAtomView 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 请求,以防您需要访问区域设置。仅针对 Cookie 或其他 HTTPHeaders 的设置传入 HTTP 响应。方法返回后,该提要将自动写入响应对象。
1.9.8. PDF 和 Excel
Spring 提供了返回 HTML 以外的输出的方法,包括 PDF 和 Excel 电子表格。本节介绍如何使用这些功能。
文档视图简介
HTML 页面并非始终是用户查看模型输出的最佳方法,而 Spring 使从模型数据动态生成 PDF 文档或 Excel 电子表格变得简单。该文档是视图,并从服务器以正确的 Content Type 进行流传输,以(有希望)使 Client 端 PC 能够运行其电子表格或 PDF 查看器应用程序作为响应。
为了使用 Excel 视图,您需要将 Apache POI 库添加到 Classpath 中,要生成 PDF,需要添加(最好是)OpenPDF 库。
Note
如果可能,您应该使用基础文档生成库的最新版本。特别是,我们强烈建议您使用 OpenPDF(例如,OpenPDF 1.0.5)而不是过时的原始 iText 2.1.7,因为 OpenPDF 会得到积极维护并修复了不可信任 PDF 内容的重要漏洞。
PDF Views
单词列表的简单 PDF 视图可以扩展org.springframework.web.servlet.view.document.AbstractPdfView
并实现buildPdfDocument()
方法,如以下示例所示:
public class PdfWordList extends AbstractPdfView { protected void buildPdfDocument(Map<String, Object> model, Document doc, PdfWriter writer, HttpServletRequest request, HttpServletResponse response) throws Exception { List<String> words = (List<String>) model.get("wordList"); for (String word : words) { doc.add(new Paragraph(word)); } } }
控制器可以从外部视图定义(按名称引用)或作为处理程序方法的View
实例返回此类视图。
Excel Views
从 Spring Framework 4.2 开始,org.springframework.web.servlet.view.document.AbstractXlsView
作为 Excel 视图的 Base Class 提供。它基于 Apache POI,具有取代过时的AbstractExcelView
类的专用子类(AbstractXlsxView
和AbstractXlsxStreamingView
)。
编程模型类似于AbstractPdfView
,其中buildExcelDocument()
作为中心模板方法,控制器能够从外部定义(按名称)或从 handler 方法作为View
实例返回这种视图。
1.9.9. Jackson
Spring 提供了对 Jackson JSON 库的支持。
基于 Jackson 的 JSON 视图
MappingJackson2JsonView
使用 Jackson 库的ObjectMapper
将响应内容呈现为 JSON。默认情况下,模型 Map 的所有内容(特定于框架的类除外)均编码为 JSON。对于需要过滤 Map 内容的情况,可以使用modelKeys
属性指定一组特定的模型属性进行编码。您还可以使用extractValueFromSingleKeyModel
属性来直接提取和序列化单键模型中的值,而不是将其作为模型属性的 Map。
您可以根据需要使用 Jackson 提供的注解 来自定义 JSONMap。当需要进一步控制时,可以在需要为特定类型提供自定义 JSON 序列化器和反序列化器的情况下,通过ObjectMapper
属性注入自定义ObjectMapper
。
基于 Jackson 的 XML 视图
MappingJackson2XmlView
使用Jackson XML 扩展的 XmlMapper
将响应内容呈现为 XML。如果模型包含多个条目,则应使用modelKey
bean 属性显式设置要序列化的对象。如果模型包含单个条目,那么它将自动序列化。
您可以根据需要使用 JAXB 或 Jackson 提供的注解 自定义 XMLMap。当需要进一步控制时,可以通过ObjectMapper
属性注入自定义XmlMapper
,对于自定义 XML 的情况,您需要为特定类型提供序列化器和反序列化器。
1.9.10. XML 编组
MarshallingView
使用 XML Marshaller
(在org.springframework.oxm
包中定义)将响应内容呈现为 XML。您可以使用MarshallingView
实例的modelKey
bean 属性来显式设置要编组的对象。或者,视图遍历所有模型属性,并封送Marshaller
支持的第一个类型。有关org.springframework.oxm
软件包中功能的更多信息,请参见使用 O/XMap 器编组 XML。
1.9.11. XSLT 视图
XSLT 是 XML 的一种转换语言,在 Web 应用程序中作为一种视图技术而流行。如果您的应用程序自然地处理 XML,或者如果您的模型可以轻松转换为 XML,那么 XSLT 可以作为视图技术的不错选择。下一节说明如何将 XML 文档生成为模型数据,以及如何在 Spring Web MVC 应用程序中使用 XSLT 对其进行转换。
这个例子是一个普通的 Spring 应用程序,它在Controller
中创建一个单词列表,并将它们添加到模型图中。返回该 Map 以及 XSLT 视图的视图名称。有关 Spring Web MVC 的Controller
界面的详细信息,请参见Annotated Controllers。 XSLT 控制器将单词列表转换为准备转换的简单 XML 文档。
Beans
配置是简单的 Spring Web 应用程序的标准配置:MVC 配置必须定义XsltViewResolver
bean 和常规 MVCComments 配置。以下示例显示了如何执行此操作:
@EnableWebMvc @ComponentScan @Configuration public class WebConfig implements WebMvcConfigurer { @Bean public XsltViewResolver xsltViewResolver() { XsltViewResolver viewResolver = new XsltViewResolver(); viewResolver.setPrefix("/WEB-INF/xsl/"); viewResolver.setSuffix(".xslt"); return viewResolver; } }
Controller
我们还需要一个封装词生成逻辑的控制器。
控制器逻辑封装在@Controller
类中,其中 handler 方法的定义如下:
@Controller public class XsltController { @RequestMapping("/") public String home(Model model) throws Exception { Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); Element root = document.createElement("wordList"); List<String> words = Arrays.asList("Hello", "Spring", "Framework"); for (String word : words) { Element wordNode = document.createElement("word"); Text textNode = document.createTextNode(word); wordNode.appendChild(textNode); root.appendChild(wordNode); } model.addAttribute("wordList", root); return "home"; } }
到目前为止,我们仅创建了一个 DOM 文档并将其添加到 ModelMap 中。请注意,您还可以将 XML 文件作为Resource
加载并使用它代替自定义 DOM 文档。
有可用的软件包自动“对象化”对象图,但是在 Spring 中,您可以完全灵活地以任何选择的方式从模型中创建 DOM。这样可以防止 XML 转换在模型数据的结构中扮演过多的角色,这在使用工具来 ManagementDOMification 流程时是一种危险。
Transformation
最后,XsltViewResolver
解析“主” XSLT 模板文件,并将 DOM 文档合并到其中以生成我们的视图。如XsltViewResolver
配置所示,XSLT 模板位于WEB-INF/xsl
目录中的war
文件中,并以xslt
文件 extensions 结尾。
以下示例显示了 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.10. MVC 配置
MVC Java 配置和 MVC XML 名称空间提供适用于大多数应用程序的默认配置以及用于对其进行自定义的配置 API。
有关配置 API 中没有的更高级的自定义设置,请参见高级 Java 配置和高级 XML 配置。
您不需要了解由 MVC Java 配置和 MVC 名称空间创建的基础 bean。如果您想了解更多信息,请参阅特殊 bean 类和Web MVC 配置。
public interface WebMvcConfigurer {
/*配置路径匹配参数*/ 3 public void configurePathMatch(PathMatchConfigurer configurer) {} 4 /*配置Web Service或REST API设计中内容协商,即根据客户端的支持内容格式情况来封装响应消息体,如xml,json*/ 5 public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {} 6 /*配置路径匹配参数*/ 7 public void configureAsyncSupport(AsyncSupportConfigurer configurer) {} 8 /* 使得springmvc在接口层支持异步*/ 9 public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {} 10 /* 注册参数转换和格式化器*/ 11 public void addFormatters(FormatterRegistry registry) {} 12 /* 注册配置的拦截器*/ 13 public void addInterceptors(InterceptorRegistry registry) {} 14 /* 自定义静态资源映射*/ 15 public void addResourceHandlers(ResourceHandlerRegistry registry) {} 16 /* cors跨域访问*/ 17 public void addCorsMappings(CorsRegistry registry) {} 18 /* 配置页面直接访问,不走接口*/ 19 public void addViewControllers(ViewControllerRegistry registry) {} 20 /* 注册自定义的视图解析器*/ 21 public void configureViewResolvers(ViewResolverRegistry registry) {} 22 /* 注册自定义控制器(controller)方法参数类型*/ 23 public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {} 24 /* 注册自定义控制器(controller)方法返回类型*/ 25 public void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers) {} 26 /* 重载会覆盖掉spring mvc默认注册的多个HttpMessageConverter*/ 27 public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {} 28 /* 仅添加一个自定义的HttpMessageConverter,不覆盖默认注册的HttpMessageConverter*/ 29 public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {} 30 /* 注册异常处理*/ 31 public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {} 32 /* 多个异常处理,可以重写次方法指定处理顺序等*/ 33 public void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {}
@Nullable default Validator getValidator() { return null; }
@Nullable default MessageCodesResolver getMessageCodesResolver() { return null; } }
1.10.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
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven/>
</beans>
1.10.2. MVC Config API
在 Java 配置中,您可以实现WebMvcConfigurer
接口,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
在 XML 中,您可以检查<mvc:annotation-driven/>
的属性和子元素。您可以查看Spring MVC XML 模式或使用 IDE 的代码完成功能来发现可用的属性和子元素。
1.10.3. 类型转换
默认情况下,安装了Number
和Date
类型的格式化程序,包括对@NumberFormat
和@DateTimeFormat
注解的支持。如果 Classpath 中存在 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 http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://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>
Note
有关何时使用 FormatterRegistrar 实现的更多信息,请参见FormatterRegistrar SPI和FormattingConversionServiceFactoryBean
。
1.10.4. Validation
默认情况下,如果Bean Validation存在于 Classpath 中(例如,Hibernate Validator),则LocalValidatorFactoryBean
被注册为全局Validator,以便与控制器方法参数上的@Valid
和Validated
一起使用。
在 Java 配置中,您可以自定义全局Validator
实例,如以下示例所示:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public Validator getValidator(); { // ... } }
以下示例显示了如何在 XML 中实现相同的配置:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd">
<mvc:annotation-driven validator="globalValidator"/>
</beans>
请注意,您还可以在本地注册Validator
实现,如以下示例所示:
@Controller public class MyController { @InitBinder protected void initBinder(WebDataBinder binder) { binder.addValidators(new FooValidator()); } }
Tip
如果需要在某处注入LocalValidatorFactoryBean
,请创建一个 bean 并用@Primary
进行标记,以避免与 MVC 配置中声明的那个冲突。
1.10.5. Interceptors
在 Java 配置中,您可以注册拦截器以应用于传入的请求,如以下示例所示:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new LocaleChangeInterceptor()); registry.addInterceptor(new ThemeChangeInterceptor()).addPathPatterns("/**").excludePathPatterns("/admin/**"); registry.addInterceptor(new SecurityInterceptor()).addPathPatterns("/secure/*"); } }
以下示例显示了如何在 XML 中实现相同的配置:
<mvc:interceptors> <bean class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor"/> <mvc:interceptor> <mvc:mapping path="/**"/> <mvc:exclude-mapping path="/admin/**"/> <bean class="org.springframework.web.servlet.theme.ThemeChangeInterceptor"/> </mvc:interceptor> <mvc:interceptor> <mvc:mapping path="/secure/*"/> <bean class="org.example.SecurityInterceptor"/> </mvc:interceptor> </mvc:interceptors>
1.10.6. Content Type
您可以配置 Spring MVC 如何根据请求确定请求的媒体类型(例如Accept
Headers,URL 路径扩展,查询参数等)。
默认情况下,将首先检查 URL 路径 extensions,即将json
,xml
,rss
和atom
注册为已知 extensions(取决于 Classpath 依赖项)。 Accept
Headers 被第二次检查。
考虑将这些默认值仅更改为Accept
Headers,并且,如果必须使用基于 URL 的 Content Type 解析,请考虑对路径扩展使用查询参数策略。
在 Java 配置中,您可以自定义请求的 Content Type 解析,如以下示例所示:
@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.10.7. 信息转换器
您可以通过覆盖configureMessageConverters()(以替换 Spring MVC 创建的默认转换器)或覆盖extendMessageConverters()(以自定义默认转换器或向默认转换器添加其他转换器)来自定义 Java 配置中的HttpMessageConverter
。
以下示例使用自定义的ObjectMapper
而非默认的添加了 XML 和 Jackson JSON 转换器:
@Configuration @EnableWebMvc public class WebConfiguration implements WebMvcConfigurer { @Override public void configureMessageConverters(List<HttpMessageConverter<?>> converters) { Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder() .indentOutput(true) .dateFormat(new SimpleDateFormat("yyyy-MM-dd")) .modulesToInstall(new ParameterNamesModule()); converters.add(new MappingJackson2HttpMessageConverter(builder.build())); converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build())); } }
在前面的示例中,使用Jackson2ObjectMapperBuilder来为MappingJackson2HttpMessageConverter
和MappingJackson2XmlHttpMessageConverter
创建启用缩进的通用配置,自定义的日期格式和jackson-module-parameter-names的注册,这增加了对访问参数名称的支持(Java 8 中添加的功能)。
此构建器自定义 Jackson 的默认属性,如下所示:
如果在 Classpath 中检测到以下知名模块,它还将自动注册以下知名模块:
-
jackson-datatype-jdk7:支持 Java 7 类型,例如
java.nio.file.Path
。 -
jackson-datatype-joda:支持 Joda-Time 类型。
-
jackson-datatype-jsr310:支持 Java 8 日期和时间 API 类型。
-
jackson-datatype-jdk8:支持其他 Java 8 类型,例如
Optional
。
Note
启用具有 Jackson XML 支持的缩进除了jackson-dataformat-xml个之外还需要woodstox-core-asl个依赖。
其他有趣的 Jackson 模块也可用:
-
jackson-datatype-money:支持
javax.money
类型(非官方模块)。 -
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.10.8. 视图控制器
这是定义被调用时立即转发到视图的ParameterizableViewController
的快捷方式。在视图生成响应之前没有 Java 控制器逻辑要执行的静态情况下,可以使用它。
以下 Java 配置示例将对/
的请求转发到名为home
的视图:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { registry.addViewController("/").setViewName("home"); } }
以下示例通过使用<mvc:view-controller>
元素,实现了与上述示例相同的操作,但使用 XML:
<mvc:view-controller path="/" view-name="home"/>
1.10.9. 查看解析器
MVC 配置简化了视图解析器的注册。
以下 Java 配置示例通过使用 JSP 和 Jackson 作为 JSON 渲染的默认View
来配置内容协商视图解析:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.enableContentNegotiation(new MappingJackson2JsonView()); registry.jsp(); } }
以下示例显示了如何在 XML 中实现相同的配置:
<mvc:view-resolvers> <mvc:content-negotiation> <mvc:default-views> <bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/> </mvc:default-views> </mvc:content-negotiation> <mvc:jsp/> </mvc:view-resolvers>
但是请注意,FreeMarker,Tiles,Groovy 标记和脚本模板也需要配置基础视图技术。
MVC 命名空间提供了专用元素。以下示例适用于 FreeMarker:
<mvc:view-resolvers>
<mvc:content-negotiation>
<mvc:default-views>
<bean class="org.springframework.web.servlet.view.json.MappingJackson2JsonView"/>
</mvc:default-views>
</mvc:content-negotiation>
<mvc:freemarker cache="false"/>
</mvc:view-resolvers>
<mvc:freemarker-configurer>
<mvc:template-loader-path location="/freemarker"/>
</mvc:freemarker-configurer>
在 Java 配置中,您可以添加相应的Configurer
bean,如以下示例所示:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureViewResolvers(ViewResolverRegistry registry) { registry.enableContentNegotiation(new MappingJackson2JsonView()); registry.freeMarker().cache(false); } @Bean public FreeMarkerConfigurer freeMarkerConfigurer() { FreeMarkerConfigurer configurer = new FreeMarkerConfigurer(); configurer.setTemplateLoaderPath("/freemarker"); return configurer; } }
1.10.10. 静态资源
在下一个示例中,给定一个以/resources
开头的请求,相对路径用于在 Web 应用程序根目录下或/static
下的 Classpath 下相对于/public
查找和提供静态资源。这些资源的有效期为一年,以确保最大程度地利用浏览器缓存并减少浏览器发出的 HTTP 请求。还将评估Last-Modified
Headers,并返回304
状态代码(如果存在)。
以下 Lists 显示了如何使用 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" />
资源处理程序还支持ResourceResolver实现和ResourceTransformer实现的链,您可以使用它们来创建工具链以使用优化的资源。
您可以将VersionResourceResolver
用于基于资源,固定应用程序版本或其他内容计算出的 MD5 哈希的版本化资源 URL。 ContentVersionStrategy
(MD5 哈希)是一个不错的选择,其中有一些值得注意的 exception,例如与模块加载器一起使用的 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> <mvc:resource-cache/> <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,以便可以将其注入其他对象。对于 Thymeleaf,JSP,FreeMarker 以及其他依赖于HttpServletResponse#encodeURL
的 URL 标记,您也可以使用ResourceUrlEncodingFilter
使重写透明。
请注意,同时使用EncodedResourceResolver
(例如,用于提供压缩或 brotli 编码的资源)和VersionedResourceResolver
时,必须按此 Sequences 注册它们。这样可以确保始终基于未编码文件可靠地计算基于内容的版本。
WebJars也受WebJarsResourceResolver
支持,并且当org.webjars:webjars-locator
存在于 Classpath 中时会自动注册。解析程序可以重写 URL 以包括 jar 的版本,还可以与不带版本的传入 URL 进行匹配,例如/jquery/jquery.min.js
到/jquery/1.2.0/jquery.min.js
。
1.10.11. 默认 Servlet
Spring MVC 允许将DispatcherServlet
映射到/
(从而覆盖了容器默认 Servlet映射),同时仍允许容器默认 Servlet 处理静态资源请求。它将DefaultServletHttpRequestHandler
配置为/**
的 URL映射,并且相对于其他 URL映射具有最低的优先级。
该处理程序将所有请求转发到默认 Servlet。因此,它必须按所有其他 URL HandlerMappings
的 Sequences 保留在最后。如果使用<mvc:annotation-driven>
就是这种情况。另外,如果您设置自己的自定义HandlerMapping
实例,请确保将其order
属性设置为小于DefaultServletHttpRequestHandler
(即Integer.MAX_VALUE
)的值。
以下示例显示了如何使用默认设置启用功能:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable(); } }
以下示例显示了如何在 XML 中实现相同的配置:
<mvc:default-servlet-handler/>
覆盖/
Servlet映射的警告是,默认 Servlet 的RequestDispatcher
必须通过名称而不是通过路径来检索。 DefaultServletHttpRequestHandler
尝试使用大多数主要 Servlet 容器(包括 Tomcat,Jetty,GlassFish,JBoss,Resin,WebLogic 和 WebSphere)的已知名称列表,在启动时自动检测容器的默认 Servlet。如果已使用其他名称自定义配置了默认 Servlet,或者在默认 Servlet 名称未知的情况下使用了不同的 Servlet 容器,则必须显式提供默认 Servlet 的名称,如以下示例所示:
@Configuration @EnableWebMvc public class WebConfig implements WebMvcConfigurer { @Override public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) { configurer.enable("myCustomDefaultServlet"); } }
以下示例显示了如何在 XML 中实现相同的配置:
<mvc:default-servlet-handler default-servlet-name="myCustomDefaultServlet"/>
1.10.12. 路径匹配
您可以自定义与路径匹配和 URL 处理有关的选项。
以下示例显示了如何在 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.10.13. 高级 Java 配置
@EnableWebMvc
导入DelegatingWebMvcConfiguration
,其中:
-
为 Spring MVC 应用程序提供默认的 Spring 配置
-
检测并委托
WebMvcConfigurer
实现自定义该配置。
对于高级模式,可以删除@EnableWebMvc
并直接从DelegatingWebMvcConfiguration
扩展,而不是实现WebMvcConfigurer
,如以下示例所示:
@Configuration public class WebConfig extends DelegatingWebMvcConfiguration { // ... }
您可以将现有方法保留在WebConfig
中,但是现在您还可以覆盖 Base Class 中的 bean 声明,并且在 Classpath 上仍然可以具有任意数量的其他WebMvcConfigurer
实现。
1.10.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.11. HTTP/2
需要 Servlet 4 容器支持 HTTP/2,并且 Spring Framework 5 与 Servlet API 4 兼容。从编程模型的角度来看,应用程序不需要做任何特定的事情。但是,有一些与服务器配置有关的注意事项。有关更多详细信息,请参见HTTP/2 Wiki 页面。
Servlet API 确实公开了一种与 HTTP/2 相关的构造。您可以使用javax.servlet.http.PushBuilder
主动将资源推送到 Client 端,并且受method argument到@RequestMapping
方法支持。