Spring MVC組件之HandlerAdapter

Spring MVC組件之HandlerAdapter

HandlerAdapter概述

HandlerAdapter組件是一個處理器Handler的適配器。HandlerAdapter組件的主要作用是適配特定的Handler來處理相應的請求。

在SpringMvc的源碼中, HandlerAdapter是一個介面。該介面主要定義了三個方法。

         1.boolean supports(Object handler)

              判斷HandlerAdapter組件是否支援這個handler實例。

         2.ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception

             HandlerAdapter組件使用handler實例來處理具體的請求。

          3.long getLastModified(HttpServletRequest request, Object handler)

             獲取資源的最後修改值,該方法已經被遺棄。

HandlerAdapter類圖

 

 

從以上類圖中可以看出, HandlerAdapter介面系列的繼承結構是比較簡單的。HandlerFunctionAdapter,HttpRequestHandlerAdapter,SimpleControllerHandlerAdapter,SimpleServletHandlerAdapter這四個類直接繼承了HandlerAdapter介面。只有RequestMappingHandlerAdapter類稍微複雜一些。RequestMappingHandlerAdapter類是間接繼承了HandlerAdapter這個介面。RequestMappingHandlerAdapter類首先是繼承了AbstractHandlerMethodAdapter這個抽象類,而AbstractHandlerMethodAdapter這個抽象類再繼承了HandlerAdapter介面。

RequestMappingHandlerAdapter

AbstractHandlerMethodAdapter

AbstractHandlerMethodAdapter是一個抽象類,它繼承了HandlerAdapter介面,分別實現了HandlerAdapter介面的supports,handle,getLastModified這三個方法。

getLastModified方法已經棄用,就不多做描述。

supports方法

    @Override

    public final boolean supports(Object handler) {

        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));

}

supports方法主要是判斷該適配器是否支援這個handler。程式碼中的邏輯主要是判斷handler是否是HandlerMethod類的實例,再綜合了supportsInternal方法的返回值。

supportsInternal方法在AbstractHandlerMethodAdapter類中只是一個虛方法,主要是提供給子類RequestMappingHandlerAdapter來做具體的實現。

    @Override

    @Nullable

    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)

           throws Exception {

 

        return handleInternal(request, response, (HandlerMethod) handler);

}

在handle方法中,又直接調用了handleInternal方法。handleInternal方法在AbstractHandlerMethodAdapter類中也只是一個虛方法的聲明,專門是提供給子類來實現,handleInternal這個虛方法,最終是在RequestMappingHandlerAdapter類中給出了具體的邏輯實現。

AbstractHandlerMethodAdapter實現了Ordered介面。該介面主要是用於排序。

AbstractHandlerMethodAdapter還繼承了WebContentGenerator類,該類是一個web內容生成器的超類,它提供了如瀏覽器快取控制、是否必須有session開啟、支援的請求方法類型(GET、POST等)等一些屬性和方法。

WebContentGenerator中有個checkRequest方法,主要是對請求進行檢測。子類RequestMappingHandlerAdapter中會調用這個方法。

RequestMappingHandlerAdapter

Spring MVC容器在初始化HandlerAdapter類型的組件時,默認初始化的就是RequestMappingHandlerAdapter這個組件。

RequestMappingHandlerAdapter除了繼承至AbstractHandlerMethodAdapter這個父類,它還實現了InitializingBean和BeanFactoryAware介面。

我們知道在Spring中如果一個類實現了InitializingBean介面,Spring容器就會在實例化該Bean時,會調用這個Bean的afterPropertiesSet方法。

   

 @Override

    public void afterPropertiesSet() {

        // Do this first, it may add ResponseBody advice beans

        initControllerAdviceCache();

 

        if (this.argumentResolvers == null) {

           List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();

           this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

        }

        if (this.initBinderArgumentResolvers == null) {

           List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();

           this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);

        }

        if (this.returnValueHandlers == null) {

           List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();

           this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);

        }

}

在RequestMappingHandlerAdapter類中的afterPropertiesSet方法中,主要做了以下四件事情。

