Spring Web MVC
Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架。
与 Spring Web MVC 并行,Spring Framework 5.0 引入了一个响应式堆栈 Web 框架,其名称“Spring WebFlux”
DispatcherServlet
Spring MVC 与许多其他 Web 框架一样,是围绕前端控制器模式设计的,其中一个中央控制器Servlet
为DispatcherServlet
请求处理提供共享算法,而实际工作由可配置的委托组件执行。该模型非常灵活,支持多种工作流程。
和DispatcherServlet
any 一样Servlet
,需要根据 Servlet 规范使用 Java 配置或在web.xml
. 反过来,DispatcherServlet
使用 Spring 配置来发现请求映射、视图解析、异常处理等所需的委托组件。
以下 Java 配置示例注册并初始化DispatcherServlet
Servlet 容器自动检测到的 :
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(AppConfig.class);
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(context);
ServletRegistration.Dynamic registration = servletContext.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
以下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>
上下文层次结构
对于许多应用程序,拥有一个WebApplicationContext
简单且足够。也可以有一个上下文层次结构,其中一个根WebApplicationContext
在多个DispatcherServlet
(或其他Servlet
)实例之间共享,每个实例都有自己的子WebApplicationContext
配置。
根WebApplicationContext
通常包含基础设施 bean,例如需要跨多个Servlet
实例共享的数据存储库和业务服务。这些 bean 是有效继承的,并且可以在特定于 Servlet 的 child 中被覆盖(即重新声明)WebApplicationContext
,它通常包含给定的本地 beans Servlet
。
应用上下文一般分成两大块:一块是servlet WebApplicationContext:里面放的是与Spring mvc相关的一些bean。早期在配置componentScan会加上一些filter只要扫描controller注解的一些bean就好,在web.xml中配置其它配置文件的加载。
servlet WebApplicationContext里面放controller,root WebApplicationContext里面放service和repository。
servlet WebApplicationContext继承了root WebApplicationContext,在servlet里面找不到对应的bean时会在root里面找。
以下示例配置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/*" };
}
}
特殊Bean
豆类 | 解释 |
---|---|
HandlerMapping | 将请求与用于预处理和后处理的拦截器列表一起映射到处理程序 。映射基于一些标准,其细节因HandlerMapping 实施而异。两个主要HandlerMapping 实现是RequestMappingHandlerMapping (支持带@RequestMapping 注释的方法)和SimpleUrlHandlerMapping (维护向处理程序的 URI 路径模式的显式注册)。 |
HandlerAdapter | 帮助DispatcherServlet 调用映射到请求的处理程序,而不管实际调用处理程序的方式。例如,调用带注释的控制器需要解析注释。a 的主要目的HandlerAdapter 是屏蔽DispatcherServlet 这些细节。 |
HandlerExceptionResolver | 解决异常的策略,可能将它们映射到处理程序、HTML 错误视图或其他目标。请参阅例外。 |
ViewResolver | String 将从处理程序返回的基于逻辑的视图名称解析View 为用于呈现响应的实际视图名称。请参阅查看分辨率和查看技术。 |
LocaleResolver , LocaleContextResolver | 解析Locale 客户正在使用的可能以及他们的时区,以便能够提供国际化视图。请参阅区域设置。 |
ThemeResolver | 解决您的 Web 应用程序可以使用的主题——例如,提供个性化的布局。请参阅主题。 |
MultipartResolver | 在一些多部分解析库的帮助下解析多部分请求(例如,浏览器表单文件上传)的抽象。请参阅多部分解析器。 |
FlashMapManager | FlashMap 存储和检索可用于将属性从一个请求传递到另一个请求的“输入”和“输出” ,通常通过重定向。请参阅Flash 属性。 |
Web MVC 配置
Spring Boot 依赖于 MVC Java 配置来配置 Spring MVC,并提供了许多额外的方便选项。
@EnableWebMvc // 启用Spring MVC
程序配置
在 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("/");
}
}
建议使用基于 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[] { "/" };
}
}
工作原理
DispatcherServlet
进程请求如下:
- 在
WebApplicationContext
请求中搜索并绑定作为控制器和流程中其他元素可以使用的属性。默认绑定在DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE
key下。 - 区域设置解析器绑定到请求,以让流程中的元素解析处理请求(呈现视图、准备数据等)时要使用的区域设置。如果不需要区域设置解析,则不需要区域设置解析器。
- 主题解析器与请求绑定,让视图等元素决定使用哪个主题。如果你不使用主题,你可以忽略它。
- 如果您指定多部分文件解析器,则会检查请求中的多部分。如果找到多部分,则将请求包装在 中,以
MultipartHttpServletRequest
供流程中的其他元素进一步处理。 - 搜索适当的处理程序。如果找到处理程序,则运行与处理程序相关联的执行链(预处理器、后处理器和控制器)以准备渲染模型。或者,对于带注释的控制器,可以(在 内
HandlerAdapter
)呈现响应而不是返回视图。 - 如果返回模型,则呈现视图。如果没有返回模型(可能是由于预处理器或后处理器拦截了请求,可能是出于安全原因),则不会呈现视图,因为请求可能已经完成。
异常
如果在请求映射期间发生异常或从请求处理程序抛出异常,则DispatcherServlet
委托到HandlerExceptionResolver
bean 链以解决异常并提供替代处理,这通常是错误响应。
要自定义容器的默认错误页面,可以在web.xml
. 以下示例显示了如何执行此操作:
<error-page>
<location>/error</location>
</error-page>
给定前面的示例,当出现异常或响应具有错误状态时,Servlet 容器会在容器内将 ERROR 分派到配置的 URL(例如,/error
)。然后由 处理DispatcherServlet
,可能将其映射到 @Controller
,这可以实现为返回带有模型的错误视图名称或呈现 JSON 响应,如以下示例所示:
@RestController
public class ErrorController {
@RequestMapping(path = "/error")
public Map<String, Object> handle(HttpServletRequest request) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("status", request.getAttribute("javax.servlet.error.status_code"));
map.put("reason", request.getAttribute("javax.servlet.error.message"));
return map;
}
}
视图解析
Spring MVC 定义了ViewResolver
和View
接口,使您可以在浏览器中呈现模型,而无需将您绑定到特定的视图技术。
配置
您可以通过声明多个解析器 bean 并在必要时通过设置order
属性来指定排序来链接视图解析器。请记住,order 属性越高,视图解析器在链中的位置就越晚。
重定向
视图名称中的特殊redirect:
前缀允许您执行重定向
转发
您还可forward:
以为最终由子类解析的视图名称使用特殊前缀UrlBasedViewResolver
。这将创建一个 InternalResourceView
,它执行一个RequestDispatcher.forward()
.
内容协商
ContentNegotiatingViewResolver
不解析视图本身,而是委托给其他视图解析器并选择类似于客户端请求的表示的视图。表示可以根据Accept
标头或查询参数(例如,"/path?format=pdf"
)确定。
通过将请求媒体类型与与其相关联的媒体类型(也称为 )进行比较,ContentNegotiatingViewResolver
选择适当的处理请求。列表中具有兼容的第一个将表示返回给客户端。如果链不能提供兼容的视图,则查询通过属性指定的视图列表。后一个选项适用于可以呈现当前资源的适当表示的单例,而不管逻辑视图名称如何。标 头可以包含通配符(例如),在这种情况下,其 is是兼容匹配
过滤器
FormContentFilter
该spring-web
模块提供FormContentFilter
拦截内容类型为 的 HTTP PUT、PATCH 和 DELETE 请求,application/x-www-form-urlencoded
从请求正文中读取表单数据,并通过一系列方法包装ServletRequest
表单数据以使表单数据可用。
转发的标头
当请求通过代理(例如负载均衡器)时,主机、端口和方案可能会发生变化,这使得从客户端的角度创建指向正确主机、端口和方案的链接成为一项挑战。
ForwardedHeaderFilter
是一个 Servlet 过滤器,它修改请求以便 根据Forwarded
标头更改主机、端口和方案,以及 删除这些标头以消除进一步的影响。过滤器依赖于包装请求,因此它必须在其他过滤器之前排序,例如RequestContextFilter
,它应该与修改后的请求一起使用,而不是原始请求。
转发的标头存在安全考虑,因为应用程序无法知道报头是由代理添加的、按预期添加的,还是由恶意客户端添加的。这就是为什么应该配置信任边界的代理以删除Forwarded
来自外部的不受信任的标头。您还可以配置ForwardedHeaderFilter
with removeOnly=true
,在这种情况下,它会删除但不使用报头。
ShallowEtagHeaderFilter
过滤器ShallowEtagHeaderFilter
通过缓存写入响应的内容并从中计算 MD5 哈希来创建一个“浅层”ETag。下次客户端发送时,它会执行相同的操作,但它还会将计算值与If-None-Match
请求标头进行比较,如果两者相等,则返回 304 (NOT_MODIFIED)。
此策略节省了网络带宽,但不节省 CPU,因为必须为每个请求计算完整的响应。
CORS
Spring MVC 通过控制器上的注解为 CORS 配置提供细粒度的支持。但是,当与 Spring Security 一起使用时,我们建议依赖 CorsFilter
必须在 Spring Security 的过滤器链之前。
带注释的控制器
Spring MVC 提供了一个基于注解的编程模型,其中@Controller
组件 @RestController
使用注解来表达请求映射、请求输入、异常处理等。带注释的控制器具有灵活的方法签名,不必扩展基类,也不必实现特定的接口。以下示例显示了由注释定义的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
组件扫描
您可以使用 Servlet 中的标准 Spring bean 定义来定义控制器 bean WebApplicationContext
。构造@Controller
型允许自动检测,与 Spring 对检测@Component
类路径中的类并为它们自动注册 bean 定义的一般支持保持一致。它还充当带注释类的原型,表明其作为 Web 组件的角色。
要启用此类 bean 的自动检测@Controller
,您可以将组件扫描添加到 Java 配置中,如以下示例所示:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
@RestController
是一个组合注释,它本身是元注释的,@Controller
并@ResponseBody
指示一个控制器,其每个方法都继承类型级别的@ResponseBody
注释,因此直接写入响应正文而不是视图分辨率并使用 HTML 模板呈现。
如果控制器必须实现不是 Spring Context 回调的接口(例如InitializingBean
、*Aware
等),您可能需要显式配置基于类的代理。
请求映射
您可以使用@RequestMapping
注释将请求映射到控制器方法。它有各种属性来匹配 URL、HTTP 方法、请求参数、标头和媒体类型。您可以在类级别使用它来表达共享映射,或者在方法级别使用它来缩小到特定的端点映射。
还有 HTTP 方法特定的快捷方式变体@RequestMapping
:
@GetMapping
@PostMapping
@PutMapping
@DeleteMapping
@PatchMapping
大多数控制器方法应该映射到特定的 HTTP 方法,而不是 使用@RequestMapping
,默认情况下,它匹配所有 HTTP 方法。在类级别仍然需要@RequestMapping
来表示共享映射。
以下示例具有类型和方法级别的映射:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
URI 模式
@RequestMapping
可以使用 URL 模式映射方法。有两种选择:
PathPattern
— 与 URL 路径匹配的预解析模式也预解析为PathContainer
. 该解决方案专为 Web 使用而设计,可有效处理编码和路径参数,并有效匹配。AntPathMatcher
— 将字符串模式与字符串路径匹配。这是在 Spring 配置中用于选择类路径、文件系统和其他位置上的资源的原始解决方案。它的效率较低,并且字符串路径输入对于有效处理 URL 的编码和其他问题是一个挑战。
PathPattern
是 Web 应用程序的推荐解决方案,也是 Spring WebFlux 中的唯一选择。在 5.3 版本之前,AntPathMatcher
它是 Spring MVC 中的唯一选择,并且仍然是默认设置。但是PathPattern
可以在 MVC 配置中启用。
PathPattern
支持与AntPathMatcher
. 此外,它还支持捕获模式,例如{*spring}
,用于匹配路径末端的 0 个或多个路径段。PathPattern
还限制了**
for 匹配多个路径段的使用,因此它只允许在模式的末尾使用。在为给定请求选择最佳匹配模式时,这消除了许多模棱两可的情况。有关完整的模式语法,请参阅 PathPattern和 AntPathMatcher。
一些示例模式:
"/resources/ima?e.png"
- 匹配路径段中的一个字符"/resources/*.png"
- 匹配路径段中的零个或多个字符"/resources/**"
- 匹配多个路径段"/projects/{project}/versions"
- 匹配路径段并将其捕获为变量"/projects/{project:[a-z]+}/versions"
- 使用正则表达式匹配和捕获变量
捕获的 URI 变量可以使用@PathVariable
. 例如:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
您可以在类和方法级别声明 URI 变量
URI 变量会自动转换为适当的类型,或者TypeMismatchException
被引发。默认支持简单类型(int
、long
、Date
等),您可以注册对任何其他数据类型的支持。
模式比较
当多个模式匹配一个 URL 时,必须选择最佳匹配。
后缀匹配
从 5.3 开始,默认情况下 Spring MVC 不再执行.*
后缀模式匹配,其中映射到的控制器/person
也隐式映射到 /person.*
. 因此,不再使用路径扩展来解释请求的响应内容类型——例如,、、/person.pdf
等等/person.xml
。
有一种方法来请求内容类型而不是通过"Accept"
标头仍然很有用,例如在浏览器中键入 URL 时。路径扩展的安全替代方法是使用查询参数策略。如果您必须使用文件扩展名,请考虑通过ContentNegotiationConfigurermediaTypes
的属性 将它们限制为显式注册的扩展名列表。
消费者类型
您可以根据请求缩小请求映射Content-Type
,如以下示例所示:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
使用consumes
属性按内容类型缩小映射范围。
consumes
您可以在类级别声明共享属性。然而,与大多数其他请求映射属性不同的是,在类级别使用时,方法级别的consumes
属性会覆盖
生产者媒体类型
您可以根据Accept
请求标头和控制器方法生成的内容类型列表来缩小请求映射,如以下示例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
使用produces
属性按内容类型缩小映射范围。
参数、标题
您可以根据请求参数条件缩小请求映射。您可以测试请求参数是否存在 ( myParam
)、是否存在 ( !myParam
) 或特定值 ( myParam=myValue
)。以下示例显示了如何测试特定值:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
测试是否myParam
等于myValue
。
HTTP 头,选项
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)透明地支持 HTTP HEAD 以进行请求映射。控制器方法不需要更改。中应用的响应包装器javax.servlet.http.HttpServlet
确保将Content-Length
标头设置为写入的字节数(而不实际写入响应)。
@GetMapping
@RequestMapping(method=HttpMethod.GET)
) 隐式映射到并支持 HTTP HEAD。一个 HTTP HEAD 请求的处理就像它是 HTTP GET 一样,除了不是写入正文,而是计算字节数并设置Content-Length
标头。
默认情况下,HTTP OPTIONS 是通过将响应标头设置为具有匹配 URL 模式Allow
的所有方法中列出的 HTTP 方法列表来处理的。@RequestMapping
对于@RequestMapping
没有 HTTP 方法声明的,Allow
标头设置为 GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS
. 控制器方法应始终声明支持的 HTTP 方法(例如,通过使用 HTTP 方法特定的变体: @GetMapping
、@PostMapping
等)。
您可以将该方法显式映射@RequestMapping
到 HTTP HEAD 和 HTTP OPTIONS,但这在常见情况下不是必需的。
显式注册
您可以以编程方式注册处理程序方法,您可以将其用于动态注册或高级案例,例如不同 URL 下同一处理程序的不同实例。以下示例注册了一个处理程序方法:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build();
Method method = UserHandler.class.getMethod("getUser", Long.class);
mapping.registerMapping(info, handler, method);
}
}
处理程序方法
方法参数
@RequestBody
用于访问 HTTP 请求正文。使用实现将正文内容转换为声明的方法参数类型HttpMessageConverter
。
@RequestParam
用于访问 Servlet 请求参数,包括多部分文件。
@PathVariable
用于访问 URI 模板变量。
返回值
@ResponseBody
返回值通过HttpMessageConverter
实现转换并写入响应。
类型转换
类型转换会根据配置的转换器自动应用。默认情况下,支持简单类型(int
、long
、Date
等)。
@RequestParam
您可以使用@RequestParam
注解将 Servlet 请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
以下示例显示了如何执行此操作:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
用于@RequestParam
绑定petId
.
@RequestHeader
您可以使用@RequestHeader
注解将请求标头绑定到控制器中的方法参数。
考虑以下带有标头的请求:
主机本地主机:8080
接受 text/html,application/xhtml+xml,application/xml;q=0.9
接受语言 fr,en-gb;q=0.7,en;q=0.3
接受编码 gzip,deflate
接受字符集 ISO-8859-1,utf-8;q=0.7,*;q=0.7
保活300
以下示例获取Accept-Encoding
和Keep-Alive
标头的值:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
@CookieValue
您可以使用@CookieValue
注解将 HTTP cookie 的值绑定到控制器中的方法参数。
考虑一个带有以下 cookie 的请求:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
以下示例显示了如何获取 cookie 值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
@ModelAttribute
您可以使用@ModelAttribute
方法参数上的注释来访问模型中的属性,或者如果不存在则将其实例化。模型属性还覆盖有名称与字段名称匹配的 HTTP Servlet 请求参数的值。这称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
// method logic...
}
数据绑定可能会导致错误。默认情况下,BindException
会引发 。但是,要检查控制器方法中的此类错误,您可以在BindingResult
旁边立即添加一个参数@ModelAttribute
,如以下示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
javax.validation.Valid
您可以通过添加注解或 Spring 的@Validated
注解在数据绑定后自动应用验证 。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
@SessionAttributes
@SessionAttributes
用于在请求之间的 HTTP Servlet 会话中存储模型属性。它是一个类型级别的注释,用于声明特定控制器使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,这些属性应透明地存储在会话中以供后续请求访问。
以下示例使用@SessionAttributes
注释:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
@SessionAttribute
如果您需要访问全局管理的预先存在的会话属性(即,在控制器外部 - 例如,通过过滤器)并且可能存在也可能不存在,您可以@SessionAttribute
在方法参数上使用注释,如下所示示例显示:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
@RequestAttribute
与 类似@SessionAttribute
,您可以使用@RequestAttribute
注释来访问先前创建的预先存在的请求属性(例如,通过 ServletFilter
或HandlerInterceptor
):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
重定向属性
默认情况下,所有模型属性都被视为在重定向 URL 中作为 URI 模板变量公开。在剩余的属性中,那些是原始类型或原始类型的集合或数组的属性会自动附加为查询参数。
如果专门为重定向准备了模型实例,则将原始类型属性作为查询参数附加可能是所需的结果。但是,在带注释的控制器中,模型可以包含为呈现目的而添加的其他属性(例如,下拉字段值)。为避免此类属性出现在 URL 中的可能性,@RequestMapping
方法可以声明一个类型的参数RedirectAttributes
并使用它来指定可用于的确切属性RedirectView
。如果该方法确实重定向,RedirectAttributes
则使用的内容。否则,使用模型的内容。
RequestMappingHandlerAdapter
提供了一个名为 的标志 ,ignoreDefaultModelOnRedirect
您可以使用它来指示 Model
如果控制器方法重定向,则不应使用默认内容。相反,控制器方法应该声明一个类型的属性,RedirectAttributes
或者,如果它不这样做,则不应将任何属性传递给RedirectView
. MVC 命名空间和 MVC Java 配置都将此标志设置为false
,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true
.
请注意,当前请求中的 URI 模板变量在扩展重定向 URL 时会自动可用,您无需通过Model
或显式添加它们RedirectAttributes
。以下示例显示了如何定义重定向:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
MultipartFile
MultipartResolver
,POST 请求的内容将被解析并作为常规请求参数访问。以下示例访问一个常规表单字段和一个上传文件:multipart/form-data
@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";
}
}
@RequestBody
您可以使用@RequestBody
注释将请求正文读取并反序列 Object
化为HttpMessageConverter
. 以下示例使用@RequestBody
参数:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
实体
HttpEntity
与@RequestBody 使用或多或少相同,但基于公开请求标头和正文的容器对象。以下清单显示了一个示例:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
@ResponseBody
您可以使用@ResponseBody
方法上的注释通过 HttpMessageConverter将返回序列化到响应正文。以下清单显示了一个示例:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
@ResponseBody
在类级别也支持,在这种情况下,它被所有控制器方法继承。这就是@RestController
的效果,只不过是一个用@Controller
和@ResponseBody
组合。
您可以将@ResponseBody
方法与 JSON 序列化视图结合使用。
响应实体
ResponseEntity
就像@ResponseBody
但有状态和标题。例如:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}
模型
您可以使用@ModelAttribute
注释:
- 在方法中的方法参数上从模型
@RequestMapping
创建或访问一个Object
并通过一个将其绑定到请求WebDataBinder
。 @Controller
作为一个或多个类中的方法级注释@ControllerAdvice
,有助于在任何@RequestMapping
方法调用之前初始化模型。- 在一个
@RequestMapping
方法上标记它的返回值是一个模型属性。
@ModelAttribute标注可被应用在方法或方法参数上。
被 @ModelAttribute 注释的方法会在Controller每个方法执行之前都执行,因此对于一个Controller中包含多个URL的时候,要谨慎使用。
您还可以@ModelAttribute
在方法上用作方法级别的注释@RequestMapping
,在这种情况下,方法的返回值@RequestMapping
被解释为模型属性。这通常不是必需的,因为它是 HTML 控制器中的默认行为,除非返回值是String
则会被解释为视图名称的 。 @ModelAttribute
也可以自定义模型属性名称,如下例所示:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
@GetMapping("/accounts/{id}")
@ModelAttribute
public Account handle() {
// ...
return account;
}
上面相当于model.addAttribute("account", account);
您可以使用@ModelAttribute
方法参数上的注释来访问模型中的属性,或者如果不存在则将其实例化。模型属性还覆盖有名称与字段名称匹配的 HTTP Servlet 请求参数的值。这称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
// method logic...
}
DataBinder
@Controller
或@ControllerAdvice
类可以具有@InitBinder
初始化 的实例的方法WebDataBinder
,而这些方法又可以:
- 将请求参数(即表单或查询数据)绑定到模型对象。
- 将基于字符串的请求值(例如请求参数、路径变量、标头、cookie 等)转换为控制器方法参数的目标类型。
String
在呈现 HTML 表单时将模型对象值格式化为值。
@InitBinder
方法可以注册特定于控制器java.beans.PropertyEditor
或 SpringConverter
和Formatter
组件。此外,您可以使用 MVC 配置来注册Converter
和Formatter
键入全局共享的FormattingConversionService
.
@InitBinder
方法支持许多与@RequestMapping
方法相同的参数,除了@ModelAttribute
(命令对象)参数。通常,它们使用WebDataBinder
参数(用于注册)和void
返回值声明。以下清单显示了一个示例:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
或者,当您Formatter
通过 共享使用基于 FormattingConversionService
- 的设置时,您可以重复使用相同的方法并注册特定于控制器的Formatter
实现,如以下示例所示:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
异常
@Controller
和@ControllerAdvice类可以具有 @ExceptionHandler
处理来自控制器方法的异常的方法,如以下示例所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
异常可以与正在传播的顶级异常(例如,直接 IOException
抛出)或与包装器异常中的嵌套原因(例如,IOException
包装在 中IllegalStateException
)匹配。从 5.3 开始,这可以匹配任意原因级别,而之前只考虑直接原因。
对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。当多个异常方法匹配时,根异常匹配通常优于原因异常匹配。更具体地说,ExceptionDepthComparator
它用于根据抛出的异常类型的深度对异常进行排序。
或者,注释声明可以缩小要匹配的异常类型,如以下示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
您甚至可以使用具有非常通用的参数签名的特定异常类型列表,如以下示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
REST 服务的一个常见要求是在响应正文中包含错误详细信息。Spring Framework 不会自动执行此操作,因为响应正文中错误详细信息的表示是特定于应用程序的。但是, @RestController
可以使用@ExceptionHandler
带有ResponseEntity
返回值的方法来设置响应的状态和正文。这些方法也可以在@ControllerAdvice
类中声明以全局应用它们。
增强控制器
@ExceptionHandler
, @InitBinder
, 和@ModelAttribute
方法仅适用 在@Controller
声明它们的类或类层次结构。相反,如果它们在@ControllerAdvice
或者@RestControllerAdvice
类中声明,则它们适用于任何控制器。此外,从 5.3 开始,@ExceptionHandler
方法 @ControllerAdvice
可用于处理来自任何@Controller
或任何其他处理程序的异常。
@ControllerAdvice
是元注释的@Component
,因此可以通过组件扫描注册为 Spring bean 。 使用@RestControllerAdvice`进行元注释,这意味着方法将通过响应正文消息转换而不是通过 HTML 视图呈现其返回值。
@ControllerAdvice
注释具有允许您缩小它们应用的控制器和处理程序集的属性。例如:
// Target all Controllers annotated with @RestController
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}
@ControllerAdvice注解的使用
@ControllerAdvice
public class SpringControllerAdvice {
/**
* 应用到所有被@RequestMapping注解的方法,在其执行之前初始化数据绑定器
* @param binder
*/
@InitBinder
public void initBinder(WebDataBinder binder) {}
/**
* 把值绑定到Model中,使全局@RequestMapping可以获取到该值
* @param model
*/
@ModelAttribute
public void addAttributes(Model model) {
model.addAttribute("words", "hello world");
}
/**
* 全局异常捕捉处理
* @param ex
* @return
*/
@ResponseBody
@ExceptionHandler(value = Exception.class)
public Map errorHandler(Exception ex) {
Map map = new HashMap();
map.put("code", 100);
map.put("msg", ex.getMessage());
return map;
}
}
在启动应用之后,被@ExceptionHandler、@InitBinder和@ModelAttribute注解的方法都会作用在被@RequestMappping注解的方法上。比如上面的@ModelAttribute注解的方法参数model上设置的值,所有被@RequestMapping注解的方法中都可以通过ModelMap获取。
@RequestMapping("/index")
public String index(ModelMap modelMap) {
System.out.println(modelMap.get("words"));
}
// 也可以通过@ModelAttribute获取
@RequestMapping("/index")
public String index(@ModelAttribute("words") String words) {
System.out.println(words);
}
@ExceptionHandler拦截异常并统一处理
@ExceptionHandler的作用主要在于声明一个或多个类型的异常,当符合条件的Controller抛出这些异常之后将会对这些异常进行捕获,然后按照其标注的方法的逻辑进行处理,从而改变返回的视图信息。
@Controller
public class UserController {
@RequestMapping(value = "/users", method = RequestMethod.GET)
public void users() {
throw new RuntimeException("没有任何用户。");
}
}
使用@InitBinder绑定一些自定义参数
对于@InitBinder,该注解的主要作用是绑定一些自定义的参数。一般情况下我们使用的参数通过@RequestParam,@RequestBody或者@ModelAttribute等注解就可以进行绑定了,但对于一些特殊类型参数,比如Date,它们的绑定Spring是没有提供直接的支持的,我们只能为其声明一个转换器,将request中字符串类型的参数通过转换器转换为Date类型的参数,从而供给@RequestMapping标注的方法使用。
使用@InitBinder注册Date类型参数转换器的实现
@ControllerAdvice
public class SpringControllerAdvice {
@InitBinder
public void globalInitBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
@Controller
public class UserController {
@RequestMapping(value = "/users", method = RequestMethod.GET)
public void users(Date date) {
System.out.println(date); // Tue May 02 00:00:00 CST 2019
}
}
使用@ModelAttribute在方法执行前进行一些操作
关于@ModelAttribute的用法,除了用于方法参数时可以用于转换对象类型的属性之外,其还可以用来进行方法的声明。如果声明在方法上,并且结合@ControllerAdvice,该方法将会在@ControllerAdvice所指定的范围内的所有接口方法执行之前执行,并且@ModelAttribute标注的方法的返回值还可以供给后续会调用的接口方法使用。
使用@ModelAttribute注解进行方法标注的一个例子
@ControllerAdvice
public class SpringControllerAdvice {
@ModelAttribute(value = "message")
public String globalModelAttribute() {
System.out.println("添加了message全局属性。");
return "输出了message全局属性。";
}
}
@Controller
public class UserController {
@RequestMapping(value = "/users", method = RequestMethod.GET)
public void users(@ModelAttribute("message") String message) {
System.out.println(message);
}
}
函数式控制器
这是一个轻量级的函数式编程模型,其中函数用于路由和处理请求,并且为不变性设计了合约。它是基于注释的编程模型的替代方案。
概述
HTTP 请求使用HandlerFunction
: 处理,该函数接受 ServerRequest
并返回ServerResponse
. 请求和响应对象都有不可变的契约,提供对 HTTP 请求和响应的 JDK 8 友好访问。 HandlerFunction
相当于@RequestMapping
基于注解的编程模型中的方法体。
传入的请求被路由到一个带有RouterFunction
: 的处理函数,该函数接受ServerRequest
并返回一个可选的HandlerFunction
(即Optional<HandlerFunction>
)。当路由函数匹配时,返回一个处理函数;否则为空 Optional。 RouterFunction
相当于@RequestMapping
注解,但主要区别在于路由器功能不仅提供数据,还提供行为。
RouterFunctions.route()
提供了一个有助于创建路由器的路由器构建器,如以下示例所示:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
import static org.springframework.web.servlet.function.RouterFunctions.route;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.build();
public class PersonHandler {
// ...
public ServerResponse listPeople(ServerRequest request) {
// ...
}
public ServerResponse createPerson(ServerRequest request) {
// ...
}
public ServerResponse getPerson(ServerRequest request) {
// ...
}
}
如果您将 注册RouterFunction
为 bean,例如通过在 @Configuration
类中公开它,它将被 servlet 自动检测到
处理函数
ServerRequest
和ServerResponse
是不可变接口,提供对 JDK 8 友好的 HTTP 请求和响应访问,包括标头、正文、方法和状态代码。
服务器请求
ServerRequest
提供对 HTTP 方法、URI、标头和查询参数的访问,而对正文的访问是通过body
方法提供的。
以下示例将请求正文提取到String
:
String string = request.body(String.class);
以下示例将正文提取到 List<Person>
,其中Person
对象从序列化形式(例如 JSON 或 XML)解码:
List<Person> people = request.body(new ParameterizedTypeReference<List<Person>>() {});
以下示例显示了如何访问参数:
MultiValueMap<String, String> params = request.params();
服务器响应
ServerResponse
提供对 HTTP 响应的访问,并且由于它是不可变的,因此您可以使用一种build
方法来创建它。您可以使用构建器设置响应状态、添加响应标头或提供正文。以下示例使用 JSON 内容创建 200 (OK) 响应:
Person person = ...
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
以下示例显示了如何构建带有Location
标头但没有正文的 201 (CREATED) 响应:
URI location = ...
ServerResponse.created(location).build();
还可以使用异步结果作为主体,以 a CompletableFuture
、 Publisher
或ReactiveAdapterRegistry
. 例如:
Mono<Person> person = webClient.get().retrieve().bodyToMono(Person.class);
ServerResponse.ok().contentType(MediaType.APPLICATION_JSON).body(person);
如果不仅正文,而且状态或标头都基于异步类型,您可以使用静态async
方法 on ServerResponse
,它接受CompletableFuture<ServerResponse>
,Publisher<ServerResponse>
,或任何其他受ReactiveAdapterRegistry
. 例如:
Mono<ServerResponse> asyncResponse = webClient.get().retrieve().bodyToMono(Person.class)
.map(p -> ServerResponse.ok().header("Name", p.name()).body(p));
ServerResponse.async(asyncResponse);
服务器发送的事件可以通过 on 的静态sse
方法提供ServerResponse
。该方法提供的构建器允许您将字符串或其他对象作为 JSON 发送。例如:
public RouterFunction<ServerResponse> sse() {
return route(GET("/sse"), request -> ServerResponse.sse(sseBuilder -> {
// Save the sseBuilder object somewhere..
}));
}
// In some other thread, sending a String
sseBuilder.send("Hello world");
// Or an object, which will be transformed into JSON
Person person = ...
sseBuilder.send(person);
// Customize the event by using the other methods
sseBuilder.id("42")
.event("sse event")
.data(person);
// and done at some point
sseBuilder.complete();
处理程序类
我们可以将处理函数编写为 lambda,如以下示例所示:
HandlerFunction<ServerResponse> helloWorld =
request -> ServerResponse.ok().body("Hello World");
这很方便,但在应用程序中我们需要多个函数,并且多个内联 lambda 会变得混乱。因此,将相关的处理程序函数组合到一个处理程序类中是很有用的,该处理程序类的作用与@Controller
基于注释的应用程序中的作用相似。例如,以下类公开了一个反应式Person
存储库:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
public class PersonHandler {
private final PersonRepository repository;
public PersonHandler(PersonRepository repository) {
this.repository = repository;
}
public ServerResponse listPeople(ServerRequest request) {
List<Person> people = repository.allPeople();
return ok().contentType(APPLICATION_JSON).body(people);
}
public ServerResponse createPerson(ServerRequest request) throws Exception {
Person person = request.body(Person.class);
repository.savePerson(person);
return ok().build();
}
public ServerResponse getPerson(ServerRequest request) {
int personId = Integer.parseInt(request.pathVariable("id"));
Person person = repository.getPerson(personId);
if (person != null) {
return ok().contentType(APPLICATION_JSON).body(person);
}
else {
return ServerResponse.notFound().build();
}
}
}
验证
功能端点可以使用 Spring 的验证工具将验证应用于请求正文。例如,给定一个自定义 Spring Validator实现Person
:
public class PersonHandler {
private final Validator validator = new PersonValidator();
// ...
public ServerResponse createPerson(ServerRequest request) {
Person person = request.body(Person.class);
validate(person);
repository.savePerson(person);
return ok().build();
}
private void validate(Person person) {
Errors errors = new BeanPropertyBindingResult(person, "person");
validator.validate(person, errors);
if (errors.hasErrors()) {
throw new ServerWebInputException(errors.toString());
}
}
}
RouterFunction
路由器功能用于将请求路由到相应的HandlerFunction
. 通常,您不会自己编写路由器函数,而是使用 RouterFunctions
实用程序类上的方法来创建一个。 RouterFunctions.route()
(无参数)为您提供了一个流畅的构建器来创建路由器功能,同时RouterFunctions.route(RequestPredicate, HandlerFunction)
提供了一种直接的方式来创建路由器。
一般来说,建议使用route()
构建器,因为它为典型的映射场景提供了方便的快捷方式,而不需要难以发现的静态导入。例如,路由器函数构建器提供了GET(String, HandlerFunction)
为 GET 请求创建映射的方法;和POST(String, HandlerFunction)
帖子。
除了基于 HTTP 方法的映射之外,路由构建器还提供了一种在映射到请求时引入额外谓词的方法。对于每个 HTTP 方法,都有一个以 aRequestPredicate
作为参数的重载变体,通过它可以表达额外的约束。
谓词
您可以编写自己的RequestPredicate
,但RequestPredicates
实用程序类提供了常用的实现,基于请求路径、HTTP 方法、内容类型等。以下示例使用请求谓词基于Accept
标头创建约束:
RouterFunction<ServerResponse> route = RouterFunctions.route()
.GET("/hello-world", accept(MediaType.TEXT_PLAIN),
request -> ServerResponse.ok().body("Hello World")).build();
可以使用以下方法将多个请求谓词组合在一起:
RequestPredicate.and(RequestPredicate)
— 两者必须匹配。RequestPredicate.or(RequestPredicate)
——两者都可以匹配。
很多谓词 fromRequestPredicates
是组合的。例如,RequestPredicates.GET(String)
由RequestPredicates.method(HttpMethod)
和组成RequestPredicates.path(String)
。上面显示的示例还使用了两个请求谓词,构建器在 RequestPredicates.GET
内部使用,并将其与accept
谓词组合在一起。
路线
路由器函数按顺序评估:如果第一个路由不匹配,则评估第二个,依此类推。因此,在一般路由之前声明更具体的路由是有意义的。这在将路由器功能注册为 Spring bean 时也很重要,稍后将进行描述。请注意,此行为与基于注释的编程模型不同,后者自动选择“最具体”的控制器方法。
使用路由函数构建器时,所有定义的路由都组合成一个 RouterFunction
从build()
. 还有其他方法可以将多个路由器功能组合在一起:
add(RouterFunction)
在RouterFunctions.route()
建设者上RouterFunction.and(RouterFunction)
RouterFunction.andRoute(RequestPredicate, HandlerFunction)``RouterFunction.and()
—嵌套 的快捷方式RouterFunctions.route()
。
以下示例显示了四个路由的组成:
import static org.springframework.http.MediaType.APPLICATION_JSON;
import static org.springframework.web.servlet.function.RequestPredicates.*;
PersonRepository repository = ...
PersonHandler handler = new PersonHandler(repository);
RouterFunction<ServerResponse> otherRoute = ...
RouterFunction<ServerResponse> route = route()
.GET("/person/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET("/person", accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson)
.add(otherRoute)
.build();
嵌套路由
一组路由器功能通常具有共享谓词,例如共享路径。在上面的示例中,共享谓词将是匹配的路径谓词,/person
由三个路由使用。使用注解时,您可以使用@RequestMapping
映射到/person
. 在 WebMvc.fn 中,路径谓词可以通过path
路由器函数构建器上的方法共享。例如,上面示例的最后几行可以通过使用嵌套路由通过以下方式进行改进:
RouterFunction<ServerResponse> route = route()
.path("/person", builder -> builder
.GET("/{id}", accept(APPLICATION_JSON), handler::getPerson)
.GET(accept(APPLICATION_JSON), handler::listPeople)
.POST("/person", handler::createPerson))
.build();
尽管基于路径的嵌套是最常见的,但您可以使用nest
构建器上的方法嵌套在任何类型的谓词上。上面仍然包含一些以 shared Accept
-header 谓词形式存在的重复。我们可以通过nest
结合使用该方法来进一步改进accept
:
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST("/person", handler::createPerson))
.build();
运行服务器
您通常通过MVC ConfigDispatcherHandler
在基于设置的设置 中运行路由器功能,它使用 Spring 配置来声明处理请求所需的组件。MVC Java 配置声明了以下基础结构组件以支持功能端点:
RouterFunctionMapping
: 检测 Spring 配置中的一个或多个RouterFunction<?>
bean,对它们进行排序,通过它们组合它们RouterFunction.andOther
,并将请求路由到生成的组合RouterFunction
.HandlerFunctionAdapter
:简单的适配器,可以DispatcherHandler
调用HandlerFunction
映射到请求的 a。
前面的组件让功能端点适合DispatcherServlet
请求处理生命周期,并且(可能)与带注释的控制器(如果已声明)一起运行。这也是 Spring Boot Web 启动器启用功能端点的方式。
以下示例显示了 WebFlux Java 配置:
@Configuration
@EnableMvc
public class WebConfig implements WebMvcConfigurer {
@Bean
public RouterFunction<?> routerFunctionA() {
// ...
}
@Bean
public RouterFunction<?> routerFunctionB() {
// ...
}
// ...
@Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
// configure message conversion...
}
@Override
public void addCorsMappings(CorsRegistry registry) {
// configure CORS...
}
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
// configure view resolution for HTML rendering...
}
}
过滤处理函数
您可以使用路由函数生成器上的 、 或 方法before
过滤after
处理函数。使用注释,您可以通过使用、 a或同时使用两者filter
来实现类似的功能。过滤器将应用于构建器构建的所有路由。这意味着嵌套路由中定义的过滤器不适用于“顶级”路由。例如,考虑以下示例:@ControllerAdvice``ServletFilter
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople)
.before(request -> ServerRequest.from(request)
.header("X-RequestHeader", "Value")
.build()))
.POST("/person", handler::createPerson))
.after((request, response) -> logResponse(response))
.build();
filter`路由器构建器上的方法接受一个 : 一个接受 a并返回 a`HandlerFilterFunction`的函数。处理函数参数表示链中的下一个元素。这通常是路由到的处理程序,但如果应用多个,它也可以是另一个过滤器。`ServerRequest``HandlerFunction``ServerResponse
现在我们可以为我们的路由添加一个简单的安全过滤器,假设我们有一个SecurityManager
可以确定是否允许特定路径的安全过滤器。以下示例显示了如何执行此操作:
SecurityManager securityManager = ...
RouterFunction<ServerResponse> route = route()
.path("/person", b1 -> b1
.nest(accept(APPLICATION_JSON), b2 -> b2
.GET("/{id}", handler::getPerson)
.GET(handler::listPeople))
.POST("/person", handler::createPerson))
.filter((request, next) -> {
if (securityManager.allowAccessTo(request.path())) {
return next.handle(request);
}
else {
return ServerResponse.status(UNAUTHORIZED).build();
}
})
.build();
URI 链接
本节介绍 Spring Framework 中可用于处理 URI 的各种选项。
UriComponents
Spring MVC 和 Spring WebFlux
UriComponentsBuilder
有助于从带有变量的 URI 模板构建 URI,如以下示例所示:
UriComponents uriComponents = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.build();
URI uri = uriComponents.expand("Westin", "123").toUri();
前面的示例可以合并为一个链并用 缩短buildAndExpand
,如以下示例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("Westin", "123")
.toUri();
您可以通过直接转到 URI(这意味着编码)来进一步缩短它,如以下示例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
您还可以使用完整的 URI 模板进一步缩短它,如以下示例所示:
URI uri = UriComponentsBuilder
.fromUriString("https://example.com/hotels/{hotel}?q={q}")
.build("Westin", "123");
UriBuilder
Spring MVC 和 Spring WebFlux
UriComponentsBuilder
实现UriBuilder
。UriBuilder
反过来,您可以 使用UriBuilderFactory
. 一起,UriBuilderFactory
并 UriBuilder
提供一种可插入的机制,以基于共享配置(例如基本 URL、编码首选项和其他详细信息)从 URI 模板构建 URI。
您可以配置RestTemplate
并WebClient
用 aUriBuilderFactory
来自定义编写 URI。DefaultUriBuilderFactory
是内部UriBuilderFactory
使用UriComponentsBuilder
并公开共享配置选项的默认实现。
以下示例显示了如何配置RestTemplate
:
// import org.springframework.web.util.DefaultUriBuilderFactory.EncodingMode;
String baseUrl = "https://example.org";
DefaultUriBuilderFactory factory = new DefaultUriBuilderFactory(baseUrl);
factory.setEncodingMode(EncodingMode.TEMPLATE_AND_VALUES);
RestTemplate restTemplate = new RestTemplate();
restTemplate.setUriTemplateHandler(factory);
也可以DefaultUriBuilderFactory
直接使用。它类似于使用 UriComponentsBuilder
,但不是静态工厂方法,而是一个包含配置和首选项的实际实例,如以下示例所示:
String baseUrl = "https://example.com";
DefaultUriBuilderFactory uriBuilderFactory = new DefaultUriBuilderFactory(baseUrl);
URI uri = uriBuilderFactory.uriString("/hotels/{hotel}")
.queryParam("q", "{q}")
.build("Westin", "123");
URI 编码
UriComponentsBuilder
在两个级别公开编码选项:
- UriComponentsBuilder#encode():先对 URI 模板进行预编码,然后在展开时对 URI 变量进行严格编码。
- UriComponents#encode() :在URI 变量展开后对URI 组件进行编码。
这两个选项都用转义的八位字节替换非 ASCII 和非法字符。但是,第一个选项也会替换 URI 变量中出现的具有保留含义的字符。
对于大多数情况,第一个选项可能会给出预期的结果,因为它将 URI 变量视为要完全编码的不透明数据,而如果 URI 变量确实包含保留字符,则第二个选项很有用。当根本不扩展 URI 变量时,第二个选项也很有用,因为这也会对任何看起来像 URI 变量的东西进行编码。
以下示例使用第一个选项:
URI uri = UriComponentsBuilder.fromPath("/hotel list/{city}")
.queryParam("q", "{q}")
.encode()
.buildAndExpand("New York", "foo+bar")
.toUri();
// Result is "/hotel%20list/New%20York?q=foo%2Bbar"
您还可以使用完整的 URI 模板进一步缩短它,如以下示例所示:
URI uri = UriComponentsBuilder.fromUriString("/hotel list/{city}?q={q}")
.build("New York", "foo+bar");
相对 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();
控制器链接
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();
异步请求
Spring MVC 与 Servlet 3.0 异步请求处理有广泛的集成 :
DeferredResult
控制器方法中的返回值和Callable
返回值为单个异步返回值提供了基本支持。- 控制器可以流式传输多个值,包括 SSE和原始数据。
- 控制器可以使用响应式客户端并返回 响应式类型以进行响应处理。
DeferredResult
在 Servlet 容器中启用异步请求处理功能 后,控制器方法可以使用 包装任何支持的控制器方法返回值DeferredResult
,如以下示例所示:
@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {
DeferredResult<String> deferredResult = new DeferredResult<String>();
// Save the deferredResult somewhere..
return deferredResult;
}
// From some other thread...
deferredResult.setResult(result);
控制器可以从不同的线程异步生成返回值——例如,响应外部事件(JMS 消息)、计划任务或其他事件。
Callable
控制器可以用 包装任何支持的返回值java.util.concurrent.Callable
,如以下示例所示:
@PostMapping
public Callable<String> processUpload(final MultipartFile file) {
return new Callable<String>() {
public String call() throws Exception {
// ...
return "someView";
}
};
}
然后可以通过 配置 TaskExecutor
的运行给定任务来获得返回值。
CORS
Spring MVC 允许您处理 CORS(跨源资源共享)。本节介绍如何执行此操作。
介绍
出于安全原因,浏览器禁止对当前来源之外的资源进行 AJAX 调用。
@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) {
// ...
}
}
全局配置
除了细粒度的控制器方法级别配置之外,您可能还想定义一些全局 CORS 配置。您可以CorsConfiguration
在任何HandlerMapping
. 然而,大多数应用程序使用 MVC Java 配置或 MVC XML 命名空间来执行此操作。
默认情况下,全局配置启用以下功能:
- 所有的起源。
- 所有标题。
GET
,HEAD
, 和POST
方法。
allowCredentials
默认情况下不启用,因为这会建立一个信任级别,该级别会公开敏感的用户特定信息(例如 cookie 和 CSRF 令牌),并且只能在适当的情况下使用。启用时,allowOrigins
必须将其设置为一个或多个特定域(但不是特殊值"*"
),或者该allowOriginPatterns
属性可用于匹配一组动态的来源。
maxAge
设置为 30 分钟。
Java 配置
要在 MVC Java 配置中启用 CORS,您可以使用CorsRegistry
回调,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://domain2.com")
.allowedMethods("PUT", "DELETE")
.allowedHeaders("header1", "header2", "header3")
.exposedHeaders("header1", "header2")
.allowCredentials(true).maxAge(3600);
// Add more mappings...
}
}
CORS 过滤器
您可以通过内置的 CorsFilter
.
如果您尝试使用CorsFilter
Spring Security,请记住 Spring Security 内置了 对 CORS 的支持。
要配置过滤器,请 传递 CorsConfigurationSource
给其构造函数,如以下示例所示:
CorsConfiguration config = new CorsConfiguration();
// Possibly...
// config.applyPermitDefaultValues()
config.setAllowCredentials(true);
config.addAllowedOrigin("https://domain1.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", config);
CorsFilter filter = new CorsFilter(source);
网络安全
Spring Security项目为保护 Web 应用程序免受恶意攻击提供了支持。
HDIV是另一个与 Spring MVC 集成的 Web 安全框架。
HTTP缓存
HTTP 缓存可以显着提高 Web 应用程序的性能。HTTP 缓存围绕Cache-Control
响应标头以及随后的条件请求标头(例如Last-Modified
和ETag
)。Cache-Control
建议私有(例如,浏览器)和公共(例如,代理)缓存如何缓存和重用响应。如果ETag
内容未更改,标头用于发出可能导致没有正文的 304 (NOT_MODIFIED) 的条件请求。ETag
可以看作是Last-Modified
头部的更复杂的继承者。
本节介绍 Spring Web MVC 中可用的与 HTTP 缓存相关的选项。
CacheControl
CacheControl
为配置与标头相关的设置提供支持,Cache-Control
并在许多地方被接受为参数:
虽然RFC 7234Cache-Control
描述了响应标头的所有可能指令,但该CacheControl
类型采用面向用例的方法,专注于常见场景:
// Cache for an hour - "Cache-Control: max-age=3600"
CacheControl ccCacheOneHour = CacheControl.maxAge(1, TimeUnit.HOURS);
// Prevent caching - "Cache-Control: no-store"
CacheControl ccNoStore = CacheControl.noStore();
// Cache for ten days in public and private caches,
// public caches should not transform the response
// "Cache-Control: max-age=864000, public, no-transform"
CacheControl ccCustom = CacheControl.maxAge(10, TimeUnit.DAYS).noTransform().cachePublic();
控制器
控制器可以添加对 HTTP 缓存的显式支持。我们建议这样做,因为需要先计算资源的 lastModified
或者ETag
值,然后才能将其与条件请求标头进行比较。控制器可以将ETag
标头和Cache-Control
设置添加到ResponseEntity`,如以下示例所示:
@GetMapping("/book/{id}")
public ResponseEntity<Book> showBook(@PathVariable Long id) {
Book book = findBook(id);
String version = book.getVersion();
return ResponseEntity
.ok()
.cacheControl(CacheControl.maxAge(30, TimeUnit.DAYS))
.eTag(version) // lastModified is also available
.body(book);
}
如果与条件请求标头的比较表明内容未更改,则前面的示例将发送带有空正文的 304 (NOT_MODIFIED) 响应。否则, ETag
和Cache-Control
标头将添加到响应中。
您还可以在控制器中检查条件请求标头,如以下示例所示:
@RequestMapping
public String myHandleMethod(WebRequest request, Model model) {
long eTag = ...
if (request.checkNotModified(eTag)) {
return null;
}
model.addAttribute(...);
return "myViewName";
}
有三种变体用于根据eTag
值、lastModified
值或两者检查条件请求。对于条件GET
和HEAD
请求,您可以将响应设置为 304 (NOT_MODIFIED)。对于条件POST
、PUT
和DELETE
,您可以改为将响应设置为 412 (PRECONDITION_FAILED),以防止并发修改。
静态资源
您应该使用带有Cache-Control
条件响应标头的静态资源来提供最佳性能。请参阅配置静态资源部分。
ETag
过滤器
您可以使用ShallowEtagHeaderFilter
添加从响应内容计算的“浅”eTag
值,从而节省带宽但不节省 CPU 时间。
MVC 配置
MVC Java 配置和 MVC XML 命名空间提供了适用于大多数应用程序的默认配置和一个配置 API 来定制它。
有关配置 API 中不可用的更高级自定义,请参阅高级 Java 配置和高级 XML 配置。
您不需要了解 MVC Java 配置和 MVC 命名空间创建的底层 bean。如果您想了解更多信息,请参阅特殊 Bean 类型 和Web MVC 配置。
启用 MVC 配置
在 Java 配置中,您可以使用@EnableWebMvc
注解来启用 MVC 配置,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig {
}
MVC 配置 API
在 Java 配置中,您可以实现该WebMvcConfigurer
接口,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
// Implement configuration methods...
}
类型转换
默认情况下,安装了各种数字和日期类型的格式化程序,并支持通过字段进行自@NumberFormat
定义@DateTimeFormat
。
要在 Java 配置中注册自定义格式化程序和转换器,请使用以下命令:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// ...
}
}
默认情况下,Spring MVC 在解析和格式化日期值时会考虑请求 Locale。这适用于日期表示为带有“输入”表单字段的字符串的表单。然而,对于“日期”和“时间”表单字段,浏览器使用 HTML 规范中定义的固定格式。对于这种情况,可以按如下方式自定义日期和时间格式:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
DateTimeFormatterRegistrar registrar = new DateTimeFormatterRegistrar();
registrar.setUseIsoFormat(true);
registrar.registerFormatters(registry);
}
}
验证
默认情况下,如果类路径中存在Bean Validation(例如,Hibernate Validator),则将LocalValidatorFactoryBean
其注册为全局Validator ,以便与控制器方法参数一起使用@Valid
和 Validated
在控制器方法参数上使用。
在 Java 配置中,您可以自定义全局Validator
实例,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public Validator getValidator() {
// ...
}
}
请注意,您还可以Validator
在本地注册实现,如以下示例所示:
@Controller
public class MyController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addValidators(new FooValidator());
}
}
拦截器
在 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/*");
}
}
内容类型
您可以配置 Spring MVC 如何从请求中确定请求的媒体类型(例如,Accept
标头、URL 路径扩展、查询参数等)。
默认情况下,仅Accept
检查标题。
如果您必须使用基于 URL 的内容类型解析,请考虑使用查询参数策略而不是路径扩展。有关更多详细信息,请参阅 后缀匹配和后缀匹配和 RFD。
在 Java 配置中,您可以自定义请求的内容类型解析,如以下示例所示:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.mediaType("json", MediaType.APPLICATION_JSON);
configurer.mediaType("xml", MediaType.APPLICATION_XML);
}
}
消息转换器
您可以通过覆盖 (替换 Spring MVC 创建的默认转换器)或覆盖 (自定义默认转换器或向默认转换器添加其他转换器)HttpMessageConverter
在 Java 配置中 进行自定义。
以下示例添加了自定义的 XML 和 Jackson JSON 转换器, ObjectMapper
而不是默认转换器:
@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()));
}
}
视图控制器
这是定义ParameterizableViewController
在调用时立即转发到视图的快捷方式。您可以在视图生成响应之前没有运行 Java 控制器逻辑的静态情况下使用它。
以下 Java 配置示例将请求转发/
到名为 的视图home
:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
registry.addViewController("/").setViewName("home");
}
}
视图解析器
MVC 配置简化了视图解析器的注册。
以下 Java 配置示例通过使用 JSP 和 Jackson 作为View
JSON 呈现的默认值来配置内容协商视图解析:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.enableContentNegotiation(new MappingJackson2JsonView());
registry.jsp();
}
}
静态资源
Resource
此选项提供了一种从基于位置的列表中提供静态资源的便捷方式 。
在下一个示例中,给定一个以 开头的请求,/resources
相对路径用于查找和提供相对于/public
Web 应用程序根目录下或类路径下 的静态资源/static
。这些资源的服务期限为一年,以确保最大限度地使用浏览器缓存并减少浏览器发出的 HTTP 请求。该Last-Modified
信息是从中推导出来的,Resource#lastModified
以便标头支持 HTTP 条件请求"Last-Modified"
。
以下清单显示了如何使用 Java 配置进行此操作:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**")
.addResourceLocations("/public", "classpath:/static/")
.setCacheControl(CacheControl.maxAge(Duration.ofDays(365)));
}
}
默认 Servlet
Spring MVC 允许映射DispatcherServlet
到/
(从而覆盖容器的默认 Servlet 的映射),同时仍然允许由容器的默认 Servlet 处理静态资源请求。它配置了 DefaultServletHttpRequestHandler
一个 URL 映射/**
和相对于其他 URL 映射的最低优先级。
此处理程序将所有请求转发到默认 Servlet。因此,它必须在所有其他 URL 的顺序中保持在最后HandlerMappings
。如果您使用<mvc:annotation-driven>
. 或者,如果您设置自己的自定义HandlerMapping
实例,请确保将其order
属性设置为低于 的值DefaultServletHttpRequestHandler
,即Integer.MAX_VALUE
.
以下示例显示如何使用默认设置启用该功能:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}
}
路径匹配
您可以自定义与路径匹配和 URL 处理相关的选项。
以下示例显示了如何在 Java 配置中自定义路径匹配:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void configurePathMatch(PathMatchConfigurer configurer) {
configurer
.setPatternParser(new PathPatternParser())
.addPathPrefix("/api", HandlerTypePredicate.forAnnotation(RestController.class));
}
private PathPatternParser patternParser() {
// ...
}
}
高级 Java 配置
@EnableWebMvc
进口DelegatingWebMvcConfiguration
,其中:
- 为 Spring MVC 应用程序提供默认 Spring 配置
- 检测并委托给
WebMvcConfigurer
实现以自定义该配置。
对于高级模式,您可以直接删除@EnableWebMvc
和扩展 from DelegatingWebMvcConfiguration
而不是实现WebMvcConfigurer
,如以下示例所示:
@Configuration
public class WebConfig extends DelegatingWebMvcConfiguration {
// ...
}