Spring 注解面面通 之 @RequestMapping headers 条件匹配源码解析
作者:互联网
@RequestMapping
支持基于value
、path
、method
、params
、headers
、consumers
、produces
的匹配,本文对基于params
的匹配过程进行分析。
系列博文《Spring 注解面面通 之 @RequestMapping 请求匹配处理方法源码解析》中对请求匹配@RequestMapping
注释方法流程进行了分析。
AbstractHandlerMethodMapping.lookupHandlerMethod(...)
方法负责查找请求最佳匹配的处理方法。HeadersRequestCondition
类负责基于headers
的匹配过程,可以配置多个标头条件,多个条件之间是逻辑与(&&
)的关系。
源码解析
1) AbstractHandlerMethodMapping.lookupHandlerMethod(...)
方法。
① 在mappingRegistry.urlLookup
中查找与请求路径完全匹配的映射。
② 在①
中查找结果,查找与请求完全匹配的匹配器。
③ 若②
中查找无结果,则遍历注册的所有映射,进行进一步匹配,以查找合适的匹配器。
④ 若③
中查找匹配器列表不为空,则从中通过MatchComparator
比较器选择最优匹配器。
⑤ 若请求是有效的CORS
类型的请求,则返回指定的处理方法,默认是PREFLIGHT_AMBIGUOUS_MATCH
,即new HandlerMethod(new EmptyHandler(), ClassUtils.getMethod(EmptyHandler.class, "handle"))
。
⑥ 判断③
中查找匹配器列表,最优和次优匹配器是否一致,若两者一致,则违背映射规则,抛出异常。
⑦ 处理查找到最优匹配器的情况,这步骤大致包括:存储到最优匹配到请求属性、解析URI模板变量,存储到请求属性、解析矩阵变量,存储到请求属性、解析可生成媒体类型,存储到请求属性。
⑧ 处理未查找到匹配器的情况,这步骤大致包括:为确保无误,再次进行匹配查找、若仍无匹配结果,则抛出异常。
/**
* 查找请求最优匹配的处理方法.
* 如果找到多个处理方法,则选择最优匹配的处理方法.
* @param lookupPath 在当前Servlet映射中映射查找路径.
* @param request 当前请求实例.
* @return 最优匹配的处理方法,如果没有匹配处理方法,返回null.
*/
@Nullable
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
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()) {
// 获取匹配比较器.
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
// 根据比较器对匹配器进行排序.
matches.sort(comparator);
if (logger.isTraceEnabled()) {
logger.trace("Found " + matches.size() + " matching mapping(s) for [" + lookupPath + "] : " + matches);
}
// 取得第一个匹配器,用于选取最优匹配器.
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
// 如果请求是有效的CORS类型请求,返回指定的处理方法.
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
// 取得第二个匹配器.
Match secondBestMatch = matches.get(1);
// 比较第一个匹配器和第二个匹配器,若两者一致,则违背规则,抛出异常.
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
throw new IllegalStateException("Ambiguous handler methods mapped for HTTP path '" +
request.getRequestURL() + "': {" + m1 + ", " + m2 + "}");
}
}
// 处理匹配器.
// 1.存储到最优匹配到请求属性.
// 2.解析URI模板变量,存储到请求属性.
// 3.解析矩阵变量,存储到请求属性.
// 4.解析可生成媒体类型,存储到请求属性.
handleMatch(bestMatch.mapping, lookupPath, request);
// 返回最优匹配器的处理方法.
return bestMatch.handlerMethod;
}
else {
// 处理无匹配器的情况.
// 1.再次进行匹配查找.
// 2.若仍无匹配,抛出异常.
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
2) AbstractHandlerMethodMapping.addMatchingMappings(...)
-> RequestMappingInfoHandlerMapping.getMatchingMapping(...)
-> RequestMappingInfo.getMatchingCondition(...)
。
AbstractHandlerMethodMapping.addMatchingMappings(...)
-> RequestMappingInfoHandlerMapping.getMatchingMapping(...)
-> RequestMappingInfo.getMatchingCondition(...)
中处理的中间流程,其中并没有涉及过多步骤,在此不做深入分析。
3) HeadersRequestCondition.getMatchingCondition(...)
方法。
① 如果请求是有效的CORS
类型请求,则返回PRE_FLIGHT_MATCH
,即new HeadersRequestCondition()
。
② 遍历当前映射配置的标头表达式,用其与请求进行匹配处理,只有在所有表达式都匹配成功的情况下,标头条件才认为匹配成功。
/**
* 若请求标头匹配当前映射的所有标头表达式时,返回当前条件表达式实例.
* 否则,返回null,表明不匹配.
*/
@Override
@Nullable
public HeadersRequestCondition getMatchingCondition(HttpServletRequest request) {
// 如果请求是有效的CORS类型请求,直接返回空的HeadersRequestCondition.
if (CorsUtils.isPreFlightRequest(request)) {
return PRE_FLIGHT_MATCH;
}
// 遍历当前映射的所有标头表达式.
for (HeaderExpression expression : expressions) {
if (!expression.match(request)) {
return null;
}
}
return this;
}
4) AbstractNameValueExpression.match(...)
方法。
① 当前标头表达式下,进行标头值的匹配。
② 当前标头表达式下,进行标头名的匹配。
③ isNegated
表示是否使用了!
的否定操作,根据其对①②
中的匹配结果进行转换操作。
/**
* 请求标头匹配单个标头表达式.
*/
public final boolean match(HttpServletRequest request) {
boolean isMatch;
// 进行标头值的匹配.
if (this.value != null) {
isMatch = matchValue(request);
}
// 进行标头名的匹配.
else {
isMatch = matchName(request);
}
// 是否使用!来表达否定操作.
return (this.isNegated ? !isMatch : isMatch);
}
5) HeadersRequestCondition.HeaderExpression.matchName(...)
和 HeadersRequestCondition.HeaderExpression.matchValue(...)
方法。
① 当前标头表达式下,进行标头值的匹配:
· 判断请求标头中是否包含映射配置的标头名。
② 当前标头表达式下,进行标头名的匹配:
· 映射配置的标头值式与request.getHeader(...)
进行匹配。
· 映射配置的标头值与请求标头值不其一为null
,则匹配失败。
· 匹配过程首先使用=
进行比较,然后使用equals
进行比较。
· 标头值是Array类型时,需特殊处理,逐项比较数组各索引位置的值。
/**
* 进行标头名的匹配.
*/
@Override
protected boolean matchName(HttpServletRequest request) {
// 判断请求标头中是否包含映射配置的标头名.
return (request.getHeader(this.name) != null);
}
/**
* 进行标头值的匹配.
*/
@Override
protected boolean matchValue(HttpServletRequest request) {
// 映射配置的标头表达式与request.getHeader(...)进行匹配.
// 1.标头表达式的值与请求标头值不为null,否则匹配失败.
// 2.标头首先使用=进行比较,然后使用equals进行比较.
// 3.若标头值是Array类型,需逐项比较数组各索引位置的值.
return ObjectUtils.nullSafeEquals(this.value, request.getHeader(this.name));
}
总结
@RequestMapping
的headers
匹配过程不是很复杂,逻辑相对亦比较简单。
源码解析基于spring-framework-5.0.5.RELEASE
版本源码。
若文中存在错误和不足,欢迎指正!
标签:...,标头,匹配,RequestMapping,Spring,request,查找,源码,请求 来源: https://blog.csdn.net/securitit/article/details/111468192