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方法中,主要是做了兩個檢測。
-
- 檢測請求的方法是否被支援,在AbstractHandlerMethodAdapter類中由於restrictDefaultSupportedMethods的值設置為false,所以對請求方法的檢測不會執行。
- 檢測請求的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的。他主要做了兩件事件。
- 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中。
- 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類,它在父類的基礎上增加了三個屬性。
- dataBinderFactory:WebDataBinderFactory類型,主要是用於@InitBinder注釋的參數。
- argumentResolvers:HandlerMethodArgumentResolverComposite類型,用於參數解析器。
- 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對象返回即可。