@PathVariable性能损耗分析
前端时间参与了一次业务线排障,是接口服务并发性能比较差,性能损耗大的问题,我经过几次研究分析和压测,确定了故障源是@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的整个流程展示出来,思路会更清晰一些。
我们来对关键代码进行解读分析,
查找路径的过程中的核心代码代码,即流程图中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的时候应该依据自身的实际状况,充分了解要使用的组件,去应用它,这样才能避免走弯路。
最新文章
- 记从安装centos系统在到使用mono3.2部署MVC过程遇到的问题
- [LeetCode] Island Perimeter 岛屿周长
- C++STL算法函数总结
- printf函数重定向
- 我又回来了,这回是带着C++来的
- Android中获取正在运行的应用程序-----ActivityManager.RunningAppProcessInfo类详解
- npm install 本地安装与全局安装
- 通过iframe在其父窗口中打开隐藏元素
- Servlet(二)GenericServlet
- Java开发速度神器Lombok,Eclipse端安装使用教程
- 安装VMware workstation遇到的两个问题:安装过程中的DLL问题和安装后打开需要的管理权限问题
- Java (三、数组)
- Oracle impdp的ignore及 fromuser / touser 功能
- 微信OpenID获取
- Oracle数据导入指定表空间
- KMP算法(next数组方法)
- SAP 表类型
- 一次失败的尝试hdfs的java客户端编写(在linux下使用eclipse)
- Android中的数据存储(一):SharedPreferences 2017-05-24 10:35 64人阅读 评论(1) 收藏
- leetcode674
热门文章
- 1. 源码分析---SOFARPC可扩展的机制SPI
- axios配置请求头content-type
- 心里想的VS嘴上说的
- Appium+python自动化(三十一)- 元芳,你怎么看? - 日志收集-logging(超详解)
- Spring Boot Security Oauth2之客户端模式及密码模式实现
- (十一)c#Winform自定义控件-列表
- 简述关于ASP.NET&#160;MVC与.NET CORE 的区别
- Kafka 原理和实战
- java io读取性能对比
- ServerResponse(服务器统一响应数据格式)