Spring自定義RequestMappingHandlerMapping避免PathVariable的性能低下

背景: 由于公司最近在做一個(gè)廣告系統(tǒng), 其中我負(fù)責(zé)的廣告跟蹤模塊有一個(gè)記錄用戶點(diǎn)擊數(shù)的Api接口, 接口的url /api/event/click/{userId}, 接口中使用了路徑變量, 因此在處理的方法中就要用@PathVariable進(jìn)行處理. 之前一直沒有關(guān)注路徑變量和參數(shù)變量(@RequestParam)在性能上的區(qū)別, 但是這個(gè)參與的廣告系統(tǒng)對請求的響應(yīng)要求很高. 因此在代碼review的時(shí)候, 架構(gòu)師發(fā)了一篇達(dá)達(dá)科技的文章, 里面提到了路徑變量和參數(shù)變量在性能上的區(qū)別, 同時(shí)提出了相應(yīng)的解決思路. 因此就按照達(dá)達(dá)科技的思路對原本的廣告系統(tǒng)的實(shí)現(xiàn)進(jìn)行了改造. 具體的達(dá)達(dá)科技提到的思路請看這里

1. 解決路徑參數(shù)帶來的性能問題的步驟

思路: 因?yàn)槭褂寐窂絽?shù)需要進(jìn)行復(fù)雜的匹配流程以及正則匹配, 因此性能會(huì)比較低. 所以解決的思路就是跳過復(fù)雜的匹配流程以及正則匹配. 因?yàn)閺?fù)雜的匹配流程和正則匹配的目的就是為了找到處理當(dāng)前url的方法是哪一個(gè), 因?yàn)檫@是一個(gè)內(nèi)部系統(tǒng), 因此調(diào)用端完全可以知道處理當(dāng)前的url的方法是哪一個(gè),可以通過url傳遞過來使用哪個(gè)方法進(jìn)行處理, 因此就可以跳過復(fù)雜的匹配流程.

1.1 自定義查找url對應(yīng)的處理方法

RequestMappingHandlerMapping中查找url對應(yīng)的處理方法是由lookupHandlerMethod這個(gè)函數(shù)實(shí)現(xiàn)的, 在這個(gè)函數(shù)中會(huì)優(yōu)先查找參數(shù)變量其次是路徑變量url, 在查找到路徑變量url后, 再進(jìn)行正則的替換. 因此我們要做的就是如果url是路徑變量就跳過這個(gè)方法, 而使用我們自己的查找方式

代碼如下:

public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {

private final static Map<HandlerMethod, RequestMappingInfo> HANDLER_METHOD_REQUEST_MAPPING_INFO_MAP = Maps.newHashMap();

 // 用于保存處理方法和RequestMappingInfo的映射關(guān)系(這個(gè)方法在解析@RequestMapping時(shí)就會(huì)被調(diào)用, 達(dá)達(dá)科技中這個(gè)地方可能寫的有問題, 文中提到覆寫AbstractHandlerMethodMapping#registerMapping方法, 但是經(jīng)過實(shí)驗(yàn)之后覆寫這個(gè)方法不能生效)
    @Override
    protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
        HandlerMethod handlerMethod = super.createHandlerMethod(handler, method);
        HANDLER_METHOD_REQUEST_MAPPING_INFO_MAP.put(handlerMethod, mapping);
        super.registerHandlerMethod(handler, method, mapping);
    }

    @Override
    protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
      
      // 判斷請求參數(shù)中是否帶了event字段
        String event = request.getParameter("event");
   
   // 如果沒有帶則說明這次的請求不帶路徑參數(shù), 則使用默認(rèn)的處理
        if(StringUtils.isEmpty(event)) {
            return super.lookupHandlerMethod(lookupPath, request);
        }
        
        // 如果帶了, 則從Map(這個(gè)Map中的entry在后面介紹)中獲取處理當(dāng)前url的方法
        List<HandlerMethod> handlerMethods = super.getHandlerMethodsForMappingName(event);
        if(CollectionUtils.isEmpty(handlerMethods)) throw new ServiceException("沒有找到指定的方法");
        if(handlerMethods.size() > 1) throw new ServiceException("存在多個(gè)匹配的方法");

        HandlerMethod handlerMethod = handlerMethods.get(0);
        
        // 根據(jù)處理方法查找RequestMappingInfo, 用于解析路徑url中的參數(shù)
        RequestMappingInfo requestMappingInfo = HANDLER_METHOD_REQUEST_MAPPING_INFO_MAP.get(handlerMethod);
        if(requestMappingInfo == null) throw new ServiceException("沒有對應(yīng)的匹配方法");
        super.handleMatch(requestMappingInfo, lookupPath, request);
        return handlerMethod;
    }
}

1.2 注入自定義的RequestMappingHandlerMapping

因?yàn)槲覀兊膹V告系統(tǒng)使用的是Spring boot, 因此可以通過繼承WebMvcRegistrationsAdapter, 并且覆寫其中的getRequestMappingHandlerMapping方法注入自己的RequestMappingHandlerMapping.最后在繼承WebMvcRegistrationsAdapter的類上加上@Configuration注解

