1.4 Annotated Controllers

Spring WebFlux提供了一个基于注解的编程模型,其中@Controller和@RestController组件使用注解来表达请求映射,请求输入,处理异常等。 带注解的控制器具有灵活的方法签名,无需扩展基类或实现特定的接口。

以下清单显示了一个基本示例:

@RestController
public class HelloController {

    @GetMapping("/hello")
    public String handle() {
        return "Hello WebFlux";
    }
}

1.4.1 @Controller

您可以使用标准的Spring bean定义来定义控制器bean。 @Controller构造型允许自动检测,并且与Spring常规支持保持一致,以支持在类路径中检测@Component类并为其自动注册Bean定义。 它还充当带注解类的构造型,表明其作为Web组件的作用。

要启用对此类@Controller bean的自动检测,可以将组件扫描添加到Java配置中,如以下示例所示:

@Configuration
@ComponentScan("org.example.web") 
public class WebConfig {

    // ...
}

@RestController是一个组合式注解,其本身使用@Controller和@ResponseBody进行了元注解,表示一个控制器,其每个方法都继承了类型级别的@ResponseBody注解,因此直接将其写入响应主体(与视图分辨率相对)并使用 HTML模板。

1.4.2 Request Mapping

您可以使用@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 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 WebFlux使用PathPattern和PathPatternParser获得URI路径匹配支持。 这两个类都位于spring-web中,并专门设计用于在运行时匹配大量URI路径模式的Web应用程序中的HTTP URL路径。

Spring WebFlux不支持后缀模式匹配,这与Spring MVC不同,后者的映射(例如/ person)也匹配到/person.*。 对于基于URL的内容协商,如果需要,我们建议使用查询参数,该参数更简单,更明确,并且不易受到基于URL路径的攻击。

Pattern Comparison

当多个模式与URL匹配时,必须将它们进行比较以找到最佳匹配。 这是通过PathPattern.SPECIFICITY_COMPARATOR完成的,该工具查找更具体的模式。

对于每个模式,都会根据URI变量和通配符的数量计算得分,其中URI变量的得分低于通配符。 总得分较低的模式将获胜。 如果两个模式的分数相同,则选择更长的时间。

包罗万象的模式(例如_*,{_ varName})不计入评分,而是始终排在最后。 如果两种模式都适用,则选择更长的时间。

Consumable Media Types

您可以根据请求的Content-Type缩小请求映射,如以下示例所示:

@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
    // ...
}

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

您可以在类级别上声明一个共享的消耗属性。 但是,与大多数其他请求映射属性不同,在类级别使用方法级别时,方法级别使用属性覆盖而不是扩展类级别声明。

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); 
    }

}

1.4.3 Handler Methods

@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实例解析如下:

  • 从模型(如果已通过使用模型添加)。

  • 通过使用@SessionAttributes在HTTP会话中进行。

  • 来自通过Converter传递的URI路径变量(请参见下一个示例)。

  • 从默认构造函数的调用开始。

  • 从调用具有与Servlet请求参数匹配的参数的“主要构造函数”开始。 参数名称是通过JavaBeans @ConstructorProperties或字节码中运行时保留的参数名称确定的。

尽管通常使用模型来用属性填充模型,但另一种替代方法是依赖于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属性用于重定向方案。

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与反应类型一起使用。 有关更多详细信息,请参见异步请求和响应类型。

您可以使用MVC Config的“消息转换器”选项来配置或自定义消息转换。

您可以将@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";
    }
}

1.4.4 Model

您可以使用@ModelAttribute批注:

  • 在@RequestMapping方法中的方法参数上,可从模型创建或访问对象,并将其通过WebDataBinder绑定到请求。

  • 作为@Controller或@ControllerAdvice类中的方法级注解,可在任何@RequestMapping方法调用之前帮助初始化模型。

  • 在@RequestMapping方法上标记其返回值的是模型属性。

本节讨论@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(用于返回值)来分配显式名称。

与Spring MVC不同,Spring WebFlux在模型中显式支持反应式类型(例如Mono <Account>或io.reactivex.Single <Account>)。 可以在@RequestMapping调用时将此类异步模型属性透明地解析(并更新模型)为其实际值,只要声明了@ModelAttribute参数而没有包装,如以下示例所示:

@ModelAttribute
public void addAccount(@RequestParam String number) {
    Mono<Account> accountMono = accountRepository.findAccount(number);
    model.addAttribute("account", accountMono);
}

@PostMapping("/accounts")
public String handle(@ModelAttribute Account account, BindingResult errors) {
    // ...
}

另外,任何具有反应性类型包装器的模型属性都将在视图渲染之前解析为其实际值(并更新了模型)。

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

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

1.4.5 DataBinder

@Controller或@ControllerAdvice类可以具有用于初始化WebDataBinder实例的@InitBinder方法,而这些方法又可以:

  • 将请求参数(即表单或查询数据)绑定到模型对象。

  • 将基于字符串的请求值(例如请求参数,路径变量,标头,Cookie等)转换为控制器方法参数的目标类型。

  • 呈现HTML表单时,将模型对象的值格式化为String值。

@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"));
    }

    // ...
}

1.4.6 Managing Exceptions

@Controller和@ControllerAdvice类可以具有@ExceptionHandler方法来处理来自控制器方法的异常。 下面的示例包括这样的处理程序方法:

@Controller
public class SimpleController {

    // ...

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

该异常可以与正在传播的顶级异常(即,直接IOException被抛出)匹配,也可以与顶级包装器异常(例如,包装在IllegalStateException内部的IOException)内的直接原因匹配。

对于匹配的异常类型,最好将目标异常声明为方法参数,如前面的示例所示。或者,注解声明可以缩小异常类型以使其匹配。我们通常建议在参数签名中尽可能具体,并在以相应顺序优先的@ControllerAdvice上声明您的主根异常映射。有关详细信息,请参见MVC部分。

WebFlux中的@ExceptionHandler方法支持与@RequestMapping方法相同的方法参数和返回值,但与请求正文和@ModelAttribute相关的方法参数除外。

HandlerAdapter为@RequestMapping方法提供对Spring WebFlux中@ExceptionHandler方法的支持。有关更多详细信息,请参见DispatcherHandler。

REST API exceptions

REST服务的常见要求是在响应正文中包含错误详细信息。 Spring框架不会自动这样做,因为响应主体中错误详细信息的表示是特定于应用程序的。 但是,@ RestController可以将@ExceptionHandler方法与ResponseEntity返回值一起使用,以设置响应的状态和主体。 也可以在@ControllerAdvice类中声明此类方法,以将其全局应用。

请注意,Spring WebFlux与Spring MVC ResponseEntityExceptionHandler没有等效项,因为WebFlux仅引发ResponseStatusException(或其子类),并且不需要将其转换为HTTP状态代码。

1.4.7 Controller Advice

通常,@ 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。

最后更新于