[转] 公司使用SpringMVC的RESTful接口的坑

简介: 原文出处:https://tech.imdada.cn/2015/12/23/springmvc-restful-optimize/

原文出处:https://tech.imdada.cn/2015/12/23/springmvc-restful-optimize/

这边文章的根源可以理解成Spring是何如路径解析的

背景:在公司还没有服务化的时候,在整个DB层上架构了一个系统作为数据访问层(封装业务系统对数据层的调用,实现对数据库的分库分表,实现对数据的缓存)对外提供的是RESTful接口

RESTful:

@RequestMapping(path = "/list/cityId/{cityId}", method = RequestMethod.GET)
@ResponseBody
public String getJsonByCityId(@PathVariable Integer cityId)

客户端请求: GET /list/cityId/1

非RESTful

@RequestMapping(path = "/list/cityId", method = RequestMethod.GET)
@ResponseBody
public String getJsonByCityId(@RequestParam Integer cityId)

客户端请求: GET /list/cityId?cityId=1

在接口越来越多的情况下,系统的性能在降低

image

image

从结果上可以看出,URL的数量越多,SpringMVC的性能越差,查看源码,依然从DispatcherServlet#doDispatch方法入手

(1)AbstractHandlerMapping #getHandler
(2)AbstractHandlerMethodMapping #getHandlerInternal
(3)AbstractHandlerMethodMapping #lookupHandlerMethod

protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
        List<Match> matches = new ArrayList<Match>();
        // 通过lookupPath获取匹配到的path
        List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
        if (directPathMatches != null) {
                //  非restful的接口,会匹配到并且添加到matches
            addMatchingMappings(directPathMatches, matches, request);
        }
        if (matches.isEmpty()) {
            // No choice but to go through all mappings...
            // restful的接口,会匹配所有的URL (这就是根源所在,数量越多,性能越差)
            addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
        }

        if (!matches.isEmpty()) {
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
            Collections.sort(matches, comparator);
    
                    // 选取排序后的第一个作为最近排序条件
            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 + "}");
                }
            }
            handleMatch(bestMatch.mapping, lookupPath, request);
            return bestMatch.handlerMethod;
        }
        else {
            return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
        }
    }

SpringMVC首先对HTTP请求中的path与已注册的RequestMappingInfo(经解析的@RequestMapping)中的path进行一个完全匹配来查找对应的HandlerMethod,即处理该请求的方法,这个匹配就是一个Map#get方法。若找不到则会遍历所有的RequestMappingInfo进行查找。这个查找是不会提前停止的,直到遍历完全部的RequestMappingInfo

springMVC的匹配逻辑:

在遍历的过程中,SpringMVC首先会根据@RequestMapping的headers,params,produces,consumes,methods与实际的HttpServletRequest中的信息对比,剔除一些明显不合格的RequestMapping,如果以上信息都能匹配上,那么就会对path进行正则匹配,进行打分

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

Comparator comparator = new MatchComparator(getMappingComparator(request))

String match = getMatchingPattern(pattern, lookupPath);

path的匹配首先会把url按照“/”分割,然后对于每一部分都会使用到正则表达式,即使该字符串是定长的静态的。所以该匹配逻辑的性能可能会很差

至于解决方案,公司的文章里面有写到,我正好趁这次机会把原理重新熟悉了一遍

目录
相关文章
|
29天前
|
XML 开发框架 JSON
flea-jersey使用之Flea RESTful接口客户端接入
本篇介绍 Flea框架下的 flea-jersey-client模块,并提供客户端依赖管理及接入步骤
25 1
flea-jersey使用之Flea RESTful接口客户端接入
|
30天前
|
开发框架 API 网络架构
flea-jersey使用之Flea RESTful接口服务端接入
本篇介绍 Flea框架下的 flea-jersey-server模块,并提供服务端依赖管理及接入步骤
29 1
flea-jersey使用之Flea RESTful接口服务端接入
|
2月前
|
前端开发 JavaScript
[SpringMVC]restful风格
[SpringMVC]restful风格
36 1
[SpringMVC]restful风格
|
3月前
|
XML JSON 数据格式
【第20期】一文读懂Restful接口规范
【第20期】一文读懂Restful接口规范
366 0
|
4月前
|
JSON 网络架构 数据格式
SpringMVC-REST风格简介及RESTful入门案例
SpringMVC-REST风格简介及RESTful入门案例
35 0
|
2月前
|
监控 安全 测试技术
《Eolink 征文活动- -RESTful接口全解测试-全方位了解Eolink-三神技超亮点》
《Eolink 征文活动- -RESTful接口全解测试-全方位了解Eolink-三神技超亮点》
64 0
|
1月前
|
前端开发 API 网络架构
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!
Python 如何开发出RESTful Web接口,DRF框架助力灵活实现!
|
1月前
|
XML 开发框架 JSON
flea-jersey使用之Flea RESTful接口介绍
本篇介绍 Flea框架下 flea-jersey 模块下的 Flea RESTful接口
20 1
flea-jersey使用之Flea RESTful接口介绍
|
2月前
|
XML JSON Java
基于springMVC的RESTful服务实现
  RESTful(RESTful Web Services)一种架构风格,表述性状态转移,它不是一个软件,也不是一个标准,而是一种思想,不依赖于任何通信协议,但是开发时要成功映射到某协议时也需要遵循其标准,但不包含对通信协议的更改
21 1
|
2月前
|
Cloud Native Go 数据库
一文搞懂Go搭建Restful接口
一文搞懂Go搭建Restful接口
18 0