Spring5MVC——請求參數綁定到方法形參,執行方法處理請求

SpringMVC的核心流程

  • 建立請求和Controller方法的映射集合的流程。

  • 根據請求查找對應的Controller方法的流程。

  • 請求參數綁定到方法形參,執行方法處理請求,返回結果進行視圖渲染的流程。

RequestMappingHandlerMapping繼承類圖

HandlerMapping

HandlerMapping接口作用是將請求映射到處理程序,以及預處理和處理后的攔截器列表,映射是基于一些標準的,其中的細節因不同的實現而不相同。這是官方文檔上一段描述,該接口只有一個方法getHandler(request),返回一個HandlerExecutionChain對象,接口本身很簡單。

public interface HandlerMapping {


    String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";


    String LOOKUP_PATH = HandlerMapping.class.getName() + ".lookupPath";


    String PATH_WITHIN_HANDLER_MAPPING_ATTRIBUTE = HandlerMapping.class.getName() + ".pathWithinHandlerMapping";


    String BEST_MATCHING_PATTERN_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingPattern";


    String INTROSPECT_TYPE_LEVEL_MAPPING = HandlerMapping.class.getName() + ".introspectTypeLevelMapping";


    String URI_TEMPLATE_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".uriTemplateVariables";


    String MATRIX_VARIABLES_ATTRIBUTE = HandlerMapping.class.getName() + ".matrixVariables";


    String PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE = HandlerMapping.class.getName() + ".producibleMediaTypes";

    // 返回請求的一個處理程序handler和攔截器interceptors
    @Nullable
    HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception;

}

HandlerExecutionChain

HandlerExecutionChain 是Handler執行鏈,包含handler Object、interceptor。所以HandlerExecutionChain 提供了getHandler、getInterceptors方法,配置文件中配置的interceptor都會加入到HandlerExecutionChain。

public class HandlerExecutionChain {

    //handler實例
    private final Object handler;

    //一組攔截器實例
    @Nullable
    private HandlerInterceptor[] interceptors;

    //HandlerInterceptor的鏈表
    @Nullable
    private List<HandlerInterceptor> interceptorList;

    ......
}

AbstractHandlerMapping

從類圖中可知,HandlerMapping主要由AbstractHandlerMapping實現。

public abstract class AbstractHandlerMapping extends WebApplicationObjectSupport
        implements HandlerMapping, Ordered, BeanNameAware {

    @Override
    @Nullable
    public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
        //獲取handler的具體邏輯,留給子類實現
        Object handler = getHandlerInternal(request);
        // 如果獲取到的handler為null,則采用默認的handler,即屬性defaultHandler
        if (handler == null) {
            handler = getDefaultHandler();
        }
        if (handler == null) {
            //如果連DefaultHandler也為空,則直接返回空
            return null;
        }
        // Bean name or resolved handler?
        //如果handler是beanName,從容器里獲取對應的bean
        if (handler instanceof String) {
            String handlerName = (String) handler;
            handler = obtainApplicationContext().getBean(handlerName);
        }

        //根據handler和request獲取處理器鏈HandlerExecutionChain
        HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);

        if (logger.isTraceEnabled()) {
            logger.trace("Mapped to " + handler);
        }
        else if (logger.isDebugEnabled() && !request.getDispatcherType().equals(DispatcherType.ASYNC)) {
            logger.debug("Mapped to " + executionChain.getHandler());
        }

        if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
            CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
            CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
            config = (config != null ? config.combine(handlerConfig) : handlerConfig);
            executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
        }

        return executionChain;
    }
}
  • 用戶發起請求,會進入到AbstractHandlerMapping類中的getHandler方法中。

  • 沿著方法的調用棧發現,請求先經過FrameworkServlet的service方法去處理。

  • 在由HttpServlet的service方法轉發到FrameworkServlet的doPost方法中。

  • doPost方法中會調用FrameworkServlet的processRequest方法。

  • processRequest方法會調用DispatcherServlet的doService方法。

  • doService方法會調用DispatcherServlet的doDispatch方法。

  • doDispatch方法會調用到DispatcherServlet的getHandler方法。

  • DispatcherServlet的getHandler方法會調用到AbstractHandlerMapping中的getHandler方法。

DispatcherServlet#doDispatch

