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

@PathVariable性能损耗分析的更多相关文章

  1. SQL SERVER 查询性能优化——分析事务与锁(五)

    SQL SERVER 查询性能优化——分析事务与锁(一) SQL SERVER 查询性能优化——分析事务与锁(二) SQL SERVER 查询性能优化——分析事务与锁(三) 上接SQL SERVER ...

  2. list 、set 、map 粗浅性能对比分析

    list .set .map 粗浅性能对比分析   不知道有多少同学和我一样,工作五年了还没有仔细看过list.set的源码,一直停留在老师教导的:"LinkedList插入性能比Array ...

  3. Android App性能评测分析-流畅度篇

    1.前言 在手机App竞争越来越激烈的今天,Android App的各项性能特别是流畅度不如IOS,安卓基于java虚拟机运行,触控响应的延迟和卡顿比IOS系统严重得多.一些下拉上滑.双指缩放快速打字 ...

  4. 开源性能监控分析工具glowroot

    最近在做java性能瓶颈定位分析工具的研究,发现glowroot工具是一款相当不错的APM工具(Wonderful tool),架构简洁,部署简单,上手容易. 经过亲身搭建体验,总结了产品的架构,工具 ...

  5. MySQL索引及性能优化分析

    一.SQL性能下降的原因 查询语句问题,各种连接.子查询 索引失效(单值索引.复合索引) 服务器调优及各个参数设置(缓冲.线程池等) 二.索引 排好序的快速查找数据结构 1. 索引分类 单值索引 一个 ...

  6. 浅谈C++之冒泡排序、希尔排序、快速排序、插入排序、堆排序、基数排序性能对比分析之后续补充说明(有图有真相)

    如果你觉得我的有些话有点唐突,你不理解可以想看看前一篇<C++之冒泡排序.希尔排序.快速排序.插入排序.堆排序.基数排序性能对比分析>. 这几天闲着没事就写了一篇<C++之冒泡排序. ...

  7. ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...

  8. Web服务器性能监控分析与优化

    Web服务器性能监控分析与优化 http://www.docin.com/p-759040698.html

  9. ArrayList和LinkedList遍历方式及性能对比分析

    ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayLis ...

随机推荐

  1. S2:面向对象

    面向对象七大设计原则 1. 开闭原则 2. 里氏替换原则 3. 单一职责原则 4. 接口隔离原则 5. 依赖倒置原则 6. 迪米特原则 7.组合/聚合复用原则 原则一:(SRP:Single resp ...

  2. istio入门教程

    广告 | kubernetes各版本离线安装包 安装 安装k8s 强势插播广告 三步安装,不多说 安装helm, 推荐生产环境用helm安装,可以调参 release地址 如我使用的2.9.1版本 y ...

  3. git的使用学习笔记

    一.git Git 是一个开源的分布式版本控制系统,项目版本管理工具,可以在本地提交修改再合并到主分支上,最为出色的是它的合并跟踪(merge tracing)能力. 可以通过Linux命令进行增加, ...

  4. Java实现调用Bartender控制条码打印机

    官方提供的主要是C#支持. 基于java调用bartender二次开发官方给了一份1998年的J#代码,,,完全用不了,,,百度谷歌搜索万能的网友的答案,发现也没有可参考的.. 最后想到了之前用到了一 ...

  5. Java ActionListenner类的一些理解

    Java的ActionListenner事实上我去年年这个时候大概就已经接触到了,也学会了比较简单的使用.但却始终不能理解ActionListenner的一系列的运行是怎么维持这么一个联系的? 我产生 ...

  6. 【C/C++】随机数的生成

    C/C++:rand()函数 rand()函数的头文件:#include<stdlib.h> 该函数产生的随机数随机性差,速度慢,周期小(0-32767) 用法如下所示: #include ...

  7. Redis——发布和订阅

    发布与订阅(又称pub/sub),订阅者(listener)负责订阅频道(channel),发送者(publisher)负责向频道发送二进制字符串消息(binary string message).每 ...

  8. exe4j打包--exe转安装包

    前面一篇已经详细的说明了打包成exe的步骤了,下面谈谈exe如何压缩成安装文件.这里用到之前的另外一个软件,具体软件看这篇文章 exe4j打包成exe 打开inno 编辑器 打开软件后我们选择 用[脚 ...

  9. windbg 使用与技巧

    基本知识和常用命令 (1)       Windbg下载地址http://msdn.microsoft.com/en-us/windows/hardware/gg463009.aspx 安装完后执行w ...

  10. (十五)c#Winform自定义控件-键盘(二)

    前提 入行已经7,8年了,一直想做一套漂亮点的自定义控件,于是就有了本系列文章. 开源地址:https://gitee.com/kwwwvagaa/net_winform_custom_control ...