前端时间参与了一次业务线排障,是接口服务并发性能比较差,性能损耗大的问题,我经过几次研究分析和压测,确定了故障源是@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的时候应该依据自身的实际状况,充分了解要使用的组件,去应用它,这样才能避免走弯路。

最新文章

  1. 记从安装centos系统在到使用mono3.2部署MVC过程遇到的问题
  2. [LeetCode] Island Perimeter 岛屿周长
  3. C++STL算法函数总结
  4. printf函数重定向
  5. 我又回来了,这回是带着C++来的
  6. Android中获取正在运行的应用程序-----ActivityManager.RunningAppProcessInfo类详解
  7. npm install 本地安装与全局安装
  8. 通过iframe在其父窗口中打开隐藏元素
  9. Servlet(二)GenericServlet
  10. Java开发速度神器Lombok,Eclipse端安装使用教程
  11. 安装VMware workstation遇到的两个问题:安装过程中的DLL问题和安装后打开需要的管理权限问题
  12. Java (三、数组)
  13. Oracle impdp的ignore及 fromuser / touser 功能
  14. 微信OpenID获取
  15. Oracle数据导入指定表空间
  16. KMP算法(next数组方法)
  17. SAP 表类型
  18. 一次失败的尝试hdfs的java客户端编写(在linux下使用eclipse)
  19. Android中的数据存储(一):SharedPreferences 2017-05-24 10:35 64人阅读 评论(1) 收藏
  20. leetcode674

热门文章

  1. 1. 源码分析---SOFARPC可扩展的机制SPI
  2. axios配置请求头content-type
  3. 心里想的VS嘴上说的
  4. Appium+python自动化(三十一)- 元芳,你怎么看? - 日志收集-logging(超详解)
  5. Spring Boot Security Oauth2之客户端模式及密码模式实现
  6. (十一)c#Winform自定义控件-列表
  7. 简述关于ASP.NET&#160;MVC与.NET CORE 的区别
  8. Kafka 原理和实战
  9. java io读取性能对比
  10. ServerResponse(服务器统一响应数据格式)