這里有一個(gè)需要注意的地方: 如果使用的spring boot的版本低于1.4.1的話是沒有WebMvcRegistrationsAdapter, 這個(gè)時(shí)候如果直接繼承WebMvcConfigurationSupport來實(shí)現(xiàn)自定義的RequestMappingHandlerMapping的話就會(huì)導(dǎo)致WebMvcAutoConfiguration失效, 造成的結(jié)果就是DefaultViewResolver, WelcomePageHandlerMapping等的一些配置失效, 這個(gè)時(shí)候應(yīng)該怎么辦呢?可以參考Stack Overflow上的這兩個(gè)issue: issue1, issue2 這里就不再贅述了. 代碼如下

@Configuration
public class WebMvcConfig extends WebMvcRegistrationsAdapter {

    @Override
    public RequestMappingHandlerMapping getRequestMappingHandlerMapping() {
        return new CustomRequestMappingHandlerMapping();
    }
}

1.3 在@RequestMapping中加上name屬性, 讓解析的時(shí)候就生成<字符串, 處理方法>的集合

因?yàn)?code>@RequestMapping中name屬性不會(huì)用于url的匹配, spring會(huì)解析name屬性, 并將name屬性的值和處理方法進(jìn)行關(guān)聯(lián), 這就正好可以滿足我們的需求. 因此url中傳的event的值就對應(yīng)著name屬性的值, 這樣就可以找到對應(yīng)的處理方法了, 而不需要我們再維護(hù)一個(gè)集合

代碼如下:

@Controller
@Api(tags = "2-User Event", description = "用戶事件API")
@Validated
public class UserEventController extends BaseNasdaqController {

    /**
     * 點(diǎn)擊計(jì)數(shù)
     *
     * @param userId
     * @param hash
     * @return
     */
    @RequestMapping(name="click", value = EVENT_CLICK, method = GET)
    @ApiOperation(value = "點(diǎn)擊計(jì)數(shù)", notes = "點(diǎn)擊計(jì)數(shù)+1")
    public String click(@PathVariable Integer userId, @NotNull String hash) {
        ...
        ...
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末怀偷,一起剝皮案震驚了整個(gè)濱河市跌捆,隨后出現(xiàn)的幾起案子呈枉,更是在濱河造成了極大的恐慌梢褐,老刑警劉巖栓拜,帶你破解...
    沈念sama閱讀 211,743評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件摘完,死亡現(xiàn)場離奇詭異抖甘,居然都是意外死亡曙蒸,警方通過查閱死者的電腦和手機(jī)润绵,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評論 3 385
  • 文/潘曉璐 我一進(jìn)店門线椰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人尘盼,你說我怎么就攤上這事憨愉。” “怎么了卿捎?”我有些...
    開封第一講書人閱讀 157,285評論 0 348
  • 文/不壞的土叔 我叫張陵配紫,是天一觀的道長。 經(jīng)常有香客問我午阵,道長躺孝,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,485評論 1 283
  • 正文 為了忘掉前任底桂,我火速辦了婚禮植袍,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘籽懦。我一直安慰自己于个,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評論 6 386
  • 文/花漫 我一把揭開白布猫十。 她就那樣靜靜地躺著览濒,像睡著了一般呆盖。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上贷笛,一...
    開封第一講書人閱讀 49,821評論 1 290
  • 那天应又,我揣著相機(jī)與錄音,去河邊找鬼乏苦。 笑死株扛,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的汇荐。 我是一名探鬼主播洞就,決...
    沈念sama閱讀 38,960評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼掀淘!你這毒婦竟也來了旬蟋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評論 0 266
  • 序言:老撾萬榮一對情侶失蹤革娄,失蹤者是張志新(化名)和其女友劉穎倾贰,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拦惋,經(jīng)...
    沈念sama閱讀 44,186評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡匆浙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了厕妖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片首尼。...
    茶點(diǎn)故事閱讀 38,650評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖言秸,靈堂內(nèi)的尸體忽然破棺而出软能,到底是詐尸還是另有隱情,我是刑警寧澤井仰,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布埋嵌,位于F島的核電站,受9級特大地震影響俱恶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜范舀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評論 3 313
  • 文/蒙蒙 一合是、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锭环,春花似錦聪全、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽娃圆。三九已至,卻和暖如春蛾茉,著一層夾襖步出監(jiān)牢的瞬間讼呢,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評論 1 266
  • 我被黑心中介騙來泰國打工谦炬, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悦屏,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,370評論 2 360
  • 正文 我出身青樓键思,卻偏偏與公主長得像础爬,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子吼鳞,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理看蚜,服務(wù)發(fā)現(xiàn),斷路器赔桌,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法失乾,類相關(guān)的語法,內(nèi)部類的語法纬乍,繼承相關(guān)的語法碱茁,異常的語法,線程的語...
    子非魚_t_閱讀 31,598評論 18 399
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評論 6 342
  • ¥開啟¥ 【iAPP實(shí)現(xiàn)進(jìn)入界面執(zhí)行逐一顯】 〖2017-08-25 15:22:14〗 《//首先開一個(gè)線程仿贬,因...
    小菜c閱讀 6,365評論 0 17
  • 這是我參加文魁大腦俱樂部思維導(dǎo)圖武林計(jì)劃的第五十幅作品纽竣,主題是:《教育制度》 中心圖:教育二字定義成編碼思維“魚”...
    崔小九閱讀 510評論 2 1