主頁 > 後端開發 > SpringMVC詳解

SpringMVC詳解

2022-11-01 06:17:41 後端開發

 SpringMVC的介紹

 【1】Spring Web MVC是基于Servlet API構建的原始Web框架,從一開始就已包含在Spring框架中,正式名稱“ Spring Web MVC”來自其源模塊的名稱(spring-webmvc),但它通常被稱為“ Spring MVC”,

 

SpringMVC的具體執行流程:

【1】說明:

  1)Spring MVC 是圍繞前端控制器模式設計的,其中:中央 Servlet DispatcherServlet 為請求處理流程提供統一調度,實際作業則交給可配置組件執行,這個模型是靈活的且開放的,我們可以通過自己去定制這些組件從而進行定制自己的作業流,

  2)說白了就是用一個DispatcherServlet 封裝了一個Servlet的調度中心, 由調度中心幫我們呼叫我們的處理方法:在這個程序中調度中心委托給各個組件執行具體作業 ,比如幫我們映射方法請求、幫我決議引數、呼叫處理方法、回應資料和頁面 等

【2】圖示:

 

 

 

【3】組件說明:

DispatcherServlet: 前端調度器 , 負責將請求攔截下來分發到各控制器方法中
HandlerMapping: 負責根據請求的URL和配置@RequestMapping映射去匹配, 匹配到會回傳Handler(具體控制器的方法)
HandlerAdaper: 負責呼叫Handler-具體的方法-  回傳視圖的名字  Handler將它封裝到ModelAndView(封裝視圖名,request域的資料)
ViewReslover: 根據ModelAndView里面的視圖名地址去找到具體的jsp封裝在View物件中
View:進行視圖渲染(將jsp轉換成html內容 --這是Servlet容器的事情了) 最終response到的客戶端

 

【4】流程說明:

1)用戶發送請求至前端控制器DispatcherServlet
2)DispatcherServlet收到請求呼叫處理器映射器HandlerMapping,
    2.1)處理器映射器根據請求url找到具體的處理器,生成處理器執行鏈HandlerExecutionChain(包括處理器物件和處理器攔截器)一并回傳給DispatcherServlet,
3)DispatcherServlet根據處理器Handler獲取處理器配接器HandlerAdapter,執行HandlerAdapter處理一系列的操作,如:引數封裝,資料格式轉換,資料驗證等操作
4)執行處理器Handler(Controller,也叫頁面控制器),
    4.1)Handler執行完成回傳ModelAndView
    4.2)HandlerAdapter將Handler執行結果ModelAndView回傳到DispatcherServlet
5)DispatcherServlet將ModelAndView傳給ViewReslover視圖決議器
    5.1)ViewReslover決議后回傳具體View
6)DispatcherServlet對View進行渲染視圖(即將模型資料model填充至視圖中),
7)DispatcherServlet回應用戶,

 

SpringMVC請求流程圖解:

  

 

 

 

執行流程原始碼決議(我是直接開啟SpringBoot里面分析的)

【1】分析主線流程,DispatcherServlet類#doDispatch方法

