一、 过滤器(Filter)
spring-web 模块提供了一些有用的过滤器(Filter):
- Form Data
- Forwarded Header
- Shallow ETag
- CORS
1. Form Data
浏览器只能通过 HTTP GET 或 HTTP POST 提交表单数据(form data),但非浏览器客户端也可以使用HTTP PUT、PATCH和DELETE。Servlet API要求 ServletRequest.getParameter*()
方法只支持HTTP POST的表单字段访问。
spring-web
模块提供了 FormContentFilter
来拦截内容类型为 application/x-www-form-urlencoded
的 HTTP PUT、PATCH 和 DELETE 请求,从 request body 中读取 form data,并包裹 ServletRequest
,使 form data 通过 ServletRequest.getParameter*()
系列方法可用。
2. Forwarded Header
请求通过代理(如负载均衡器)时,host、port和scheme可能会发生变化,这使得从客户角度创建指向正确host、port和scheme的链接成为一种挑战。
RFC 7239 定义了 Forwarded
HTTP头,代理可以用它来提供关于原始请求的信息。还有其他非标准的头,包括 X-Forwarded-Host
、X-Forwarded-Port
、X-Forwarded-Proto
、X-Forwarded-SSL
和 X-Forwarded-Prefix
。
ForwardedHeaderFilter
是一个Servlet过滤器,它修改了请求,以便a)根据 Forwarded
头信息改变host、port和scheme,以及b)删除这些头信息以消除进一步的影响。这个过滤器依赖于对请求的包装,因此它必须排在其他过滤器的前面,比如 RequestContextFilter
,它应该与修改后的请求而不是原始请求一起工作。
对 forwarded 头有安全方面的考虑,因为应用程序无法知道这些头是由代理添加的,还是由恶意的客户端添加的。这就是为什么在信任边界的代理应该被配置为删除来自外部的不被信任的 Forwarded
头信息。你也可以将 ForwardedHeaderFilter
配置为 removeOnly=true
,在这种情况下,它将删除但不使用这些头信息。
为了支持 异步请求 和 error dispatch(调度),这个过滤器应该与 DispatcherType.ASYNC
和 DispatcherType.ERROR
进行映射。如果使用Spring框架的 AbstractAnnotationConfigDispatcherServletInitializer
,所有过滤器都会自动注册为所有的 dispatch 类型。然而,如果通过 web.xml
或在Spring Boot中通过 FilterRegistrationBean
注册过滤器,请确保除了 DispatcherType.ASYNC
和 DispatcherType.ERROR
之外,还要包括 DispatcherType.REQUEST
。
3. Shallow ETag
ShallowEtagHeaderFilter
通过缓存写入响应的内容并计算出MD5哈希值来创建一个 “shallow” ETag。客户端下次发送时,它做同样的事情,但它也将计算的值与 If-None-Match
请求头进行比较,如果两者相等,则返回304(NOT_MODIFIED)。
这种策略可以节省网络带宽,但不能节省CPU,因为必须为每个请求计算出完整的响应。前面描述的控制器层面的其他策略,可以避免计算的发生
这个过滤器有一个 writeWeakETag
参数,可以配置该过滤器写入类似以下的 weak ETag: W/"02a2d595e6ed9a0b24f027f2b63b134d6"
为了支持 异步请求,这个过滤器必须用 DispatcherType.ASYNC 来映射,这样过滤器就可以延迟并成功地生成ETag到最后一个异步dispatch的结束。如果使用Spring框架的 AbstractAnnotationConfigDispatcherServletInitializer(见 Servlet 配置),所有过滤器都会自动注册为所有的dispatch类型。然而,如果通过 web.xml 或Spring Boot中的 FilterRegistrationBean 注册过滤器,请确保包含 DispatcherType.ASYNC。
4. CORS
Spring MVC通过控制器(controller)上的注解为CORS配置提供了细粒度的支持。然而,当与Spring Security一起使用时,我们建议依靠内置的 CorsFilter
,它的 order 必须在 Spring Security 的过滤器链之前。
二、 注解式 Controller
Spring MVC提供了一个基于注解的编程模型,其中 @Controller
和 @RestController
组件使用注解来表达请求映射、请求输入、异常处理等内容。注解的控制器具有灵活的方法签名,不需要继承基类,也不需要实现特定的接口。下面的例子显示了一个由注解定义的控制器(Controller):
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
在前面的例子中,该方法接受一个 Model
,并返回一个 String
的视图名称,但也存在许多其他选项,在本章后面会有解释。
1. 声明
你可以通过在Servlet的 WebApplicationContext
中使用标准的Spring Bean定义来定义 controller Bean。@Controller
stereotype 允许自动检测,与Spring对检测 classpath 中的 @Component
类并为其自动注册bean定义的一般支持一致。它也是注解类的一个 stereotype,表明它是一个 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 组成的元注解,表示一个controller的每个方法都继承了类型级的 @ResponseBody 注解,因此,直接写入响应体,而不是用HTML模板进行视图解析和渲染。
AOP 代理
在某些情况下,你可能需要在运行时用AOP代理来装饰一个controller。一个例子是,如果你选择在 controller 上直接使用 @Transactional
注解。在这种情况下,特别是对于controller,我们建议使用基于类的代理,这种注解会自动出现在直接出现在 controller 上。
如果 controller 实现了一个接口,并且需要AOP代理,你可能需要明确配置基于类的代理。例如,对于 @EnableTransactionManagement
,你可以改为 @EnableTransactionManagement(proxyTargetClass = true)
,而对于 <tx:annotation-driven/>
,你可以改为 <tx:annotation-driven proxy-target-class="true"/>
。
2. 请求映射(Request Mapping)
你可以使用 @RequestMapping
注解来映射请求到controller方法。它有各种属性,可以通过URL、HTTP方法、请求参数、header和媒体类型(meida type)进行匹配。你可以在类的层面上使用它来表达共享映射,也可以在方法的层面上使用它来缩小到一个特定的端点映射。
还有HTTP方法特定的 @RequestMapping
的快捷方式变体:
-
@GetMapping
-
@PostMapping
-
@PutMapping
-
@DeleteMapping
-
@PatchMapping
捷径是 自定义注解,因为可以说,大多数 controller 方法应该被映射到一个特定的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 pattern
@RequestMapping 方法可以使用 URL pattern 进行映射。有两种选择:
-
PathPattern
— 一个与URL路径相匹配的预解析pattern,也被预解析为PathContainer
。这个解析方案是为web使用而设计的,它有效地处理了编码和路径参数,并有效地进行了匹配。 -
AntPathMatcher
— 匹配字符串 pattern 和字符串路径。这也是Spring配置中用来选择classpath、文件系统和其他位置资源的原始解析方案。它的效率较低,而且字符串路径输入对于有效处理URL的编码和其他问题是一个挑战。
PathPattern 是Web应用程序的推荐解析方案,也是Spring WebFlux的唯一选择。它从5.3版本开始在Spring MVC中启用,从6.0版本开始默认启用。
PathPattern
支持与 AntPathMatcher
相同的 pattern 语法。此外,它还支持捕获 pattern,例如 {*spring}
,用于匹配路径末端的0个或多个路径段。PathPattern
还限制了 **
在匹配多个路径段时的使用,即只允许在 pattern 的末端使用。这就消除了在为一个给定的请求选择最佳匹配 pattern 时的许多模糊情况。
一些示例 pattern:
-
"/resources/ima?e.png"
- 匹配路径段中的一个字符 -
"/resources/*.png"
- 匹配一个路径段中的零个或多个字符 -
"/resources/**"
- 匹配多个路径段 -
"/projects/{project}/versions"
- 匹配一个路径段并将其作为一个变量捕获 -
"/projects/{project:[a-z]+}/versions"
- 匹配并捕获一个带有正则的变量
捕获的URI变量可以用 @PathVariable
访问。比如说:
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
你可以在类和方法层面上声明URI变量,如下例所示:
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
URI变量会自动转换为适当的类型,否则会引发 TypeMismatchException
。简单的类型(int
、long
、Date
等)是默认支持的,你可以注册对任何其他数据类型的支持。
你可以明确地命名URI变量(例如,@PathVariable("customId")
),但是如果名称相同,并且你的代码是用 -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 name, @PathVariable String version, @PathVariable String ext) {
// ...
}
URI路径模式也可以有嵌入的 ${…}
占位符,在启动时通过使用 PropertySourcesPlaceholderConfigurer
针对本地、系统、环境和其他属性源进行解析。例如,你可以使用它来根据一些外部配置对基本URL进行参数化。
Pattern 比较
当多个 pattern 匹配一个URL时,必须选择最佳匹配。这是根据是否启用使用解析的 PathPattern 来完成的,取决于是否启用使用:
- PathPattern.SPECIFICITY_COMPARATOR
- AntPathMatcher.getPatternComparator(String path)
两者都有助于对 pattern 进行分类,更具体的 pattern 在上面。如果一个 pattern 的URI变量(计为1)、单通配符(计为1)和双通配符(计为2)的数量较少,那么它的具体内容就较少。在得分相同的情况下,选择较长的 pattern 。在分数和长度相同的情况下,选择URI变量多于通配符的 pattern。
默认的映射模式(/**)被排除在评分之外,并且总是排在最后。另外,前缀模式(如 /public/**)被认为比其他没有双通配符的模式更不具体。
如需了解全部细节,请按照上述链接查看 pattern 比较器(Comparators)。