public class DispatcherServlet extends FrameworkServlet {

    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.
                // 根據當前request獲取HandlerExecutionChain,
                // HandlerExecutionChain中包含了請求url,以及對應的controller以及controller中的方法
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }

                // Determine handler adapter for the current request.
                // 通過HandlerExecutionChain獲取對應的適配器,adapter負責完成參數解析
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());

                // Process last-modified header, if supported by the handler.
                //處理GET、HEAD請求的Last-modified
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 遍歷所有定義的 interceptor,執行 preHandle 方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }

                // Actually invoke the handler.
                // 調用目標Controller中的方法
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                // 處理成默認視圖名,也就是添加前綴和后綴等
                applyDefaultViewName(processedRequest, mv);
                // 攔截器postHandle方法進行處理
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            }
            catch (Exception ex) {
                dispatchException = ex;
            }
            catch (Throwable err) {
                // As of 4.3, we're processing Errors thrown from handler methods as well,
                // making them available for @ExceptionHandler methods and other scenarios.
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 處理最后的結果,渲染之類的邏輯都在這里
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        }
        catch (Exception ex) {
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        }
        catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        }
        finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                // Instead of postHandle and afterCompletion
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            }
            else {
                // Clean up any resources used by a multipart request.
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
}

DispatcherServlet#getHandlerAdapter

  • 通過HandlerExecutionChain獲取對應的適配器,adapter負責完成參數解析。
public class DispatcherServlet extends FrameworkServlet {

    protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
        if (this.handlerAdapters != null) {
            // 遍歷所有的 HandlerAdapter,找到和當前 Handler 匹配的HandlerAdapter實例
            for (HandlerAdapter adapter : this.handlerAdapters) {
                if (adapter.supports(handler)) {
                    return adapter;
                }
            }
        }
        throw new ServletException("No adapter for handler [" + handler +
                "]: The DispatcherServlet configuration needs to include a HandlerAdapter that supports this handler");
    }
}

該方法會去遍歷事先注冊好的HandlerAdapter實現類,去調用實現類的supports方法判斷是否支持當前的handler實例的處理,一旦找到匹配的HandlerAdapter實現類,直接返回HandlerAdapter實例。

通過方法調用棧發現,HandlerAdapter初始化的時候,,一共向List<HandlerAdapter> handlerAdapters集合中注冊了4個HandlerAdapter的實現類:

  • requestMappingHandlerAdapter -> RequestMappingHandlerAdapter

  • handlerFunctionAdapter -> HandlerFunctionAdapter

  • httpRequestHandlerAdapter -> HttpRequestHandlerAdapter

  • simpleControllerHandlerAdapter -> SimpleControllerHandlerAdapter

這里主要關注RequestMappingHandlerAdapter

supports

public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

    @Override
    public final boolean supports(Object handler) {
        return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
    }
}

supports方法入參為RequestMappingHandlerAdapter 時,滿足條件,直接返回該實例


接著將實現拉回doDispatch方法中

HandlerExecutionChain#applyPreHandle

  • 攔截器執行攔截,對客戶端請求requset進行攔截,一旦某個HandlerInterceptor的preHandle方法返回false,則直接return,不在對請求在進行處理。
// 遍歷所有定義的 interceptor,執行 preHandle 方法
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
    return;
}

public class HandlerExecutionChain {

    boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HandlerInterceptor[] interceptors = getInterceptors();
        if (!ObjectUtils.isEmpty(interceptors)) {
            for (int i = 0; i < interceptors.length; i++) {
                HandlerInterceptor interceptor = interceptors[i];
                if (!interceptor.preHandle(request, response, this.handler)) {
                    triggerAfterCompletion(request, response, null);
                    return false;
                }
                this.interceptorIndex = i;
            }
        }
        return true;
    }
}

如果HandlerExecutionChain#applyPreHandle方法返回true,則執行HandlerAdapter的handle方法執行請求。

HandlerAdapter#handle

  • 調用目標Controller中的方法
public abstract class AbstractHandlerMethodAdapter extends WebContentGenerator implements HandlerAdapter, Ordered {