1.initControllerAdviceCache方法主要是初始化RequestMappingHandlerAdapter類中,initBinderAdviceCache,modelAttributeAdviceCache,          requestResponseBodyAdvice這三個屬性。

在initControllerAdviceCache方法中會首先在Spring容器中,獲取所帶有@ControllerAdvice註解的bean。

然後依次遍歷每個bean,在每個bean查找有@InitBinder註解的方法,再將這些方法初始化initBinderAdviceCache這個屬性。

再次查找每個bean中有@ModelAttribute註解的方法,再將這些方法初始化到modelAttributeAdviceCache這個屬性。

將實現了RequestBodyAdvice介面和ResponseBodyAdvice介面的bean,先收集起來。最後放入requestResponseBodyAdvice這個屬性列表的最前面。

 

2.初始化RequestMappingHandlerAdapter類的argumentResolvers屬性。

在RequestMappingHandlerAdapter類的argumentResolvers屬性為空的情況下,會調用getDefaultArgumentResolvers()方法,來構造默認解析器的列表。

解析器的列表是按照注釋解析,類型解析,自定義解析,所有類型解析的四種類型的順序來進行構造的。

 

3.初始化RequestMappingHandlerAdapter類的initBinderArgumentResolvers屬性。

在RequestMappingHandlerAdapter類的initBinderArgumentResolvers屬性為空的情況下,會調用getDefaultInitBinderArgumentResolvers ()方法,來構造默認解析器的列表。

解析器的列表同樣是按照」注釋解析」, 」類型解析」, 」自定義解析」, 」所有類型解析」這四種類型的順序來進行構造。

 

4.初始化RequestMappingHandlerAdapter類的returnValueHandlers屬性,在RequestMappingHandlerAdapter類的returnValueHandlers屬性為空的情況下,會調用getDefaultReturnValueHandlers ()方法,來構造默認解析器的列表。

解析器的列表是按照」單個意圖」, 」注釋解析」, 」多個意圖」, 」類型解析」, 」自定義解析」, 」所有類型解析」等這幾種類型的順序來進行構造的。

 

RequestMappingHandlerAdapter繼承了AbstractHandlerMethodAdapter類,它主要重寫了父類AbstractHandlerMethodAdapter提供的三個模板方法。

1.supportsInternal方法

在supportsInternal方法中,直接返回了true。所以真正起作用的還是父類中的supports方法。

 

2.getLastModifiedInternal方法

該方法直接返回了-1。

 

3.handleInternal方法

這個方法是實際處理請求的方法。整個方法大致分了三個步驟。

1)         準備好處理請求的所有參數

2)         使用處理器處理請求

3)         將不同的類型的返回值統一成ModelAndView類型返回

 

 protected ModelAndView handleInternal(HttpServletRequest request,

           HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

 

        ModelAndView mav;

        checkRequest(request);

 

        // Execute invokeHandlerMethod in synchronized block if required.

        if (this.synchronizeOnSession) {

           HttpSession session = request.getSession(false);

           if (session != null) {

               Object mutex = WebUtils.getSessionMutex(session);

               synchronized (mutex) {

                   mav = invokeHandlerMethod(request, response, handlerMethod);

               }

           }

           else {

               // No HttpSession available -> no mutex necessary

               mav = invokeHandlerMethod(request, response, handlerMethod);

           }

        }

        else {

           // No synchronization on session demanded at all...

           mav = invokeHandlerMethod(request, response, handlerMethod);

        }

 

        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {

           if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {

               applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);

           }

           else {

               prepareResponse(response);

           }

        }

 

        return mav;

    }

從以上程式碼中可以看出,handleInternal的方法中主要是調用了checkRequest,invokeHandlerMethod等這幾個方法。

  • checkRequest方法

checkRequest方法主要是對請求進行檢測。RequestMappingHandlerAdapter類的

checkRequest方法其實調用的是父類WebContentGenerator的checkRequest方法。