//DispatcherServlet類#doDispatch方法
//主流程1,執行DispatcherServlet類#doDispatch方法
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 {
            //檢查請求是否是multipart(即檔案上傳),若是進行相關處理
            processedRequest = checkMultipart(request);
            multipartRequestParsed = (processedRequest != request);

            //通過handermapping映射獲取HandlerExecutionChain(處理鏈中包括了interceptor的前置和后置方法)
            //主流程2,獲取HandlerExecutionChain
            mappedHandler = getHandler(processedRequest);
            if (mappedHandler == null) {
                noHandlerFound(processedRequest, response);
                return;
            }
            // 根據處理器(handler及HandlerExecutionChain)獲取處理器配接器(處理器配接器是為了提供統一介面進行后續處理,從而支持多種型別的處理器)
       // 主流程3的具體地方
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler()); // Process last-modified header, if supported by the handler. String method = request.getMethod(); boolean isGet = HttpMethod.GET.matches(method); if (isGet || HttpMethod.HEAD.matches(method)) { long lastModified = ha.getLastModified(request, mappedHandler.getHandler()); if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) { return; } } //執行chain中攔截器附加的預處理方法,即preHandle方法  if (!mappedHandler.applyPreHandle(processedRequest, response)) { // 回傳false就不進行后續處理了 return; } // 執行HandlerAdapter處理一系列的操作,如:引數封裝,資料格式轉換,資料驗證等操作 ,主流程4的具體地方 // 執行處理器Handler(Controller,也叫頁面控制器), // Handler執行完成回傳ModelAndView // HandlerAdapter將Handler執行結果ModelAndView回傳到DispatcherServlet mv = ha.handle(processedRequest, response, mappedHandler.getHandler()); if (asyncManager.isConcurrentHandlingStarted()) { return; } // 如果沒有視圖,給你設定默認視圖 json忽略 applyDefaultViewName(processedRequest, mv); // 后置攔截器 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); } // DispatcherServlet將ModelAndView傳給ViewReslover視圖決議器 // ViewReslover決議后回傳具體View // DispatcherServlet對View進行渲染視圖(即將模型資料model填充至視圖中), // DispatcherServlet回應用戶, // 如果有例外,還會處理例外 ,主流程5,6的具體地方 processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException); } catch (Exception ex) { //攔截器afterCompletion處理器 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); } } } }

 

【2】分析getHandler方法如何回傳 處理器執行鏈HandlerExecutionChain

//分析getHandler
//this.handlerMappings的內容
//0.RequestMappingHandlerMapping
//1.BeanNameUrlHandlerMapping
//2.RouterFunctionMapping
//3.SimpleUrlHandlerMapping
//4.WelcomePageHandlerMapping
@Nullable
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    if (this.handlerMappings != null) {
        for (HandlerMapping mapping : this.handlerMappings) {
            HandlerExecutionChain handler = mapping.getHandler(request);
            if (handler != null) {
                return handler;
            }
        }
    }
    return null;
}

//呼叫到了AbstractHandlerMapping類#getHandler方法
@Override
@Nullable
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    //getHandlerInternal(request)方法為抽象方法,供子類實作
    //獲取到的handler物件一般為bean/HandlerMethod
    Object handler = getHandlerInternal(request);
    //上述找不到則使用默認的處理類,沒有設定則回傳null,則會回傳前臺404錯誤
    if (handler == null) {
        handler = getDefaultHandler();
    }
    if (handler == null) {
        return null;
    }
    // Bean name or resolved handler?
    if (handler instanceof String) {
        String handlerName = (String) handler;
        handler = obtainApplicationContext().getBean(handlerName);
    }

    // Ensure presence of cached lookupPath for interceptors and others
    if (!ServletRequestPathUtils.hasCachedPath(request)) {
        initLookupPath(request);
    }
    //創建處理鏈物件
    HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
    //針對cros跨域請求的處理
    if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
        CorsConfiguration config = getCorsConfiguration(handler, request);
        if (getCorsConfigurationSource() != null) {
            CorsConfiguration globalConfig = getCorsConfigurationSource().getCorsConfiguration(request);
            config = (globalConfig != null ? globalConfig.combine(config) : config);
        }
        if (config != null) {
            config.validateAllowCredentials();
        }
        executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
    }

    return executionChain;
}

【2.1】分析AbstractHandlerMapping類#getHandler方法

【2.1.1】如何獲取handler物件【一般為bean/HandlerMethod】

//RequestMappingInfoHandlerMapping類#getHandlerInternal方法
//因為它是RequestMappingHandlerMapping的父類
@Override
@Nullable
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    request.removeAttribute(PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE);
    try {
        //呼叫父類AbstractHandlerMethodMapping類
        return super.getHandlerInternal(request);
    }
    finally {
        ProducesRequestCondition.clearMediaTypesAttribute(request);
    }
}