    @Override
    @Nullable
    public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {

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

該方法的返回值是ModelAndView實例。

RequestMappingHandlerAdapter#handleInternal

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    @Override
    protected ModelAndView handleInternal(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ModelAndView mav;
        // 1.檢測當前請求,驗證請求方法合法性和session合法性
        checkRequest(request);

        // Execute invokeHandlerMethod in synchronized block if required.
        // 2.根據synchronizeOnSession值判斷,當前請求是否需串行化訪問。
        if (this.synchronizeOnSession) {
            HttpSession session = request.getSession(false);
            if (session != null) {
                // 獲取最佳互斥鎖,即同步當前回話對象;如未能獲取到互斥鎖,將返回HttpSession對象本身
                Object mutex = WebUtils.getSessionMutex(session);
                synchronized (mutex) {
                    mav = invokeHandlerMethod(request, response, handlerMethod);
                }
            }
            else {
                // No HttpSession available -> no mutex necessary
                // 即無最佳互斥鎖,也未能獲取到HttpSession,則當前會話無需串行化訪問
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // No synchronization on session demanded at all...
            //當前會話無需串行化訪問
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
        // 3.相應信息不包含Cache-Control
        if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
            if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
                applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
            }
            else {
                prepareResponse(response);
            }
        }

        return mav;
    }
}

invokeHandlerMethod

  • 具體的處理方法委托給了invokeHandlerMethod方法
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    @Nullable
    protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
            HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

        ServletWebRequest webRequest = new ServletWebRequest(request, response);
        try {
            //WebDataBinderFactory --> 工廠類,為目標對象創建一個WebDataBinder實例
            // 1.WebDataBinder繼承了DataBinder類,為web請求提供了參數綁定服務
            WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);

            // 獲取ModelFactory:
            // 2.ModelFactory可以協助控制器在調用方法之前初始化模型,并在調用之后更新模型
            ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);

            // 創建ServletInvocableHandlerMethod對象
            // 3.ServletInvocableHandlerMethod繼承并擴展了InvocableHandlerMethod
            ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);

            // 4.嘗試綁定參數、返回值解析器
            if (this.argumentResolvers != null) {
                //設置參數解析器
                invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
            }
            if (this.returnValueHandlers != null) {
                //設置spring mvc請求controller的method返回值處理器
                invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
            }
            invocableMethod.setDataBinderFactory(binderFactory);
            //設置參數名稱發現器
            invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);

            // 5.創建ModelAndViewContainer,并初始化Model對象
            ModelAndViewContainer mavContainer = new ModelAndViewContainer();
            mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
            modelFactory.initModel(webRequest, mavContainer, invocableMethod);
            mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

            // 6.異步請求相關
            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);

            if (asyncManager.hasConcurrentResult()) {
                Object result = asyncManager.getConcurrentResult();
                mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
                asyncManager.clearConcurrentResult();
                LogFormatUtils.traceDebug(logger, traceOn -> {
                    String formatted = LogFormatUtils.formatValue(result, !traceOn);
                    return "Resume with async result [" + formatted + "]";
                });
                invocableMethod = invocableMethod.wrapConcurrentResult(result);
            }

            // 7.調用Controller中的具體方法并處理返回值
            //核心邏輯,最終去執行Handler方法處理請求
            invocableMethod.invokeAndHandle(webRequest, mavContainer);
            if (asyncManager.isConcurrentHandlingStarted()) {
                return null;
            }

            // 8.返回ModelAndView對象
            return getModelAndView(mavContainer, modelFactory, webRequest);
        }
        finally {
            // 完成請求后續處理,并將當前請求置為未激活
            webRequest.requestCompleted();
        }
    }
}

invokeHandlerMethod方法還是很復雜的,下面我們對該方法進行詳細的分析。。。

getDataBinderFactory

  • WebDataBinderFactory --> 工廠類,為目標對象創建一個WebDataBinder實例。
  • WebDataBinder繼承了DataBinder類,為web請求提供了參數綁定服務。
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
        // 1.獲取handlerType,即目標類
        Class<?> handlerType = handlerMethod.getBeanType();
        // 2.優先嘗試從緩存中獲取對應的InitBinder方法
        Set<Method> methods = this.initBinderCache.get(handlerType);
        // 如未能從緩存中獲取,則根據handlerType對應的類,去類中查找所有標注了@InitBinder注解的方法,并將其緩存
        if (methods == null) {
            methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
            this.initBinderCache.put(handlerType, methods);
        }
        // 3.從標注了@ControllerAdvice類中尋找InitBinder方法,并優先為其創建InvocableHandlerMethod對象
        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));
                }
            }
        });
        // 4.為普通的InitBinder創建InvocableHandlerMethod對象
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            initBinderMethods.add(createInitBinderMethod(bean, method));
        }
        // 5.創建InitBinderDataBinderFactory對象
        return createDataBinderFactory(initBinderMethods);
    }
}

