上篇講的是,HandlerMapping 初始化過程當中,是如何將 Handler 與 請求的 url 創建起映射的,咱們能夠假想一下,「 http://localhost/test 」 的請求過來了,經過映射關係咱們找到了 Handler,但這個 Handler 具體是什麼類型呢,是基於 @RequestMapping 註解的?仍是實現了接口 org.springframework.web.servlet.mvc.Controller 的?不一樣的實現,處理方式也不同java
這篇講的 HandlerAdapter 就是對 Handler 的一個適配,來看源碼。web
首先來看下接口定義。spring
public interface HandlerAdapter { // 支持適配的類型 boolean supports(Object handler); // 調用具體的處理邏輯 ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception; // 獲取 Header:Last-Modified,用於頁面緩存 long getLastModified(HttpServletRequest request, Object handler); }
下面會重點分析下 support 和 handle 方法。前者是邏輯比較簡單,將 Handler 實例傳入,具體實現類來判斷是否支持適配。然後者涉及到的處理就相對麻煩一些。json
來此以前,依然先回顧下 HandlerAdapter 的初始化過程。數組
public class DispatcherServlet extends FrameworkServlet { // 存放加載的 HandlerAdapter實現 private List<HandlerAdapter> handlerAdapters; private boolean detectAllHandlerAdapters = true; // 銜接第一節 mvc初始化 @Override protected void onRefresh(ApplicationContext context) { initStrategies(context); } protected void initStrategies(ApplicationContext context) { ...... // 關注該方法:HandlerAdapter的初始化 initHandlerAdapters(context); ...... } private void initHandlerAdapters(ApplicationContext context) { this.handlerAdapters = null; // 默認探測容器內全部的 HandlerAdapter類型 if (this.detectAllHandlerAdapters) { // beansOfTypeIncludingAncestors:經過 getBeansOfType獲取子容器和父容器內的 HandlerAdapter // getBeansOfType會首先調用 getBeanNamesForType獲取指定類型的全部 beanName // 而後遍歷這些 beanName,使用 getBean建立實例 Map<String, HandlerAdapter> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerAdapter.class, true, false); if (!matchingBeans.isEmpty()) { this.handlerAdapters = new ArrayList<HandlerAdapter>(matchingBeans.values()); AnnotationAwareOrderComparator.sort(this.handlerAdapters); } } else { try { // 找 beanName爲 handlerAdapter的 HandlerAdapter HandlerAdapter ha = context.getBean(HANDLER_ADAPTER_BEAN_NAME, HandlerAdapter.class); this.handlerAdapters = Collections.singletonList(ha); } catch (NoSuchBeanDefinitionException ex) { } } if (this.handlerAdapters == null) { // 默認策略:見 DispatcherServlet.properties this.handlerAdapters = getDefaultStrategies(context, HandlerAdapter.class); if (logger.isDebugEnabled()) { .....// 省略日誌 } } } }
跟「 HandlerMapping 初始化 」相同的處理方式,這裏不作贅述。那麼接下來,就看看 HandlerAdapter 到底有多少種實現。緩存
public class HttpRequestHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof HttpRequestHandler); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((HttpRequestHandler) handler).handleRequest(request, response); return null; } }
支持 HttpRequestHandler 類型的處理器,handle 方法實現也只是簡單的向下轉型,而後調用接口方法。從源碼也能猜想出,若是要使用這樣的處理器,只須要實現 HttpRequestHandler.handleRequest 便可。服務器
public class SimpleControllerHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Controller); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return ((Controller) handler).handleRequest(request, response); } }
對 Controller 類型的支持。session
public class SimpleServletHandlerAdapter implements HandlerAdapter { @Override public boolean supports(Object handler) { return (handler instanceof Servlet); } @Override public ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { ((Servlet) handler).service(request, response); return null; } }
對 Servlet 類型的支持。併發
@Deprecated public class AnnotationMethodHandlerAdapter extends WebContentGenerator implements HandlerAdapter, Ordered, BeanFactoryAware { @Override public boolean supports(Object handler) { // Handler 下的方法有被 @RequestMapping標識的就返回 true return getMethodResolver(handler).hasHandlerMethods(); } }
該類就是 Spring 3.2 版本之前,對於註解形式 Handler 的適配器。它的 handle 方法就不像前面說起的,只是簡單作了轉型而調用方法就好了。由於還要涉及到不少其餘 mvc 註解的解析工做,以及響應信息的處理。mvc
但它帶來的優點是,沒有像接口那樣,限制了一個 Handler 只能實現對應的一個方法,入參以及返回類型都被接口定義,而不能自定義。由此看來,咱們所見靈活性實際上是基於框架層面複雜邏輯的封裝處理。
因爲這個類已被聲明廢棄,接替它的是 RequestMappingHandlerAdapter ,因此咱們直接來看最新的實現。
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered { @Override public final boolean supports(Object handler) { // supportsInternal總返回 true,因此只要知足 Handler爲 HandlerMethod便可 return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler)); } @Override public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return handleInternal(request, response, (HandlerMethod) handler); } protected abstract ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception; }
從其抽象父類實現來看,支持的類型爲 HandlerMethod,是否是很眼熟,就是咱們上篇 「 HandlerMapping 初始化 」時建立的。具體的 handle 調用了抽象方法 handleInternal,實現委託給了子類。
// RequestMappingHandlerAdapter實現 @Override protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ModelAndView mav; // 若是有必要:對請求 method、以及 session進行校驗 checkRequest(request); // 會話級別的同步:默認爲 false if (this.synchronizeOnSession) { HttpSession session = request.getSession(false); if (session != null) { Object mutex = WebUtils.getSessionMutex(session); synchronized (mutex) { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } } else { mav = invokeHandlerMethod(request, response, handlerMethod); } /** * 若是響應頭裏不包含 「Cache-Control」處理 * - private/must-revalidate:僅在首次訪問時訪問服務器 * - no-cache:每次訪問都會訪問服務器 * - max-age:過時以前不會訪問服務器 */ if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { /** * 該類被 @SessionAttributes標識並指定了 names 或 types * 根據 cacheSecondsForSessionAttributeHandlers大小不一樣來給頭賦值 * HTTP 1.0使用的是 Expires頭,HTTP 1.1使用的是 Cache-Control */ applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { // 未被 @SessionAttributes標識類,請求緩存處理 prepareResponse(response); } } return mav; }
這個方法主要調用了 invokeHandlerMethod 來調用方法來獲取結果,而後就是緩存相關 Header 信息的設置。
須要注意的是,HTTP 1.0 版本,是經過 「expired」 來記錄緩存時間的,它記錄的是一個時間戳,在時間戳到期以前,都不會去請求服務端,但時間戳爲服務端返回的,並非客戶端生成的,所以存在偏差。
HTTP 1.1 版本,經過 「Cache-Control」 來記錄過時時長的,使用 max-age = 秒數,來表示資源在客戶端緩存的時長。使用 no-cache 表示不緩存。
// ServletInvocableHandlerMethod實現 protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception { ServletWebRequest webRequest = new ServletWebRequest(request, response); try { // 根據類中被 @InitBinder標識的方法,建立一個 WebDataBinder工廠 WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod); // 根據類中被 @ModelAttribute標識的方法,建立一個 Model工廠 ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory); // 這裏傳入的 handlerMethod就是 HandlerMapping初始化時建立的 // 至於如何找到請求 url對應的 HandlerMethod,以後的章節會講解 // 繼承體系:ServletInvocableHandlerMethod——>InvocableHandlerMethod——>HandlerMethod ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod); // 賦值操做,用於請求的解析和相應的處理 invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 用於記錄請求解析和相應 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); // FlashMap:重定向保存上次請求的參數 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); // 調用被 @ModelAttribute標識的方法,填充 mavContainer // 將 @SessionAttributes指定的 Session中的屬性,填充 mavContainer // 將 @ModelAttribute標識的參數,若是 @SessionAttributes中存在,一樣填充 mavContainer modelFactory.initModel(webRequest, mavContainer, invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); // 建立一個異步請求 AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response); asyncWebRequest.setTimeout(this.asyncRequestTimeout); // 異步管理器:任務線程池等屬性的賦值 WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request); asyncManager.setTaskExecutor(this.taskExecutor); asyncManager.setAsyncWebRequest(asyncWebRequest); asyncManager.registerCallableInterceptors(this.callableInterceptors); asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors); // 經過調用 startCallableProcessing啓動併發請求處理 // 直到請求處理完前,hasConcurrentResult返回的都是 false // 初始值爲:RESULT_NONE,因此判斷 (this.concurrentResult != RESULT_NONE)爲 false if (asyncManager.hasConcurrentResult()) { Object result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0]; // 已經獲取異步結果,因此要清除維護的結果 asyncManager.clearConcurrentResult(); if (logger.isDebugEnabled()) { logger.debug("Found concurrent result value [" + result + "]"); } // 建立一個嵌套的 ServletInvocableHandlerMethod子類,返回給定的值 // 而不是實際調用控制器方法。 在處理異步返回值時,這很是有用 invocableMethod = invocableMethod.wrapConcurrentResult(result); } // 調用對應方法,並使用 HandlerMethodReturnValueHandler處理返回值 invocableMethod.invokeAndHandle(webRequest, mavContainer); if (asyncManager.isConcurrentHandlingStarted()) { return null; } // mavContainer存儲了響應的 viewName、Model數據、Http狀態碼等信息 // 根據這些信息建立 ModelAndView返回 // 若是是重定向,還會轉存 FlashMap數據 return getModelAndView(mavContainer, modelFactory, webRequest); } finally { webRequest.requestCompleted(); } }
方法的最開始,有對 @InitBinder、@ModelAttribute 標識的方法進行收集並建立一個工廠,由於這些方法將做用於 Handler 中全部的方法。
以後就用上篇 HandlerMapping初始化 時建立的 HandlerMethod 來建立一個 ServletInvocableHandlerMethod,由於下面的賦值方法是該子類特有的。
ModelAndViewContainer,這個類就像一個承載請求和相應的容器,請求前將放置一些參數(重定向前參數、@ModelAttribute註解方法返回等等),這些會在調用 invokeAndHandle 時傳入,以便處理時須要。
接下來來看 invokeAndHandle 的實現。
// ServletInvocableHandlerMethod實現 public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 調用方法並獲得返回結果 Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs); // 根據 @ResponseStatus註解來設置相應狀態 setResponseStatus(webRequest); if (returnValue == null) { // 沒有返回值 if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) { mavContainer.setRequestHandled(true); return; } } // 若是有返回值,且被 @ResponseStatus指定了 reason else if (StringUtils.hasText(getResponseStatusReason())) { mavContainer.setRequestHandled(true); return; } // 設置本次請求是否已被徹底處理,一般都是 false // 例如:@ResponseBody標識的返回須要額外處理 mavContainer.setRequestHandled(false); try { // 挑選一個 HandlerMethodReturnValueHandler處理返回值(策略) // 例如:RequestResponseBodyMethodProcessor.handleReturnValue(源碼在下方) this.returnValueHandlers.handleReturnValue( returnValue, getReturnValueType(returnValue), mavContainer, webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(getReturnValueHandlingErrorMessage("Error handling return value", returnValue), ex); } throw ex; } }
這裏的 invokeForRequest 方法下邊咱們會講,其實就是經過一次反射調用,來獲取到方法的返回值(例如,UserController.select 返回了查詢到的 User 對象)。
固然,也有 void 無返回值方法,那麼判斷請求是否處於緩存期內、或指定了 @ResponseStatus.code 屬性,就直接返回了。
若是有返回值,但指定了 @ResponseStatus.reason 屬性,那也直接返回了。
Ps:關於 @ResponseStatus,它通常和 @ExceptionHandler 結合使用,用於異常的統一處理,以及響應碼、提示信息的統一返回。
不然就要經過 HandlerMethodReturnValueHandler 來對響應結果進行處理。
// InvocableHandlerMethod實現 public Object invokeForRequest(NativeWebRequest request, 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 方法邏輯很簡單,第一步調用方法 getMethodArgumentValues 對入參進行解析,第二步直接經過反射調用相應方法拿到響應結果。因此難點在參數的解析上。
// InvocableHandlerMethod實現 private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception { // 獲取調用方法的參數 MethodParameter[] parameters = getMethodParameters(); Object[] args = new Object[parameters.length]; for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; // ParameterNameDiscovery:用於獲取方法入參名稱 // - LocalVariableTableParameterNameDiscoverer:根據 class文件中的 LocalVariableTable解析 // - AspectJAnnotationParameterNameDiscoverer:根據 aspectj註解的 argNames // - StandardReflectionParameterNameDiscoverer:基於反射獲取 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); // 根據入參提供的 providedArgs,找出符合類型的 // 根據上面代碼追溯,入參 providedArgs爲 null,所以直接返回 null args[i] = resolveProvidedArgument(parameter, providedArgs); if (args[i] != null) { continue; } // argumentResolvers類型爲:HandlerMethodArgumentResolverComposite // 組合模式將全部 HandlerMethodArgumentResolver實現包裝 // 例如:RequestResponseBodyMethodProcessor,支持 @RequestBody、@ResponseBody if (this.argumentResolvers.supportsParameter(parameter)) { try { // 調用實現方法對參數進行解析 args[i] = this.argumentResolvers.resolveArgument( parameter, mavContainer, request, this.dataBinderFactory); continue; } catch (Exception ex) { if (logger.isDebugEnabled()) { ....// 省略日誌 } throw ex; } } if (args[i] == null) { ....// 未解析出的參數會拋異常 IllegalStateException } } return args; }
Spring 會將方法的入參相關信息封裝成 MethodParameter 對象,具體的解析邏輯交給了不一樣的 HandlerMethodArgumentResolver 實現類,好比咱們常使用的 Json 請求響應,咱們來看 RequestResponseBodyMethodProcessor 是如何解析的。
public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor { @Override public boolean supportsParameter(MethodParameter parameter) { // 支持解析參數的條件:被 @RequestBody標識 return parameter.hasParameterAnnotation(RequestBody.class); } @Override public boolean supportsReturnType(MethodParameter returnType) { // 支持處理返回的條件:返回方法或類型被 @ResponseBody標識 return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) || returnType.hasMethodAnnotation(ResponseBody.class)); } @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // 若是是參數類型 Optional,返回一個嵌套級別的 MethodParameter parameter = parameter.nestedIfOptional(); // 調用 HttpMessageConverter解析請求參數 // 根據 contentType選擇不一樣的轉換器處理,好比 application/json,可使用 MappingJackson2HttpMessageConverter Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType()); // 獲取參數(包括數組和集合)常規名稱:見 ClassUtils.getShortNameAsProperty String name = Conventions.getVariableNameForParameter(parameter); WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name); if (arg != null) { // 對 @Validated或 @Valid的校驗 // 實現:調用 validate.validate validateIfApplicable(binder, parameter); // 若是有校驗未經過的,拋出 MethodArgumentNotValidException if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) { throw new MethodArgumentNotValidException(parameter, binder.getBindingResult()); } } mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult()); // 對於入參類型爲 Optional的,要封裝一下 return adaptArgumentIfNecessary(arg, parameter); } @Override public void handleReturnValue(Object returnValue, MethodParameter returnType, ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { mavContainer.setRequestHandled(true); ServletServerHttpRequest inputMessage = createInputMessage(webRequest); ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // 調用 HttpMessageConverter處理響應結果 writeWithMessageConverters(returnValue, returnType, inputMessage, outputMessage); } }
支持的入參和返回,從方法能夠很容易看出,就是支持 body 體數據解析的 @RequestBody,和支持 body 體數據響應的 @ResponseBody。但具體支持的是 Json 格式仍是 Xml 格式,是由 HttpMessageConverter 來支持的。
這裏還有一點就是對 @Validate 和 @Valid 標識入參的校驗,若是校驗不經過,會以 MethodArgumentNotValidException 異常拋出,具體的提示信息(例如 @NotNull( message="不能爲空" ))也會封裝在內。
到這裏,一次請求的大體流程也基本解讀完畢。涉及到一些具體的像「Json數據解析過程」,「響應數據的處理」本篇沒有展開講解。
這一節主要的內容就是 Handler 的適配以及調用邏輯,這些邏輯將做爲後續章節介紹一次具體請求流程的鋪墊。