文章目录

写在前面

Spring MVC提供了一个基于注解的编程模型,其中@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一样都会被检测到,定义在spring容器中作为一个bean。

// 启用此类@ Controller beans的自动检测,可以将组件扫描添加到Java配置中,如下例所示:
@Configuration
@ComponentScan("org.example.web")
public class WebConfig {

// ...
}

也可以使用xml配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">

<context:component-scan base-package="org.example.web"/>

<!-- ... -->

</beans>

@RestController是一个复合注释,它本身用@Controller和@ResponseBody进行了元注释,以指示控制器的每个方法都继承了类型级别的@ResponseBody注释,因此直接写入响应体,而不是呈现一个HTML页面。

1、AOP代理

如果要用AOP代理一个Controller,如果您选择在控制器上直接使用@Transactional注解。在这种情况下,特别是对于Controller,建议使用基于类的代理。这通常是Controller的默认选择。

如果一个控制器必须实现一个不是Spring上下文回调的接口(比如InitializingBean,*Aware等等),可能需要显式地配置基于类的代理。例如,使用< tx:annotation-driven/>可以更改为< tx:annotation-driven proxy-target-class = " true "/>,使用@EnableTransactionManagement可以更改为@ EnableTransactionManagement(proxyTargetClass = true)。

二、请求映射

可以使用@RequestMapping注解将请求映射到Controller的方法。它有各种属性,可以通过URL、HTTP方法、请求参数、头和媒体类型进行匹配。可以修饰类来表达共享映射,或者修饰方法来缩小到特定的端点映射。

@RequestMapping也有特定于HTTP方法的快捷方式变体:

  • @GetMapping
  • @PostMapping
  • @PutMapping
  • @DeleteMapping
  • @PatchMapping

快捷方式是提供的自定义注释,一般情况下都应该指定请求的方法类型,而不是直接使用@RequestMapping(匹配所有HTTP方法)。但是在类级别仍然需要@RequestMapping来表达共享映射。

// 快捷方法本质上也是一个@RequestMapping,只是加上了method属性
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@RequestMapping(method = RequestMethod.GET)
public @interface GetMapping {
// 下面的示例具有类型和方法级别的映射:
@RestController
@RequestMapping("/persons")
class PersonController {

@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}

@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}

1、URI表达式

@RequestMapping修饰的方法可以使用URL模式进行映射。有两种选择:

  • PathPattern —与URL路径匹配的预解析模式,也预解析为PathContainer。这个解决方案是为web使用而设计的,可以有效地处理编码和路径参数,并高效地进行匹配。
  • AntPathMatcher根据字符串路径匹配字符串模式。这是Spring配置中使用的原始解决方案,用于选择类路径、文件系统和其他位置上的资源。它效率较低,字符串路径输入对于有效处理编码和URL的其他问题是一个挑战。

PathPattern是web应用程序的推荐解决方案,也是Spring WebFlux中的唯一选择。在5.3版本之前,AntPathMatcher是Spring MVC中的唯一选择,并且仍然是默认选择。然而,可以在MVC配置中启用PathPattern。

PathPattern支持与AntPathMatcher相同的表达式语法。此外,它还支持捕获表达式,例如{*spring},用于匹配路径末端的0个或多个路径段。PathPattern还限制**用于匹配多个路径段,因此它只允许在表达式的结尾使用。这消除了为给定请求选择最佳匹配表达式时的许多模糊情况。