該方法的處理流程很簡單,但是這里涉及到兩個點,普通的@InitBinder注解和標注了@ControllerAdvice注解類中的@InitBinder注解。從代碼中可以看到,@ControllerAdvice注解中的InitBinder方法,是作為全局方法優先創建的。而普通的InitBinder方法只能應用于其類本身,無法作為全局對象應用到所有的Controller方法。

getModelFactory

  • 獲取ModelFactory:ModelFactory可以協助控制器在調用方法之前初始化模型,并在調用之后更新模型。
public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
        // 1.處理@SessionAttributes注解
        SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
        Class<?> handlerType = handlerMethod.getBeanType();
        // 2.處理@ModelAttribute注解
        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
        // 3.優先處理全局@ModelAttribute注解的方法,例如被@ControllerAdvice標注的類中存在被@ModelAttribute注解的方法,則優先處理
        this.modelAttributeAdviceCache.forEach((controllerAdviceBean, methodSet) -> {
            if (controllerAdviceBean.isApplicableToBeanType(handlerType)) {
                Object bean = controllerAdviceBean.resolveBean();
                for (Method method : methodSet) {
                    attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
                }
            }
        });
        // 4.循環所有標注了@ModelAttribute注解的方法,并創建InvocableHandlerMethod對象
        // InvocableHandlerMethod:負責具體的HandlerMethod的調用、參數解析等工作
        for (Method method : methods) {
            Object bean = handlerMethod.getBean();
            attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
        }
        // 5.返回ModelFactory對象
        // ModelFactory:協助在控制器方法調用之前初始化模型,并在調用之后更新它。
        return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
    }
}

該方法主要作用是處理@ModelAttribute和@SessionAttributes兩個注解。關于兩者的作用,不多贅述了!在最后創建了ModelFactory對象,該對象的作用可以協助在控制器方法調用之前初始化模型,并在調用之后更新它。

ModelFactory的initModel初始化

  • modelFactory.initModel(webRequest, mavContainer, invocableMethod)
public final class ModelFactory {

    public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
            throws Exception {
        // 1.解析并合并@SessionAttributes注解
        Map<String, ?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
        container.mergeAttributes(sessionAttributes);
        // 2.調用被@ModelAttribute注解的方法
        invokeModelAttributeMethods(request, container);
        // 3.查找標注了@ModelAttribute、@SessionAttributes的方法參數,確保其解析過程中不會發生異常
        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);
            }
        }
    }
}

注意這里會有一個Expected session attribute xxx的異常,如果類上標注了@SessionAttributes注解,且在方法中標注了@ModelAttribute注解,如果@ModelAttribute為空,則會拋出此異常。。。

ServletInvocableHandlerMethod#invokeAndHandle

  • 核心邏輯,最終去執行Handler方法處理請求
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

    public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //在這里通過反射調用controller中的method方法
        Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);

        // 2.設置返回狀態碼
        setResponseStatus(webRequest);

        // 3.當前請求無返回值或者返回值中包含錯誤,則將請求完成標識設置為true并返回
        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;
        }

        // 4.當前請求有返回值且無錯誤信息,則將請求完成標識設置為false,并繼續處理當前請求
        mavContainer.setRequestHandled(false);
        Assert.state(this.returnValueHandlers != null, "No return value handlers");
        try {
            // 選取合適的HandlerMethodReturnValueHandler,并處理返回值
            //在這里通過返回值處理器處理器進行二次處理,
            // 例如:如果加了方法加了@ReponseBody注解,就把結果序列化json格式再返回
            this.returnValueHandlers.handleReturnValue(
                    returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
        }
        catch (Exception ex) {
            if (logger.isTraceEnabled()) {
                logger.trace(formatErrorForReturnValue(returnValue), ex);
            }
            throw ex;
        }
    }
}

InvocableHandlerMethod#invokeForRequest

  • 通過反射調用controller中的method方法
public class InvocableHandlerMethod extends HandlerMethod {

