官网参考文档:https://docs.spring.io/spring-framework/docs/5.3.10-SNAPSHOT/reference/html/web.html#mvc
【1】Controller的声明
SpringMVC提供了一个基于注解的编程模型,带有@Controller
和@RestController
注解的组件使用注解来表示请求映射、请求输入、异常处理等。带注解的控制器具有灵活的方法签名,不必扩展基类,也不必实现特定的接口。
以下示例显示了由注解定义的控制器:
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
你可以通过在Servlet
的WebApplicationContext
中使用标准Spring bean
定义来定义控制器bean。@Controller
模式允许自动检测,这与Spring
对检测类路径中的@Component
类以及为它们自动注册bean定义的基本支持一致。它还充当带注解类的模式印象,指示其作为web组件的角色。
为了启动对@Controller
这些bean的自动扫描,你可以在java configuration中添加组件扫描。实例如下:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {
// ...
}
在xml中配置则如下:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="org.example.web"/>
<!-- ... -->
</beans>
@RestController
是一个组合注解,它本身由@Controller
和@ResponseBody
进行元注解,以指示其每个方法都继承类型级别@ResponseBody
注解的控制器。因此结果将直接写入响应体,而不是使用HTML模板进行视图解析和呈现。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {
@AliasFor(annotation = Controller.class)
String value() default "";
}
AOP代理
在某些情况下,可能需要在运行时用AOP代理修饰控制器。例如,如果您选择在控制器上直接使用@Transactional
注解。在这种情况下,特别是对于控制器,我们建议使用基于类的代理。这通常是控制器的默认选择。
但是,如果控制器必须实现一个不是Spring上下文回调的接口(例如InitializingBean
、*Aware
等),则可能需要显式配置基于类的代理。例如,使用<tx:annotation-driven/>
可以更改为<tx:annotation-driven proxy-target class=“true”/>
,使用@EnableTransactionManagement
可以更改为@EnableTransactionManagement(proxyTargetClass=true)
。
【2】请求映射
你可以使用@RequestMapping
注解将请求映射到控制器方法。它具有各种属性,可以与URL、HTTP方法、请求参数、头和媒体类型进行匹配。可以在类级别使用它来表示共享映射,或者在方法级别使用它来指定到特定的端点映射。
@RequestMapping
还有一些特定于HTTP方法的快捷方式变体:
- @GetMapping
- @PostMapping
- @PutMapping
- @DeleteMapping
- @PatchMapping
快捷方式是提供的自定义注解,因为大多数控制器方法都应该映射到特定的HTTP方法,而不是使用@RequestMapping
(默认情况下,它与所有HTTP方法都匹配)。同时,在类级别仍然需要@RequestMapping
来表示共享映射。
以下示例具有类和方法级别的映射:
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
① URI匹配规则
可以使用URL模式映射@RequestMapping
注解的方法。有两种选择:
- PathPattern 路径模式 — 与URL路径匹配的预解析模式(预解析为PathContainer)。此解决方案专为web使用而设计,可有效处理编码和路径参数,并进行高效匹配。
- AntPathMatcher 蚂蚁匹配器 — 根据字符串路径匹配字符串模式。这是最初的解决方案,也在Spring配置中用于选择类路径、文件系统和其他位置上的资源。它的效率较低,而且字符串路径输入对于有效处理URL的编码和其他问题是一个挑战。
PathPattern
是web应用程序的推荐解决方案,也是Spring WebFlux
中的唯一选择。在版本5.3之前,AntPathMatcher
是SpringMVC中唯一的选择,并且仍然是默认的。但是,可以在MVC配置中启用PathPattern
。
PathPattern
支持与AntPathMatcher
相同的模式语法。此外,它还支持捕获模式,例如{*spring
},用于在路径末尾匹配0个或多个路径段。PathPattern
还限制使用**
来匹配多个路径段,因此只允许在模式的末尾使用**
。
这消除了为给定请求选择最佳匹配模式时出现的许多模糊情况。有关完整的模式语法,请参阅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变量:
@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 and DataBinder.
您可以显式地命名URI变量(例如@PathVariable(“customId”)
),但如果名称相同,并且您的代码是使用调试信息或Java 8上的-parameters
编译器标志编译的,则可以省略该细节。
语法{varName:regex}
使用语法为{varName:regex}
的正则表达式声明URI变量。例如,给定URL“/spring-web-3.0.5.jar”
,以下方法提取名称、版本和文件扩展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}")
public void handle(@PathVariable String name, @PathVariable String version, @PathVariable String ext) {
// ...
}
URI路径模式也可以具有嵌入的${…}
占位符,通过对本地、系统、环境和其他属性源使用PropertyPlaceHolderConfigure在启动时解析。例如,您可以使用它根据一些外部配置参数化基本URL。
② 匹配规则(模式)比较
当多个模式匹配一个URL时,必须选择最佳匹配。根据是否启用了对已解析PathPattern的使用,可以通过以下方式之一执行此操作:
-
PathPattern.SPECIFICITY_COMPARATOR
-
AntPathMatcher.getPatternComparator(String path)
这两种方法都有助于对模式进行排序,并将更具体的模式放在顶部。如果一个模式的URI变量(计为1)、单通配符(计为1)和双通配符(计为2)
的计数较低,那么它就不那么具体。如果分数相等,则选择较长的匹配模式。给定相同的分数和长度,将选择URI变量多于通配符的模式
。
默认映射模式(/**
)被排除在评分之外,并且总是最后排序。此外,前缀模式(例如/public/**
)被认为比其他没有双通配符的模式更不具体。
③ 后缀匹配
从5.3开始,默认情况下SpringMVC不再执行.*
后缀模式匹配,其中映射到/person
的控制器也隐式映射到/person.*
。因此,路径扩展不再用于解释请求的响应内容类型 — 例如,/person.pdf、/person.xml
等等。
当浏览器用来发送难以一致解释的接受头时,以这种方式使用文件扩展名是必要的。目前,这不再是必要的,使用Accept头应该是首选。
随着时间的推移,文件扩展名的使用在许多方面被证明是有问题的。当覆盖使用URI变量、路径参数和URI编码时,可能会导致歧义。关于基于URL的授权和安全性的推理(详见下一节)也变得更加困难。
要在5.3之前的版本中完全禁用路径扩展的使用,请设置以下选项:
-
useSuffixPatternMatching(false), 参考PathMatchConfigurer
-
favorPathExtension(false), 参考ContentNegotiationConfigurer
在浏览器中键入URL时,使用除“Accept”请求头之外的其他方式请求内容类型仍然很有用。路径扩展的安全替代方法是使用查询参数策略
。如果必须使用文件扩展,请考虑通过ContentNegotiationConfigurer配置器的mediaType属性将它们限制为显式注册的扩展列表。
④ 后缀匹配和RFD攻击
反射文件下载(RFD)攻击与XSS类似,因为它依赖于响应中反映的请求输入(例如,查询参数和URI变量)。但是,RFD攻击不是将JavaScript插入HTML,而是依赖浏览器切换来执行下载,并在稍后双击时将响应视为可执行脚本。
在SpringMVC中,@ResponseBody和ResponseEntity
方法存在风险,因为它们可以呈现不同的内容类型,客户端可以通过URL路径扩展请求这些内容类型。禁用后缀模式匹配和使用路径扩展进行内容协商可以降低风险,但不足以防止RFD攻击。
为了防止RFD攻击,在呈现响应主体之前,SpringMVC添加了一个Content-Disposition:inline;filename=f.txt
响应头,建议使用固定的安全下载文件。仅当URL路径包含既不允许安全也不明确注册用于内容协商的文件扩展名时,才能执行此操作。但是,当URL直接输入到浏览器中时,它可能会有潜在的副作用。
默认情况下,许多公共路径扩展都是安全的。具有自定义HttpMessageConverter实现的应用程序可以显式注册用于内容协商的文件扩展名,以避免为这些扩展添加Content-Disposition
响应头。
⑤ 可支持的Media Type(Content-Type)
您可以根据请求的内容类型缩小请求映射范围,如下例所示(表示只接受application/json类型请求):
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
consumes
属性还支持否定表达式 — 例如!text/plain
是指除text/plain
以外的任何内容类型。
您可以在类级别声明共享的consumes
属性。但是,与大多数其他请求映射属性不同,当在类级别使用时,方法级别使用consumes
属性重写
而不是扩展
类级别声明的consumes
。
⑥ 返回的Media Type(Content-Type)
您可以根据Accept请求头和控制器方法生成的内容类型列表缩小请求映射范围,如下例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
媒体类型可以指定字符集。支持否定表达式 — 例如!text/plain
是指除text/plain
以外的任何内容类型。
您可以在类级别声明共享的produces
属性。但是,与大多数其他请求映射属性不同,当在类级别使用时,方法级别使用produces
属性重写
而不是扩展
类级别声明的produces
。
⑦参数和请求头
您可以根据请求参数条件缩小请求映射范围。您可以测试是否存在请求参数(myParam
),是否缺少一个(!myParam
),或针对特定值(myParam=myValue
)。以下示例显示了如何测试特定值:
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
您还可以将其用于请求头条件,如下例所示:
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
⑧ HTTP HEAD, OPTIONS
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)透明地支持HTTP头进行请求映射。控制器方法不需要更改。javax.servlet.http.HttpServlet中
应用的响应包装器确保将Content-Length
头设置为写入的字节数(而不是实际写入响应)。
@GetMapping
(和@RequestMapping(method=HttpMethod.GET)
)隐式映射并支持HTTP头。HTTP头请求的处理方式与HTTP GET的处理方式相同,只是不写入body,而是计算字节数并设置Content-Length
头。
默认情况下,通过将Allow
响应头设置为所有@RequestMapping
方法中列出的HTTP方法列表(具有匹配的URL模式)来处理HTTP OPTIONS
。
对于没有HTTP方法声明的@RequestMapping
,Allow
头设置为GET、HEAD、POST、PUT、PATCH、DELETE和OPTIONS
。控制器方法应始终声明受支持的HTTP方法(例如,通过使用HTTP方法特定的变形体:@GetMapping
、@PostMapping
和其他)。
⑨ 自定义注解
SpringMVC支持使用组合注解进行请求映射。这些注解本身是使用@RequestMapping进行元注解的,它们的组合目的是重新声明@RequestMapping
属性的一个子集(或全部),具有更窄、更具体的用途。
@GetMapping、@PostMapping、@PutMapping、@DeleteMapping
和@PatchMapping
是组合注解的示例。之所以提供它们,是因为大多数控制器方法都应该映射到特定的HTTP方法,而不是使用@RequestMapping
。默认情况下,@RequestMapping与所有HTTP方法都匹配。
SpringMVC还支持具有自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要继承RequestMappingHandlerMapping
并重写getCustomMethodCondition
方法,在该方法中,您可以检查自定义属性并返回自己的RequestCondition
。
⑩ 显式注册
您可以通过编程方式注册处理程序方法,这些方法可用于动态注册或高级情况,例如同一处理程序在不同URL下的不同实例。以下示例注册处理程序方法:
@Configuration
public class MyConfig {
@Autowired
public void setHandlerMapping(RequestMappingHandlerMapping mapping, UserHandler handler)
throws NoSuchMethodException {
RequestMappingInfo info = RequestMappingInfo
.paths("/user/{id}").methods(RequestMethod.GET).build();
Method method = UserHandler.class.getMethod("getUser", Long.class);
mapping.registerMapping(info, handler, method);
}
}
- 为控制器注入目标处理程序和处理程序映射。
- 准备请求映射元数据。
- 获取处理程序方法。
- 添加注册。
【3】处理器方法
@RequestMapping
处理程序方法具有灵活的签名,可以从一系列受支持的控制器方法参数和返回值中进行选择。
① 方法参数
下表描述了支持的控制器方法参数。任何参数都不支持Reactive
类型。JDK8的java.util.Optional
作为方法参数与包含required
属性的注解(如,@RequestParam
, @RequestHeader
及其他)结合是受支持的,其等效于required=false.
方法参数 | 描述 |
---|---|
WebRequest, NativeWebRequest | 对request parameters and request and session attributes 请求参数、请求和会话属性的通用访问,无需直接使用Servlet API。 |
javax.servlet.ServletRequest, javax.servlet.ServletResponse | 选择任何特定的请求或响应类型—例如, ServletRequest, HttpServletRequest, or Spring’s MultipartRequest, MultipartHttpServletRequest . |
javax.servlet.http.HttpSession | 强制会话的存在。因此,这样的参数永远不会为空。请注意,会话访问不是线程安全的。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession 标记设置为true。 |
javax.servlet.http.PushBuilder | Servlet 4.0 中关于HTTP/2资源推送的API。需要注意的是,如果客户端不支持HTTP/2特性,则PushBuilder 可能为空。 |
java.security.Principal | 当前经过身份验证的用户 — 可能是一个特定的Principal 实现类(如果已知)。请注意,如果对该参数进行注解是为了允许自定义解析程序在通过HttpServletRequest#getUserPrincipal 返回默认解析之前解析该参数,则该参数不会立即解析。例如,Spring Security Authentication 实现了Principal 可以通过 HttpServletRequest#getUserPrincipal 注入,除非它被 @AuthenticationPrincipal 注解标识。在这种情况下,它可以通过 Authentication#getPrincipal 被一个自定义的 Spring Security 解析器解析。 |
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 模板/路径变量 |
@MatrixVariable | 用于访问URI路径段中的name-value 对 |
@RequestParam | 用于访问Servlet请求中的参数,包括multipart files。参数值将会被转换为声明的方法参数类型。对于简单参数,@RequestParam 是可选的。 |
@RequestHeader | 获取请求头中的值,其值将会被转换为声明的方法参数类型 |
@CookieValue | 获取cookie,值将会被转换为声明的方法参数类型 |
@RequestBody | 获取HTTP请求体。请求体内容将会被HttpMessageConverter 实现转换为方法声明的参数类型。 |
HttpEntity<B> | 获取HTTP请求头和请求体。请求体将会被HttpMessageConverter 转换。 |
@RequestPart | 获取multipart/form-data 请求的一部分,body被HttpMessageConverter 转换 |
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap | 获取model,model在HTML控制器中使用并作为视图渲染的一部分暴露给模板。 |
RedirectAttributes | 指定重定向时要使用的属性(即,要附加到查询字符串中)和在重定向后的请求之前要临时存储的闪存属性。 |
@ModelAttribute | 用于访问Model中的现有属性(如果不存在则实例化),并应用数据绑定和验证。 |
Errors, BindingResult | 用于访问命令对象(即@ModelAttribute 参数)的校验和数据绑定错误,或@RequestBody或@RequestPart参数校验错误。必须在校验的方法参数之后立即声明Errors或BindingResult参数。 |
SessionStatus + class-level @SessionAttributes | 用于标记表单处理完成,这将触发清理通过类级别@SessionAttributes 注解声明的会话属性 |
UriComponentsBuilder | 用于准备相对于当前请求的主机、端口、协议、上下文路径和servlet映射的文本部分的URL。 |
@SessionAttribute | 用于访问任何会话属性,这与由于类级别@SessionAttributes 声明而存储在会话中的Model属性不同。 |
@RequestAttribute | 获取请求属性request attributes |
其他参数 | 如果方法参数不匹配上面的任何一种,简单类型则作为@RequestParam 被解析,复杂类型作为@ModelAttribute 被解析。简单类型有a primitive or primitive wrapper, an enum, a String or other CharSequence, a Number, a Date, a Temporal, a URI, a URL, a Locale, or a Class |
② 返回值
下表描述了支持的控制器方法返回值。所有返回值都支持Reactive 类型。
A view name to be resolved with ViewResolver implementations and used together with the implicit model — determined through command objects and @ModelAttribute methods. The handler method can also programmatically enrich the model by declaring a Model argument
方法返回值 | 描述 |
---|---|
@ResponseBody | 方法返回值被HttpMessageConverter 实现转换并写入到response |
HttpEntity<B>, ResponseEntity<B> | 指定完整响应(包括HTTP头和正文)的返回值将通过HttpMessageConverter 实现转换并写入响应response。 |
HttpHeaders | 返回的响应只有header,没有body |
String | 返回的视图名称,将会被ViewResolver 实现解析,通常与隐形Model一起使用(Model通过@ModelAttribute 方法和命令对象确定)。处理程序方法同样可以声明一个Model类型参数来丰富Model数据 |
View | 一个将和Model一起渲染的视图实例。隐形Model通过@ModelAttribute 方法和命令对象确定,处理程序方法同样可以声明一个Model类型参数来丰富Model数据 |
java.util.Map, org.springframework.ui.Model | 要添加到隐式Model的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute | 要添加到模型中的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
ModelAndView object | 要使用的View和Model属性,以及(可选)响应状态response status。 |
void | 如果具有void返回类型(或null返回值)的方法还具有ServletResponse 、OutputStream 参数或@ResponseStatus 注解,则认为该方法已完全处理响应。如果控制器进行了积极的ETag或lastModified 时间戳检查,也同样如此。如果上述条件均不成立,则void返回类型还可以指示REST控制器的“无响应主体”,或HTML控制器的默认视图名称选择。 |
DeferredResult<V> | 从任何线程异步生成之前的任何返回值 — 例如,作为某个事件或回调的结果 |
Callable<V> | 在SpringMVC托管线程中异步生成上述任何返回值。 |
ListenableFuture<V>, java.util.concurrent.CompletionStage<V>, java.util.concurrent.CompletableFuture<V> | 作为DelferredResult的替代方案,以方便起见(例如,当底层服务返回其中一个时)。 |
ResponseBodyEmitter, SseEmitter | 通过HttpMessageConverter实现异步发出要写入响应的对象流。也被作为ResponseEntity的主体。 |
StreamingResponseBody | 异步写入响应OutputStream 。也被作为ResponseEntity的body。 |
Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistry | Alternative to DeferredResult with multi-value streams (for example, Flux, Observable) collected to a List.For streaming scenarios (for example, text/event-stream, application/json+stream), SseEmitter and ResponseBodyEmitter are used instead, where ServletOutputStream blocking I/O is performed on a Spring MVC-managed thread and back pressure is applied against the completion of each write. |
其他返回值 | 如果返回值与此表中的任何值不匹配且为字符串或空值,则将其视为视图名称(通过RequestToViewNameTranslator 选择的默认视图名称适用),前提是它不是由BeanUtils#isSimpleProperty 确定的简单类型。简单类型的值仍然无法解析。 |
③ 类型转换
某些表示基于字符串的请求输入的带注解的控制器方法参数(如@RequestParam、@RequestHeader、@PathVariable、@MatrixVariable和@CookieValue
)如果声明为字符串以外的内容,则可能需要进行类型转换。
对于这种情况,类型转换将根据配置的转换器自动应用。默认支持int, long, Date和其他简单类型。你可以通过WebDataBinder 或者注册Formatters
到FormattingConversionService
自定义类型转换。
类型转换中的一个实际问题是空字符串源值的处理。如果由于类型转换而变为null,则此类值将被视为缺少。对于Long、UUID和其他目标类型,情况可能就是这样。如果要允许注入null,请在参数注释上使用必需的标志,或者将参数声明为@Nullable
。
从5.3开始,即使在类型转换之后,也将强制执行非空参数(不允许为空)。如果处理程序方法也打算接受null
值,要么将参数声明为@Nullable
,要么设置 required=false
在类似于@RequestParam
注解中。这是一个最佳实践,也是5.3升级中遇到的回归的推荐解决方案。
或者,您也可以在需要
@PathVariable
的情况下专门处理,例如结果MissingPathVariableException。转换后的空值将被视为空的原始值,因此相应的缺少值…将抛出异常变量。
④ Matrix Variables
RFC 3986讨论了路径段中的name-value
对。在SpringMVC中,我们根据TimBerners-Lee的“旧帖子”将它们称为“矩阵变量
”,但它们也可以称为URI路径参数
。
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如,/cars;color=red,green;year=2012
)。还可以通过重复的变量名指定多个值(例如,color=red;color=green;color=blue
)。
如果URL预期包含矩阵变量,则控制器方法的请求映射必须使用URI变量来屏蔽该变量内容,并确保能够成功匹配请求,而不依赖于矩阵变量的顺序和存在。以下示例使用矩阵变量:
// GET /pets/42;q=11;r=22
@GetMapping("/pets/{petId}")
public void findPet(@PathVariable String petId, @MatrixVariable int q) {
// petId == 42
// q == 11
}
考虑到所有路径段都可能包含矩阵变量,您有时可能需要消除矩阵变量预期位于哪个路径变量中的歧义。以下示例显示了如何执行此操作:
// GET /owners/42;q=11/pets/21;q=22
@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable(name="q", pathVar="ownerId") int q1,
@MatrixVariable(name="q", pathVar="petId") int q2) {
// q1 == 11
// q2 == 22
}
矩阵变量可定义为可选,并指定默认值,如下例所示:
// 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配置中,需要通过Path Matching
将UrlPathHelper
设置为removeSemicolonContent=false
。在MVC XML名称空间中,可以设置<mvc:annotation-driven enable-matrix-variables="true"/>
。
⑤ @RequestParam
可以使用@RequestParam
注解绑定Servlet 请求中的参数(query parameters or form data
)到控制器方法参数。使用实例如下:
@Controller
@RequestMapping("/pets")
public class EditPetForm {
// ...
@GetMapping
public String setupForm(@RequestParam("petId") int petId, Model model) {
Pet pet = this.clinic.loadPet(petId);
model.addAttribute("pet", pet);
return "petForm";
}
// ...
}
默认情况下,@RequestParam
注解的参数是必须存在的,但是你可以选择设置其属性required =false
,或者使用java.util.Optional
包装参数。
如果目标方法参数类型不是String,则类型转换器将自动工作去进行类型转换。
将参数类型声明为数组
或列表
允许解析一参数名的多个参数值(如id=1&id=2)。
当@RequestParam
注解声明为Map<String,String>
或MultiValueMap<String,String>
且未在注解中指定参数名称时,将使用每个给定参数name
的请求参数值
填充该Map。
请注意,使用@RequestParam
是可选的(例如,设置其属性)。默认情况下,任何简单值类型(由BeanUtils#isSimpleProperty
确定)且未由任何其他参数解析程序解析的参数都将被视为使用@RequestParam
注解。
⑥ @RequestHeader
可以使用@RequestHeader
注解绑定请求头到控制器方法参数。
考虑以下请求头:
Host localhost:8080
Accept text/html,application/xhtml+xml,application/xml;q=0.9
Accept-Language fr,en-gb;q=0.7,en;q=0.3
Accept-Encoding gzip,deflate
Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7
Keep-Alive 300
下面实例获取请求头Accept-Encoding
和 Keep-Alive
值:
@GetMapping("/demo")
public void handle(
@RequestHeader("Accept-Encoding") String encoding,
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}
如果目标方法参数类型不是String,则类型转换器将自动工作去进行类型转换。
当@RequestHeader
注解作用于Map<String, String>, MultiValueMap<String, String>, or HttpHeaders
类型的参数上时,map将会被所有请求头填充。
内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。例如,用@RequestHeader(“Accept”)
注解的方法参数可以是String
类型,也可以是String[]
或List<String>
。
⑦ @CookieValue
可以使用@CookieValue
注解绑定HTTP cookie到控制器方法参数。
如下所示一个请求带有cookie:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
下面实例标明如何获取cookie值:
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}
如果目标方法参数类型不是String,则类型转换器将自动工作去进行类型转换。
⑧ @ModelAttribute
可以使用在方法参数上使用@ModelAttribute
注解来获取Model中属性或者不存在时进行初始化。Model属性被HTTP Servlet请求参数的值覆盖(这些参数的名称与字段名称匹配)。这称为数据绑定,它使您不必解析和转换单个查询参数和表单字段。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute Pet pet) {
// method logic...
}
上述pet实例将通过如下方式获取:
- 从可能已由
@ModelAttribute
方法添加的Model中检索。 - 如果Model属性在类级别@SessionAttributes注解中存在,则从HTTP session中检索。
- 通过转换器Converter获得,其中Model属性名称与请求值(如路径变量或请求参数)的名称匹配。
- 使用默认构造器进行实例化。
- 通过“
主构造函数primary constructor
”实例化,参数与Servlet请求参数匹配。参数名称通过JavaBeans的@ConstructorProperties注解或字节码中运行时保留的参数名称确定。
使用 @ModelAttribute
方法来提供它或依赖框架来创建模型属性的一种替代方法是使用Converter<String, T>
来提供实例。当模型属性名称与请求值(如路径变量或请求参数)的名称匹配,并且存在从字符串到模型属性类型的转换器Converter 时,将应用此选项。在下面的示例中,模型属性名称是account,它与URI路径变量account匹配,并且有一个注册的转换器Converter<String, Account>
,它可以从数据存储加载帐户:
@PutMapping("/accounts/{account}")
public String save(@ModelAttribute("account") Account account) {
// ...
}
获取模型属性实例后,应用数据绑定。WebDataBinder
类将Servlet请求参数名称(查询参数和表单字段)与目标对象上的字段名称相匹配。必要时,在应用类型转换后填充匹配字段。有关数据绑定(和验证)的更多信息,请参阅Validation。有关自定义数据绑定的详细信息,请参阅DataBinder。
数据绑定可能会导致错误。默认情况下,会引发BindException
。但是,要检查controller方法中是否存在此类错误,可以在@ModelAttribute
旁边添加BindingResult
参数(位置一定紧挨),如下例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
在某些情况下,您可能希望在不进行数据绑定的情况下访问模型属性。对于这种情况,您可以将模型注入控制器并直接访问它,或者设置@ModelAttribute(binding=false)
,如下例所示:
@ModelAttribute
public AccountForm setUpForm() {
return new AccountForm();
}
@ModelAttribute
public Account findAccount(@PathVariable String accountId) {
return accountRepository.findOne(accountId);
}
@PostMapping("update")
public String update(@Valid AccountForm form, BindingResult result,
@ModelAttribute(binding=false) Account account) {
// ...
}
通过添加javax.validation.Valid
注解或Spring
的@Validated
注解(Bean验证和Spring验证),可以在数据绑定后自动应用验证。以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit")
public String processSubmit(@Valid @ModelAttribute("pet") Pet pet, BindingResult result) {
if (result.hasErrors()) {
return "petForm";
}
// ...
}
请注意,使用@ModelAttribute
是可选的(例如,设置其属性)。默认情况下,任何不是简单值类型(由 BeanUtils#isSimpleProperty
确定)且未由任何其他参数解析程序解析的参数都将被视为使用@ModelAttribute
注解。
@Validated
其实JSR-303
的(javax.validation.Valid)的变体,支持验证组的规范。专为方便使用而设计,支持Spring的JSR-303,但不支持JSR-303
⑨ @SessionAttributes
@SessionAttributes
用于在请求之间的HTTP Servlet session中存储模型属性。它是一个类型级注解,用于声明特定控制器使用的会话属性。这通常会列出model属性的名称或model属性的类型,这些属性应显示地存储在会话中,以供后续请求访问。
下面实例展示了@SessionAttributes
注解使用:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}
在第一个请求中,当一个名为pet的model属性被添加到model中时,它会自动升级到HTTP Servlet session并保存在该会话中。在另一个控制器方法使用SessionStatus
方法参数清除存储之前,它将一直保留,如下例所示:
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
@ModelAttribute
public void addPet(Model model){
model.addAttribute("pet",new Pet());
}
@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete();
// ...
}
}
}
- 在Servlet session中存储Pet
- 从Servlet session中清除Pet
什么时候清除sessionAttributesHandler
中的属性呢?在调用目标方法后获取ModelAndView时,会调用ModelFactory.updateModel
方法。如下所示,如果判断当前请求被处理那么就会尝试清理sessionAttributesHandler
中的属性-如上面示例的pet。
public void updateModel(NativeWebRequest request, ModelAndViewContainer container) throws Exception {
ModelMap defaultModel = container.getDefaultModel();
if (container.getSessionStatus().isComplete()){
this.sessionAttributesHandler.cleanupAttributes(request);
}
else {
this.sessionAttributesHandler.storeAttributes(request, defaultModel);
}
if (!container.isRequestHandled() && container.getModel() == defaultModel) {
updateBindingResult(request, defaultModel);
}
}
⑩ @SessionAttribute
如果您需要访问全局管理的预先存在的会话属性(即控制器外部) — 例如,通过过滤器),并且可能存在也可能不存在,您可以对方法参数使用@SessionAttribute
注解,如下例所示:
@RequestMapping("/")
public String handle(@SessionAttribute User user) {
// ...
}
对于需要添加或删除会话属性的情况,考虑将org.springframework.web.context.request.WebRequest
或 javax.servlet.http.HttpSession
会话注入到控制器方法中。为了在会话中临时存储model属性作为控制器工作流的一部分,请考虑使用@SessionAttributes。
11 @RequestAttribute
与@SessionAttribute
类似,您可以使用@RequestAttribute
注解访问先前创建的预先存在的请求属性(例如,通过Servlet过滤器或HandlerInterceptor
创建的请求属性):
@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}
12 Redirect Attributes
默认情况下,所有模型属性都被视为在重定向URL中作为URI模板变量暴露。在其余的属性中,基本类型或基本类型的集合或基本类型的数组将自动附加为查询参数。
如果专门为重定向准备了一个模型实例,那么将基本类型属性作为查询参数附加可能是理想的结果。但是,在带注解的控制器中,模型可以包含为渲染目的添加的其他属性(例如,下拉字段值)。为了避免此类属性出现在URL中,@RequestMapping方法可以声明类型为RedirectAttributes
的参数,并使用它指定可供RedirectView
使用的确切属性。如果方法确实重定向,则使用RedirectAttributes
的内容。否则,将使用model的内容。
RequestMappingHandlerAdapter
提供了一个标记叫做ignoreDefaultModelOnRedirect
,可以用来声明如果控制器方法重定向时,默认的Model是否使用。RequestMappingHandlerAdapter
提供了一个名为ignoreDefaultModelOnRedirect
的标志,您可以使用该标志指示如果控制器方法重定向,则不应使用默认模型的内容。相反,控制器方法应该声明RedirectAttributes
类型的属性,如果不这样做,则不应该将任何属性传递给RedirectView
。MVC命名空间和MVC Java配置都将此标志设置为false,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true。
请注意,当前请求中的URI模板变量在扩展重定向URL时自动可用,您不需要通过模型或重定向属性显式添加它们。以下示例显示如何定义重定向:
@PostMapping("/files/{path}")
public String upload(...) {
// ...
return "redirect:files/{path}";
}
向重定向目标传递数据的另一种方法是使用flash属性。与其他重定向属性不同,flash属性保存在HTTP会话中(因此不会出现在URL中)。
13 Flash Attributes
Flash属性为一个请求提供了一种方法来存储打算在另一个请求中使用的属性。这是重定向时最常用的 — 例如Post-Redirect-Get
模式。闪存属性在重定向之前(通常在会话中)临时保存,以便在重定向后可用于请求,之后会被删除。
SpringMVC有两个主要的类来支持flash属性。FlashMap类用于保存flash属性,而FlashMapManager接口的实例用于存储、检索和管理FlashMap实例。
默认支持Flash 属性,你不需要显示进行启用。但是如果不使用,它不会导致HTTP会话创建。在每个请求上,都有一个 “input” FlashMap
和一个“output” FlashMap
,前者具有从上一个请求(如果有)传递的属性,后者具有为后续请求保存的属性。这两个FlashMap实例都可以通过RequestContextUtils
中的静态方法从SpringMVC中的任何位置访问。
带注解的控制器通常不需要直接使用FlashMap
。相反,@RequestMapping
注解的方法可以接受RedirectAttributes
类型的参数,并使用它为重定向场景添加flash属性。通过RedirectAttributes 添加的Flash属性会自动传播到“output
”FlashMap。类似地,在重定向之后,“input
”FlashMap中的属性会自动添加到为目标URL提供服务的控制器的Model中。
为flash 属性匹配请求
flash属性的概念存在于许多其他web框架中,并且已经证明有时会遇到并发问题。这是因为,根据定义,闪存属性将存储到下一个请求。但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,flash属性被过早删除。
为了减少出现此类问题的可能性,RedirectView
会使用目标重定向URL的路径和查询参数自动“标记
”FlashMap
实例。反过来,默认FlashMapManager
在查找“input
”FlashMap
时将该信息与传入请求相匹配。
这并不能完全消除并发问题的可能性,但可以通过重定向URL中已有的信息大大减少并发问题。因此,我们建议您主要在重定向场景中使用flash属性。
14 Multipart
启用MultipartResolver
后,将解析POST(content-type=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";
}
}
如果参数类型声明为List<MultipartFile>
,则可以解析具有相同名字的多个multiple files
。如果@RequestParam
注解声明为Map<String,MultipartFile>
或MultiValueMap<String,MultipartFile>
,且注解中未指定参数名称,则会使用每个给定参数名称的 multipart files
填充该Map。
使用Servlet3.0 multipart
解析,您还可以将javax.Servlet.http.Part
声明为方法参数或集合值类型,而不是Spring’s MultipartFile
。
您还可以将multipart
内容用作到命令对象的数据绑定的一部分。例如,前面示例中的表单字段和文件可以是表单对象上的字段,如下例所示:
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 服务场景中,也可以从非浏览器客户端提交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
作为字符串访问“meta-data
”部分,但您可能希望它从JSON反序列化(类似于@RequestBody
)。在使用HttpMessageConverter
转换multipart
后,使用@RequestPart
注释访问multipart
:
@PostMapping("/")
public String handle(@RequestPart("meta-data") MetaData metadata,
@RequestPart("file-data") MultipartFile file) {
// ...
}
您可以将@RequestPart
与javax.validation.Valid
结合使用,或者使用Spring
的@Validated
注解,这两种注解都会应用Standard Bean Validation验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,该异常会转换为400(BAD_REQUEST
)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内本地处理验证错误,如下例所示:
@PostMapping("/")
public String handle(@Valid @RequestPart("meta-data") MetaData metadata,
BindingResult result) {
// ...
}
15 @RequestBody
你可以使用@RequestBody注解获取请求体并通过HttpMessageConverter将其反序列化到Object对象。
您可以使用@RequestBody注释,通过HttpMessageConverter将请求body读取并反序列化为对象。以下示例使用@RequestBody参数:
@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}
您可以使用MVC Config的 Message Converters选项来配置或自定义消息转换。
您可以将@RequestBody
与javax.validation.Valid
结合使用,或者使用Spring
的@Validated
注解,这两种注解都会应用Standard Bean Validation验证。默认情况下,验证错误会导致MethodArgumentNotValidException
,该异常会转换为400(BAD_REQUEST
)响应。或者,您可以通过Errors
或BindingResult
参数在控制器内本地处理验证错误,如下例所示:
@PostMapping("/accounts")
public void handle(@Valid @RequestBody Account account, BindingResult result) {
// ...
}
16 HttpEntity
HttpEntity
与使用@RequestBody
大致相同,但基于暴露请求头和请求体的容器对象。下面的列表显示了一个示例:
@PostMapping("/accounts")
public void handle(HttpEntity<Account> entity) {
// ...
}
HttpEntity主要属性和构造方法
public class HttpEntity<T> {
//The empty {@code HttpEntity}, with no body or headers.
public static final HttpEntity<?> EMPTY = new HttpEntity<>();
//请求头 响应头
private final HttpHeaders headers;
//请求体 响应体
@Nullable
private final T body;
//创建一个空的HttpEntity
protected HttpEntity() {
this(null, null);
}
//使用body创建HttpEntity,此时headers为null
public HttpEntity(T body) {
this(body, null);
}
//使用headers创建HttpEntity,此时body为null
public HttpEntity(MultiValueMap<String, String> headers) {
this(null, headers);
}
//使用body和headers创建HttpEntity
public HttpEntity(@Nullable T body, @Nullable MultiValueMap<String, String> headers) {
this.body = body;
this.headers = HttpHeaders.readOnlyHttpHeaders(headers != null ? headers : new HttpHeaders());
}
//...
}
其类继承树图示如下
与template整合使用
//POST
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
HttpEntity<String> entity = new HttpEntity<String>(helloWorld, headers);
URI location = template.postForLocation("https://example.com", entity);
//GET
HttpEntity<String> entity = template.getForEntity("https://example.com", String.class);
String body = entity.getBody();
MediaType contentType = entity.getHeaders().getContentType();
作为SpringMVC中方法的返回值
@RequestMapping("/handle")
public HttpEntity<String> handle() {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.set("MyResponseHeader", "MyValue");
return new HttpEntity<String>("Hello World", responseHeaders);
}
17 @ResponseBody
可以在方法上使用@ResponseBody
注解,通过HttpMessageConverter
将返回序列化到响应体。下面的列表显示了一个示例:
@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}
在类级别上也支持@ResponseBody
,在这种情况下,它由所有控制器方法继承。这就是@RestController的效果,它只不过是一个用@Controller
和@ResponseBody
标记的元注解。
你可以将@ResponseBody与reactive 类型一起使用,更多参考 Asynchronous Requests和 Reactive Types。
你可以使用MVC Config的Message Converters选项配置或者自定义信息转换。
您可以将@ResponseBody方法与JSON序列化视图相结合。
18 ResponseEntity
ResponseEntity
类似于@ResponseBody
,但有Status
和header
。例如:
@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).build(body);
}
Spring MVC支持使用单值反应类型异步生成响应,和/或为主体生成单值和多值反应类型。这允许以下类型的异步响应:
ResponseEntity<Mono<T>>
或ResponseEntity<Flux<T>>
可在稍后异步提供主体时立即了解响应状态和头。如果主体由0..1
个值组成,则使用Mono
;如果主体可以生成多个值,则使用Flux
。Mono<ResponseEntity<T>>
提供了这三种功能 —response status, headers, and body
。这允许response status and headers
根据异步请求处理的结果而变化。
通过使用可通过静态方法created访问的生成器来获取ResponseEntity:
@RequestMapping("/handle")
public ResponseEntity<String> handle() {
URI location = ...;
return ResponseEntity.created(location).header("MyResponseHeader", "MyValue").body("Hello World");
}
SpringMVC中作为方法的返回值(直接返回response,不跳页面):
@PostMapping("test")
public ResponseEntity test(@RequestParam List name) throws URISyntaxException {
HttpHeaders responseHeaders = new HttpHeaders();
responseHeaders.setLocation(new URI("/test/location"));
responseHeaders.set("MyResponseHeader", "MyValue");
ResponseEntity<String> responseEntity = new ResponseEntity<>("hello", responseHeaders, HttpStatus.CREATED);
return responseEntity;
}
方法返回值类型ResponseEntity就表示响应体信息,此时无需在方法上面使用@ResponseBody注解。当然,你加上注解@ResponseBody,效果不变。
19 Jackson JSON
Spring 提供对Jackson JSON库的支持。
JSON Views
SpringMVC为Jackson的序列化视图提供了内置支持,该视图只允许呈现对象中所有字段的子集。要与@ResponseBody
或ResponseEntity
控制器方法一起使用,可以使用Jackson的@JsonView
注解来激活序列化视图类,如下例所示:
@RestController
public class UserController {
@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}
public class User {
public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};
private String username;
private String password;
public User() {
}
public User(String username, String password) {
this.username = username;
this.password = password;
}
@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}
@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}
@JsonView
允许一个视图类数组,但每个控制器方法只能指定一个。如果需要激活多个视图,可以使用composite 接口。
如果希望以编程方式执行上述操作,而不是声明@JsonView
注释,请使用MappingJacksonValue
包装返回值,并使用它提供序列化视图:
@RestController
public class UserController {
@GetMapping("/user")
public MappingJacksonValue getUser() {
User user = new User("eric", "7!jd#h23");
MappingJacksonValue value = new MappingJacksonValue(user);
value.setSerializationView(User.WithoutPasswordView.class);
return value;
}
}
对于依赖视图解析的控制器,可以将序列化视图类添加到模型中,如下例所示:
@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";
}
}
【4】Model
你可以使用@ModelAttribute
注解:
- 在
@RequestMapping
方法中的方法参数上使用,来创建或访问模型中的对象并通过WebDataBinder将其绑定到请求。 - 作为
@Controller
或@ControllerAdvice
类中的方法级注解,以便于在任何@RequestMapping
方法调用之前初始化模型。 - 在
@RequestMapping
方法上,标记其返回值的是Model属性。
Controller控制器可以有任意数量的@ModelAttribute
方法。所有这些方法都在同一控制器中的@RequestMapping
方法之前调用。@ModelAttribute
方法也可以通过@ControllerAdvice
在控制器之间共享。
@ModelAttribute
方法具有灵活的方法签名。它们支持许多与@RequestMapping
方法相同的参数,除了@ModelAttribute
本身或任何与请求体相关的参数。
下面实例展示了一个@ModelAttribut
方法:
@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}
以下示例仅添加一个属性到Model:
@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}
如果未显式指定名称,将根据对象类型选择默认名称,如javadoc for Conventions中所述。您始终可以使用重载的addAttribute方法或通过@ModelAttribute
上的name属性(用于返回值)来指定显式名称。
您还可以使用@ModelAttribute
作为@RequestMapping
方法的方法级注解,在这种情况下,@RequestMapping
方法的返回值被解释为model属性。这通常不是必需的,因为这是HTML控制器中的默认行为。除非返回值是一个字符串,将被解释为视图名称。
@ModelAttribute
还可以自定义模型属性名称,如下例所示:
@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}
【5】DataBinder
@Controller
或@ControllerAdvice
类可以具有初始化WebDataBinder
实例的@InitBinder
方法,而这些方法又可以:
-
绑定请求参数(form or query data)到一个模型对象
-
将基于字符串的请求值(例如请求参数、路径变量、请求头、cookie和其他)转换为控制器方法参数的目标类型。
-
在呈现HTML表单时,将模型对象值格式化为字符串值。
@InitBinder
方法可以注册特定于控制器的java.beans.PropertyEditor
或Spring Converter
和Formatter
程序组件。此外,您可以使用MVC配置在全局共享的FormattingConversionService
中注册转换器Converter
和格式化程序Formatter
。
@InitBinder
方法支持许多与@RequestMapping
方法相同的参数,但@ModelAttribute
(command 对象)参数除外。通常,它们使用WebDataBinder
参数(用于注册)和void返回值。下面显示了一个示例:
@Controller
public class FormController {
@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}
// ...
}
或者,当您通过共享FormattingConversionService
实例使用Formatter-based
的设置时,您可以重复使用相同的方法并注册特定于控制器的格式化Formatter
程序实现,如下例所示:
@Controller
public class FormController {
@InitBinder
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
// ...
}
更多实例应用参考博文:SpingMVC中使用@InitBinder进行日期的格式化
【6】Exceptions
@Controller
和@ControllerAdvice
类可以有@ExceptionHandler
方法来处理来自控制器方法的异常,如下例所示:
@Controller
public class SimpleController {
// ...
@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}
该异常可能与正在传播的顶级异常(例如,引发的直接IOException
)或包装异常中的嵌套异常(例如,包装在IllegalStateException
中的IOException
)匹配。从5.3开始,这可以在任意异常level
上匹配,而以前只考虑直接原因。
对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。当多个异常方法匹配时,根异常匹配通常优先于原因异常匹配。更具体地说,ExceptionDepthComparator
用于根据抛出异常类型的深度对异常进行排序。
或者,注解声明可以缩小异常类型以匹配,如下例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(IOException ex) {
// ...
}
您甚至可以使用具有非常通用的参数签名的特定异常类型列表
,如下例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class})
public ResponseEntity<String> handle(Exception ex) {
// ...
}
根异常匹配和原因异常匹配之间的区别可能令人惊讶。
在前面显示的IOException变量中,通常使用实际的FileSystemException
或RemoteException
实例作为参数来调用该方法,因为它们都是从IOException
扩展而来的。但是,如果任何这样的匹配异常在本身是IOException
的包装异常中传播,则传入的异常实例就是该包装异常。
在handle(Exception)变量中,行为更简单。在包装场景中,这总是与包装异常一起调用,在这种情况下,实际匹配的异常将通过ex.getCause()
找到。传入的异常仅在作为顶级异常抛出时才是实际的FileSystemException
或RemoteException
实例。
我们通常建议您在参数签名中尽可能具体,以减少根异常类型和原因异常类型之间不匹配的可能性。考虑将多个匹配方法拆分为单独的@ExceptionHandler
方法,每个方法通过其签名匹配单个特定的异常类型。
在多个@ControllerAdvice中,我们建议在@ControllerAdvice上声明主根异常映射,并按相应顺序排列优先级。虽然根异常匹配优于原因匹配,但这是在给定控制器或@ControllerAdvice
类的方法中定义的。这意味着高优先级@ControllerAdvice
bean上的原因匹配优先于低优先级@ControllerAdvice
bean上的任何匹配(例如,根)。
最后但并非最不重要的一点是,@ExceptionHandler
方法实现可以通过以原始形式重新抛出给定的异常实例来选择退出处理该异常实例。这在您只对根级别匹配或无法静态确定的特定上下文中的匹配感兴趣的场景中非常有用。重新抛出的异常通过剩余的解析链传播,就好像给定的@ExceptionHandler
方法一开始就不匹配一样。
也就是说此处不处理该异常,继续抛出供其他方法处理。比如MyBatis源码中解析xxxxMapper.xml时找不到对应的MappedStatement就这样干了。
SpringMVC中对@ExceptionHandler
方法的支持建立在DispatcherServlet
级别的HandlerExceptionResolver
机制上。
① 方法参数
@ExceptionHandler
注解的方法支持如下参数。
方法参数 | 描述 |
---|---|
Exception type | 获取暴露的异常 |
HandlerMethod | 获取暴露异常的控制器方法 |
WebRequest, NativeWebRequest | 对request parameters and request and session attributes 请求参数、请求和会话属性的通用访问,无需直接使用Servlet API。 |
javax.servlet.ServletRequest, javax.servlet.ServletResponse | 选择任何特定的请求或响应类型—例如, ServletRequest, HttpServletRequest, or Spring’s MultipartRequest, MultipartHttpServletRequest . |
javax.servlet.http.HttpSession | 强制会话的存在。因此,这样的参数永远不会为空。请注意,会话访问不是线程安全的。如果允许多个请求同时访问会话,请考虑将RequestMappingHandlerAdapter 实例的synchronizeOnSession 标记设置为true。 |
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暴露的原始响应主体 |
java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap | 获取model,model在HTML控制器中使用并作为视图渲染的一部分暴露给模板。 |
RedirectAttributes | 指定重定向时要使用的属性(即,要附加到查询字符串中)和在重定向后的请求之前要临时存储的闪存属性。 |
@SessionAttribute | 用于访问任何会话属性,这与由于类级别@SessionAttributes 声明而存储在会话中的Model属性不同。 |
@RequestAttribute | 获取请求属性request attributes |
② 返回值
下表描述了支持的控制器方法返回值。
方法返回值 | 描述 |
---|---|
@ResponseBody | 方法返回值被HttpMessageConverter 实现转换并写入到response |
HttpEntity<B>, ResponseEntity<B> | 指定完整响应(包括HTTP头和正文)的返回值将通过HttpMessageConverter 实现转换并写入响应response。 |
String | 返回的视图名称,将会被ViewResolver 实现解析,通常与隐形Model一起使用(Model通过@ModelAttribute 方法和命令对象确定)。处理程序方法同样可以声明一个Model类型参数来丰富Model数据 |
View | 一个将和Model一起渲染的视图实例。隐形Model通过@ModelAttribute 方法和命令对象确定,处理程序方法同样可以声明一个Model类型参数来丰富Model数据 |
java.util.Map, org.springframework.ui.Model | 要添加到隐式Model的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
@ModelAttribute | 要添加到模型中的属性,视图名称通过RequestToViewNameTranslator 隐式确定。 |
ModelAndView object | 要使用的View和Model属性,以及(可选)响应状态response status。 |
void | 如果具有void返回类型(或null返回值)的方法还具有ServletResponse 、OutputStream 参数或@ResponseStatus 注解,则认为该方法已完全处理响应。如果控制器进行了积极的ETag或lastModified 时间戳检查,也同样如此。如果上述条件均不成立,则void返回类型还可以指示REST控制器的“无响应主体”,或HTML控制器的默认视图名称选择。 |
其他返回值 | 如果返回值与此表中的任何值不匹配且为字符串或空值,则将其视为视图名称(通过RequestToViewNameTranslator 选择的默认视图名称适用),前提是它不是由BeanUtils#isSimpleProperty 确定的简单类型。简单类型的值仍然无法解析。 |
③ REST API exceptions
REST服务的一个常见要求是在响应主体中包含错误详细信息。Spring框架不会自动执行此操作,因为响应体中错误详细信息的表示是特定于应用程序的。但是,@RestController
可以使用带有ResponseEntity
返回值的@ExceptionHandler
方法来设置响应的状态和主体。这些方法也可以在@ControllerAdvice
类中声明以全局应用。
在响应体中实现具有错误细节的全局异常处理的应用程序应考虑继承ResponseEntityExceptionHandler
,它提供对Spring MVC所引发异常的处理,并提供钩子来定制响应体。要利用它,请创建ResponseEntityExceptionHandler
的子类,用@ControllerAdvice
注释它,重写必要的方法,并将其声明为Spring bean
。
ResponseEntityExceptionHandler
是一个抽象的、方便的基类,用于@ControllerAdvice
类。这些类希望通过@ExceptionHandler
方法(对所有@RequestMapping
方法抛出的异常)提供集中异常处理。
该类提供了一个@ExceptionHandler
方法来处理Spring MVC内部异常。该方法返回ResponseEntity
将使用HttpMessageConverter
写入response,而DefaultHandlerExceptionResolver
将会返回一个ModelAndView
。
如果不需要往response
写入错误信息或者视图解析使用ContentNegotiatingViewResolver
,那么DefaultHandlerExceptionResolver
就足够了。
注意,以便@ControllerAdvice
可以被检测到,那么ExceptionHandlerExceptionResolver
必须被配置。
异常解析器家族结构图示
【7】Controller Advice
通常在@Controller
注解的类中使用@ExceptionHandler, @InitBinder, and @ModelAttribute
方法。如果你想在全局使用(也就是跨controller),那么你可以在@ControllerAdvice
或@RestControllerAdvice
类中声明这些方法。
@ControllerAdvice
注解是被@Component
注解的,也就是说标注@ControllerAdvice
的类可以通过组件扫描被注册为Spring Bean。@RestControllerAdvice
是一种复合注解,它同时使用@ControllerAdvice
和@ResponseBy
进行注释,这本质上意味着@ExceptionHandler
方法通过消息转换(相对于视图解析或模板呈现)呈现给响应体
在项目启动时,@RequestMapping
和@ExceptionHandler
方法的基础结构类检测用注解的Spring bean,然后在运行时应用它们的方法。全局
@ExceptionHandler
方法(来自@ControllerAdvice
)在本地方法(来自@Controller
)之后应用。相比之下,全局@ModelAttribute 和@InitBinder 方法
应用于本地方法(当前controller中方法)之前。
默认情况下,@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 {}
前面示例中的选择器在运行时进行评估,如果广泛使用,可能会对性能产生负面影响。
参考博文:SpringMVC基础配置详解与实践