我在前面的文章中介绍了Spring MVC最核心的组件DispatcherServlet,DispatcherServlet把Servlet容器(如Tomcat)中的请求和Spring中的组件联系到一起,是SpringWeb应用的枢纽。但是我们在日常开发中往往不需要详细知道枢纽的作用,我们只需要处理枢纽分发给我们的请求。Spring中处理请求业务逻辑最常见的组件是Controller,本文会对Spring的Controller及相关组件做详细介绍。
Controller的定义
Controller是Spring中的一个特殊组件,这个组件会被Spring识别为可以接受并处理网页请求的组件。Spring中提供了基于注解的Controller定义方式:@Controller和@RestController注解。基于注解的Controller定义不需要继承或者实现接口,用户可以自由的定义接口签名。以下为Spring Controller定义的示例。
@Controller
public class HelloController {
@GetMapping("/hello")
public String handle(Model model) {
model.addAttribute("message", "Hello World!");
return "index";
}
}
@Controller注解继承了Spring的@Component注解,会把对应的类声明为Spring对应的Bean,并且可以被Web组件管理。@RestController注解是@Controller和@ResponseBody的组合,@ResponseBody表示函数的返回不需要渲染为View,应该直接作为Response的内容写回客户端。
映射关系RequestMapping
路径的定义
定义好一个Controller之后,我们需要将不同路径的请求映射到不同的Controller方法之上,Spring同样提供了基于注解的映射方式:@RequestMapping。通常情况下,用户可以在Controller类和方法上面添加@RequestMapping注解,Spring容器会识别注解并将满足路径条件的请求分配到对应的方法进行处理。在下面的示例中,"GET /persons/xxx"会调用getPerson
方法处理。
@RestController
@RequestMapping("/persons")
class PersonController {
@GetMapping("/{id}")
public Person getPerson(@PathVariable Long id) {
// ...
}
@PostMapping
@ResponseStatus(HttpStatus.CREATED)
public void add(@RequestBody Person person) {
// ...
}
}
路径的匹配
Spring支持两种路径匹配方式,二者之间可以很好的兼容,Spring默认使用PathPattern进行路径的匹配。
- PathPattern:使用预解析的方法匹配路径。专门为Web路径匹配而设计,可以支持复杂的表达式,执行效率很高。
- AntPathMatcher:Spring中用于类路径、文件系统和其它资源的解决方案,效率比较低。
PathPattern基本可以向下兼容AntPathMatcher的逻辑,并且支持路径变量和"**"多段路径匹配,以下列出几种PathPattern的示例:
路径示例 | 说明 |
---|---|
/resources/ima?e.png | 路径中有一个字符是可变的,如/resources/image.png |
/resources/*.png | 路径中多个字符是可变的,如/resources/test.png |
/resources/** | 路径中多段可变,如/resources/test/path/xxx |
/projects/{project}/versions | 匹配一段路径,并且把路径中的值提取出来,如/projects/MyApp/versions |
/projects/{project:[a-z]+}/versions | 匹配一段符合正则表达式路径,并且把路径中的值提取出来,如/projects/myapp/versions |
路径中匹配到的变量可以使用@PathVariable获取,Path变量可以是方法或者类级别的,匹配到的变量会自动进行类型转换,如果转换失败则会抛出异常。
@GetMapping("/owners/{ownerId}/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
@Controller
@RequestMapping("/owners/{ownerId}")
public class OwnerController {
@GetMapping("/pets/{petId}")
public Pet findPet(@PathVariable Long ownerId, @PathVariable Long petId) {
// ...
}
}
路径冲突
当一次请求匹配到多个Pattern,那么就需要选出最接近的Pattern路径。Spring为Pattern和AntPathMatcher提供了选择最接近的路径策略,二者之间逻辑相近,此处只介绍PathPattern。对于PathPattern,Spring提供了PathPattern.SPECIFICITY_COMPARATOR用于对比路径之间的优先级,对比的规则如下:
- null的pattern具有最低优先级。
- 包含通配符的pattern的具有最低优先级(如/**)。
- 如果两个pattern都包含通配符,长度比较长的有更高的优先级。
- 包含越少匹配符号和越少路径变量的pattern有越高的优先级。
- 路径越长的优先级越高。
Spring 5.3之后不再支持.*后缀匹配,默认情况下“/person”就会匹配到所有的 “/person.*”
接受和返回参数的类型
RequestMapping还可以指定接口接受什么类型的参数以及返回什么类型的参数,这通常会在请求头的Content-Type中指定:
@PostMapping(path = "/pets", consumes = "application/json")
public void addPet(@RequestBody Pet pet) {
// ...
}
@GetMapping(path = "/pets/{petId}", produces = "application/json")
@ResponseBody
public Pet getPet(@PathVariable String petId) {
// ...
}
根据参数或Header选择
RequestMapping还支持按照请求的参数或者Header判断是否处理请求。
-
如只接受参数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) { // ... }
编程式注册RequestMapping
我们前面的教程中讲的都是怎么通过@RequestMapping进行路径的映射,使用这种方式会自动把路径映射为添加了注解的方法。这种方式虽然使用很方便,但是灵活性方面有一些欠缺,如果我想要根据Bean的配置信息动态映射路径之间的关系时,注解的方式就无法做到这种需求。Spring提供了一种动态注册RequestMapping的方法,注册示例如下所示:
@Configuration
public class MyConfig {
// 从容器中获取维护映射关系的RequestMappingHandlerMapping和自定义组件UserHandler
@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.