首页 技术 正文
技术 2022年11月21日
0 收藏 502 点赞 3,842 浏览 4752 个字

前端时间参与了一次业务线排障,是接口服务并发性能比较差,性能损耗大的问题,我经过几次研究分析和压测,确定了故障源是@PathVariable耗时过长引起的。

@PathVariable使用形式:

@RequestMapping(value = "/api/test/{appCode}/queryUserByUserId", method = RequestMethod.GET)
@ResponseBody
String queryUserByUserId(@PathVariable(name = "appCode") String appCode,@RequestParam("userId") Long userId);

这个注解是Spring MVC提供的,之前没有想到过这个注解会带来性能损耗,为了彻底分析清楚@PathVariable是什么原因导致了性能损耗,于是我对DispatcherServlet.getHandler()这个方法的操作过程进行了仔细地研读,下面我们来对它进行逐步的分析。

首先我把获取handlerMethod的整个流程展示出来,思路会更清晰一些。

@PathVariable性能损耗分析

我们来对关键代码进行解读分析,

查找路径的过程中的核心代码代码,即流程图中org.springframework.web.servlet.handler.AbstractHandlerMethodMapping.lookupHandlerMethod方法

List<Match> matches = new ArrayList<Match>();
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);
}

AbstractHandlerMethodMapping首先对request请求中的lookupPath与已注册的RequestMappingInfo(@RequestMapping注解的方法)中的path进行完全匹配来查找对应的HandlerMethod,即处理该请求的方法,LinkedMultiValueMap#get方法。若没有找到则会遍历所有的RequestMappingInfo进行查找。这个查找是不会提前停止的,直到遍历完全部的RequestMappingInfo;这个查找会进行正则匹配,将请求lookupPath按”/”为分隔符,逐条进行匹配,如果完全匹配,则返回该RequestMappingInfo。

org.springframework.web.servlet.mvc.method.RequestMappingInfo.getMatchingCondition()方法

@Override
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());
}

在遍历过程中,RequestMappingInfo首先会根据@RequestMapping中的headers, params, produces, consumes, methods与实际的HttpServletRequest中的信息对比,以剔除掉不符合条件的RequestMappingInfo。
如果以上信息都能够匹配上,那么SpringMVC会对RequestMapping中的path进行正则匹配,剔除不能进行匹配的RequestMappingInfo。

遍历完mappingRegistry后

RequestMappingInfo ===> Match(RequestMappingInfo,HandlerMethod) ===>List<Match> matches.add(Match);

Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
Collections.sort(matches, comparator);

接下来会对所有符合条件Match进行评分并排序。最后选择分数最高的那个作为结果。

@Override
public int compareTo(RequestMappingInfo other, HttpServletRequest request) {
int result;
 // Automatic vs explicit HTTP HEAD mapping
 if (HttpMethod.HEAD.matches(request.getMethod())) {
result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
 }
}
result = this.patternsCondition.compareTo(other.getPatternsCondition(), request);
if (result != 0) {
return result;
 }
result = this.paramsCondition.compareTo(other.getParamsCondition(), request);
if (result != 0) {
return result;
 }
result = this.headersCondition.compareTo(other.getHeadersCondition(), request);
if (result != 0) {
return result;
 }
result = this.consumesCondition.compareTo(other.getConsumesCondition(), request);
if (result != 0) {
return result;
 }
result = this.producesCondition.compareTo(other.getProducesCondition(), request);
if (result != 0) {
return result;
 }
// Implicit (no method) vs explicit HTTP method mappings
 result = this.methodsCondition.compareTo(other.getMethodsCondition(), request);
if (result != 0) {
return result;
 }
result = this.customConditionHolder.compareTo(other.customConditionHolder, request);
if (result != 0) {
return result;
 }
return 0;
}

可以看出它的评分优先级

HttpMethod > pathPattern > param > headers > consumes > produces > (Implicit (no method) vs explicit HTTP method) > custom

如果matches有多个,接下来会比较第一个和第二个是否相等,如果compare==0,就会抛出异常了

Match bestMatch = matches.get(0);
if (matches.size() > 1) {
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 + "}");
 }
}

通过上述的分析可以得出一个结论:在使用非RESTful风格的URL时,SpringMVC可以立刻找到对应的HandlerMethod来处理请求。但是当在URL中存在变量时,即使用了@PathVariable时,SpringMVC就会进行上述的复杂流程,并且每次请求都会走这个复杂的流程。

总结一下:Spring 不仅给我们提供了方便我们使用的框架,更是在此基础上配合提供了各种各样的功能强大的组件,她的伟大是毋庸置疑的,spring 面向大众,尽可能为更多的开发者提供便利,我们在使用spring的时候应该依据自身的实际状况,充分了解要使用的组件,去应用它,这样才能避免走弯路。

相关推荐
python开发_常用的python模块及安装方法
adodb:我们领导推荐的数据库连接组件bsddb3:BerkeleyDB的连接组件Cheetah-1.0:我比较喜欢这个版本的cheeta…
日期:2022-11-24 点赞:878 阅读:9,076
Educational Codeforces Round 11 C. Hard Process 二分
C. Hard Process题目连接:http://www.codeforces.com/contest/660/problem/CDes…
日期:2022-11-24 点赞:807 阅读:5,552
下载Ubuntn 17.04 内核源代码
zengkefu@server1:/usr/src$ uname -aLinux server1 4.10.0-19-generic #21…
日期:2022-11-24 点赞:569 阅读:6,400
可用Active Desktop Calendar V7.86 注册码序列号
可用Active Desktop Calendar V7.86 注册码序列号Name: www.greendown.cn Code: &nb…
日期:2022-11-24 点赞:733 阅读:6,176
Android调用系统相机、自定义相机、处理大图片
Android调用系统相机和自定义相机实例本博文主要是介绍了android上使用相机进行拍照并显示的两种方式,并且由于涉及到要把拍到的照片显…
日期:2022-11-24 点赞:512 阅读:7,812
Struts的使用
一、Struts2的获取  Struts的官方网站为:http://struts.apache.org/  下载完Struts2的jar包,…
日期:2022-11-24 点赞:671 阅读:4,894