在checkRequest方法中,主要是做了兩個檢測。

    1. 檢測請求的方法是否被支援,在AbstractHandlerMethodAdapter類中由於restrictDefaultSupportedMethods的值設置為false,所以對請求方法的檢測不會執行。
    2. 檢測請求的session是否存在
  • invokeHandlerMethod方法

1.首先會用request和response構建一個ServletWebRequest對象。

2.構建一個WebDataBinderFactory對象

3.構建一個ModelFactory對象

4.創建一個ServletInvocableHandlerMethod的實例,實際請求的處理就是通過它來執行的。ServletInvocableHandlerMethod實例創建後,會把argumentResolvers,returnValueHandlers,parameterNameDiscoverer這些屬性值賦值給這個實例。

ServletInvocableHandlerMethod實例處理請求使用的是invokeAndHandle方法。在invokeAndHandle方法中,會先調用父類的invokeForRequest方法。再對Response的狀態進行設置,最後使用HandlerMethodReturnValueHandler來處理返回值。

在invokeForRequest方法中,會先使用getMethodArgumentValues這個方法來獲取方法調用的所有參數。再調用doInvoke(args)方法。doInvoke(args)方法是實際執行請求處理的方法,它是HandlerMethod系列的最核心的方法。在doInvoke(args)方法中,通過getBridgedMethod()獲取Method的橋方法。再利用java的反射技術,調用BridgedMethod的invoke(getBean(), args)方法,將具體的handler執行。

 

WebDataBinderFactory

在RequestMappingHandlerAdapter類中,通過getDataBinderFactory方法,創建了一個WebDataBinderFactory類型的實例。

   

 private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {

        Class<?> handlerType = handlerMethod.getBeanType();

        Set<Method> methods = this.initBinderCache.get(handlerType);

        if (methods == null) {

           methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);

           this.initBinderCache.put(handlerType, methods);

        }

        List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>();

        // Global methods first

        this.initBinderAdviceCache.forEach((controllerAdviceBean, methodSet) -> {

           if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {

               Object bean = controllerAdviceBean.resolveBean();

               for (Method method : methodSet) {

                   initBinderMethods.add(createInitBinderMethod(bean, method));

               }

           }

        });

        for (Method method : methods) {

           Object bean = handlerMethod.getBean();

           initBinderMethods.add(createInitBinderMethod(bean, method));

        }

        return createDataBinderFactory(initBinderMethods);

    }

在getDataBinderFactory方法中,會先通過handlerMethod所屬bean類型,查找帶有@InitBinder註解的方法。

這裡使用了initBinderCache快取,一般是先在快取中查找,在找不到的情況下,再通過MethodIntrospector.selectMethods方法去查找。最後把查找到的methods放入到快取中。
    在initBinderAdviceCache快取中,快取的是全局的@InitBinder註解的方法。所謂全局的方法就是@ControllerAdvice註解類裡面的@InitBinder註解的方法。

程式碼中會先將全局的method構建成InvocableHandlerMethod對象放入initBinderMethods的集合中。再將handlerMethod所屬bean類型的method放入集合。最後通過initBinderMethods集合,來創建一個ServletRequestDataBinderFactory對象。

WebDataBinderFactory是一個工廠類,專門用來創建DataBinder類型的對象。DataBinder類型的對象用於參數綁定,主要功能就是實現參數跟String之間的類型轉換,ArgumentResolver在進行參數解析的過程中會用到WebDataBinder,另外ModelFactory在更新Model時也會用到它。

ModelFactory

在RequestMappingHandlerAdapter類中,通過getModelFactory方法,創建了一個ModelFactory類型的實例。

    

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {

        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);

        Class<?> handlerType = handlerMethod.getBeanType();

        Set<Method> methods = this.modelAttributeCache.get(handlerType);

        if (methods == null) {

           methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);

           this.modelAttributeCache.put(handlerType, methods);

        }

        List<InvocableHandlerMethod> attrMethods = new ArrayList<>();

        // Global methods first

       this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {

           if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {

               Object bean = controllerAdviceBean.resolveBean();

               for (Method method : methodSet) {

                   attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));

               }

           }

        });

        for (Method method : methods) {

           Object bean = handlerMethod.getBean();

           attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));

        }

        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);

    }

 