    @Nullable
    public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {

        //將請求的參數值綁定到Controller方法里
        Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
        if (logger.isTraceEnabled()) {
            logger.trace("Arguments: " + Arrays.toString(args));
        }
        return doInvoke(args);
    }
}

InvocableHandlerMethod#getMethodArgumentValues

  • 將請求的參數值綁定到Controller方法里
public class InvocableHandlerMethod extends HandlerMethod {

    protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
            Object... providedArgs) throws Exception {
        //獲取Controller方法實例里面的參數列表
        MethodParameter[] parameters = getMethodParameters();
        if (ObjectUtils.isEmpty(parameters)) {
            return EMPTY_ARGS;
        }
        //創建一個參數數組,保存從request解析出的方法參數
        Object[] args = new Object[parameters.length];
        for (int i = 0; i < parameters.length; i++) {
            MethodParameter parameter = parameters[i];
            //給每一個Controller方法實例參數初始化一個參數名稱發現器
            parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
            args[i] = findProvidedArgument(parameter, providedArgs);
            if (args[i] != null) {
                continue;
            }
            if (!this.resolvers.supportsParameter(parameter)) {
                throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
            }
            try {
                //解析并綁定參數的核心邏輯
                args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
            }
            catch (Exception ex) {
                // Leave stack trace for later, exception may actually be resolved and handled...
                if (logger.isDebugEnabled()) {
                    String exMsg = ex.getMessage();
                    if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
                        logger.debug(formatArgumentError(parameter, exMsg));
                    }
                }
                throw ex;
            }
        }
        return args;
    }
}

resolveArgument

  • 解析并綁定參數的核心邏輯
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    @Override
    @Nullable
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        //首先獲取參數解析器,這里獲取的邏輯是首先從argumentResolverCache緩存中
        // 獲取該MethodParameter匹配的HandlerMethodArgumentResolver。
        // 如果為空,遍歷初始化定義的那26個解析器實例,
        // 查找匹配的HandlerMethodArgumentResolver,然后添加至argumentResolverCache緩存中
        HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
        if (resolver == null) {
            throw new IllegalArgumentException("Unsupported parameter type [" +
                    parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
        }
        // 解析參數,不同的參數解析器實例,有不同的解析方式
        return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
    }
}

HandlerMethodArgumentResolverComposite#getArgumentResolver

  • 獲取和參數相匹配的解析器
  • 首先獲取參數解析器,這里獲取的邏輯是首先從argumentResolverCache緩存中
  • 獲取該MethodParameter匹配的HandlerMethodArgumentResolver。
  • 如果為空,遍歷初始化定義的那26個解析器實例,
  • 查找匹配的HandlerMethodArgumentResolver,然后添加至argumentResolverCache緩存中。
public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

    @Nullable
    private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
        HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
        if (result == null) {
            for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
                if (resolver.supportsParameter(parameter)) {
                    result = resolver;
                    this.argumentResolverCache.put(parameter, result);
                    break;
                }
            }
        }
        return result;
    }
}

AbstractNamedValueMethodArgumentResolver#resolveArgument

  • 解析參數,不同的參數解析器實例,有不同的解析方式
public abstract class AbstractNamedValueMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    @Nullable
    public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        //從@RequestParam里獲取到對應參數名
        // 1.NamedValueInfo對象包含了name,required,defaultValue三個信息
        NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
        // 獲取MethodParameter對象,該對象封裝了方法參數的規范
        MethodParameter nestedParameter = parameter.nestedIfOptional();

        // 2.解析參數名,包括占位符和表達式等
        Object resolvedName = resolveStringValue(namedValueInfo.name);
        if (resolvedName == null) {
            throw new IllegalArgumentException(
                    "Specified name must not resolve to null: [" + namedValueInfo.name + "]");
        }
        //和請求里面的參數名進行對照,獲取請求參數對應的String類型的值
        // 3.將給定的參數類型和值名稱解析為參數值。
        Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
        if (arg == null) {
            // 如果默認值不為空,則
            if (namedValueInfo.defaultValue != null) {
                arg = resolveStringValue(namedValueInfo.defaultValue);
            }
            // 指定了required屬性且該參數不是為非不必須,則調動handleMissingValue方法處理缺失值,該方法一般會拋出異常
            else if (namedValueInfo.required && !nestedParameter.isOptional()) {
                handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
            }
            // 最后處理將該參數值處理為null即可
            arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
        }
        else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
            arg = resolveStringValue(namedValueInfo.defaultValue);
        }

        if (binderFactory != null) {
            // 4.創建WebDataBinder實例
            WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
            try {
                //參數類型轉換
                arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
            }
            catch (ConversionNotSupportedException ex) {
                throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
            catch (TypeMismatchException ex) {
                throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
                        namedValueInfo.name, parameter, ex.getCause());
            }
        }

        handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);

        return arg;
    }
}