//呼叫到AbstractHandlerMethodMapping類getHandlerInternal方法
//針對HandlerMethod的獲取
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    //獲取訪問的路徑,一般類似于request.getServletPath()回傳不含contextPath的訪問路徑
    String lookupPath = initLookupPath(request);
    //獲取讀鎖
    this.mappingRegistry.acquireReadLock();
    try {
        //獲取HandlerMethod作為handler物件,這里涉及到路徑匹配的優先級
        //優先級:精確匹配>最長路徑匹配>擴展名匹配
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        //HandlerMethod內部含有bean物件,其實指的是對應的Controller
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        //釋放讀鎖
        this.mappingRegistry.releaseReadLock();
    }
}

【2.1.2】如何創建處理鏈

//創建處理器鏈的方法
protected HandlerExecutionChain getHandlerExecutionChain(Object handler, HttpServletRequest request) {
    /創建HandlerExecutionChain
    HandlerExecutionChain chain = (handler instanceof HandlerExecutionChain ? (HandlerExecutionChain) handler : new HandlerExecutionChain(handler));
    //與請求url進行匹配,滿足的才加入
    for (HandlerInterceptor interceptor : this.adaptedInterceptors) {
        if (interceptor instanceof MappedInterceptor) {
            MappedInterceptor mappedInterceptor = (MappedInterceptor) interceptor;
            if (mappedInterceptor.matches(request)) {
                chain.addInterceptor(mappedInterceptor.getInterceptor());
            }
        }
        else {
            chain.addInterceptor(interceptor);
        }
    }
    return chain;
}

 

【3】分析getHandlerAdapter尋找的處理器配接器

//分析getHandlerAdapter
//this.handlerAdapters的內容
//0.RequestMappingHandlerAdapter
//1.HandlerFunctionAdapter
//2.HttpRequestHandlerAdapter
//3.SimpleControllerHandlerAdapter
protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
    if (this.handlerAdapters != null) {
        for (HandlerAdapter adapter : this.handlerAdapters) {
            if (adapter.supports(handler)) {
                //所以一般回傳的也就是RequestMappingHandlerAdapter
                return adapter;
            }
        }
    }
    throw new ServletException(...);
}

//因為RequestMappingHandlerAdapter沒有重寫故呼叫父類的
//呼叫AbstractHandlerMethodAdapter類#supports方法
@Override
public final boolean supports(Object handler) {
    //如果是類的方法的話默認是true
    return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}

 

【4】分析mappedHandler.applyPreHandle前置處理器與mappedHandler.applyPostHandle后置處理器

//分析mappedHandler.applyPreHandle前置處理器與mappedHandler.applyPostHandle后置處理器
//前置處理器是從0到size
boolean applyPreHandle(HttpServletRequest request, HttpServletResponse response) throws Exception {
    for (int i = 0; i < this.interceptorList.size(); i++) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        if (!interceptor.preHandle(request, response, this.handler)) {
            triggerAfterCompletion(request, response, null);
            return false;
        }
        this.interceptorIndex = i;
    }
    return true;
}

//前置處理器是從size到0
void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @Nullable ModelAndView mv) throws Exception {

    for (int i = this.interceptorList.size() - 1; i >= 0; i--) {
        HandlerInterceptor interceptor = this.interceptorList.get(i);
        interceptor.postHandle(request, response, this.handler, mv);
    }
}

 

【5】分析ha.handle(processedRequest, response, mappedHandler.getHandler());,如何進行呼叫控制器里面的方法

//AbstractHandlerMethodAdapter類#handle方法
//因為配接器RequestMappingHandlerAdapter類沒有所以定位到了父類
public final ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
    return handleInternal(request, response, (HandlerMethod) handler);
}

