從源碼的角度徹底搞懂 HandlerMapping 和 HandlerAdapter

  • 2019 年 10 月 22 日
  • 筆記

徹底搞懂 HandlerMapping和HandlerAdapter

知識點的回顧:

當Tomcat接收到請求後會回調Servlet的service方法,一開始入門Servlet時,我們會讓自己的Servlet去實現HttpServlet接口,重寫它的doGet()doPost()方法

DispatcherServlet繼承 體系
在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

好吧, 雖然這也不算跑題,但是我們還是回到DispatcherServletinit()方法,其實去這個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,源碼如下: 在下面做了很多事,我們還是僅僅關注兩點,初始化了handlerMappinghandlerAdapater

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是如何玩轉HandlerMappingHandlerAdapter的, 分兩步,先跟進mappedHandler = getHandler(processedRequest);方法

看看他幹啥了,遍歷所有的HandlerMapping

第一個問題: 還記不記得哪裡來的RequestMappingHandlerMapping,沒錯就是文章一開始我們去看初始化DispatcherServlet時在靜態塊裏面完成的加載已經後續的初始化,所以按理說,下面的數組中就存在兩個handlerMapping分別是BeanNameUrlHandlerMappingRequestMappingHandlerMapping

第二個問題: 什麼是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");      }  

咋樣? 其實本文倒是也沒什麼難度,就是覺得確實挺好玩的…