RequestParamMethodArgumentResolver#resolveName

  • 和請求里面的參數名進行對照,獲取請求參數對應的String類型的值
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
        implements UriComponentsContributor {

    @Override
    @Nullable
    protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
        HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

        if (servletRequest != null) {
            Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
            if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
                return mpArg;
            }
        }

        Object arg = null;
        MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
        if (multipartRequest != null) {
            List<MultipartFile> files = multipartRequest.getFiles(name);
            if (!files.isEmpty()) {
                arg = (files.size() == 1 ? files.get(0) : files);
            }
        }
        if (arg == null) {
            //通過參數名嘗試從請求里獲取對應的值
            String[] paramValues = request.getParameterValues(name);
            if (paramValues != null) {
                arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
            }
        }
        return arg;
    }
}

convertIfNecessary

  • 參數類型轉換
public class DataBinder implements PropertyEditorRegistry, TypeConverter {

    @Override
    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
            @Nullable MethodParameter methodParam) throws TypeMismatchException {

        return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);
    }

}


public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport implements TypeConverter {

    @Override
    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
            @Nullable MethodParameter methodParam) throws TypeMismatchException {

        return convertIfNecessary(value, requiredType,
                (methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType)));
    }

    @Nullable
    @Override
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,
            @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {

        Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");
        try {
            return this.typeConverterDelegate.convertIfNecessary(null, null, value, requiredType, typeDescriptor);
        }
        catch (ConverterNotFoundException | IllegalStateException ex) {
            throw new ConversionNotSupportedException(value, requiredType, ex);
        }
        catch (ConversionException | IllegalArgumentException ex) {
            throw new TypeMismatchException(value, requiredType, ex);
        }
    }
}


class TypeConverterDelegate {
    @Nullable
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
            @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

        // Custom editor for this type?
        // 1、判斷有無自定義屬性編輯器
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

        ConversionFailedException conversionAttemptEx = null;

