上一篇:SpringMVC源碼分析-DispatcherServlet實例化幹了些什麼html
先吐槽一下。。。寫了兩小時的博客忽然被俺家小屁孩按了刷新,東西不見了,建議OSCHINA可以自動定時保存啊。讓我先安靜一下。。。。java
由於前面在閱讀Tomcat源碼的時候時序圖中沒有畫調用Servlet init方法的步驟,在這裏補充一下文字說明。 Tomcat調用順序:web
StandardWrapperValve.invoke() StandardWrapper.allocate() StandardWrapper.loadServlet() DispatcherServet(實際是父類的).init()
經過上面的調用過程最終到達了DispatcherServlet的init(),由Spring的DefaultListableBeanFactory去完成DispatcherServlet.properties中配置類的初始化工做spring
圖中最核心的方法就是DispatcherServlet.initStrategies(),見源代碼json
protected void initStrategies(ApplicationContext context) { /** * 這裏的每個方法都很是的重要,他們都遵循一個邏輯:若是在容器中沒有拿到對應類型的對象,則使用DispatcherServlet.properties當中配置 * 的值,使用Spring容器建立一個新的Bean(注意是prototype,因此在singletonObjebs中沒法找到它們。。。爲何是prototype喃,說實話我也不 * 是很肯定,可能由於Servlet可能會有多個吧。。。但想一想又以爲很牽強由於DispatcherServlet只有一個對象,並且又是在init方法初始化的, * init方法只會被調用一次,不管處理多少請求,它們都會是線程共享的啊。。。也許Spring並不想在singletonObjects當中存放它才設置爲prototype * 的吧 * * 由於是用Spring進行實例化的,因此可使用Spring提供的擴展點干預這些默認的類的行爲,好比能夠將protyotype設置爲singleton,能夠修改它們 * 一些屬性的默認值等等 */ //文件上傳相關 initMultipartResolver(context); //國際化 先從容器中拿名爲localResolve的LocaleResolver.class對象,若是沒有則使用默認的AcceptHeaderLocaleResolver(Spring-createBean) initLocaleResolver(context); //主題樣式 先從容器中拿名爲themeResolver的ThemeResolver.class對象,若是沒有則使用默認的FixedThemeResolver(Spring-createBean) initThemeResolver(context); /**--------------很是重要------------- * 初始化請求映射規則 * 先從容器中拿名爲HandlerMapping.class對象列表,若是沒有則使用默認的如下三個配置項 * RequestMappingHandlerMapping:這是最重要的,是一般咱們在Controller當中配置的RequestMapping的映射處理類 * BeanNameUrlHandlerMapping:是咱們的Controller實現了Controller接口,而後用@Component("beanName"),訪問時url:localhost:port/contextpath/beanName * RouterFunctionMapping:經過HttpRequestHandler實現Controller,具體細節不清楚,歷來沒見過更沒用過 */ initHandlerMappings(context); /**--------------很是重要------------- * Request\Response的重要處理,好比入參與出參的一樣格式化 * 先從容器中拿名爲HandlerAdapter.class對象列表,若是沒有則使用默認的如下四個配置項: * HttpRequestHandlerAdapter: * SimpleControllerHandlerAdapter: * RequestMappingHandlerAdapter: * HandlerFunctionAdapter: */ initHandlerAdapters(context); /** * 異常 先從容器中拿HandlerExceptionResolver.class對象列表,若是沒有則使用默認的如下三個配置項 * ExceptionHandlerExceptionResolver: * ResponseStatusExceptionResolver: * DefaultHandlerExceptionResolver: */ initHandlerExceptionResolvers(context); //請求到視圖的轉換 從容器中拿名爲viewNameTranslator的RequestToViewNameTranslator,若是沒有則使用默認的DefaultRequestToViewNameTranslator initRequestToViewNameTranslator(context); //視圖轉換器 從容器中拿ViewResolver對象列表,若是沒有則使用默認的InternalResourceViewResolver initViewResolvers(context); //重定向視圖管理器 從容器中拿名爲flashMapManager的FlashMapManager,若是沒有則使用默認的SessionFlashMapManager initFlashMapManager(context); }
源碼中已經比較詳細的寫了每一個方法的註釋,就再也不贅述了。緩存
這裏面的每一個方法固然都很重要,可是結合平常開發來分析,initHandlerMappings,initHandlerAdapters,initViewResolvers它們三個是解決請求映射、參數解析-綁定-格式化、視圖渲染等功能,因此重點拿initHandlerMappings,initHandlerAdapters方法來分析。這兩個方法底層邏輯都是先從Spring中找有麼有對應的Bean,若是沒有則使用DispatcherServlet.properties中配置的Bean,由Spring完成他們的實例化。因爲時間、篇幅等緣由就不分析裏面的每個類的實例化和功能,這三種分別拿一個比較典型的類進行分析。app
使用RequestMappingHandlerMapping做爲典型進行講解它的實例化,不包含它的處理請求的部分源碼分析
此類乾的重要事情就是收集Controller當中的符合規則的HandlerMethod,在處理請求的時候,使用請求路徑和這些HandlerMethod進行匹配,找最優匹配進行處理ui
雖然我選擇了這個類作講解,可是從哪裏入手看喃?既然這個類是被Spring實例化的,那麼它確定實現什麼接口口、繼承了什麼類或者使用了什麼Spring的特殊註解,拿出它的類結構圖看看this
能夠看到實現了InitializingBean接口,Spring在實例化對象快結束的時候會調用實現類的afterPropertiesSet()方法。
那就看看這個方法
public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager()); //這裏面就是收集Controller的映射關係 super.afterPropertiesSet(); }
把斷點打到這裏一步步的往下走,最終能夠找到關鍵代碼,這個過程就省略了,我直接把調用棧貼出來,已經對比較重要方法作註釋
RequestMappingHandlerMapping.afterPropertiesSet() AbstractHandlerMethodMapping.afterPropertiesSet() AbstractHandlerMethodMapping.initHandlerMethods() AbstractHandlerMethodMapping.processCandidateBean() AbstractHandlerMethodMapping.detectHandlerMethods() RequestMappingHandlerMapping.getMappingForMethod()
AbstractHandlerMethodMapping.initHandlerMethods()
protected void initHandlerMethods() { //循環Spring Context當中全部的Bean,看是否知足被@Controller或者@RequestMapping修飾的條件,若是知足則進行映射關係收集 for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { processCandidateBean(beanName); } } /** * 開啓鎖,返回一個只可讀的請求路徑與HandlerMethod的映射Map */ handlerMethodsInitialized(getHandlerMethods()); }
AbstractHandlerMethodMapping.processCandidateBean()
protected void processCandidateBean(String beanName) { Class<?> beanType = null; try { beanType = obtainApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isTraceEnabled()) { logger.trace("Could not resolve type for bean '" + beanName + "'", ex); } } //isHandler:看BeanType是否@Controller或者@RequestMapping修飾,若是知足條件,才進行下面的映射關係收集邏輯 if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }
AbstractHandlerMethodMapping.detectHandlerMethods()
protected void detectHandlerMethods(Object handler) { Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass()); if (handlerType != null) { Class<?> userType = ClassUtils.getUserClass(handlerType); /** * 從這裏開始有兩層調用都用到了lambda表達式,看源碼時須要注意,在DEBUG時,會跳回到上一層的lambda表達式對應的代碼段執行 * * selectMethods裏面是在從緩存(建立Bean的時候由於其餘功能已經解析了一遍類的Class)中拿類的全部Method,而後 * 調用getMappingForMethod()方法將Controller符合條件的method與指望請求路創建關係以備請求時作匹配使用,並將匹配到結果放入methods集合 * 當中 */ Map<Method, T> methods = MethodIntrospector.selectMethods(userType, (MethodIntrospector.MetadataLookup<T>) method -> { try { return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } }); if (logger.isTraceEnabled()) { logger.trace(formatMappings(userType, methods)); } /** * 註冊HandlerMethod,只有註冊以後,在後面才能從registry當中拿到一個只讀的Mapping */ methods.forEach((method, mapping) -> { Method invocableMethod = AopUtils.selectInvocableMethod(method, userType); registerHandlerMethod(handler, invocableMethod, mapping); }); } }
RequestMappingHandlerMapping.getMappingForMethod()
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { /** * 從Method上面拿到請求路徑 */ RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { /** * 從Type上面拿到請求路徑 */ RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { /** * 進行組合:好比method上面是"/user/{age}",type上面是"/my",則組合後的結果就是"/my/user/{age}" */ info = typeInfo.combine(info); } String prefix = getPathPrefix(handlerType); if (prefix != null) { info = RequestMappingInfo.paths(prefix).build().combine(info); } } return info; }
這裏有必要把RequestMappingInfo單獨解釋一下,RequestMappingInfo對應@RequestMapping,裏面的屬性在init的時候也會根據@RequestMapping的參數值對應設置
public final class RequestMappingInfo implements RequestCondition<RequestMappingInfo> { @Nullable private final String name; //Patterns對應url,就是RequestMapping註解value中的配置 private final PatternsRequestCondition patternsCondition; //Methods對應 http method,如GET,POST,PUT,DELETE等 private final RequestMethodsRequestCondition methodsCondition; //params對應http request parameter private final ParamsRequestCondition paramsCondition; //headers 對應http request 的請求頭 private final HeadersRequestCondition headersCondition; //consumes對應request的提交內容類型content type,如application/json, text/html private final ConsumesRequestCondition consumesCondition; //produces指定返回的內容類型的content type,僅當request請求頭中的(Accept)類型中包含該指定類型才返回 private final ProducesRequestCondition producesCondition; //若是以上都還不能達到你過濾請求的目的,還能夠自定義 private final RequestConditionHolder customConditionHolder; //-------------省略不少代碼------------------ /** * 請求匹配到多個RequestMapping,則須要排序,選擇最優的HandlerMethod進行處理 */ 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; } }
到此Spring已經使用RequestMappingHandlerMapping爲使用@Controller和@RequestMapping註解的Controller類收集好了全部的請求映射,等待處理請求。
使用RequestMappingHandlerAdapter做爲典型進行講解它的實例化,不包含它的處理請求的部分
分析它的套路和RequestMappingHandlerMapping同樣,上來就看afterPropertiesSet()方法
public void afterPropertiesSet() { /** * 找到Spring容器中被@ControllerAdvice和@RestControllerAdvice修飾的Bean,並添加到一個List當中 * @ControllerAdvice是一個Controller加強器,在項目中曾經被用來作異常統一處理 * 這兩個註解能夠結合@ExceptionHandler, @InitBinder, @ModelAttribute三個註解使用將方法做用到全局上面 * 詳細使用參考官網:https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html */ initControllerAdviceCache(); /** * ----------------很是重要---------------------- * argumentResolvers:參數解析器 * initBinderArgumentResolvers:初始化參數綁定解析器 * returnValueHandlers:Controller.method invoke以後返回值解析器 * * SpringMVC都默認提供了一大批各類各樣的解析器,它們共同組成了SpringMVC的強大功能 * */ if (this.argumentResolvers == null) { /** * 獲取默認的參數解析器,這些解析器是寫死在代碼中的 */ List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers(); this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.initBinderArgumentResolvers == null) { /** * 取默認的InitBinder參數解析器,這些解析器是寫死在代碼中的 */ List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers(); this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers); } if (this.returnValueHandlers == null) { /** * 取默認的返回值解析器,這些解析器是寫死在代碼中的 */ List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers(); this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers); } }
此方法很簡單,就是實例化默認的各類解析器,在此以前將@ControllerAdvice修飾的Bean找出來放在一個集合當中
關於@ControllerAdvice能夠在官網文檔中去出看或者百度
隨便找一個Revolver看看它的功能
RequestParamMethodArgumentResolver負責識別@RequestParam的參數,將其中的name和真正請求當中的Key進行匹配
PathVariableMethodArgumentResolver負責識別rest風格中具體位置中的參數值
Spring命名確實也很規範,經過看名稱幾乎就能知道它是幹什麼的,因此開發當中命名很重要,不要嫌棄名稱太長,寫起來太麻煩。