從源碼的角度徹底搞懂 HandlerMapping 和 HandlerAdapter
- 2019 年 10 月 22 日
- 筆記
徹底搞懂 HandlerMapping和HandlerAdapter
知識點的回顧:
當Tomcat接收到請求後會回調Servlet的service方法,一開始入門Servlet時,我們會讓自己的Servlet去實現HttpServlet接口,重寫它的doGet()
和doPost()
方法
在SpringMvc中,SpringMvc的核心組件DispatcherSerlvet的繼承圖如上,可以看到上圖,其實這個DispatcherServlet終究還是一個Servlet
我們追蹤一下他的生命周期創建過程, 首先是說Servlet的創建時機,其實是存在兩種情況的, 這取決於.setLoadOnStartup(1);
設置的啟動級別,當然一般都會設置成正數,表示當容器啟動時實例化Servlet
於是Tomcat實例化Servlet,Servlet被初始化時首先被回調的方法是init()
這大家都知道的,但是SpringMvc提供的DispatcherServlet中存在一個靜態塊,源碼如下: 這個靜態塊幹了什麼事呢? 讀取的是class path
下面的 DispatcherServlet.properties
static { // Load default strategy implementations from properties file. // This is currently strictly internal and not meant to be customized // by application developers. try { // todo 它讀取的是class path 下面的 DispatcherServlet.properties 配置文件 // todo resource/web/servlet/DispatcherServlet.properties // todo 將這些默認的實現信息,封裝進了Properties defaultStrategies ClassPathResource resource = new ClassPathResource(DEFAULT_STRATEGIES_PATH, DispatcherServlet.class); defaultStrategies = PropertiesLoaderUtils.loadProperties(resource); } catch (IOException ex) { throw new IllegalStateException("Could not load '" + DEFAULT_STRATEGIES_PATH + "': " + ex.getMessage()); } }
那問題來了,這個配置文件到底存放的什麼? 讓DispatcherServlet
如此迫切的去加載? 我們文件貼在下面,可以看到存放的是一些全類名,這些是DiapacherServlet針對不同策略接口提供的八個默認的實現,當在上下文中沒有匹配到程序員添加的這些實現時,就會使用這些默認的實現
# Default implementation classes for DispatcherServlet's strategy interfaces. # Used as fallback when no matching beans are found in the DispatcherServlet context. # Not meant to be customized by application developers. # todo 這裡存在 DiapacherServlet策略接口的八個默認的實現 # todo 當在上下文中沒有匹配到程序員添加的這些實現時,就會使用這些默認的實現 org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping, org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter, org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter, org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver, org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver, org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
好吧, 雖然這也不算跑題,但是我們還是回到DispatcherServlet
的init()
方法,其實去這個DispatcherServlet
中是找不到這個init()
方法的, 那這個方法在哪裡了呢? 其實就在他的祖父類HttpServletBean
中,源碼如下:
意圖很明確,前面用來初始化環境參數,後者調用initServletBean();
@Override public final void init() throws ServletException { if (logger.isDebugEnabled()) { logger.debug("Initializing servlet '" + getServletName() + "'"); } // Set bean properties from init parameters. // todo 設置初始化參數 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); if (!pvs.isEmpty()) { try { BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); initBeanWrapper(bw); bw.setPropertyValues(pvs, true); } catch (BeansException ex) { if (logger.isErrorEnabled()) { logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); } throw ex; } } // Let subclasses do whatever initialization they like. // todo 初始化SerlvetBean initServletBean(); if (logger.isDebugEnabled()) { logger.debug("Servlet '" + getServletName() + "' configured successfully"); } }
initServletBean();
見名知意,初始化ServletBean
,說白了就是想去初始化DispatcherServlet
唄,跟進去查看,不出意料,他是個抽象方法,跟進它的實現類
他的實現類是FrameworkServlet
,進去跟進,看他去創建應用的上下文,但是如果上下文已經被初始化了,他是不會重複創建上下文的
我們繼續跟進它的onRefresh()
方法,同樣這個方法是一個抽象方法,而它是實現就是我們關注的DispatcherServlet
,源碼如下: 在下面做了很多事,我們還是僅僅關注兩點,初始化了handlerMapping
和handlerAdapater
protected void onRefresh(ApplicationContext context) { // todo 進行跟進,方法就在下面 initStrategies(context); } protected void initStrategies(ApplicationContext context) { // todo 初始化和文件上傳相關的解析器 initMultipartResolver(context); initLocaleResolver(context); initThemeResolver(context); /** * todo 初始化處理器映射器,這是我們看到重點 * todo 什麼是處理器映射器? * todo Controller在Spring中有多種情況, 那當一個用戶的請求到來時, 如何進一步找到哪一種Controller來處理用戶的請求呢? * todo 這一步就是通過處理器映射器完成的 * todo 說白了, 通過處理器映射器我們可以找到那個處理當前請求的特定的組件, 我們稱它為handler * todo 但是這之間存在一個問題,就是說,這個handler到底是方法級別的,還是類級別的我們是不知道的,只能說,這個handler就肯定能處理這次請求而已 */ initHandlerMappings(context); initHandlerAdapters(context); initHandlerExceptionResolvers(context); initRequestToViewNameTranslator(context); initViewResolvers(context); initFlashMapManager(context); }
到現在為止,本位關注的兩個組件其實就完成了初始化了, 下一個問題就來了,什麼時候使用他們呢?
那就得從Servlet的service()
方法說起了,大家都知道這個方法會在Serlvet每一次收到請求時就會被回調一次,再回想我們原來是怎麼變成來着? 但是後來我們都直接實現HttpServlet
接口,然後重寫他們的doGet()
和doPost()
來實現我們自己的Servlet, 那SpringMvc是怎麼做的呢?
源碼如下:
@Override protected final void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); } @Override protected final void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { processRequest(request, response); }
都是通過processRequest(request, response);
來實現的,我們往下追蹤這個方法,最終也是不出所料,我們來到了DispacherServlet
中
我截取部分源碼如下:
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; boolean multipartRequestParsed = false; //異步編程 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); try { ModelAndView mv = null; Exception dispatchException = null; try { //檢查請求中是否存在文件上傳的操作 processedRequest = checkMultipart(request); multipartRequestParsed = (processedRequest != request); // Determine handler for the current request. //todo 確定當前請求的處理程序,跟進去 //todo 換句話說就是 推斷Controller的類型, Controller存在三種類型 // todo 跟進去看看這個handlerMapping的獲取方式 mappedHandler = getHandler(processedRequest); if (mappedHandler == null) { noHandlerFound(processedRequest, response); return; } // Determine handler adapter for the current request. // todo 用到了適配器設計模式, // todo 如果 上面的HandlerExecutionChain 是bean類型, 經過這個方法後將被設置成bean // todo 如果 上面的HandlerExecutionChain 是 method 類型, 經過這個方法後將被設置成嗎method HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = "GET".equals(method); if (isGet || "HEAD".equals(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (logger.isDebugEnabled()) { logger.debug("Last-Modified value for [" + getRequestUri(request) + "] is: " + lastModified); } if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; }
同樣的,我們只是關註上面的兩點,看看SpringMvc是如何玩轉HandlerMapping
和HandlerAdapter
的, 分兩步,先跟進mappedHandler = getHandler(processedRequest);
方法
看看他幹啥了,遍歷所有的HandlerMapping
第一個問題: 還記不記得哪裡來的RequestMappingHandlerMapping
,沒錯就是文章一開始我們去看初始化DispatcherServlet
時在靜態塊裏面完成的加載已經後續的初始化,所以按理說,下面的數組中就存在兩個handlerMapping
分別是BeanNameUrlHandlerMapping
和RequestMappingHandlerMapping
第二個問題: 什麼是HandlerMapping
? 直接看它的中文翻譯就是處理器映射器是不好理解的,其實也沒有特別難懂, 就是一個映射器嘛,映射什麼呢? 就是映射用戶的請求與後端程序員編寫的Controller之間的關係, 再直白一點, 就是一個用戶的請求經過了這個HandlerMapping
就可以百分百確定出哪一個控制器是用來處理它的
第三個問題: 下面的HandlerMapping
是個數組,意味着這個映射的規則是多種多樣的,所以來個循環,如果沒有任何一個映射器滿足條件怎麼辦呢? 404唄
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { if (this.handlerMappings != null) { for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } } return null; }
於是經過了上面HandlerMapping
的處理我們獲取出來了一個 HandlerExecutionChain ,並且我們百分百確定這個 HandlerExecutionChain 就是用來處理當前的請求的,但是!!! 我們卻不能直接使用,因為我們是不清楚前面的獲取到的這個執行器鏈是個方法,還是個類,於是適配器就用上了
源碼如下:
這個適配器就是HandlerAdapter
,使用設配器設計模式,不管得到的handler到底是什麼類型的,都可以找到正確的方法區執行它
HandlerAdapter
同樣是DisapcherServlet
的一大組件,它和上面的處理器映射器是一樣的,同樣是從配置文件中被讀取出來
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException { if (this.handlerAdapters != null) { for (HandlerAdapter ha : this.handlerAdapters) { if (logger.isTraceEnabled()) { logger.trace("Testing handler adapter [" + ha + "]"); } if (ha.supports(handler)) { return ha; } } } throw new ServletException("No adapter for handler [" + handler + "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler"); }
咋樣? 其實本文倒是也沒什麼難度,就是覺得確實挺好玩的…