        // No custom editor but custom ConversionService specified?
        // 2、判斷有無自定義ConversionService
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
                catch (ConversionFailedException ex) {
                    // fallback to default conversion logic below
                    conversionAttemptEx = ex;
                }
            }
        }

        Object convertedValue = newValue;

        // Value not of required type?
        // ClassUtils.isAssignableValue(requiredType, convertedValue)-->判斷requiredType和convertedValue的class,是否相同,
        // 相同返回->true;否則返回->false
        // 3、 如果有自定義屬性編輯器或者通過解析出來的值類型與真實的值類型的class不同
        // 例如<property name="age" value="3"/>,我們需要將value轉換成int時
        if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
                    convertedValue instanceof String) {
                TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
                if (elementTypeDesc != null) {
                    Class<?> elementType = elementTypeDesc.getType();
                    if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                }
            }
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;

        // 4、執行轉換
        if (requiredType != null) {
            // Try to apply some standard type conversion rules if appropriate.

            if (convertedValue != null) {
                // Object類型
                if (Object.class == requiredType) {
                    return (T) convertedValue;
                }
                // 數組類型
                else if (requiredType.isArray()) {
                    // Array required -> apply appropriate conversion of elements.
                    if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                    }
                    return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
                }
                // 集合類型
                else if (convertedValue instanceof Collection) {
                    // Convert elements to target type, if determined.
                    convertedValue = convertToTypedCollection(
                            (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }
                // map類型
                else if (convertedValue instanceof Map) {
                    // Convert keys and values to respective target type, if determined.
                    convertedValue = convertToTypedMap(
                            (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }

                // 注意:這里是新開啟的if,不接上面的else if
                // 如果經過轉換過的值是數組類型,且其長度只有1,那么只取其第0個作為最終轉換值
                if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                    convertedValue = Array.get(convertedValue, 0);
                    standardConversion = true;
                }

                // 如果類型是String,并且是java的基本數據類型或者包裝類型
                // 包括 boolean, byte, char, short, int, long, float, double
                // 和 Boolean, Byte, Character, Short, Integer, Long, Float, Double
                // 那么直接調用toString()方法返回即可,注意convertedValue是Object類型,不是基本或包裝類型,所以是可以調用toString()方法的
                if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                    // We can stringify any primitive value...
                    return (T) convertedValue.toString();
                }
                // 如果轉換值是String類的實例,但是我們又不能轉換為解析出來的requiredType的實例
                // 例如枚舉類型值的注入
                else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                    if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                        try {
                            Constructor<T> strCtor = requiredType.getConstructor(String.class);
                            return BeanUtils.instantiateClass(strCtor, convertedValue);
                        }
                        // 刪除logger信息
                        catch (NoSuchMethodException ex) {
                            // proceed with field lookup
                            if (logger.isTraceEnabled()) {
                                logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
                            }
                        }
                        catch (Exception ex) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
                            }
                        }
                    }
                    String trimmedValue = ((String) convertedValue).trim();
                    if (requiredType.isEnum() && trimmedValue.isEmpty()) {
                        // It's an empty enum identifier: reset the enum value to null.
                        return null;
                    }
                    convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                    standardConversion = true;
                }
                // 數值類型
                else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
                    convertedValue = NumberUtils.convertNumberToTargetClass(
                            (Number) convertedValue, (Class<Number>) requiredType);
                    standardConversion = true;
                }
            }
            else {
                // convertedValue == null
                if (requiredType == Optional.class) {
                    convertedValue = Optional.empty();
                }
            }

            // 5、 判定requiredType是否可從convertedValue轉換而來,并嘗試使用conversionService轉換,及處理轉換異常
            if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
                if (conversionAttemptEx != null) {
                    // Original exception from former ConversionService call above...
                    throw conversionAttemptEx;
                }
                else if (conversionService != null && typeDescriptor != null) {
                    // ConversionService not tried before, probably custom editor found
                    // but editor couldn't produce the required type...
                    TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
                    if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                        return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                    }
                }

                // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
                // 到此為止,可以確定類型不匹配,無法轉換,拋出IllegalArgumentException/IllegalStateException
                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }
                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName()).append(
                            "] returned inappropriate value of type '").append(
                            ClassUtils.getDescriptiveType(convertedValue)).append("'");
                    throw new IllegalArgumentException(msg.toString());
                }
                else {
                    msg.append(": no matching editors or conversion strategy found");
                    throw new IllegalStateException(msg.toString());
                }
            }
        }

        if (conversionAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
                throw conversionAttemptEx;
            }
            logger.debug("Original ConversionService attempt failed - ignored since " +
                    "PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
        }
        // 6、返回轉換值
        return (T) convertedValue;
    }
}

doInvoke方法調用

public class InvocableHandlerMethod extends HandlerMethod {

    protected Object doInvoke(Object... args) throws Exception {
        ReflectionUtils.makeAccessible(getBridgedMethod());
        try {
            return getBridgedMethod().invoke(getBean(), args);
        }
        catch (IllegalArgumentException ex) {
            assertTargetBean(getBridgedMethod(), getBean(), args);
            String text = (ex.getMessage() != null ? ex.getMessage() : "Illegal argument");
            throw new IllegalStateException(formatInvokeError(text, args), ex);
        }
        catch (InvocationTargetException ex) {
            // Unwrap for HandlerExceptionResolvers ...
            Throwable targetException = ex.getTargetException();
            if (targetException instanceof RuntimeException) {
                throw (RuntimeException) targetException;
            }
            else if (targetException instanceof Error) {
                throw (Error) targetException;
            }
            else if (targetException instanceof Exception) {
                throw (Exception) targetException;
            }
            else {
                throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
            }
        }
    }
}

setResponseStatus

  • 設置相應狀態碼以及handleReturnValue處理返回值
public class ServletInvocableHandlerMethod extends InvocableHandlerMethod {

