1. 程式人生 > >從原始碼的角度徹底搞懂 HandlerMapping 和 HandlerAdapter

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

徹底搞懂 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

好吧, 雖然這也不算跑題,但是我們還是回到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");
    }

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