ModelFactory類型實例的創建和WebDataBinderFactory的創建過程十分類似。都是先在handlerMethod所屬bean類型查找@ModelAttribute註解的局部方法。再從modelAttributeAdviceCache快取中,查找全局的@ModelAttribute註解的方法。

將全局和局部的@ModelAttribute註解方法,組合起來形成一個HandlerMothed的列表。全局的@ModelAttribute註解方法會放在這個列表的前面。

ModelFactory與WebDataBinderFactory的創建過程不同的是,多了一個SessionAttributesHandler實例的獲取。最後會把HandlerMothed的列表,SessionAttributesHandler的實例和binderFactory作為參數,來構建ModelFactory的實例。

ModelFactory類是專門用來維護Model的。他主要做了兩件事件。

  1. ModelFactory類的initModel方法初始化Model

initModel方法主要是在處理器Handler執行前,將相應的數據設置到Model中。

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)

           throws Exception {

 

        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);

        container.mergeAttributes(sessionAttributes);

        invokeModelAttributeMethods(request, container);

 

        for (String name : findSessionAttributeArguments(handlerMethod)) {

           if (!container.containsAttribute(name)) {

               Object value = this.sessionAttributesHandler.retrieveAttribute(request, name);

               if (value == null) {

                   throw new HttpSessionRequiredException("Expected session attribute '" + name + "'", name);

               }

               container.addAttribute(name, value);

           }

        }

    }

從initModel方法的程式碼中可以看出,initModel方法一共做了三件事件。

  • 從SessionAttributesHandler對象中取出sessionAttributes,合併到ModelAndViewContainer的對象中。
  • 調用invokeModelAttributeMethods方法,該方法主要是依次執行所有@ModelAttribute註解的方法,並將最終的結果放到Model中。
  • 找到即是@ModelAttribute註解又是@SessionAttribute註解的參數名稱,再遍歷所有的參數,判斷這個參數是否已經在Model中,如果沒有,則把參數名和參數值存到Model中。
  1. ModelFactory類的updateModel方法,主要是將參數更新到SessionAttributes。updateModel方法主要做了兩件事情。
  • 對SessionAttributes中的進行設置。
  • 給Model中需要的參數設置BindingResult,以備視圖使用。

ServletInvocableHandlerMethod

類圖

 

 

從類圖中可以看出,ServletInvocableHandlerMethod類繼承了InvocableHandlerMethod類,而InvocableHandlerMethod類又繼承了HandlerMethod這個父類。

HandlerMethod

HandlerMethod類主要是封裝了處理程式方法的資訊,並提供了對方法參數、方法返回值、方法注釋等資訊的訪問。

HandlerMethod類中的屬性

名稱

類型

描述

bean

Object 

Web控制器方法所在的Web控制器 bean。可以是字元串,代表 bean 的名稱; 也可以是 bean 實例對象本身。

beanType

Class

Web控制器方法所在的Web控制器bean 的類型, 如果該bean被代理,這裡記錄的是被代理的用戶類資訊

method

Method

Web控制器方法

bridgedMethod

Method 

被橋接的Web控制器方法

parameters

MethodParameter[]

Web控制器方法的參數資訊: 所在類所在方法,參數,索引,參數類型

responseStatus

HttpStatus 

註解@ResponseStatus的code屬性

responseStatusReason

String 

註解@ResponseStatus的reason屬性

 

InvocableHandlerMethod

InvocableHandlerMethod類繼承了HandlerMethod類,它在父類的基礎上增加了三個屬性。

  1. dataBinderFactory:WebDataBinderFactory類型,主要是用於@InitBinder注釋的參數。
  2. argumentResolvers:HandlerMethodArgumentResolverComposite類型,用於參數解析器。
  3. parameterNameDiscoverer:ParameterNameDiscoverer類型,用於獲取參數名。