    private void setResponseStatus(ServletWebRequest webRequest) throws IOException {
        // 獲取HttpStatus
        HttpStatus status = getResponseStatus();
        // 未發現HttpStatus直接返回
        if (status == null) {
            return;
        }

        HttpServletResponse response = webRequest.getResponse();
        if (response != null) {
            String reason = getResponseStatusReason();
            if (StringUtils.hasText(reason)) {
                /**
                 * 注意 注意 注意:這里是 sendError , 不是 setError
                 * 使用指定的狀態碼并清空緩沖,發送一個錯誤響應至客戶端。如果響應已經被提交,這個方法會拋出IllegalStateException。
                 * 服務器默認會創建一個HTML格式的服務錯誤頁面作為響應結果,其中包含參數msg指定的文本信息,
                 * 這個HTML頁面的內容類型為“text/html”,保留cookies和其他未修改的響應頭信息。
                 *
                 * 如果一個對應于傳入的錯誤碼的錯誤頁面已經在web.xml中聲明,那么這個聲明的錯誤頁面將會優先于建議的msg參數服務于客戶端。
                 */
                response.sendError(status.value(), reason);
            }
            else {
                /**
                 * 設置響應的狀態碼。
                 * 這個方法被用于當響應結果正常時(例如,狀態碼為SC_OK或SC_MOVED_TEMPORARTLY)設置響應狀態碼。
                 * 如果發生錯誤,而且來訪者希望調用在web應用中定義的錯誤頁面作為顯示,那么應該使用sendError方法代替之。
                 * 使用setStatus方法之后,容器會清空緩沖并設置Location響應頭,保留cookies和其他響應頭信息。
                 */
                response.setStatus(status.value());
            }
        }

        // To be picked up by RedirectView
        webRequest.getRequest().setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, status);
    }
}

如果@RequestMapping設置了@ResponseStatus注解,這里則要根據注解設置放回狀態碼。如果getResponseStatusReason方法返回了錯誤信息,則直接通過sendError方法返回給前端。否則將狀態碼信息設置到response里即可。

在后續處理根據@RequestMapping方法返回值、相應信息等判斷,是否將當前請求設置為已經完成。例如當前請求無需返回視圖、或者當前請求的放回狀態碼包含了錯誤信息,則無需繼續后續處理。

假設當前是有視圖或者返回值,接下來應該選取合適的HandlerMethodReturnValueHandler并處理返回值,先來看一下HandlerMethodReturnValueHandler的定義:

public interface HandlerMethodReturnValueHandler {

    /**
     * 判斷當前策略(Handler)是否支持MethodParameter(方法返回類型)
     */
    boolean supportsReturnType(MethodParameter returnType);

    /**
     * 處理返回值,為模型添加屬性、視圖等想關內容
     */
    void handleReturnValue(@Nullable Object returnValue,
                           MethodParameter returnType,
                           ModelAndViewContainer mavContainer,
                           NativeWebRequest webRequest) throws Exception;
}

getModelAndView方法后續處理

public class RequestMappingHandlerAdapter extends AbstractHandlerMethodAdapter
        implements BeanFactoryAware, InitializingBean {

    private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
            ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
        // 1.更新模型
        modelFactory.updateModel(webRequest, mavContainer);
        if (mavContainer.isRequestHandled()) {
            return null;
        }
        // 2.獲取ModelMap并創建ModelAndView
        ModelMap model = mavContainer.getModel();
        ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
        // 3.處理引用類型視圖和轉發類型視圖
        if (!mavContainer.isViewReference()) {
            mav.setView((View) mavContainer.getView());
        }
        if (model instanceof RedirectAttributes) {
            Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
            HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
            if (request != null) {
                RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
            }
        }
        return mav;
    }
}

實現拉回到doDispatch方法里面:

  • 執行處理成默認視圖名,也就是添加前綴和后綴等。
  • 攔截器postHandle方法進行處理
  • 處理最后的結果,渲染之類的邏輯都在這里
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    
    ......

    try {
            ......

            // 處理成默認視圖名,也就是添加前綴和后綴等
            applyDefaultViewName(processedRequest, mv);
            // 攔截器postHandle方法進行處理
            mappedHandler.applyPostHandle(processedRequest, response, mv);
        }

        ......

        // 處理最后的結果,渲染之類的邏輯都在這里
        processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
    }

    ......

}

由于現在流行前后端分離,前后端通過json格式進行數據交互,所以視圖渲染這個就不做過多闡述。

到此,請求參數綁定到方法形參,執行方法處理請求,返回結果進行視圖渲染的流程就梳理完畢了。

參考:
https://blog.csdn.net/lyc_liyanchao/article/details/87925667

https://blog.csdn.net/lyc_liyanchao/article/details/88085797

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。