SpringMVC源碼解析(三)——HandlerAdapter

前言

    上篇講的是,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 到底有多少種實現。緩存

 

HttpRequestHandlerAdapter

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 便可。服務器

 

SimpleControllerHandlerAdapter

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

 

SimpleServletHandlerAdapter

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 類型的支持。併發

 

AnnotationMethodHandlerAdapter

@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 ,因此咱們直接來看最新的實現。 

 

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 的適配以及調用邏輯,這些邏輯將做爲後續章節介紹一次具體請求流程的鋪墊。

相關文章
相關標籤/搜索