Spring是如何通过lookupPath返回的List集合处理器定位到唯一的处理器的?
1.具体工作原理
当一个请求到达时,Spring MVC会执行以下步骤:
获取路径 :从请求中提取URL路径。
查找映射 :使用getMappingsByUrl
方法从MappingRegistry
中获取对应路径的所有处理器方法。
匹配方法 :遍历返回的处理器方法列表,根据请求的具体条件(如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(); } @GetMapping public ResponseEntity<User> getUser (@RequestParam String username, @RequestParam String email) { return ResponseEntity.ok(); } }
3.区分处理器方法的机制
Spring
MVC在处理请求时,会检查请求参数以确定哪个处理器方法应处理该请求。具体过程如下:
路径匹配 :首先,Spring
MVC会找到与请求URL匹配的处理器方法。在上面的例子中,两个方法的路径都匹配/api/users
。
HTTP方法匹配 :接下来,Spring
MVC会筛选出与请求的HTTP方法匹配的处理器方法。在上面的例子中,两个方法的HTTP方法都是GET
。此处会返回2个处理器。
参数匹配 :最后,Spring
MVC会检查请求参数,选择参数匹配的处理器方法。如果请求中包含的参数能够唯一确定一个处理器方法,Spring
MVC就会调用该方法。
4.其他匹配条件
除了URL路径和HTTP方法外,Spring
MVC还支持根据以下条件匹配处理器方法:
请求头 :使用@RequestMapping
的headers
属性。
1 @RequestMapping(value = "/example", headers = "key=value")
请求参数 :使用@RequestMapping
的params
属性。
1 @RequestMapping(value = "/example", params = "param=value")
消费的内容类型 :使用@RequestMapping
的consumes
属性。
1 @RequestMapping(value = "/example", consumes = "application/json")
产生的内容类型 :使用@RequestMapping
的produces
属性。
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); if (directPathMatches != null ) addMatchingMappings(directPathMatches, matches, request); }
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))); } } }
通过RequestMappingInfoHandlerMapping
的getMatchingMapping方法,最终跑到RequestMappingInfo
的getMatchingCondition
方法中。
在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) { 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 ; } PatternsRequestCondition patterns = this .patternsCondition.getMatchingCondition(request); if (patterns == null ) { return null ; } RequestConditionHolder custom = this .customConditionHolder.getMatchingCondition(request); if (custom == null ) { return null ; } return new RequestMappingInfo (this .name, patterns, methods, params, headers, consumes, produces, custom.getCondition()); }
你也许会好奇为什么会有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()) { addMatchingMappings(this .mappingRegistry.getMappings().keySet(), matches, request); }
我们要注意if (matches.isEmpty())
的情况。所以Spring相当于做了一个后续处理。如果该条件成立。则表示有2种情况:
directPathMatches为空。
directPathMatches不为空,但是经过匹配过滤后,没有满足条件的控制器。
这个时候Spring就做了一个后续处理:查询当前容器所有的mapping
,去匹配是否有匹配的控制器。所以这个条件就有用了。
1 2 3 4 RequestConditionHolder custom = this .customConditionHolder.getMatchingCondition(request); if (custom == null ) { return null ; }
而且这个条件在directPathMatches不为空的时候,也能通过。
博客说明
文章所涉及的资料来自互联网整理和个人总结,意在于个人学习和经验汇总,不用于任何的商业用途。如有侵权,请联系本人删除。谢谢!