//RequestMappingHandlerAdapter類#handleInternal方法
@Override
protected ModelAndView handleInternal(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {

    ModelAndView mav;
    // 檢查當前請求的method是否為支持的method(默認Null,可通過繼承AbstractController設定supportedMethods)
    // 檢查當前請求是否必須session  (默認false,可通過繼承AbstractController設定requireSession)
    checkRequest(request);

    /**
    * 判斷當前是否需要支持在同一個session中只能線性地處理請求
    * 因為鎖是通過 synchronized 是 JVM 行程級,所以在分布式環境下,
    * 無法達到同步相同 Session 的功能,默認情況下,synchronizeOnSession 為 false
    */
    if (this.synchronizeOnSession) {
        // 獲取當前請求的session物件
        HttpSession session = request.getSession(false);
        if (session != null) {
            // 為當前session生成一個唯一的可以用于鎖定的key
            Object mutex = WebUtils.getSessionMutex(session);
            synchronized (mutex) {
                // 對HandlerMethod進行引數等的適配處理,并呼叫目標handler
                mav = invokeHandlerMethod(request, response, handlerMethod);
            }
        }
        else {
            // 如果當前不存在session,則直接對HandlerMethod進行適配
            mav = invokeHandlerMethod(request, response, handlerMethod);
        }
    }
    else {
        // 這般都會走這里,重點反射呼叫
        // 如果當前不需要對session進行同步處理,則直接對HandlerMethod進行適配
        mav = invokeHandlerMethod(request, response, handlerMethod);
    }
    //判斷當前請求頭中是否包含Cache-Control請求頭,如果不包含,則對當前response進行處理    
    if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
        // 如果當前SessionAttribute中存在配置的attributes,則為其設定過期時間,
        // 這里SessionAttribute主要是通過@SessionAttribute注解生成的
        if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
            applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
        }
        else {
            // 如果當前不存在SessionAttributes,則判斷當前是否存在Cache-Control設定,
            // 如果存在,則按照該設定進行response處理,如果不存在,則設定response中的
            // Cache的過期時間為-1,即立即失效
            prepareResponse(response);
        }
    }

    return mav;
}

【5.1】分析invokeHandlerMethod方法怎么做【先是引數處理器,再是生成容器,然后去反射呼叫,最后將結果放入容器

@Nullable
protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
    // 把我們的請求req resp包裝成 ServletWebRequest
    ServletWebRequest webRequest = new ServletWebRequest(request, response);
    try {
        // 獲取容器中全域配置的InitBinder和當前HandlerMethod所對應的Controller中
        // 配置的InitBinder,用于進行引數的系結
        WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
        // 獲取容器中全域配置的ModelAttribute和當前HandlerMethod所對應的Controller 中配置的ModelAttribute,
        // 這些配置的方法將會在目標方法呼叫之前進行呼叫
        ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
        // 封裝handlerMethod,會在呼叫前決議引數、呼叫后對回傳值進行處理
        ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
        if (this.argumentResolvers != null) {
            // 讓invocableMethod擁有引數決議能力
            invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
        }
        if (this.returnValueHandlers != null) {
            // 讓invocableMethod擁有回傳值處理能力
            invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
        }
        // 讓invocableMethod擁有InitBinder決議能力
        invocableMethod.setDataBinderFactory(binderFactory);
        // 設定ParameterNameDiscoverer,該物件將按照一定的規則獲取當前引數的名稱
        invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
        // 創建ModelAndView處理容器
        ModelAndViewContainer mavContainer = new ModelAndViewContainer();
        // 將request的Attribute復制一份到ModelMap
        mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
        // 呼叫我們標注了@ModelAttribute的方法,主要是為我們的目標方法預加載
        modelFactory.initModel(webRequest, mavContainer, invocableMethod);
        // 重定向的時候,忽略model中的資料 默認false
        mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

        // 獲取當前的AsyncWebRequest,這里AsyncWebRequest的主要作用是用于判斷目標
        // handler的回傳值是否為WebAsyncTask或DeferredResult,如果是這兩種中的一種,
        // 則說明當前請求的處理應該是異步的,所謂的異步,指的是當前請求會將Controller中
        // 封裝的業務邏輯放到一個執行緒池中進行呼叫,待該呼叫有回傳結果之后再回傳到response中,
        // 這種處理的優點在于用于請求分發的執行緒能夠解放出來,從而處理更多的請求,提高吞吐,
        // 只有待目標任務完成之后才會回來將該異步任務的結果回傳,
        AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
        asyncWebRequest.setTimeout(this.asyncRequestTimeout);
        // 封裝異步任務的執行緒池、request、interceptors到WebAsyncManager中
        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);
        }
        // 對請求引數進行處理,呼叫目標HandlerMethod,并且將回傳值封裝為一個ModelAndView物件
        invocableMethod.invokeAndHandle(webRequest, mavContainer);
        if (asyncManager.isConcurrentHandlingStarted()) {
            return null;
        }
        // 對封裝的ModelAndView進行處理,主要是判斷當前請求是否進行了重定向,如果進行了重定向,還會判斷是否需要將FlashAttributes封裝到新的請求中
        return getModelAndView(mavContainer, modelFactory, webRequest);
    }
    finally {
        webRequest.requestCompleted();
    }
}

 