InvocableHandlerMethod類提供了一個重要的方法invokeForRequest,該方法主要是使用反射方式對Method進行調用。

其實@InitBinder註解和@ModelAttribute註解的方法,都是封裝成了InvocableHandlerMethod類型的實例。調用InvocableHandlerMethod類型實例的invokeForRequest方法,就是對這兩個註解下的方法進行了調用。

   

 @Nullable

    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,

           Object... providedArgs) throws Exception {

 

        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);

        if (logger.isTraceEnabled()) {

           logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +

                   "' with arguments " + Arrays.toString(args));

        }

        Object returnValue = doInvoke(args);

        if (logger.isTraceEnabled()) {

           logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +

                   "] returned [" + returnValue + "]");

        }

        return returnValue;

    }

invokeForRequest方法中主要做了兩件事件。1.是通過getMethodArgumentValues方法,準備好了調用方法所需要的參數。2. 在doInvoke方法中,通過橋接方法,使用反射方式對方法進行了調用。

ServletInvocableHandlerMethod

ServletInvocableHandlerMethod類又繼承了InvocableHandlerMethod類。它在提供了invokeAndHandle方法。

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,

           Object... providedArgs) throws Exception {

 

       Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        setResponseStatus(webRequest);

 

        if (returnValue == null) {

           if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {

               disableContentCachingIfNecessary(webRequest);

               mavContainer.setRequestHandled(true);

               return;

           }

        }

        else if (StringUtils.hasText(getResponseStatusReason())) {

           mavContainer.setRequestHandled(true);

           return;

        }

 

        mavContainer.setRequestHandled(false);

        Assert.state(this.returnValueHandlers != null, "No return value handlers");

        try {

           this.returnValueHandlers.handleReturnValue(

                   returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

        }

        catch (Exception ex) {

           if (logger.isTraceEnabled()) {

               logger.trace(formatErrorForReturnValue(returnValue), ex);

           }

           throw ex;

        }

    }

在程式碼中可以看到,ServletInvocableHandlerMethod類的invokeAndHandle方法中,先調用了父類的invokeForRequest方法。在此基礎上,1.增加了對ResponseStatus狀態的設置。2.對返回值進行了處理。

ModelAndView

ModelAndView類中包含了Model和View兩個重要的屬性。ModelAndView主要用於後台與前端頁面交互。它可以保存數據,並重定向或轉發到指定頁面,然後使用數據來渲染頁面。

Model是一個ModelMap類型,而ModelMap繼承了LinkedHashMap的這個子類。Model 對象負責在控制器(Controller)和視圖(View)之間傳遞數據。Model屬性中的數據會複製到 Servlet Response的屬性中。Model相當於一個JOPO的對象。

View是Spring MVC中的視圖。視圖的作用是渲染數據,將模型model中的數據展示給用戶。視圖可以用於重定向或轉發到指定頁面。

HttpRequestHandlerAdapter

HttpRequestHandlerAdapter是專門的http請求處理器的適配器。這裡的http請求處理器Handler,是一個實現了org.springframework.web. HttpRequestHandler介面的實例。

我們自定義的http請求處理器,需要實現HttpRequestHandler介面的handleRequest方法。在這個方法實現自己業務邏輯。

handleRequest方法的中提供了HttpServletRequest和HttpServletResponse的兩個參數,來供我們使用。

SimpleControllerHandlerAdapter

SimpleControllerHandlerAdapter是控制器Controller處理器的適配器。這裡的控制器處理器的Handler,是一個實現了org.springframework.web.servlet.mvc.Controller介面的實例。

Spring MVC中提供了org.springframework.web.servlet.mvc.AbstractController的這個抽象類,AbstractController抽象類實現了Controller介面。

在AbstractController中實現Controller介面的handleRequest方法,並提供了handleRequestInternal的這個模板方法留給子類自行擴展。

我們一般會直接繼承AbstractController抽象類,並重寫handleRequestInternal方法。在handleRequestInternal方法中,我們需要構造一個ModelAndView對象返回即可。

Tags: