Spring定位唯一的处理器

Spring是如何通过lookupPath返回的List集合处理器定位到唯一的处理器的?

1.具体工作原理

当一个请求到达时,Spring MVC会执行以下步骤:

  1. 获取路径:从请求中提取URL路径。
  2. 查找映射:使用getMappingsByUrl方法从MappingRegistry中获取对应路径的所有处理器方法。
  3. 匹配方法:遍历返回的处理器方法列表,根据请求的具体条件(如HTTP方法、请求参数等)找到合适的处理器方法。

2.示例代码

假设我们有以下控制器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@RestController
@RequestMapping("/api/users")
public class UserController {

@GetMapping
public ResponseEntity<User> getUser(@RequestParam String username) {
// 根据用户名获取用户
return ResponseEntity.ok(/* some user based on username */);
}

@GetMapping
public ResponseEntity<User> getUser(@RequestParam String username, @RequestParam String email) {
// 根据用户名和邮箱获取用户
return ResponseEntity.ok(/* some user based on username and email */);
}
}

3.区分处理器方法的机制

Spring MVC在处理请求时,会检查请求参数以确定哪个处理器方法应处理该请求。具体过程如下:

  1. 路径匹配:首先,Spring MVC会找到与请求URL匹配的处理器方法。在上面的例子中,两个方法的路径都匹配/api/users
  2. HTTP方法匹配:接下来,Spring MVC会筛选出与请求的HTTP方法匹配的处理器方法。在上面的例子中,两个方法的HTTP方法都是GET。此处会返回2个处理器。
  3. 参数匹配:最后,Spring MVC会检查请求参数,选择参数匹配的处理器方法。如果请求中包含的参数能够唯一确定一个处理器方法,Spring MVC就会调用该方法。

4.其他匹配条件

除了URL路径和HTTP方法外,Spring MVC还支持根据以下条件匹配处理器方法:

  • 请求头:使用@RequestMappingheaders属性。
1
@RequestMapping(value = "/example", headers = "key=value")
  • 请求参数:使用@RequestMappingparams属性。
1
@RequestMapping(value = "/example", params = "param=value")
  • 消费的内容类型:使用@RequestMappingconsumes属性。
1
@RequestMapping(value = "/example", consumes = "application/json")
  • 产生的内容类型:使用@RequestMappingproduces属性。
1
@RequestMapping(value = "/example", produces = "application/json")

5.匹配过程中的潜在问题

  • 歧义:如果路径匹配HTTP方法匹配,此时请求参数无法唯一确定一个处理器方法,Spring MVC会抛出异常。确保请求参数签名能够明确区分不同的方法。
  • 参数类型:如果方法参数类型相同,但数量不同,Spring MVC仍然可以根据请求参数的数量来区分方法。

6.代码实现

1
2
3
4
5
6
7
8
9
//获取当前路径下所有的处理器方法
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

//获取match的path
if (directPathMatches != null)
addMatchingMappings(directPathMatches, matches, request);
}

//AbstractHandlerMethodMapping类---->

6.1 addMatchingMappings

1
2
3
4
5
6
7
8
9
10
private void addMatchingMappings(Collection<T> mappings, List<Match> matches, HttpServletRequest request) {
for (T mapping : mappings) {
T match = getMatchingMapping(mapping, request);
if (match != null) {
matches.add(new Match(match, this.mappingRegistry.getMappings().get(mapping)));
}
}
}

//AbstractHandlerMethodMapping类---->getMatchingMapping

通过RequestMappingInfoHandlerMapping的getMatchingMapping方法,最终跑到RequestMappingInfogetMatchingCondition方法中。

在Spring MVC中,getMatchingCondition方法是用于确定一个请求是否匹配某个条件的关键方法之一。它存在于RequestCondition接口及其实现类(RequestMappingInfo)中,用于处理请求映射的各种条件是否符合某个控制器方法的映射条件。

6.2 RequestCondition接口

RequestCondition接口用于定义请求匹配条件的抽象概念。在Spring MVC中,不同类型的请求条件(如路径条件、方法条件、参数条件等)实现了这个接口,以便框架能够统一处理不同的请求匹配逻辑。

1
2
3
4
5
6
7
8
9
10
11
public interface RequestCondition<T> {

T combine(T other);

@Nullable
T getMatchingCondition(HttpServletRequest request);

int compareTo(T other, HttpServletRequest request);

}

其实现类:

6.3 RequestMappingInfo

本文只讲RequestMappingInfo:

RequestMappingInfo类包含多个条件,每个条件都实现了RequestCondition接口。这些条件包括:

  • 路径条件(PatternsRequestCondition

  • HTTP方法条件(RequestMethodsRequestCondition

  • 参数条件(ParamsRequestCondition

  • 头信息条件(HeadersRequestCondition

  • 消费内容类型条件(ConsumesRequestCondition

  • 生成内容类型条件(ProducesRequestCondition

  • 生成内容类型条件(RequestConditionHolder

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
@Override
@Nullable
public RequestMappingInfo getMatchingCondition(HttpServletRequest request) {

//HTTP方法条件匹配--get/post
RequestMethodsRequestCondition methods = this.methodsCondition.getMatchingCondition(request);

//参数条件匹配
ParamsRequestCondition params = this.paramsCondition.getMatchingCondition(request);

//头信息条件匹配
HeadersRequestCondition headers = this.headersCondition.getMatchingCondition(request);

//消费内容类型条件匹配
ConsumesRequestCondition consumes = this.consumesCondition.getMatchingCondition(request);

//生成内容类型条件匹配
ProducesRequestCondition produces = this.producesCondition.getMatchingCondition(request);

//此处如果为空,则表示无匹配的控制器,直接返回空
if (methods == null || params == null || headers == null
|| consumes == null || produces == null) {
return null;
}

//路径条件,用于表示URL路径模式条件。它包含了一组路径模式(如/api/users等),用于确定请求的路径是否与这些模式匹配。
PatternsRequestCondition patterns = this.patternsCondition.getMatchingCondition(request);
if (patterns == null) {
return null;
}

//包装RequestCondition的一个类。主要用于统一处理各种RequestCondition实现类。
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}

// 所有条件都匹配,返回一个新的RequestMappingInfo实例
return new RequestMappingInfo(this.name, patterns,
methods, params, headers, consumes, produces, custom.getCondition());
}

//RequestMappingInfo类--->getMatchingCondition

你也许会好奇为什么会有PatternsRequestCondition这个匹配条件,在前面不是已经根据URL得到控制器集合了吗?

1
2
3
4
5
6
7
8
9
10
11
List<Match> matches = new ArrayList<>();
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);

if (directPathMatches != null) {
addMatchingMappings(directPathMatches, matches, request);
}

if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}

我们要注意if (matches.isEmpty())的情况。所以Spring相当于做了一个后续处理。如果该条件成立。则表示有2种情况:

  1. directPathMatches为空。
  2. directPathMatches不为空,但是经过匹配过滤后,没有满足条件的控制器。

这个时候Spring就做了一个后续处理:查询当前容器所有的mapping,去匹配是否有匹配的控制器。所以这个条件就有用了。

1
2
3
4
RequestConditionHolder custom = this.customConditionHolder.getMatchingCondition(request);
if (custom == null) {
return null;
}

而且这个条件在directPathMatches不为空的时候,也能通过。


博客说明

文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!


Spring定位唯一的处理器
https://nanchengjiumeng123.top/2024/03/04/framework/spring/Spring MVC/7.Spring定位唯一的处理器/
作者
Yang Xin
发布于
2024年3月4日
许可协议