【5.1.1】分析invocableMethod.invokeAndHandle怎么反射呼叫

//ServletInvocableHandlerMethod類#invokeAndHandle方法
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 真正的呼叫我們的目標物件
    Object returnValue =https://www.cnblogs.com/chafry/archive/2022/10/31/ invokeForRequest(webRequest, mavContainer, providedArgs);
    // 設定相關的回傳狀態
    setResponseStatus(webRequest);
    // 如果請求處理完成,則設定requestHandled屬性
    if (returnValue =https://www.cnblogs.com/chafry/archive/2022/10/31/= null) {
        if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
            disableContentCachingIfNecessary(webRequest);
            mavContainer.setRequestHandled(true);
            return;
        }
    }
    // 如果請求失敗,但是有錯誤原因,那么也會設定requestHandled屬性
    else if (StringUtils.hasText(getResponseStatusReason())) {
        mavContainer.setRequestHandled(true);
        return;
    }

    mavContainer.setRequestHandled(false);
    Assert.state(this.returnValueHandlers != null, "No return value handlers");
    try {
        // 遍歷當前容器中所有ReturnValueHandler,判斷哪種handler支持當前回傳值的處理,
        // 如果支持,則使用該handler處理該回傳值
        this.returnValueHandlers.handleReturnValue(returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
    }
    catch (Exception ex) {
        throw ex;
    }
}

//InvocableHandlerMethod類#invokeForRequest方法
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer, Object... providedArgs) throws Exception {
    // 獲取我們目標方法入參的值
    Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
    // 反射呼叫 ,里面主要就是 method.invoke(getBean(), args) 來進行反射呼叫
    return doInvoke(args);
}

 

【5.1.2】分析 對封裝的ModelAndView進行處理

//RequestMappingHandlerAdapter類#getModelAndView方法
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
        ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {

    modelFactory.updateModel(webRequest, mavContainer);
    if (mavContainer.isRequestHandled()) {
        return null;
    }
    ModelMap model = mavContainer.getModel();
    ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
    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;
}

 

【6】分析processDispatchResult方法對回傳結果的處理

private void processDispatchResult(HttpServletRequest request, HttpServletResponse response, @Nullable HandlerExecutionChain mappedHandler, @Nullable ModelAndView mv, @Nullable Exception exception) throws Exception {

    boolean errorView = false;
    // 例外處理
    if (exception != null) {
        if (exception instanceof ModelAndViewDefiningException) {
            logger.debug("ModelAndViewDefiningException encountered", exception);
            mv = ((ModelAndViewDefiningException) exception).getModelAndView();
        }
        else {
            Object handler = (mappedHandler != null ? mappedHandler.getHandler() : null);
            mv = processHandlerException(request, response, handler, exception);
            errorView = (mv != null);
        }
    }

    // Did the handler return a view to render?
    if (mv != null && !mv.wasCleared()) {
        // 決議、渲染視圖
        render(mv, request, response);
        if (errorView) {
            WebUtils.clearErrorRequestAttributes(request);
        }
    }
    else {...}

    if (WebAsyncUtils.getAsyncManager(request).isConcurrentHandlingStarted()) {
        // Concurrent handling started during a forward
        return;
    }

    if (mappedHandler != null) {
        // Exception (if any) is already handled..
     // 攔截器的后置處理
mappedHandler.triggerAfterCompletion(request, response, null); } }

 

【6.1】分析render方法視圖渲染

protected void render(ModelAndView mv, HttpServletRequest request, HttpServletResponse response) throws Exception {
    // Determine locale for request and apply it to the response.
    Locale locale = (this.localeResolver != null ? this.localeResolver.resolveLocale(request) : request.getLocale());
    response.setLocale(locale);

    View view;
    String viewName = mv.getViewName();
    if (viewName != null) {
        // 決議視圖名
        view = resolveViewName(viewName, mv.getModelInternal(), locale, request);
        if (view == null) {
            throw new ServletException(...);
        }
    }
    else {
        // No need to lookup: the ModelAndView object contains the actual View object.
        view = mv.getView();
        if (view == null) {  throw new ServletException(...);  }
    }

    // Delegate to the View object for rendering.

    try {
        if (mv.getStatus() != null) {
            request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE, mv.getStatus());
            response.setStatus(mv.getStatus().value());
        }
        // 進行視圖渲染 ,呼叫的是 AbstractView類的方法
        view.render(mv.getModelInternal(), request, response);
    }
    catch (Exception ex) { throw ex; }
}

 

原始碼決議注解@RequestMapping

【1】@RequestMapping決議

【1.1】說明:

  1)明確一點:@RequestMapping是通過RequestMappingHandlerMapping負責決議,

  2)HandlerMapping便是負責根據請求URI 映射 到對應的handler方法,而RequestMappingHandlerMapping是HandlerMapping的其中一個實作類, 負責根據@RequestMapping注解進行映射,

  3)所以HandlerMapping有很多其他實作類,RequestMappingHandlerMapping是最常用的,HandlerMapping可分為2個程序:1決議、2映射

【1.2】分析RequestMappingHandlerMapping類

  1)基于繼承關系可以發現它實作了InitializingBean介面

  2)分析afterPropertiesSet方法做了什么

@Override
@SuppressWarnings("deprecation")
public void afterPropertiesSet() {
    //這里都是一些設定配置
    this.config = new RequestMappingInfo.BuilderConfiguration();
    this.config.setTrailingSlashMatch(useTrailingSlashMatch());
    this.config.setContentNegotiationManager(getContentNegotiationManager());

    if (getPatternParser() != null) {
        this.config.setPatternParser(getPatternParser());
    }
    else {
        this.config.setSuffixPatternMatch(useSuffixPatternMatch());
        this.config.setRegisteredSuffixPatternMatch(useRegisteredSuffixPatternMatch());
        this.config.setPathMatcher(getPathMatcher());
    }
    //呼叫父類AbstractHandlerMethodMapping類的afterPropertiesSet方法
    super.afterPropertiesSet();
}

//父類AbstractHandlerMethodMapping類的afterPropertiesSet方法
public void afterPropertiesSet() {
    initHandlerMethods();
}

//AbstractHandlerMethodMapping類#initHandlerMethods方法
protected void initHandlerMethods() {
    // 獲得所有候選beanName—— 當前容器所有的beanName
    for (String beanName : getCandidateBeanNames()) {
        if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
            // 處理候選bean——即決議@RequestMapping和映射路徑
            processCandidateBean(beanName);
        }
    }
    // 決議完所有@RequestMapping的時候呼叫,輸出日志
    handlerMethodsInitialized(getHandlerMethods());
}

 

【1.2.1】分析processCandidateBean方法怎么處理的

protected void processCandidateBean(String beanName) {
    Class<?> beanType = null;
    try {
        beanType = obtainApplicationContext().getType(beanName);
    }
    catch (Throwable ex) {..省略日志..}
    // 這一步判斷是關鍵  是否有Controller 或 RequestMapping注解
    if (beanType != null && isHandler(beanType)) {
        // 決議HandlerMethods
        detectHandlerMethods(beanName);
    }
}

protected void detectHandlerMethods(Object handler) {
    Class<?> handlerType = (handler instanceof String ? obtainApplicationContext().getType((String) handler) : handler.getClass());

    if (handlerType != null) {
        Class<?> userType = ClassUtils.getUserClass(handlerType);
        // 回圈所有方法
        Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
                (MethodIntrospector.MetadataLookup<T>) method -> {
                    try {
                        return getMappingForMethod(method, userType);
                    }
                    catch (Throwable ex) {..省略例外..}
        });
        ..省略日志..
        //回圈注冊
        methods.forEach((method, mapping) -> {
            Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
            registerHandlerMethod(handler, invocableMethod, mapping);
        });
    }
}

【1.2.1.1】決議流程

//呼叫回子類RequestMappingHandlerMapping類#getMappingForMethod方法
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
    // 如果方法上面有@RequestMapping:決議出RequestMappingInfo
    // RequestMappingInfo 是用來在請求的時候做匹對的
    RequestMappingInfo info = createRequestMappingInfo(method);
    if (info != null) {
        // 如果方法上面有@RequestMapping,看看類上面是不是有@RequestMapping
        RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType);
        // 類上面也有@RequestMapping  那就合并
        // 比如 類:/user  方法:/info 合并為 /user/info
        if (typeInfo != null) {
            info = typeInfo.combine(info);
        }
        // 合并前綴   5.1新增  默認null
        // 可通過 WebMvcConfigurer#configurePathMatch 進行定制
        String prefix = getPathPrefix(handlerType);
        if (prefix != null) {
            info = RequestMappingInfo.paths(prefix).options(this.config).build().combine(info);
        }
    }
    return info;
}

 

【1.2.1.2】注冊流程

//呼叫回子類RequestMappingHandlerMapping類#registerHandlerMethod方法
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
    super.registerHandlerMethod(handler, method, mapping);
    updateConsumesCondition(mapping, method);
}
//父類AbstractHandlerMethodMapping類#registerHandlerMethod方法
//其中private final MappingRegistry mappingRegistry = new MappingRegistry();
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
    this.mappingRegistry.register(mapping, handler, method);
}

//兩大存盤容器
//private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
//private final MultiValueMap<String, T> pathLookup = new LinkedMultiValueMap<>();
//pathLookup存盤【path, mapping】
//registry存盤【mapping,與之對應的【mapping, handlerMethod, directPaths, name, corsConfig】】

public void register(T mapping, Object handler, Method method) {
    this.readWriteLock.writeLock().lock();
    try {
        HandlerMethod handlerMethod = createHandlerMethod(handler, method);
        validateMethodMapping(handlerMethod, mapping);

        Set<String> directPaths = AbstractHandlerMethodMapping.this.getDirectPaths(mapping);
        for (String path : directPaths) {
            this.pathLookup.add(path, mapping);
        }

        String name = null;
        if (getNamingStrategy() != null) {
            name = getNamingStrategy().getName(handlerMethod, mapping);
            addMappingName(name, handlerMethod);
        }

        CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
        if (corsConfig != null) {
            corsConfig.validateAllowCredentials();
            this.corsLookup.put(handlerMethod, corsConfig);
        }

        this.registry.put(mapping,
                new MappingRegistration<>(mapping, handlerMethod, directPaths, name, corsConfig != null));
    }
    finally {
        this.readWriteLock.writeLock().unlock();
    }
}

 

【2】@RequestMapping請求映射

【2.1】回看執行流程的【2.1.1】中的getHandlerInternal方法

protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
    // 通過UrlPathHelper物件,用于來決議從們的request中決議出請求映射路徑
    String lookupPath = initLookupPath(request);
    //獲取讀鎖
    this.mappingRegistry.acquireReadLock();
    try {
        // 通過lookupPath決議最終的handler——HandlerMethod物件
        HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
        return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
    }
    finally {
        //釋放讀鎖
        this.mappingRegistry.releaseReadLock();
    }
}

//分析查找流程
//先從pathLookup里面拿
//拿不到再去進行通配符匹配,排序獲取第一個最優匹配的
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
    List<Match> matches = new ArrayList<>();
    // 根據uri從mappingRegistry.pathLookup獲取 RequestMappingInfo
    // pathLookup<path,RequestMappingInfo>會在初始化階段決議好
    List<T> directPathMatches = this.mappingRegistry.getMappingsByDirectPath(lookupPath);
    if (directPathMatches != null) {
        // 如果根據path能直接匹配的RequestMappingInfo 則用該mapping進行匹配其他條件(method、header等)
        addMatchingMappings(directPathMatches, matches, request);
    }
    if (matches.isEmpty()) {
        // 如果無path匹配,用所有的RequestMappingInfo  通過AntPathMatcher匹配
        addMatchingMappings(this.mappingRegistry.getRegistrations().keySet(), matches, request);
    }
    if (!matches.isEmpty()) {
        // 選擇第一個為最匹配的
        Match bestMatch = matches.get(0);
        /**
         * 如果匹配到多個
         @RequestMapping(value="https://www.cnblogs.com/mappin?")
         @RequestMapping(value="https://www.cnblogs.com/mappin*")
         @RequestMapping(value="https://www.cnblogs.com/{xxxx}")
         @RequestMapping(value="https://www.cnblogs.com/**")
         */
        if (matches.size() > 1) {
            //創建MatchComparator的匹配器物件
            Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));

            /** 根據精準度排序  大概是這樣的: ? > * > {} >**   具體可以去看:
             * @see org.springframework.util.AntPathMatcher.AntPatternComparator#compare(java.lang.String, java.lang.String)*/
            matches.sort(comparator);

            // 排完序后拿到優先級最高的
            bestMatch = matches.get(0);

            // 是否配置CORS并且匹配
            if (CorsUtils.isPreFlightRequest(request)) {
                for (Match match : matches) {
                    if (match.hasCorsConfig()) {
                        return PREFLIGHT_AMBIGUOUS_MATCH;
                    }
                }
            }
            else {
                //獲取第二最匹配的
                Match secondBestMatch = matches.get(1);
                //若第一個和第二個是一樣的 拋出例外
                if (comparator.compare(bestMatch, secondBestMatch) == 0) {
                    Method m1 = bestMatch.getHandlerMethod().getMethod();
                    Method m2 = secondBestMatch.getHandlerMethod().getMethod();
                    String uri = request.getRequestURI();
                    throw new IllegalStateException(...);
                }
            }
        }
        //把最匹配的設定到request中
        request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
        handleMatch(bestMatch.mapping, lookupPath, request);
        //回傳最匹配的
        return bestMatch.getHandlerMethod();
    }
    else { // return null
        return handleNoMatch(this.mappingRegistry.getRegistrations().keySet(), lookupPath, request);
    }
}

 

【2.1】期待后面繼續補充吧

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/523877.html

標籤:其他

上一篇:【HDLBits刷題筆記】11 Shift Regiters&More Circuits

下一篇:驅動開發:內核LDE64引擎計算匯編長度

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more