Spring MVC提供了一个基于注解的编程模型,其中@Controller和@RestController组件使用注解来表达请求映射,请求输入,异常处理等。 带注解的控制器具有灵活的方法签名,无需扩展基类或实现特定的接口。 以下示例显示了由注解定义的控制器:
@Controller public class HelloController { @GetMapping("/hello") public String handle(Model model) { model.addAttribute("message", "Hello World!"); return "index"; } }
在前面的示例中,该方法接受Model并以String的形式返回视图名称,但是还存在许多其他选项,本章稍后将对其进行说明。
您可以使用Servlet的WebApplicationContext中的标准Spring bean定义来定义控制器bean。 @Controller构造型允许自动检测,与Spring对在类路径中检测@Component类并为其自动注册Bean定义的常规支持保持一致。 它还充当带注解类的构造型,表明其作为Web组件的作用。
要启用对此类@Controller bean的自动检测,可以将组件扫描添加到Java配置中,如以下示例所示:
@Configuration @ComponentScan("org.example.web") public class WebConfig { // ... }
以下示例显示了与先前示例等效的XML配置:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd"> <context:component-scan base-package="org.example.web"/> <!-- ... --> </beans>
@RestController是一个组合的批注,其本身使用@Controller和@ResponseBody进行了元注解,以指示一个控制器,其每个方法都继承了类型级别的@ResponseBody批注,因此直接将其写入响应主体(与视图分辨率相对应)并使用 HTML模板。
AOP Proxies
在某些情况下,您可能需要在运行时用AOP代理装饰控制器。 一个示例是,如果您选择直接在控制器上具有@Transactional批注。 在这种情况下,特别是对于控制器,我们建议使用基于类的代理。 这通常是控制器的默认选择。 但是,如果控制器必须实现不是Spring Context回调的接口(例如InitializingBean,* Aware等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />,您可以更改为<tx:annotation-driven /> proxy-target-class =“ true” />。
您可以使用@RequestMapping批注将请求映射到控制器方法。 它具有各种属性,可以通过URL,HTTP方法,请求参数,标头和媒体类型进行匹配。 您可以在类级别使用它来表示共享的映射,也可以在方法级别使用它来缩小到特定的端点映射。
@RequestMapping还有HTTP方法特定的快捷方式:
快捷方式是提供的“自定义注解”,因为可以说,大多数控制器方法应该映射到特定的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 patterns
您可以使用以下全局模式和通配符来映射请求:
您还可以声明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等),您可以注册对任何其他数据类型的支持。 请参阅类型转换和DataBinder。
您可以显式地命名URI变量(例如,@PathVariable(“ customId”)),但是如果名称相同并且您的代码是使用调试信息或Java 8上的-parameters编译器标志进行编译的,则可以省略该详细信息。 。
语法{varName:regex}声明带有正则表达式的URI变量,语法为{varName:regex}。 例如,给定URL“ /spring-web-3.0.5 .jar”,以下方法提取名称,版本和文件扩展名:
@GetMapping("/{name:[a-z-]+}-{version:\\d\\.\\d\\.\\d}{ext:\\.[a-z]+}") public void handle(@PathVariable String version, @PathVariable String ext) { // ... }
URI路径模式也可以嵌入$ {…}占位符,这些占位符在启动时通过针对本地,系统,环境和其他属性源使用PropertyPlaceHolderConfigurer进行解析。 例如,您可以使用它来基于一些外部配置参数化基本URL。
Spring MVC使用spring-core中的PathMatcher协定和AntPathMatcher实现来进行URI路径匹配。
Pattern Comparison
当多个模式与URL匹配时,必须将它们进行比较以找到最佳匹配。这可以通过使用AntPathMatcher.getPatternComparator(String path)来完成,该工具查找更具体的模式。
如果模式的URI变量(计数为1),单通配符(计数为1)和双通配符(计数为2)的数量较少,则模式的含义不太明确。给定相等的分数,则选择更长的模式。给定相同的分数和长度,将选择具有比通配符更多URI变量的模式。
默认映射模式(/ )从评分中排除,并且始终排在最后。另外,前缀模式(例如/ public / )也被认为比其他没有双通配符的模式不太明确。
有关完整的详细信息,请参阅AntPathMatcher中的AntPatternComparator,并且请记住,您可以自定义PathMatcher实现。请参阅配置部分中的路径匹配。
后缀匹配
默认情况下,Spring MVC执行. 后缀模式匹配,以便映射到/ person的控制器也隐式映射到/person.。然后,文件扩展名用于解释请求的内容类型以用于响应(即,代替Accept标头),例如/person.pdf、/person.xml等。
当浏览器用来发送难以一致解释的Accept标头时,以这种方式使用文件扩展名是必要的。目前,这已不再是必须的,使用Accept标头应该是首选。
随着时间的流逝,文件扩展名的使用已经以各种方式证明是有问题的。当使用URI变量,路径参数和URI编码进行覆盖时,可能会引起歧义。关于基于URL的授权和安全性的推理(请参阅下一部分以了解更多详细信息)也变得更加困难。
若要完全禁用文件扩展名,必须设置以下两项:
基于URL的内容协商仍然有用(例如,在浏览器中键入URL时)。为此,我们建议使用基于查询参数的策略,以避免文件扩展名附带的大多数问题。或者,如果必须使用文件扩展名,请考虑通过ContentNegotiationConfigurer的mediaTypes属性将它们限制为已显式注册的扩展名列表。
后缀匹配和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标头。请参阅内容类型。
Consumable Media Types
您可以根据请求的Content-Type缩小请求映射,如以下示例所示:
@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。
Producible Media Types
您可以根据接受请求标头和控制器方法生成的内容类型列表来缩小请求映射,如以下示例所示:
@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8") @ResponseBody public Pet getPet(@PathVariable String petId) { // ... }
媒体类型可以指定字符集。 支持否定的表达式-例如,!text/plain表示除“ text / plain”以外的任何内容类型。
对于JSON内容类型,即使RFC7159明确声明“未为此注册定义任何字符集参数”,也应指定UTF-8字符集,因为某些浏览器要求它正确解释UTF-8特殊字符。
您可以在类级别声明共享的Produces属性。 但是,与大多数其他请求映射属性不同,在类级别使用时,方法级别会产生属性覆盖,而不是扩展类级别的声明。
MediaType为常用的媒体类型(例如APPLICATION_JSON_UTF8_VALUE和APPLICATION_XML_VALUE)提供常数。
Parameters, headers
您可以根据请求参数条件来缩小请求映射。 您可以测试是否存在请求参数(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 HEAD以进行请求映射。控制器方法不需要更改。应用于javax.servlet.http.HttpServlet的响应包装器确保将Content-Length标头设置为写入的字节数(实际上未写入响应)。
@GetMapping(和@RequestMapping(method = HttpMethod.GET))被隐式映射到并支持HTTP HEAD。像处理HTTP GET一样处理HTTP HEAD请求,不同之处在于,不是写入正文,而是计算字节数并设置Content-Length头。
默认情况下,通过将“允许响应”标头设置为所有具有匹配URL模式的@RequestMapping方法中列出的HTTP方法列表来处理HTTP OPTIONS。
对于没有HTTP方法声明的@RequestMapping,将Allow标头设置为GET,HEAD,POST,PUT,PATCH,DELETE,OPTIONS。控制器方法应始终声明支持的HTTP方法(例如,通过使用HTTP方法特定的变体:@ GetMapping,@ PostMapping等)。
您可以将@RequestMapping方法显式映射到HTTP HEAD和HTTP OPTIONS,但这在通常情况下不是必需的。
Custom Annotations
Spring MVC支持将组合注解用于请求映射。 这些注解本身使用@RequestMapping进行元注解,并且旨在以更狭窄,更具体的用途重新声明@RequestMapping属性的子集(或全部)。
@ GetMapping,@ PostMapping,@ PutMapping,@ DeleteMapping和@PatchMapping是组合注解的示例。 之所以提供它们,是因为可以说,大多数控制器方法应该映射到特定的HTTP方法,而不是使用@RequestMapping,后者默认情况下与所有HTTP方法都匹配。 如果需要组合注解的示例,请查看如何声明它们。
Spring MVC还支持带有自定义请求匹配逻辑的自定义请求映射属性。 这是一个更高级的选项,它需要子类化RequestMappingHandlerMapping并覆盖getCustomMethodCondition方法,您可以在其中检查自定义属性并返回自己的RequestCondition。
Explicit Registrations
您可以以编程方式注册处理程序方法,这些方法可用于动态注册或高级案例,例如同一处理程序在不同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处理程序方法具有灵活的签名,可以从一系列受支持的控制器方法参数和返回值中进行选择。
方法参数
下表描述了受支持的控制器方法参数。 任何参数均不支持反应性类型。
支持JDK 8的java.util.Optional作为方法参数,并与具有必需属性(例如,@ RequestParam,@ RequestHeader等)的注解结合在一起,等效于required = false。
Return Values
下表描述了受支持的控制器方法返回值。 所有返回值都支持反应性类型。
Type Conversion
如果参数声明为String以外的形式,则某些表示基于String的请求输入的带注解的控制器方法参数(例如@ RequestParam,@ RequestHeader,@ PathVariable,@ MatrixVariable和@CookieValue)可能需要类型转换。
在这种情况下,将根据配置的转换器自动应用类型转换。 默认情况下,支持简单类型(int,long,Date和其他)。 您可以通过WebDataBinder(请参见DataBinder)或通过在FormattingConversionService中注册Formatter来自定义类型转换。 参见Spring字段格式。
Matrix Variables
RFC 3986讨论了路径段中的名称/值对。 在Spring MVC中,基于Tim Berners-Lee的“旧帖子”,我们将其称为“矩阵变量”,但它们也可以称为URI路径参数。
矩阵变量可以出现在任何路径段中,每个变量用分号分隔,多个值用逗号分隔(例如,/ cars; color = red,green; year = 2012)。 也可以通过重复的变量名称指定多个值(例如,color = red; color = green; color = blue)。
如果期望URL包含矩阵变量,则控制器方法的请求映射必须使用URI变量来屏蔽该变量内容,并确保可以成功地匹配请求,而与矩阵变量的顺序和状态无关。 以下示例使用矩阵变量:
// GET /pets/42;q=11;r=22 @GetMapping("/pets/{petId}") public void findPet(@PathVariable String petId, @MatrixVariable int q) { // petId == 42 // q == 11 }
鉴于所有路径段都可能包含矩阵变量,因此有时您可能需要消除矩阵变量应位于哪个路径变量的歧义。以下示例说明了如何做到这一点:
// GET /owners/42;q=11/pets/21;q=22 @GetMapping("/owners/{ownerId}/pets/{petId}") public void findPet( @MatrixVariable(name="q", pathVar="ownerId") int q1, @MatrixVariable(name="q", pathVar="petId") int q2) { // q1 == 11 // q2 == 22 }
可以将矩阵变量定义为可选变量,并指定默认值,如以下示例所示:
// 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设置带有removeSemicolonContent = false的UrlPathHelper。 在MVC XML名称空间中,可以设置<mvc:annotation-driven enable-matrix-variables =“ true” />。
@RequestParam
您可以使用@RequestParam批注将Servlet请求参数(即查询参数或表单数据)绑定到控制器中的方法参数。
以下示例显示了如何执行此操作:
@Controller @RequestMapping("/pets") public class EditPetForm { // ... @GetMapping public String setupForm(@RequestParam("petId") int petId, Model model) { Pet pet = this.clinic.loadPet(petId); model.addAttribute("pet", pet); return "petForm"; } // ... }
默认情况下,使用此批注的方法参数是必需的,但是您可以通过将@RequestParam批注的required标志设置为false或使用java.util.Optional包装器声明该参数,来指定方法参数是可选的。
如果目标方法参数类型不是字符串,则将自动应用类型转换。请参阅类型转换。
将参数类型声明为数组或列表,可以为同一参数名称解析多个参数值。
如果将@RequestParam批注声明为Map <String,String>或MultiValueMap <String,String>,但未在批注中指定参数名称,则将使用每个给定参数名称的请求参数值填充映射。
请注意,@ RequestParam的使用是可选的(例如,设置其属性)。默认情况下,任何简单值类型的参数(由BeanUtils#isSimpleProperty确定)并且没有被任何其他参数解析器解析,就如同使用@RequestParam进行了注解一样。
@RequestHeader
您可以使用@RequestHeader批注将请求标头绑定到控制器中的方法参数。
考虑以下带有标头的请求:
Host localhost:8080 Accept text/html,application/xhtml+xml,application/xml;q=0.9 Accept-Language fr,en-gb;q=0.7,en;q=0.3 Accept-Encoding gzip,deflate Accept-Charset ISO-8859-1,utf-8;q=0.7,*;q=0.7 Keep-Alive 300
以下示例获取Accept-Encoding和Keep-Alive标头的值:
@GetMapping("/demo") public void handle( @RequestHeader("Accept-Encoding") String encoding, @RequestHeader("Keep-Alive") long keepAlive) { //... }
如果目标方法的参数类型不是String,则将自动应用类型转换。 请参阅类型转换。
在Map <String,String>,MultiValueMap <String,String>或HttpHeaders参数上使用@RequestHeader批注时,将使用所有标头值填充该映射。
内置支持可用于将逗号分隔的字符串转换为数组或字符串集合或类型转换系统已知的其他类型。 例如,用@RequestHeader(“ Accept”)注解的方法参数可以是String类型,也可以是String []或List <String>。
@CookieValue
您可以使用@CookieValue批注将HTTP cookie的值绑定到控制器中的方法参数。
考虑带有以下cookie的请求:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
以下示例显示如何获取cookie值:
@GetMapping("/demo") public void handle(@CookieValue("JSESSIONID") String cookie) { //... }
@ModelAttribute
您可以在方法参数上使用@ModelAttribute批注,以从模型访问属性,或者将其实例化(如果不存在)。 model属性还覆盖了名称与字段名称匹配的HTTP Servlet请求参数中的值。 这称为数据绑定,它使您不必处理解析和转换单个查询参数和表单字段的工作。 以下示例显示了如何执行此操作:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute Pet pet) { }
上面的Pet实例解析如下:
尽管通常使用模型来用属性填充模型,但另一种替代方法是依赖于Converter <String,T>与URI路径变量约定结合使用。 在以下示例中,模型属性名称account与URI路径变量account匹配,并且通过将String帐号传递给已注册的Converter <String,Account>来加载Account:
@PutMapping("/accounts/{account}") public String save(@ModelAttribute("account") Account account) { // ... }
获取模型属性实例后,将应用数据绑定。 WebDataBinder类将Servlet请求参数名称(查询参数和表单字段)与目标Object上的字段名称进行匹配。 必要时在应用类型转换后填充匹配字段。 有关数据绑定(和验证)的更多信息,请参见验证。 有关自定义数据绑定的更多信息,请参见DataBinder。
数据绑定可能导致错误。 默认情况下,引发BindException。 但是,要检查控制器方法中的此类错误,可以在@ModelAttribute旁边立即添加BindingResult参数,如以下示例所示:
@PostMapping("/owners/{ownerId}/pets/{petId}/edit") public String processSubmit(@ModelAttribute("pet") Pet pet, BindingResult result) { if (result.hasErrors()) { return "petForm"; } // ... }
在某些情况下,您可能希望访问没有数据绑定的模型属性。 对于这种情况,可以将模型注入控制器中并直接访问它,或者设置@ModelAttribute(binding = false),如以下示例所示:
@ModelAttribute public AccountForm setUpForm() { return new AccountForm(); } @ModelAttribute public Account findAccount(@PathVariable String accountId) { return accountRepository.findOne(accountId); } @PostMapping("update") public String update(@Valid AccountForm form, BindingResult result, @ModelAttribute(binding=false) Account account) { // ... }
您可以通过添加javax.validation.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注解。
@SessionAttributes
@SessionAttributes用于在请求之间的HTTP Servlet会话中存储模型属性。 它是类型级别的注解,用于声明特定控制器使用的会话属性。 这通常列出应透明地存储在会话中以供后续访问请求的模型属性的名称或模型属性的类型。
以下示例使用@SessionAttributes批注:
@Controller @SessionAttributes("pet") public class EditPetForm { // ... }
在第一个请求上,将名称为pet的模型属性添加到模型时,该属性会自动提升到HTTP Servlet会话并保存在该会话中。 它会一直保留在那里,直到另一个控制器方法使用SessionStatus方法参数来清除存储,如以下示例所示:
@Controller @SessionAttributes("pet") public class EditPetForm { // ... @PostMapping("/pets/{id}") public String handle(Pet pet, BindingResult errors, SessionStatus status) { if (errors.hasErrors) { // ... } status.setComplete(); // ... } } }
@SessionAttribute
如果您需要访问全局存在(例如,在控制器外部(例如,通过过滤器)管理)并且可能存在或可能不存在的预先存在的会话属性,则可以在方法参数上使用@SessionAttribute注解,例如 以下示例显示:
@RequestMapping("/") public String handle(@SessionAttribute User user) { // ... }
对于需要添加或删除会话属性的用例,请考虑将org.springframework.web.context.request.WebRequest或javax.servlet.http.HttpSession注入到控制器方法中。
要在控制器工作流中将模型属性临时存储在会话中,请考虑使用@SessionAttributes,如@SessionAttributes中所述。
@RequestAttribute
与@SessionAttribute相似,您可以使用@RequestAttribute批注来访问先前创建的预先存在的请求属性(例如,通过Servlet Filter或HandlerInterceptor):
@GetMapping("/") public String handle(@RequestAttribute Client client) { // ... }
Redirect Attributes
默认情况下,所有模型属性均被视为在重定向URL中作为URI模板变量公开。在其余属性中,那些属于原始类型或原始类型的集合或数组的属性会自动附加为查询参数。
如果专门为重定向准备了模型实例,则将原始类型属性作为查询参数附加可能是理想的结果。但是,在带注解的控制器中,模型可以包含为渲染目的添加的其他属性(例如,下拉字段值)。为避免此类属性出现在URL中的可能性,@ RequestMapping方法可以声明RedirectAttributes类型的参数,并使用它指定可用于RedirectView的确切属性。如果该方法确实重定向,则使用RedirectAttributes的内容。否则,将使用模型的内容。
RequestMappingHandlerAdapter提供了一个名为ignoreDefaultModelOnRedirect的标志,您可以使用该标志指示如果控制器方法重定向,则绝不要使用默认Model的内容。相反,控制器方法应声明一个RedirectAttributes类型的属性,或者,如果没有这样做,则不应将任何属性传递给RedirectView。 MVC名称空间和MVC Java配置都将此标志设置为false,以保持向后兼容性。但是,对于新应用程序,我们建议将其设置为true。
请注意,展开重定向URL时,本请求中的URI模板变量会自动变为可用,您无需通过Model或RedirectAttributes显式添加它们。以下示例显示了如何定义重定向:
@PostMapping("/files/{path}") public String upload(...) { // ... return "redirect:files/{path}"; }
将数据传递到重定向目标的另一种方法是使用flash attributes。 与其他重定向属性不同,flash attributes保存在HTTP会话中(因此不会出现在URL中)。 有关更多信息,请参见Flash属性。
Flash Attributes
Flash属性为一个请求提供了一种存储要在另一个请求中使用的属性的方式。重定向时最常需要此操作,例如Post-Redirect-Get模式。 Flash属性在重定向之前(通常在会话中)被临时保存,以便在重定向之后可供请求使用,并立即被删除。
Spring MVC有两个主要的抽象来支持Flash属性。 FlashMap用于保存Flash属性,而FlashMapManager用于存储,检索和管理FlashMap实例。
Flash属性支持始终处于“打开”状态,无需显式启用。但是,如果不使用它,则永远不会导致HTTP会话创建。在每个请求上,都有一个“输入” FlashMap,其属性是从上一个请求(如果有)传递过来的,而“输出” FlashMap的属性是为后续请求保存的。可以通过RequestContextUtils中的静态方法从Spring MVC中的任何位置访问这两个FlashMap实例。
带注解的控制器通常不需要直接使用FlashMap。相反,@ RequestMapping方法可以接受RedirectAttributes类型的参数,并使用它为重定向方案添加Flash属性。通过RedirectAttributes添加的Flash属性会自动传播到“输出” FlashMap。同样,重定向后,来自“输入” FlashMap的属性会自动添加到服务于目标URL的控制器的模型中。
将请求与Flash属性匹配 Flash属性的概念存在于许多其他Web框架中,并已证明有时会遇到并发问题。 这是因为根据定义,闪存属性将存储到下一个请求。 但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,过早删除了闪存属性。 为了减少此类问题的可能性,RedirectView会自动使用目标重定向URL的路径和查询参数“标记” FlashMap实例。 反过来,默认FlashMapManager在查找“输入” FlashMap时会将信息与传入请求匹配。 这不能完全消除并发问题的可能性,但可以通过重定向URL中已经可用的信息大大减少并发问题。 因此,我们建议您主要将Flash属性用于重定向方案。
将请求与Flash属性匹配 Flash属性的概念存在于许多其他Web框架中,并已证明有时会遇到并发问题。 这是因为根据定义,闪存属性将存储到下一个请求。 但是,“下一个”请求可能不是预期的接收者,而是另一个异步请求(例如,轮询或资源请求),在这种情况下,过早删除了闪存属性。
为了减少此类问题的可能性,RedirectView会自动使用目标重定向URL的路径和查询参数“标记” FlashMap实例。 反过来,默认FlashMapManager在查找“输入” FlashMap时会将信息与传入请求匹配。
这不能完全消除并发问题的可能性,但可以通过重定向URL中已经可用的信息大大减少并发问题。 因此,我们建议您主要将Flash属性用于重定向方案。
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作为方法参数或集合值类型。
您还可以将多部分内容用作数据绑定到命令对象的一部分。 例如,前面示例中的表单字段和文件可以是表单对象上的字段,如以下示例所示:
class MyForm { private String name; private MultipartFile file; // ... } @Controller public class FileUploadController { @PostMapping("/form") public String handleFormUpload(MyForm form, BindingResult errors) { if (!form.getFile().isEmpty()) { byte[] bytes = form.getFile().getBytes(); // store the bytes somewhere return "redirect:uploadSuccess"; } return "redirect:uploadFailure"; } }
在RESTful服务方案中,也可以从非浏览器客户端提交多部分请求。 以下示例显示了带有JSON的文件:
POST /someUrl Content-Type: multipart/mixed --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="meta-data" Content-Type: application/json; charset=UTF-8 Content-Transfer-Encoding: 8bit { "name": "value" } --edt7Tfrdusa7r3lNQc79vXuhIIMlatb7PQg7Vp Content-Disposition: form-data; name="file-data"; filename="file.properties" Content-Type: text/xml Content-Transfer-Encoding: 8bit ... File Data ...
您可以使用@RequestParam作为字符串访问“元数据”部分,但您可能希望将其从JSON反序列化(类似于@RequestBody)。 在使用HttpMessageConverter进行转换后,使用@RequestPart批注来访问多部分:
@PostMapping("/") public String handle(@RequestPart("meta-data") MetaData metadata, @RequestPart("file-data") MultipartFile file) { // ... }
您可以将@RequestPart与javax.validation.Valid结合使用,也可以使用Spring的@Validated注解,这两种注解都会导致应用标准Bean验证。 默认情况下,验证错误会导致MethodArgumentNotValidException,并将其转换为400(BAD_REQUEST)响应。 另外,您可以通过Errors或BindingResult参数在控制器内本地处理验证错误,如以下示例所示:
@PostMapping("/") public String handle(@Valid @RequestPart("meta-data") MetaData metadata, BindingResult result) { // ... }
@RequestBody
您可以使用@RequestBody批注使请求正文通过HttpMessageConverter读取并反序列化为Object。 以下示例使用@RequestBody参数:
@PostMapping("/accounts") public void handle(@RequestBody Account account) { // ... }
您可以使用MVC Config的“消息转换器”选项来配置或自定义消息转换。
您可以将@RequestBody与javax.validation.Valid或Spring的@Validated注解结合使用,这两种注解都会导致应用标准Bean验证。 默认情况下,验证错误会导致MethodArgumentNotValidException,并将其转换为400(BAD_REQUEST)响应。 另外,您可以通过Errors或BindingResult参数在控制器内本地处理验证错误,如以下示例所示:
@PostMapping("/accounts") public void handle(@Valid @RequestBody Account account, BindingResult result) { // ... }
HttpEntity
HttpEntity或多或少与使用@RequestBody相同,但它基于公开请求标头和正文的容器对象。 以下清单显示了一个示例:
@PostMapping("/accounts") public void handle(HttpEntity<Account> entity) { // ... }
@ResponseBody
您可以在方法上使用@ResponseBody批注,以通过HttpMessageConverter将返回序列化为响应主体。 以下清单显示了一个示例:
@GetMapping("/accounts/{id}") @ResponseBody public Account handle() { // ... }
在类级别也支持@ResponseBody,在这种情况下,所有控制器方法都将继承它。 这就是@RestController的效果,它只不过是带有@Controller和@ResponseBody标记的元注解。
您可以将@ResponseBody与反应类型一起使用。 有关更多详细信息,请参见异步请求和响应类型。
您可以将@ResponseBody方法与JSON序列化视图结合使用。 有关详细信息,请参见Jackson JSON。
ResponseEntity
ResponseEntity类似于@ResponseBody,但具有状态和标头。 例如:
@GetMapping("/something") public ResponseEntity<String> handle() { String body = ... ; String etag = ... ; return ResponseEntity.ok().eTag(etag).build(body); }
Spring MVC支持使用单值反应类型异步生成ResponseEntity,和/或为主体使用单值和多值反应类型。
Jackson JSON
Spring提供了对Jackson JSON库的支持。
Spring MVC为Jackson的序列化视图提供了内置支持,该视图仅可呈现Object中所有字段的一部分。 要将其与@ResponseBody或ResponseEntity控制器方法一起使用,可以使用Jackson的@JsonView批注来激活序列化视图类,如以下示例所示:
@RestController public class UserController { @GetMapping("/user") @JsonView(User.WithoutPasswordView.class) public User getUser() { return new User("eric", "7!jd#h23"); } } public class User { public interface WithoutPasswordView {}; public interface WithPasswordView extends WithoutPasswordView {}; private String username; private String password; public User() { } public User(String username, String password) { this.username = username; this.password = password; } @JsonView(WithoutPasswordView.class) public String getUsername() { return this.username; } @JsonView(WithPasswordView.class) public String getPassword() { return this.password; } }
@JsonView允许使用一组视图类,但是每个控制器方法只能指定一个。 如果需要激活多个视图,则可以使用复合界面。
对于依赖视图解析的控制器,可以将序列化视图类添加到模型中,如以下示例所示:
@Controller public class UserController extends AbstractController { @GetMapping("/user") public String getUser(Model model) { model.addAttribute("user", new User("eric", "7!jd#h23")); model.addAttribute(JsonView.class.getName(), User.WithoutPasswordView.class); return "userView"; } }
您可以使用@ModelAttribute批注:
本节讨论@ModelAttribute方法-前面列表中的第二项。控制器可以具有任意数量的@ModelAttribute方法。所有此类方法均在同一控制器中的@RequestMapping方法之前调用。也可以通过@ControllerAdvice在控制器之间共享@ModelAttribute方法。有关更多详细信息,请参见“控制器建议”部分。
@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); }
如果未明确指定名称,则根据Object 类型选择默认名称,如javadoc中针对的解释Conventions。您始终可以使用重载addAttribute方法或通过nameon 的属性@ModelAttribute(用于返回值)来分配显式名称。
您也可以将@ModelAttribute用作@RequestMapping方法上的方法级注解,在这种情况下,@ RequestMapping方法的返回值将解释为模型属性。 通常不需要这样做,因为它是HTML控制器的默认行为,除非返回值是一个String,否则它将被解释为视图名称。 @ModelAttribute还可以自定义模型属性名称,如以下示例所示:
@GetMapping("/accounts/{id}") @ModelAttribute("myAccount") public Account handle() { // ... return account; }
@Controller或@ControllerAdvice类可以具有用于初始化WebDataBinder实例的@InitBinder方法,而这些方法又可以:
@InitBinder方法可以注册特定于控制器的java.bean.PropertyEditor或Spring Converter和Formatter组件。 另外,您可以使用MVC配置在全局共享的FormattingConversionService中注册Converter和Formatter类型。
@InitBinder方法支持与@RequestMapping方法相同的许多参数,除了@ModelAttribute(命令对象)参数。 通常,它们使用WebDataBinder参数(用于注册)和无效的返回值声明。 以下清单显示了一个示例:
@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的设置时,可以重新使用相同的方法并注册特定于控制器的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被抛出)匹配,也可能与顶级包装器异常(例如,包装在IllegalStateException内部的IOException)内的直接原因匹配。
对于匹配的异常类型,如前面的示例所示,最好将目标异常声明为方法参数。 当多个异常方法匹配时,根源异常匹配通常比原因异常匹配更可取。 更具体地说,ExceptionDepthComparator用于根据从引发的异常类型开始的深度对异常进行排序。
另外,注解声明可以缩小异常类型以使其匹配,如以下示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity<String> handle(IOException ex) { // ... }
您甚至可以使用带有非常通用的参数签名的特定异常类型的列表,如以下示例所示:
@ExceptionHandler({FileSystemException.class, RemoteException.class}) public ResponseEntity<String> handle(Exception ex) { // ... }
根和原因异常匹配之间的区别可能令人惊讶。 在前面显示的IOException变体中,通常使用实际的FileSystemException或RemoteException实例作为参数来调用该方法,因为这两个实例均从IOException扩展。 但是,如果任何此类匹配异常都在本身是IOException的包装器异常中传播,则传入的异常实例就是该包装器异常。 在handle(Exception)变体中,行为甚至更简单。 在包装方案中,总是使用包装程序异常来调用此方法,在这种情况下,实际匹配的异常可以通过ex.getCause()找到。 仅当将它们作为顶级异常抛出时,传入的异常才是实际的FileSystemException或RemoteException实例。
根和原因异常匹配之间的区别可能令人惊讶。
在前面显示的IOException变体中,通常使用实际的FileSystemException或RemoteException实例作为参数来调用该方法,因为这两个实例均从IOException扩展。 但是,如果任何此类匹配异常都在本身是IOException的包装器异常中传播,则传入的异常实例就是该包装器异常。
在handle(Exception)变体中,行为甚至更简单。 在包装方案中,总是使用包装程序异常来调用此方法,在这种情况下,实际匹配的异常可以通过ex.getCause()找到。 仅当将它们作为顶级异常抛出时,传入的异常才是实际的FileSystemException或RemoteException实例。
我们通常建议您在参数签名中尽可能具体,以减少根类型和原因异常类型之间不匹配的可能性。考虑将多重匹配方法分解为单独的@ExceptionHandler方法,每个方法均通过其签名匹配单个特定的异常类型。
在多@ControllerAdvice安排中,我们建议在以相应顺序优先的@ControllerAdvice上声明您的主根异常映射。虽然根源异常匹配是原因的首选,但这是在给定控制器或@ControllerAdvice类的方法之间定义的。这意味着优先级较高的@ControllerAdvice Bean上的原因匹配优于优先级较低的@ControllerAdvice Bean上的任何匹配(例如,根)。
最后但并非最不重要的一点是,@ExceptionHandler方法实现可以选择通过以原始形式重新抛出异常来退出处理给定异常实例。在仅对根级别匹配或无法静态确定的特定上下文中的匹配感兴趣的情况下,这很有用。重新抛出的异常会在其余的解析链中传播,就像给定的@ExceptionHandler方法最初不会匹配一样。
Spring MVC中对@ExceptionHandler方法的支持建立在DispatcherServlet级别HandlerExceptionResolver机制上。
Method Arguments
@ExceptionHandler 方法支持以下参数:
返回值
@ExceptionHandler 方法支持以下返回值:
REST API exceptions
REST服务的常见要求是在响应正文中包含错误详细信息。 Spring框架不会自动执行此操作,因为响应主体中错误详细信息的表示是特定于应用程序的。 但是,@ RestController可以将@ExceptionHandler方法与ResponseEntity返回值一起使用,以设置响应的状态和主体。 也可以在@ControllerAdvice类中声明此类方法,以将其全局应用。
在响应主体中使用错误详细信息实现全局异常处理的应用程序应考虑扩展ResponseEntityExceptionHandler,它提供了Spring MVC引发的异常的处理并提供了自定义响应主体的钩子。 要使用此功能,请创建ResponseEntityExceptionHandler的子类,并使用@ControllerAdvice对其进行注解,重写必要的方法,并将其声明为Spring bean。
通常,@ ExceptionHandler,@ InitBinder和@ModelAttribute方法在声明它们的@Controller类(或类层次结构)中应用。如果要使此类方法更全局地应用(跨控制器),则可以在标有@ControllerAdvice或@RestControllerAdvice的类中声明它们。
@ControllerAdvice标有@Component,这意味着可以通过组件扫描将此类注册为Spring Bean。 @RestControllerAdvice还是一个标有@ControllerAdvice和@ResponseBody的元注解,这实际上意味着@ExceptionHandler方法通过消息转换(与视图分辨率或模板渲染)呈现到响应主体。
在启动时,@RequestMapping和@ExceptionHandler方法的基础结构类检测@ControllerAdvice类型的Spring bean,然后在运行时应用其方法。全局@ExceptionHandler方法(来自@ControllerAdvice)在本地方法(来自@Controller)之后应用。相比之下,全局@ModelAttribute和@InitBinder方法在本地方法之前应用。
默认情况下,@ ControllerAdvice方法适用于每个请求(即所有控制器),但是您可以通过使用批注上的属性将其范围缩小到控制器的子集,如以下示例所示:
// Target all Controllers annotated with @RestController @ControllerAdvice(annotations = RestController.class) public class ExampleAdvice1 {} // Target all Controllers within specific packages @ControllerAdvice("org.example.controllers") public class ExampleAdvice2 {} // Target all Controllers assignable to specific classes @ControllerAdvice(assignableTypes = {ControllerInterface.class, AbstractController.class}) public class ExampleAdvice3 {}
前面示例中的选择器在运行时进行评估,如果广泛使用,可能会对性能产生负面影响。 有关更多详细信息,请参见@ControllerAdvice javadoc。
Copyright© 2013-2020
All Rights Reserved 京ICP备2023019179号-8