实例:

  • “/resources/ima?e.png”:匹配路径段中的一个字符
  • “/resources/*.png”:匹配路径段中的零个或多个字符
  • “/resources/**”:匹配多个路径段
  • “/projects/{project}/versions”:匹配路径段并作为一个变量捕获
  • “/projects/{project:[a-z]+}/versions”:用正则表达式匹配和捕获变量
// 可以用@PathVariable获取捕获的URI变量
@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}用语法为{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路径模式还可以嵌入${… }占位符,这些占位符在启动时通过对本地、系统、环境和其他属性源使用propertysourceplaceholderconfigure进行解析。例如,可以使用它根据一些外部配置来参数化一个基本URL。

2、表达式比较

当多个表达式匹配一个URL时,必须选择最佳匹配。这是通过以下方式之一完成的,具体取决于是否支持使用解析路径模式:

  • PathPattern.SPECIFICITY_COMPARATOR
  • AntPathMatcher.getPatternComparator(String path)

两者都有助于将更具体的模式排在最上面。如果URI变量(计为1)、单通配符(计为1)和双通配符(计为2)的数量较少,则模式不太具体。如果得分相等,则选择较长的图案。给定相同的分数和长度,选择URI变量多于通配符的模式。

默认映射模式(/**)被排除在评分之外,并且总是最后排序。此外,前缀模式(如/public/**)被认为没有其他没有双通配符的模式那么具体。

3、后缀匹配

从5.3开始,默认情况下Spring MVC不再执行“.*”后缀模式匹配,其中映射到“/person”的控制器也隐式映射到“/person.*”。因此,路径扩展不再用于解释请求的响应内容类型,例如/person.pdf、/person.xml等等。

当浏览器用来发送难以一致解释的Accept头时,以这种方式使用文件扩展名是必要的。目前,这不再是必要的,使用Accept头应该是首选。

随着时间的推移,文件扩展名的使用在许多方面被证明是有问题的。当叠加使用URI变量、路径参数和URI编码时,可能会导致歧义。关于基于URL的授权和安全性的推理(更多细节见下一节)也变得更加困难。

要在5.3之前的版本中完全禁用路径扩展,请设置以下内容:

  • useSuffixPatternMatching(false)
  • favorPathExtension(false)

除了通过“Accept”头请求内容类型之外,还有一种方法是有用的,例如在浏览器中键入URL时。路径扩展的一个安全替代方法是使用查询参数策略。如果必须使用文件扩展名,请考虑通过ContentNegotiationConfigurer的mediaTypes属性将它们限制为显式注册的扩展名列表。

4、后缀匹配和RFD

反射文件下载(RFD)攻击与XSS类似,它依赖于响应中反映的请求输入(例如,查询参数和URI变量)。然而,RFD攻击不是将JavaScript插入HTML,而是依靠浏览器切换来执行下载,并在稍后双击时将响应视为可执行脚本。

在Spring MVC中,@ResponseBody和ResponseEntity方法存在风险,因为它们可以呈现不同的内容类型,客户端可以通过URL路径扩展请求这些内容类型。禁用后缀模式匹配和使用路径扩展进行内容协商可以降低风险,但不足以防止RFD攻击。

为了防止RFD攻击,在呈现响应体之前,Spring MVC添加了一个Content-Disposition:inline;filename=f.txt头建议一个固定和安全的下载文件。只有当URL路径包含既不允许作为安全文件也没有为内容协商显式注册的文件扩展名时,才会这样做。但是,当URL被直接输入到浏览器中时,它可能会有副作用。

默认情况下,许多常见的路径扩展是安全的。具有自定义HttpMessageConverter实现的应用程序可以为内容协商显式注册文件扩展名,以避免为这些扩展名添加Content-Disposition头。请参见Content Types。

5、可消费媒体类型

// 可以根据请求的内容类型(ContentType)缩小请求映射,使用consumes属性缩小内容类型的映射范围。
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}

consumes属性也支持否定表达式,例如 !text/plain 表示除text/plain之外的任何内容类型。

您可以在类级别声明一个共享的consumes属性。然而,与大多数其他请求映射属性不同,当在类级别使用时,方法级别使用属性重写,而不是扩展类级别声明。

MediaType为常用的媒体类型提供了常量,如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE。

总的来说,可消费的媒体类型,用来限制ContentType请求头。

6、可生产的媒体类型

// 可以根据Accept请求头和控制器方法生成的内容类型列表来缩小请求映射的范围,使用produces属性缩小内容类型的映射范围。
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}

媒体类型可以指定字符集。支持求反表达式,例如 !text/plain 是指除 “text/plain” 之外的任何内容类型。

可以在类级别声明一个共享的produces属性。然而,与大多数其他请求映射属性不同,当在类级别使用时,方法级别产生属性重写,而不是扩展类级别声明。

MediaType为常用的媒体类型提供了常量,如APPLICATION_JSON_VALUE和APPLICATION_XML_VALUE。

总的来说,可生产的媒体类型,用来限制Accept请求头。

7、params, headers

可以根据请求参数条件缩小请求映射的范围。可以根据请求参数( myParam )的存在与否( !myParam ),或特定值(myParam=myValue)来限制请求映射。

// 判断请求参数中myParam是否等于myValue。
@GetMapping(path = "/pets/{petId}", params = "myParam=myValue")
public void findPet(@PathVariable String petId) {
// ...
}
// 判断请求头中的myHeader是否等于myValue,用法与params相同
@GetMapping(path = "/pets", headers = "myHeader=myValue")
public void findPet(@PathVariable String petId) {
// ...
}

注意:虽然可以使用headers条件匹配Content-Type和Accept,但是最好使用consumes和produces。

8、HTTP HEAD和 OPTIONS

@GetMapping(和@RequestMapping(method=HttpMethod.GET))透明地支持HTTP头进行请求映射。控制器方法不需要改变。javax.servlet.http.HttpServlet中应用的响应包装器确保将Content-Length头设置为写入的字节数(不实际写入响应)。

@GetMapping(和@RequestMapping(method=HttpMethod.GET))隐式映射到并支持HTTP头。处理HTTP HEAD请求就像处理HTTP GET一样,只是不写主体,而是计算字节数并设置Content-Length头。

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

对于没有HTTP方法声明的@RequestMapping,Allow头被设置为GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS。控制器方法应该总是声明支持的HTTP方法(例如,通过使用HTTP方法特定的变体:@GetMapping、@PostMapping等)。

可以显式地将@RequestMapping方法映射到HTTP HEAD和HTTP选项,但这在一般情况下是不必要的。

9、自定义注解

Spring MVC支持使用组合注释进行请求映射。这些注释本身用@RequestMapping进行了元注释,并被组合来重新声明@RequestMapping属性的一个子集(或全部),目的更窄、更具体。

@GetMapping、@PostMapping、@PutMapping、@DeleteMapping和@PatchMapping是组合注解的示例。提供它们是因为,可以说,大多数控制器方法应该映射到特定的HTTP方法,而不是使用@RequestMapping,默认情况下,@RequestMapping匹配所有HTTP方法。如果你需要自定义一个组合注解,可以借鉴以上组合注解。

Spring MVC还支持带有定制请求匹配逻辑的定制请求映射属性。这是一个更高级的选项,需要子类化RequestMappingHandlerMapping并覆盖getCustomMethodCondition方法,在这里您可以检查自定义属性并返回您自己的RequestCondition。

10、显式注册

可以以编程方式注册处理程序方法,这些方法可用于动态注册或高级情况,例如不同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);
}
}

三、程序处理方法

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

1、方法参数

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

JDK 8的java.util.Optional支持作为方法参数与具有必需属性(例如,@RequestParam,@RequestHeader等)的批注结合使用,并等效于required=false。

controller方法参数

描述

WebRequest, NativeWebRequest

对请求参数、请求和会话属性的一般访问,而不直接使用Servlet API。

javax.servlet.ServletRequest, javax.servlet.ServletResponse

选择任何特定的请求或响应类型,例如ServletRequest、HttpServletRequest或Spring的MultipartRequest、MultipartHttpServletRequest。

javax.servlet.http.HttpSession

强制会话存在。因此,这样的论点永远不会是无效的。注意,会话访问不是线程安全的。如果允许多个请求同时访问一个会话,请考虑将RequestMappingHandlerAdapter实例的synchronizeOnSession标志设置为true。

javax.servlet.http.PushBuilder

用于编程式HTTP/2资源推送的Servlet 4.0推送构建器API。注意,根据Servlet规范,如果客户机不支持HTTP/2特性,那么注入的PushBuilder实例可以为null。

java.security.Principal

当前通过身份验证的用户——如果已知,可能是特定的主体实现类。请注意,如果对该参数进行了注释,以便允许自定义解析器在通过HttpServletRequest#getUserPrincipal返回默认解析之前对其进行解析,则不会立即解析该参数。例如,Spring安全身份验证实现了Principal,并通过HttpServletRequest#getUserPrincipal注入,除非它也用@AuthenticationPrincipal进行了注释,在这种情况下,它由自定义的Spring安全解析器通过Authentication#getPrincipal进行解析。

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表达式。

@MatrixVariable

用于访问URI路径段中的名称-值对。

@RequestParam

用于访问Servlet请求参数,包括多部分文件。参数值被转换为声明的方法参数类型。注意,对于简单的参数值,使用@RequestParam是可选的。

@RequestHeader

用于访问请求头。头值被转换为声明的方法参数类型。

@CookieValue

访问cookies。Cookies值被转换为声明的方法参数类型。

@RequestBody

用于访问HTTP请求体。通过使用HttpMessageConverter实现,正文内容被转换为声明的方法参数类型。

HttpEntity<B>

用于访问请求头和请求体。使用HttpMessageConverter转换正文。

@RequestPart

对于multipart/form-data请求中的部分访问,使用HttpMessageConverter转换该部分的正文。

java.util.Map, org.springframework.ui.Model, org.springframework.ui.ModelMap

用于访问HTML控制器中使用的模型,并作为视图呈现的一部分向模板公开。

RedirectAttributes

指定重定向时使用的属性(即,附加到查询字符串的属性)和在重定向后请求之前临时存储的闪存属性。

@ModelAttribute

用于访问应用了数据绑定和验证的模型中的现有属性(如果不存在,则进行实例化)。请注意,使用@ModelAttribute是可选的(例如,设置其属性)。

Errors, BindingResult

用于访问命令对象的验证和数据绑定错误(即@ModelAttribute参数)或@RequestBody或@RequestPart参数验证错误。必须在经过验证的方法参数后立即声明一个Errors或BindingResult参数。

SessionStatus + class-level @SessionAttributes

用于标记表单处理完成,这将触发对通过类级别的@SessionAttributes批注声明的会话属性的清理。

UriComponentsBuilder

用于准备相对于当前请求的主机、端口、模式、上下文路径和servlet映射的文字部分的URL。

@RequestAttribute

用于访问请求属性。

其他参数

如果某个方法参数与此表中的任何前面的值都不匹配,并且是简单类型(由BeanUtils#isSimpleProperty确定),则它将被解析为@RequestParam。否则,它将被解析为@ModelAttribute。

2、返回值

下表描述了支持的控制器方法返回值。所有返回值都支持反应类型。

controller方法返回值

描述

@ResponseBody

返回值通过HttpMessageConverter实现进行转换,并写入响应。

HttpEntity<B>, ResponseEntity<B>

指定完整响应(包括HTTP头和正文)的返回值将通过HttpMessageConverter实现进行转换,并写入响应。

HttpHeaders

用于返回只有header而没有正文的响应。

String

通过ViewResolver实现解析并与隐式模型一起使用的视图名称—通过command对象和@ModelAttribute方法确定。handler方法还可以通过声明模型参数以编程方式丰富模型(参见显式注册)。

View

与隐式模型一起用于渲染的视图实例,通过命令对象和@ModelAttribute方法确定。handler方法还可以通过声明模型参数以编程方式丰富模型(参见显式注册)。

java.util.Map, org.springframework.ui.Model

要添加到隐式模型的属性,视图名称通过RequestToViewNameTranslator隐式确定。

@ModelAttribute

要添加到模型中的属性,视图名称通过RequestToViewNameTranslator隐式确定。注意@ModelAttribute是可选的。

ModelAndView object

要使用的视图和模型属性,以及可选的响应状态。

void

如果具有void返回类型(或null返回值)的方法还具有ServletResponse、OutputStream参数或@ResponseStatus批注,则该方法被视为已完全处理了响应。如果控制器进行了正的ETag或lastModified时间戳检查,情况也是如此。如果以上都不是真的,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实现异步发出要写入响应的对象流。也支持作为响应实体的主体。

StreamingResponseBody

异步写入响应输出流。也支持作为响应实体的主体。

Reactive types — Reactor, RxJava, or others through ReactiveAdapterRegistry

替代DeferredResult,将多值流(例如,Flux, Observable)收集到一个List中。对于流场景(例如,text/event-stream、application/json+stream),使用SseEmitter和ResponseBodyEmitter,其中ServletOutputStream阻塞I/O在Spring MVC管理的线程上执行,并在每次写入完成时应用背压。

其他返回值

任何与此表中的任何上述值都不匹配并且是字符串或void的返回值都被视为视图名称(应用通过RequestToViewNameTranslator选择的默认视图名称),前提是它不是简单类型,由BeanUtils#isSimpleProperty确定。简单类型的值仍然无法解析。

3、类型转换

一些表示基于字符串的请求输入的带注释的控制器方法参数(如@RequestParam、@RequestHeader、@PathVariable、@MatrixVariable和@CookieValue)可能需要类型转换,如果该参数声明为字符串以外的其他形式。

在这种情况下,会根据配置的转换器自动应用类型转换。默认情况下,支持简单类型(int、long、Date等)。您可以通过WebDataBinder(请参见DataBinder)或通过向FormattingConversionService注册格式化程序来自定义类型转换。

类型转换中的一个实际问题是空字符串源值的处理。如果此类值由于类型转换而变为null,则被视为缺失值。Long、UUID和其他目标类型都是这种情况。如果希望允许注入null,可以在参数注释中使用required标志,或者将参数声明为@Nullable。

从5.3开始,即使在类型转换之后,也将强制使用非空参数。如果您的处理程序方法也打算接受空值,那么要么将您的参数声明为@Nullable,要么在相应的@RequestParam中将它标记为required=false,等等。注释。对于5.3升级中遇到的回归,这是最佳实践和推荐的解决方案。

或者,在需要@PathVariable的情况下,您可以专门处理结果MissingPathVariableException。转换后的空值将被视为空的原始值,因此将抛出相应的Missing… Exception变量。

4、矩阵变量

矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如,/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
}

要获取所有矩阵变量,可以使用多值映射,如下例所示:

// GET /owners/42;q=11;r=12/pets/21;q=22;s=23

@GetMapping("/owners/{ownerId}/pets/{petId}")
public void findPet(
@MatrixVariable MultiValueMap<String, String> matrixVars,
@MatrixVariable(pathVar="petId") MultiValueMap<String, String> petMatrixVars) {

// matrixVars: ["q" : [11,22], "r" : 12, "s" : 23]
// petMatrixVars: ["q" : 22, "s" : 23]
}

请注意,您需要启用矩阵变量的使用。在MVC Java配置中,需要通过路径匹配设置一个removeSemicolonContent=false的UrlPathHelper。在MVC XML名称空间中,可以设置<mvc:annotation-driven enable-matrix-variables=“true”/>。

5、@RequestParam

可以使用@RequestParam注释将Servlet请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。

@Controller
@RequestMapping("/pets")
public class EditPetForm {

// ...

// 使用@RequestParam绑定petId。
@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包装声明参数,来指定方法参数是可选的。

如果目标方法参数类型不是字符串,则自动应用类型转换。

将参数类型声明为数组或列表允许为同一参数名解析多个参数值。

当@RequestParam注释被声明为Map<String,String >或MultiValueMap<String,String >时,如果注释中没有指定参数名称,则映射中会填充每个给定参数名称的请求参数值。

注意@RequestParam的使用是可选的(例如,设置它的属性)。默认情况下,任何简单值类型(由BeanUtils#isSimpleProperty确定)且未被任何其他参数解析器解析的参数都被视为用@RequestParam进行了批注。

6、@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(
// 获取Accept-Encoding请求头的值
@RequestHeader("Accept-Encoding") String encoding,
// 获取Keep-Alive请求头的值
@RequestHeader("Keep-Alive") long keepAlive) {
//...
}

如果目标方法参数类型不是字符串,则自动应用类型转换。

当在Map<String,String >、MultiValueMap<String,String >或HttpHeaders参数上使用@RequestHeader批注时,Map将填充所有头值。

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

7、@CookieValue

可以使用@CookieValue注释将HTTP cookie的值绑定到控制器中的方法参数。

考虑具有以下cookie的请求:

JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
// 获取JSESSIONID  cookie值
@GetMapping("/demo")
public void handle(@CookieValue("JSESSIONID") String cookie) {
//...
}

如果目标方法参数类型不是字符串,则自动应用类型转换。

8、@ModelAttribute

可以在方法参数上使用@ModelAttribute批注来访问模型中的属性,或者在不存在属性时将其实例化。模型属性还覆盖了来自HTTP Servlet请求参数的值,这些参数的名称与字段名称相匹配。这被称为数据绑定,它使您不必解析和转换单个查询参数和表单字段。以下示例显示了如何实现这一点:

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

上面的Pet实例来源于以下方式之一:

  • 从可能通过@ModelAttribute方法添加的模型中检索。
  • 如果模型属性列在类级别的@SessionAttributes批注中,则从HTTP会话中检索。
  • 通过转换器获得,其中模型属性名称与请求值(如路径变量或请求参数)的名称相匹配(参见下一个示例)。
  • 使用其默认构造函数进行实例化。
  • 通过具有与Servlet请求参数匹配的参数的“主构造函数”实例化。参数名通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名来确定。

除了使用@ModelAttribute方法来提供模型属性或者依赖框架来创建模型属性之外,还有一种方法是使用转换器< String,T >来提供实例。当模型属性名称与请求值(如路径变量或请求参数)的名称匹配,并且有一个从字符串到模型属性类型的转换器时,将应用此选项。在以下示例中,模型属性名称为account,它与URI路径变量account相匹配,并且有一个注册的转换器< String,Account >,它可以从数据存储中加载Account:

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

获得模型属性实例后,应用数据绑定。WebDataBinder类将Servlet请求参数名称(查询参数和表单字段)与目标对象上的字段名称相匹配。必要时,在应用类型转换后填充匹配字段。有关数据绑定(和验证)的更多信息,请参见验证。有关自定义数据绑定的更多信息,请参见DataBinder。

数据绑定可能会导致错误。默认情况下,会引发BindException。但是,若要在控制器方法中检查此类错误,可以在@ModelAttribute旁边立即添加一个BindingResult参数,如下例所示:

// 在@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验证),可以在数据绑定后自动应用验证。以下示例显示了如何实现这一点:

// 验证Pet实例。
@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进行了批注。

9、@SessionAttributes

@SessionAttributes用于在请求之间的HTTP Servlet会话中存储模型属性。它是一个类型级注释,声明特定控制器使用的会话属性。这通常会列出模型属性的名称或模型属性的类型,它们应该透明地存储在会话中,以供后续的访问请求使用。

// 使用@SessionAttributes
@Controller
@SessionAttributes("pet")
public class EditPetForm {
// ...
}

在第一次请求时,当一个名为pet的模型属性被添加到模型中时,它会被自动提升并保存在HTTP Servlet会话中。它会一直保留在那里,直到另一个控制器方法使用SessionStatus方法参数来清除存储,如下例所示:

@Controller
@SessionAttributes("pet") // 在Servlet会话中存储Pet值。
public class EditPetForm {

// ...

@PostMapping("/pets/{id}")
public String handle(Pet pet, BindingResult errors, SessionStatus status) {
if (errors.hasErrors) {
// ...
}
status.setComplete(); // 从Servlet会话中清除Pet值。
// ...
}
}

10、@SessionAttribute

如果您需要访问预先存在的会话属性,这些属性是全局管理的(即,在控制器之外,例如,通过一个筛选器),并且可能存在,也可能不存在,您可以在方法参数上使用@SessionAttribute批注,如下例所示:

@RequestMapping("/")
public String handle(@SessionAttribute User user) { // 使用@SessionAttribute批注。
// ...
}

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

对于作为控制器工作流一部分的会话中模型属性的临时存储,请考虑使用@SessionAttributes,如@SessionAttributes中所述。

11、@RequestAttribute

与@SessionAttribute类似,您可以使用@RequestAttribute注释来访问先前创建的预先存在的请求属性(例如,通过Servlet过滤器或HandlerInterceptor):

@GetMapping("/")
public String handle(@RequestAttribute Client client) {
// ...
}

12、重定向属性

默认情况下,所有模型属性都被认为是作为重定向URL中的URI模板变量公开的。在其余的属性中,那些基本类型或基本类型的集合或数组被自动附加为查询参数。

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

RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect的标志,您可以使用它来指示如果控制器方法重定向,就不应该使用默认模型的内容。相反,控制器方法应该声明一个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属性。

13、Flash 属性

闪存属性为一个请求提供了一种方法来存储要在另一个请求中使用的属性。这是重定向时最常见的需要,例如,Post-Redirect-Get模式。闪存属性在重定向之前被临时保存(通常在会话中),以便在重定向之后可用于请求,并且被立即移除。

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

闪存属性支持始终处于“开启”状态,不需要显式启用。但是,如果不使用,它永远不会导致HTTP会话创建。对于每个请求,都有一个“输入”FlashMap和一个“输出”FlashMap,前者包含从上一个请求(如果有)传递来的属性,后者包含要为下一个请求保存的属性。这两个FlashMap实例都可以通过RequestContextUtils中的静态方法从Spring MVC的任何地方访问。

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

将请求与闪存属性相匹配

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

为了减少出现这种问题的可能性,RedirectView会自动用目标重定向URL的路径和查询参数“标记”FlashMap实例。反过来,默认的FlashMapManager在查找“输入”FlashMap时会将该信息与传入的请求进行匹配。

这并没有完全消除并发性问题的可能性,但是通过重定向URL中已经可用的信息,大大降低了并发性问题的可能性。因此,我们建议您主要在重定向情况下使用flash属性。

14、Multipart

启用MultipartResolver后,带有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 >时,如果标注中未指定参数名称,则会使用每个给定参数名称的多部分文件填充地图。

使用Servlet 3.0多部分解析,您还可以将javax.servlet.http.Part而不是Spring的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服务场景中,多部分请求也可以从非浏览器客户端提交。以下示例显示了一个包含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)。使用@RequestPart批注在使用HttpMessageConverter转换多部分后访问它:

@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) {
// ...
}

15、@RequestBody

可以使用@RequestBody注释通过HttpMessageConverter读取请求体并将其反序列化为对象。以下示例使用@RequestBody参数:

@PostMapping("/accounts")
public void handle(@RequestBody Account account) {
// ...
}

可以使用MVC配置的消息转换器选项来配置或定制消息转换。

可以将@RequestBody与javax.validation.Valid或Spring的@Validated注释结合使用,这两者都会导致应用标准Bean验证。默认情况下,验证错误会导致methodgargumentnotvaliexception,该异常会转化为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) {
// ...
}

17、@ResponseBody

可以在方法上使用@ResponseBody批注,通过HttpMessageConverter将返回序列化为响应正文。以下清单显示了一个示例:

@GetMapping("/accounts/{id}")
@ResponseBody
public Account handle() {
// ...
}

@ResponseBody在类级别也受支持,在这种情况下,它由所有控制器方法继承。这就是@RestController的效果,无非就是一个标有@Controller和@ResponseBody的元注释。

可以对反应类型使用@ResponseBody。有关更多详细信息,请参见异步请求和反应类型。

可以使用MVC配置的消息转换器选项来配置或定制消息转换。

可以将@ResponseBody方法与JSON序列化视图结合使用。详情见 Jackson JSON。

18、ResponseEntity

ResponseEntity类似于@ResponseBody,但有状态和标题。例如:

@GetMapping("/something")
public ResponseEntity<String> handle() {
String body = ... ;
String etag = ... ;
return ResponseEntity.ok().eTag(etag).body(body);
}

Spring MVC支持使用单值反应类型来异步产生ResponseEntity,和/或为主体使用单值和多值反应类型。这允许以下类型的异步响应:

  • ResponseEntity<Mono<T > >或ResponseEntity<Flux<T > >使响应状态和head立即为人所知,而主体则在稍后异步提供。如果正文由一个值组成,则使用Mono,产生多个值就用Flux 。
  • Mono<ResponseEntity >提供了所有三个部分——响应状态、标题和正文,稍后会异步提供。这允许响应状态和头根据异步请求处理的结果而变化。

19、Jackson JSON

Spring提供了对Jackson JSON库的支持。

JSON视图

Spring MVC为Jackson的序列化视图提供了内置支持,这种视图只允许呈现一个对象中所有字段的子集。要将它与@ResponseBody或ResponseEntity控制器方法一起使用,可以使用Jackson的@JsonView批注来激活序列化视图类,如下例所示:

@RestController
public class UserController {

@GetMapping("/user")
@JsonView(User.WithoutPasswordView.class)
public User getUser() {
return new User("eric", "7!jd#h23");
}
}

public class User {

public interface WithoutPasswordView {};
public interface WithPasswordView extends WithoutPasswordView {};

private String username;
private String password;

public User() {
}

public User(String username, String password) {
this.username = username;
this.password = password;
}

@JsonView(WithoutPasswordView.class)
public String getUsername() {
return this.username;
}

@JsonView(WithPasswordView.class)
public String getPassword() {
return this.password;
}
}

@JsonView允许视图类的数组,但是每个控制器方法只能指定一个。如果需要激活多个视图,可以使用复合界面。

如果您希望以编程方式完成上述操作,而不是声明@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";
}
}

四、Model模型

可以使用@ModelAttribute注解:

  • 在@RequestMapping方法中的方法参数上创建或访问模型中的对象,并通过WebDataBinder将其绑定到请求。
  • 作为@Controller或@ControllerAdvice类中的方法级注释,帮助在任何@RequestMapping方法调用之前初始化模型。
  • 在@RequestMapping方法上标记其返回值的是一个模型属性。

本节讨论@ModelAttribute方法——前面列表中的第二项。一个控制器可以有任意数量的@ModelAttribute方法。所有这些方法都在同一个控制器中的@RequestMapping方法之前被调用。@ModelAttribute方法也可以通过@ControllerAdvice在控制器之间共享。

@ModelAttribute方法具有灵活的方法签名。它们支持许多与@RequestMapping方法相同的参数,除了@ModelAttribute本身或任何与请求体相关的内容。

以下示例显示了@ModelAttribute方法:

@ModelAttribute
public void populateModel(@RequestParam String number, Model model) {
model.addAttribute(accountRepository.findAccount(number));
// add more ...
}

以下示例仅添加一个属性:

@ModelAttribute
public Account addAccount(@RequestParam String number) {
return accountRepository.findAccount(number);
}

如果没有显式指定名称,则根据对象类型选择默认名称,如javadoc for Conventions中所述。您始终可以通过使用重载的addAttribute方法或通过@ModelAttribute上的name属性(对于返回值)来分配显式名称。

还可以使用@ModelAttribute作为@RequestMapping方法的方法级注释,在这种情况下,@RequestMapping方法的返回值被解释为模型属性。这通常不是必需的,因为这是HTML控制器中的默认行为,除非返回值是一个字符串,否则将被解释为视图名称。@ModelAttribute还可以自定义模型属性名,如下例所示:

@GetMapping("/accounts/{id}")
@ModelAttribute("myAccount")
public Account handle() {
// ...
return account;
}

五、DataBinder数据绑定

@Controller或@ControllerAdvice类可以有初始化WebDataBinder实例的@InitBinder方法,这些方法反过来可以:

  • 将请求参数(即表单或查询数据)绑定到模型对象。
  • 将基于字符串的请求值(如请求参数、路径变量、头、cookies等)转换为控制器方法参数的目标类型。
  • 呈现HTML表单时,将模型对象值格式化为字符串值。

@InitBinder方法可以注册特定于控制器的java.beans.PropertyEditor或Spring转换器和格式化程序组件。此外,您可以使用MVC配置在全局共享的FormattingConversionService中注册转换器和格式化程序类型。

@InitBinder方法支持许多与@RequestMapping方法相同的参数,但@ModelAttribute(命令对象)参数除外。通常,它们是用WebDataBinder参数(用于注册)和void返回值声明的。以下清单显示了一个示例:

@Controller
public class FormController {

@InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
}

// ...
}

或者,当您通过共享的FormattingConversionService使用基于格式化程序的设置时,您可以重用相同的方法并注册特定于控制器的格式化程序实现,如下例所示:

@Controller
public class FormController {

@InitBinder // 在自定义格式化程序上定义@InitBinder方法。
protected void initBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}

// ...
}

1、模型设计

在web应用程序的上下文中,数据绑定包括将HTTP请求参数(即表单数据或查询参数)绑定到模型对象及其嵌套对象中的属性。

对于数据绑定,只公开遵循JavaBeans命名约定的公共属性,例如,名字属性的公共字符串getFirstName()和公共void setFirstName(String)方法。

模型对象及其嵌套对象图有时也被称为命令对象、表单支持对象或POJO(普通旧Java对象)。

默认情况下,Spring允许绑定到模型对象图中的所有公共属性。这意味着您需要仔细考虑模型有哪些公共属性,因为客户端可以将任何公共属性路径作为目标,甚至是一些不期望被给定用例作为目标的路径。

例如,给定一个HTTP表单数据端点,恶意客户端可能会为模型对象图中存在的属性提供值,但这些值不是浏览器中呈现的HTML表单的一部分。这可能会导致在模型对象及其任何嵌套对象上设置数据,而这些数据不会被更新。

推荐的方法是使用专用的模型对象,该对象只公开与表单提交相关的属性。例如,在更改用户电子邮件地址的表单上,模型对象应该声明一组最小的属性,如下面的ChangeEmailForm。

public class ChangeEmailForm {

private String oldEmailAddress;
private String newEmailAddress;

public void setOldEmailAddress(String oldEmailAddress) {
this.oldEmailAddress = oldEmailAddress;
}

public String getOldEmailAddress() {
return this.oldEmailAddress;
}

public void setNewEmailAddress(String newEmailAddress) {
this.newEmailAddress = newEmailAddress;
}

public String getNewEmailAddress() {
return this.newEmailAddress;
}

}

如果您不能或者不想为每个数据绑定用例使用一个专用的模型对象,那么您必须限制数据绑定所允许的属性。理想情况下,您可以通过WebDataBinder上的setAllowedFields()方法注册允许的字段模式来实现这一点。

例如,要在应用程序中注册允许的字段模式,可以在@Controller或@ControllerAdvice组件中实现@InitBinder方法,如下所示:

@Controller
public class ChangeEmailController {

@InitBinder
void initBinder(WebDataBinder binder) {
binder.setAllowedFields("oldEmailAddress", "newEmailAddress");
}

// @RequestMapping methods, etc.

}

除了注册允许的模式,还可以通过DataBinder及其子类中的setDisallowedFields()方法注册不允许的字段模式。但是,请注意,“允许列表”比“拒绝列表”更安全。因此,setAllowedFields()应优先于setDisallowedFields()。

请注意,匹配允许的字段模式是区分大小写的;然而,匹配不允许的字段模式是不区分大小写的。此外,匹配不允许模式的字段将不会被接受,即使它恰好匹配允许列表中的模式。

当为了数据绑定的目的直接公开您的域模型时,正确配置允许和不允许的字段模式是非常重要的。否则就是很大的安全隐患。

六、异常

@Controller和@ControllerAdvice类可以有@ExceptionHandler方法来处理来自控制器方法的异常,如下例所示:

@Controller
public class SimpleController {

// ...

@ExceptionHandler
public ResponseEntity<String> handle(IOException ex) {
// ...
}
}

该异常可以匹配正在传播的顶级异常(例如,直接抛出的IOException)或包装异常内的嵌套原因(例如,包装在IllegalStateException内的IOException)。从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) {
// ...
}

根异常匹配和原因异常匹配之间的区别可能令人惊讶。

在前面显示的IOException变体中,通常使用实际的FileSystemException或RemoteException实例作为参数来调用该方法,因为它们都是从IOException扩展而来的。但是,如果任何这样的匹配异常在本身是IOException的包装异常中传播,则传入的异常实例就是该包装异常。

handle(Exception)变体中的行为甚至更简单。在包装场景中,这总是由包装异常调用,在这种情况下,实际匹配的异常通过ex.getCause()找到。只有当FileSystemException或RemoteException作为顶级异常引发时,传入的异常才是实际的file system exception或remote exception实例。

通常建议您在参数签名中尽可能具体,以减少根异常类型和原因异常类型之间不匹配的可能性。考虑将多匹配方法分解成单独的@ExceptionHandler方法,每个方法通过其签名匹配一个特定的异常类型。

在多@ControllerAdvice安排中,我们建议在@ControllerAdvice上声明您的主根异常映射,并按相应的顺序排列优先级。虽然根异常匹配优于原因,但这是在给定控制器或@ControllerAdvice类的方法中定义的。这意味着较高优先级的@ControllerAdvice bean上的原因匹配优先于较低优先级的@ControllerAdvice bean上的任何匹配(例如root)。

最后但同样重要的是,@ExceptionHandler方法实现可以通过以原始形式重新抛出给定的异常实例来选择退出处理。这在您只对根级别的匹配或无法静态确定的特定上下文中的匹配感兴趣的情况下非常有用。重新抛出的异常通过剩余的解析链传播,就好像给定的@ExceptionHandler方法一开始就不匹配一样。

Spring MVC中对@ExceptionHandler方法的支持建立在DispatcherServlet级别的HandlerExceptionResolver机制上。

1、方法参数

@ExceptionHandler方法支持以下参数:

方法参数

描述

Exception type

用于访问引发的异常。

HandlerMethod

用于访问引发异常的控制器方法。

WebRequest, NativeWebRequest

对请求参数、请求和会话属性的一般访问,而不直接使用Servlet API。

javax.servlet.ServletRequest, javax.servlet.ServletResponse

选择任何特定的请求或响应类型(例如,ServletRequest或HttpServletRequest或Spring的MultipartRequest或MultipartHttpServletRequest)。

javax.servlet.http.HttpSession

强制会话存在。因此,这样的论点永远不会是无效的。注意,会话访问不是线程安全的。如果允许多个请求同时访问一个会话,请考虑将RequestMappingHandlerAdapter实例的synchronizeOnSession标志设置为true。

java.security.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属性。请参见重定向属性和flash属性。

@SessionAttribute

用于访问任何会话属性,这与作为类级别@SessionAttributes声明的结果存储在会话中的模型属性相反。如需详细资讯,请参阅@SessionAttribute。

@RequestAttribute

用于访问请求属性。有关更多详细信息,请参见@RequestAttribute。

2、返回值

@ExceptionHandler方法支持以下返回值:

返回值

描述

@ResponseBody

返回值通过HttpMessageConverter实例进行转换,并写入响应。见@ResponseBody。

HttpEntity<B>, ResponseEntity<B>

返回值指定完整的响应(包括HTTP头和正文)通过HttpMessageConverter实例进行转换并写入响应。参见ResponseEntity。

String

通过ViewResolver实现解析并与隐式模型一起使用的视图名称—通过command对象和@ModelAttribute方法确定。handler方法还可以通过声明模型参数(如前所述)以编程方式丰富模型。

View

与隐式模型一起用于渲染的视图实例,通过命令对象和@ModelAttribute方法确定。handler方法还可以通过声明一个模型参数(如前所述)以编程方式丰富模型。

java.util.Map, org.springframework.ui.Model

要添加到隐式模型的属性,其视图名称通过RequestToViewNameTranslator隐式确定。

@ModelAttribute

要添加到模型中的属性,其视图名称通过RequestToViewNameTranslator隐式确定。注意@ModelAttribute是可选的。

ModelAndView object

要使用的视图和模型属性,以及可选的响应状态。

void

如果具有void返回类型(或null返回值)的方法还具有ServletResponse、OutputStream参数或@ResponseStatus批注,则该方法被视为已完全处理了响应。如果控制器进行了正的ETag或lastModified时间戳检查,情况也是如此(有关详细信息,请参见控制器)。如果以上都不成立,void返回类型也可以表示REST控制器的“无响应体”或HTML控制器的默认视图名称选择。

其他返回值

如果返回值与上述任何一项都不匹配,并且不是简单类型(由BeanUtils#isSimpleProperty确定),则默认情况下,它被视为要添加到模型中的模型属性。如果是简单类型,则仍未解析。

3、REST API异常

REST服务的一个常见需求是在响应体中包含错误细节。Spring框架不会自动这样做,因为响应体中错误细节的表示是特定于应用程序的。然而,@RestController可以使用带有ResponseEntity返回值的@ExceptionHandler方法来设置响应的状态和正文。这样的方法也可以在@ControllerAdvice类中声明,以便全局应用它们。

在响应体中实现带有错误详细信息的全局异常处理的应用程序应该考虑扩展ResponseEntityExceptionHandler,它为Spring MVC引发的异常提供处理,并提供钩子来自定义响应体。为了利用这一点,创建ResponseEntityExceptionHandler的子类,用@ControllerAdvice对其进行注释,覆盖必要的方法,并将其声明为Spring bean。

七、Controller Advice

@ExceptionHandler、@InitBinder和@ModelAttribute方法仅适用于声明它们的@Controller类或类层次结构。相反,如果它们是在@ControllerAdvice或@RestControllerAdvice类中声明的,那么它们适用于任何控制器。此外,从5.3开始,@ControllerAdvice中的@ExceptionHandler方法可以用于处理来自任何@Controller或任何其他处理程序的异常。

@ControllerAdvice用@Component进行了元注释,因此可以通过组件扫描注册为Spring bean。@RestControllerAdvice用@ControllerAdvice和@ResponseBody进行了元注释,这意味着@ExceptionHandler方法将通过响应体消息转换而不是通过HTML视图来呈现它们的返回值。

在启动时,RequestMappingHandlerMapping和ExceptionHandlerExceptionResolver检测控制器通知beans,并在运行时应用它们。来自@ControllerAdvice的全局@ExceptionHandler方法在来自@Controller的局部方法之后应用。相比之下,全局@ModelAttribute和@InitBinder方法在局部方法之前应用。

@ControllerAdvice注释具有一些属性,这些属性允许您缩小它们所适用的控制器和处理程序的范围。例如:

// 将所有用@RestController注释的控制器作为目标
@ControllerAdvice(annotations = RestController.class)
public class ExampleAdvice1 {}

// 将特定包中的所有控制器作为目标
@ControllerAdvice("org.example.controllers")
public class ExampleAdvice2 {}

// 将所有可分配给特定类别的控制器作为目标
@ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class})
public class ExampleAdvice3 {}

上例中的选择器是在运行时计算的,如果广泛使用,可能会对性能产生负面影响。

参考资料

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