1.3 Annotated Controllers

一、声明

    @Controller构造型允许自动检测,与Spring一致支持,以检测类路径中的@Component类并自动注册它们的bean定义。它还充当带注释的类的构造型,表明它作为Web组件的角色。XML中配置<context:component-scan base-package="org.example.web"/>,可以扫描配置路径下所有被@Controller标注的类,并自动注册bean。

    @RestController是一个组合注释,它本身用@Controller和@ResponseBody进行元注释,以指示一个控制器,它的每个方法都继承了类型级@ResponseBody注释,因此直接写入响应主体,而不是视图结果并且渲染一个 HTML模板。

    AOP 代理:

    在某些情况下,您可能需要在运行时使用AOP代理装饰控制器。 一个例子是如果您选择直接在控制器上使用@Transactional注释。 在这种情况下,对于控制器而言,我们建议使用基于类的代理。 这通常是控制器的默认选择。 但是,如果控制器必须实现不是Spring Context回调的接口(例如InitializingBean,* Aware等),则可能需要显式配置基于类的代理。 例如,使用<tx:annotation-driven />,您可以更改为<tx:annotation-driven proxy-target-class =“true”/>。

二、请求映射

    您可以使用@RequestMapping批注将请求映射到控制器方法。 它具有各种属性,可通过URL,HTTP方法,请求参数,请求头和媒体类型进行匹配。 您可以在类级别使用它来表示共享映射,或者在方法级别使用它来缩小到特定的端点映射。以下是快捷方式定义的注解:

   @GetMapping

   @PostMapping

   @PutMapping

   @DeleteMapping

   @PatchMapping

   快捷方式是提供的自定义注释,因为可以说,大多数控制器方法应该映射到特定的HTTP方法而不是使用@RequestMapping,默认情况下,它与所有HTTP方法匹配。 同样,在类级别仍然需要@RequestMapping来表示共享映射。

   URI 匹配:

   您可以使用以下全局模式和通配符映射请求:

  ? 匹配一个字符

   *匹配路径段中的零个或多个字符

   **匹配零个或多个路径段

   您还可以使用@PathVariable声明URI变量并访问它们的值,如以下示例所示:

@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
    // ...
}

  URI变量自动转换为适当的类型,或引发TypeMismatchException。 默认情况下支持简单类型(int,long,Date等),您可以注册对任何其他数据类型的支持。 请参见类型转换和数据绑定器。

  您可以显式命名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使用PathMatcher契约和spring-core的AntPathMatcher实现进行URI路径匹配。

     匹配比较:

    当多个模式与URL匹配时,必须对它们进行比较以找到最佳匹配。 这是通过使用AntPathMatcher.getPatternComparator(String path)来完成的,它会查找更具体的模式。

    如果URI变量的计数较少(计为1),单个通配符(计为1)和双通配符(计为2),则模式的特定性较低。 给定相等的分数,选择较长的模式。 给定相同的分数和长度,选择具有比通配符更多的URI变量的模式。默认映射模式(/ **)从scoring 中排除,并始终排在最后。 此外,前缀匹配(如/public/**)被认为不如没有双通配符的其他模式具体。

     后缀匹配:

     默认情况下,Spring MVC执行.*后缀模式匹配,以便映射到/ person的控制器也隐式映射到/person.*。 然后使用文件扩展名来解释用于响应的请求内容类型(即,而不是Accept标头) - 例如,/ person.pdf,/person.xml等。

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

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

    要完全禁用文件扩展名,必须同时设置以下两项:

    useSuffixPatternMatching(false),请参阅PathMatchConfigurer

    favorPathExtension(false),请参阅ContentNegotiationConfigurer

    基于URL的内容协商仍然有用(例如,在浏览器中键入URL时)。 为此,我们建议使用基于查询参数的策略来避免文件扩展名带来的大多数问题。 或者,如果必须使用文件扩展名,请考虑通过ContentNegotiationConfigurer的mediaTypes属性将它们限制为显式注册的扩展名列表。

    后缀匹配和RFD:

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

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

为了防止RFD攻击,在呈现响应主体之前,Spring MVC添加了一个Content-Disposition:inline; filename = f.txt标头来建议一个固定且安全的下载文件。仅当URL路径包含既未列入白名单也未明确注册用于内容协商的文件扩展名时,才会执行此操作。但是,当直接在浏览器中输入URL时,它可能会产生副作用。

默认情况下,许多常见路径扩展名列入白名单。具有自定义HttpMessageConverter实现的应用程序可以显式注册文件扩展名以进行内容协商,以避免为这些扩展添加Content-Disposition标头。请参阅内容类型。

有关RFD的其他建议,请参阅CVE-2015-5211。

   可消费的媒体类型:

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

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

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

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

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

  可生产的媒体类型:

  您可以根据Accept请求标头和控制器方法生成的内容类型列表来缩小请求映射,如以下示例所示:

@GetMapping(path = "/pets/{petId}", produces = "application/json;charset=UTF-8") 
@ResponseBody
public Pet getPet(@PathVariable String petId) {
    // ...
}

   使用produce属性来缩小内容类型的映射。

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

   对于JSON内容类型,即使RFC7159明确指出“没有为此注册定义charset参数”,也应指定UTF-8字符集,因为某些浏览器要求它正确解释UTF-8特殊字符。

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

   参数,头:

   您可以根据请求参数条件缩小请求映射。 您可以测试是否存在请求参数(myParam),一个没有(!myParam)或特定值(myParam = myValue)。 以下示例显示如何测试特定值:

@GetMapping(path = "/pets/{petId}", params = "myParam=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

测试myParam是否等于myValue。

您还可以将其与请求标头条件一起使用,如以下示例所示:

@GetMapping(path = "/pets", headers = "myHeader=myValue") 
public void findPet(@PathVariable String petId) {
    // ...
}

测试myHeader是否等于myValue。

您可以将Content-Type和Accept与headers条件匹配,但最好使用consume并生成。

  HTTP HEAD,OPTIONS:

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

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

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

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

您可以将@RequestMapping方法显式映射到HTTP HEAD和HTTP OPTIONS,但在常见情况下这不是必需的。

    自定义注释:

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

@ GetMapping,@ PostMapping,@ PutMapping,@ DeleMapping和@PatchMapping是组合注释的示例。提供它们是因为,可以说,大多数控制器方法应该映射到特定的HTTP方法而不是使用@RequestMapping,默认情况下,它与所有HTTP方法匹配。如果您需要组合注释的示例,请查看如何声明这些注释。

Spring MVC还支持使用自定义请求匹配逻辑的自定义请求映射属性。这是一个更高级的选项,需要继承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); 添加注册。
    }

}





posted @ 2019-08-01 21:00  特仑苏灬  阅读(212)  评论(0编辑  收藏  举报