【springmvc】controller的初始化與匹配
- 2020 年 3 月 26 日
- 筆記
初始化
DispatcherServlet的初始化流程 講述DispatcherServlet從Servlet::init一路調用至DispatcherServlet::initStrategies的過程。
protected void initStrategies(ApplicationContext context) { initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
initHandlerMappings會初始化一些HandlerMapping,在處理請求時,DispatcherServlet會調用getHandler:
// DispatcherServlet.java @Nullable protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { // 遍歷 HandlerMapping 數組 for (HandlerMapping mapping : this.handlerMappings) { // 獲得請求對應的 HandlerExecutionChain 對象 HandlerExecutionChain handler = mapping.getHandler(request); // 獲得到,則返回 if (handler != null) { return handler; } } } return null; }
當能獲取到HandlerExecutionChain時就返回。@RequestMapping一般對應由RequestMappingHandlerMapping處理。
RequestMappingHandlerMapping初始化
我們已經知道,@Controller和@RequestMapping是由RequestMappingHandlerMapping處理的,那麼它們是如何被收集的呢? RequestMappingHandlerMapping 初始化搜集所有控制器方法的過程分析文章的開頭和結尾都總結得很好,但我在此也寫下自己的理解(與原文大同小異):
- 注入Bean。WebMvcConfigurationSupport配置類會將RequestMappingHandlerMapping作為
- 調用回調。RequestMappingHandlerMapping實現了InitializingBean,在Bean 創建時會回調afterPropertiesSet。並調用至initHandlerMethods。
- initHandlerMethods完成三件事:
- 獲取所有Bean(getCandidateBeanNames)
- 篩選符合的Bean。判斷其是否為@Controller或@RequestMapping(isHandler)
- 對符合的Bean,包裝其@RequestMapping方法為RequestMappingInfo並註冊。(AbstractHandlerMethodMapping::detectHandlerMethods)
運行時
DispatcherServlet::getHandler會一路運行至AbstractHandlerMethodMapping::lookupHandlerMethod,在這裡:
- 通過mappingRegistry會獲取可能路徑前綴匹配的RequestMappingInfo
- addMatchingMappings將RequestMappingInfo數組轉為Match數組。利用了mappingRegistry。
- 按照匹配程度排序選出最佳匹配的RequestMappingInfo,並返回其對應的HandlerMethod
Math包含了
private class Match { private final T mapping; private final HandlerMethod handlerMethod;
從下文的if (matches.isEmpty())
,結合自己調試可知: 當路徑能完美匹配時,比如以下程式碼匹配/hello/t
:
@RestController @RequestMapping("/hello") public class HelloController { @GetMapping("/t") public String hello() { return "this is hello"; } }
則會執行if (directPathMatches != null) {...}
,如果@GetMapping("/t")
換成@GetMapping("/t*")
,則沒有完美路徑匹配,會執行if (matches.isEmpty()) {...}
。
@Nullable protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<>(); 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); } ...