SpringMvc請求流程源碼解析

SpringMvc請求流程圖

image-20220829232553998

請求流程粗講解

當用戶發送請求之後,SpringMvc的DispatcherServlet就會收到請求,首先會進去父類的FrameworkServlet#service()
然後進入HttpServlet#service()
方法,作用就是判斷是什麼請求類型的,例如:GET、POST等。這個地方大致過一遍就行,主要是還是 org.springframework.web.servlet.DispatcherServlet#doService
這個方法回去調用org.springframework.web.servlet.DispatcherServlet#doDispatch
這纔是請求開始的重點方法、對應上圖中的請求--->這裏便開始了請求的處理。

org.springframework.web.servlet.DispatcherServlet#doDispatch這個方法中的內容:

  1. 會進行映射,也就是常說的找到Handler,在此步驟中拿到請求地址 例如: /user/info
    對應方法:org.springframework.web.servlet.DispatcherServlet#getHandler
  2. 選擇合適的HandlerAdapter映射適配器
  3. 執行前置攔截器 org.springframework.web.servlet.HandlerInterceptor#preHandle
  4. 調用處理適配器執行Handler
  5. 執行後置處理器 ,對應方法: org.springframework.web.servlet.HandlerInterceptor#postHandle
  6. 解析返回值
  7. 執行最終的處理器,也就是視圖返回之後的處理器 org.springframework.web.servlet.HandlerInterceptor#afterCompletion

方法細講

doDispatcher --> 核心

org.springframework.web.servlet.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 {
				processedRequest = checkMultipart(request);
				multipartRequestParsed = (processedRequest != request);

				// Determine handler for the current request.
				// 進行映射
				mappedHandler = getHandler(processedRequest);
				if (mappedHandler == null) {
					noHandlerFound(processedRequest, response);
					return;
				}

				// Determine handler adapter for the current request.
				// HandlerAdapter  選擇處理器適配器;找到最合適的 HandlerAdapter	// 默認返回的是 RequestMappingHandlerAdapter
				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;
					}
				}

				// 前置攔截器
				if (!mappedHandler.applyPreHandle(processedRequest, response)) {
					// 返回false後就不再進行處理了
					return;
				}

				// Actually invoke the handler.
                // 調用HandlerAdapter 的 handle 方法,對請求進行處理
				mv = ha.handle(processedRequest, response, mappedHandler.getHandler());

				if (asyncManager.isConcurrentHandlingStarted()) {
					return;
				}

				// 如果麼沒有mv,會給一個默認是 mv
				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);
			}
			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);
				}
			}
		}

找到Handler#getHandler

org.springframework.web.servlet.DispatcherServlet#getHandler

該方法就是在 doDispatcher()中進行調用的,也就是對應流程中的 第一步:進行映射找到合適的Handler

看到這個方法的註釋就是去找到最合適的Handler,需要方式就是去遍歷所有的Handler找到一個合適的就直接返回,這個方法裏面就會處理並且找到請求的地址
例如:http://127.0.0.1/request/mapping 就會把 /request/mapping給拿出來

方法調用鏈doDisptcher()->getHandler()->getHandlerInternal()->initLookupPath 會解析出請求路徑

調用棧如下:

接下來進入 getHandler(processedRequest);方法,傳入的參數其實就是 request: HttpServletRequest

getHandler(request)

org.springframework.web.servlet.DispatcherServlet#getHandler

再次方法中會對請求進行處理,並找到合適的 Handler並返回,方法源碼如下:

	@Nullable
	protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
		if (this.handlerMappings != null) {
			/**
			 * 拿到所有的處理器映射器(handlerMappings)---容器初始化階段拿到所有實現了HandlerMapping接口的Bean
			 * @see DispatcherServlet#initHandlerMappings
			 * 測試發現:不同的handlerMapping可以有相同的path,誰先解析就用誰
			 */
			for (HandlerMapping mapping : this.handlerMappings) {
				HandlerExecutionChain handler = mapping.getHandler(request);
				if (handler != null) {
					return handler;
				}
			}
		}
		return null;
	}

再次方法中會調用mapping.getHandler(request)this.handlerMappings
就是對請求進行處理的處理起,此處測試接口上寫得就是 @RequestMapping對應的處理器就是 RequestMappingHandlerMapping
,測試發現:找到一個合適處理器就會直接進行返回,意思就是可能不會遍歷完所有的處理器,就算後面又能夠適配的,但是如果開始又可以處理的就直接返回了: image-20220830145038870

mapping.getHandler(request)

這個方法沒什麼好講的,感興趣的可以自己去debug看看,這裏講重要的東西,多個HandlerMethod同時匹配怎麼選擇的問題,按照spring
慣用肯定是會返回最合適的一個,就如同推斷構造方法進行分值計算一樣,下次有空再跟大家分享,推斷構造方法。

這裏Debug發現mapping就是RequestMappingHandlerMapping
,並且直接匹配進行返回了,這裏回去匹配路徑,可能會匹配到多個路徑,這裏就回去選擇對應的處理的HandlerMethod
,在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
這個方法裏面,會經過方法getHandlerInternal()

getHandlerInternal()

	protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
		// 拿到請求地址,通過 UrlPathHelper 解析的
		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();
		}
	}

這個方法中就會去拿到對應的方法路徑,並且調用lookupHandlerMethod(lookupPath, request);會返回唯一的HandlerMethod進行進一步封裝

記住return的代碼,這裏如果找到的 handlerMethod有值就會去調用createWithResolvedBean(),getBean()
去獲取對應的處理Bean,最後將處理方法以及對應的Handler封裝到 HandlerMethod中。如何所示:

image-20220830160010653

	public HandlerMethod createWithResolvedBean() {
		Object handler = this.bean;
		if (this.bean instanceof String beanName) {
			Assert.state(this.beanFactory != null, "Cannot resolve bean name without BeanFactory");
			handler = this.beanFactory.getBean(beanName);
		}
		return new HandlerMethod(this, handler);
	}

lookupHandlerMethod

	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="/mappin?")
				@RequestMapping(value="/mappin*")
				@RequestMapping(value="/{xxxx}")
				@RequestMapping(value="/**")
			 */
			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);
				if (logger.isTraceEnabled()) {
					logger.trace(matches.size() + " matching mappings: " + matches);
				}

				// 是否配置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(
								"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
					}
				}
			}
			// 將最匹配的設置到 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);
		}
	}

在源碼註釋中已經表示了匹配多個的情況是怎麼區分的,匹配多個的情況並且想要整長執行必須是使用通配符的方式(? > * > {} > **)
,如果出現兩個相同的路徑,mvc則會拋出異常,講到這裏差不多第一個步驟就結束了,找到了合適的 handlerMethod
並且將HandlerMethod存儲在了request中;

  • 存儲:request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.getHandlerMethod());
  • 存儲的key:String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
  • key= org.springframework.web.servlet.HandlerMapping.bestMatchingHandler

找到之後就會返回對應的Handler,就是能夠處理這個請求的那個 Bean,也是就是我們程序員些的 Controller

這裏尋找HandlerMethod會直接使用解析出來的路徑去pathLookup中去拿。pathLookup
是一個map,在springmvc啓動的時候就會解析我們定義的@RequestMapping中的值,並作爲key存儲在pathLookup
中;下一次講springmvc啓動流程的時候在解釋

public List<T> getMappingsByDirectPath(String urlPath){
        return this.pathLookup.get(urlPath);
        }

image-20220830174101017

找到HandlerAdapter#getHandlerAdapter

org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter

這個點對應請求流程圖中的第二步,找到合適的HandlerAdapter,我們看一下具體是怎麼找的

getHandlerAdapter

這裏傳進來的就是 在第一步中找到的:HandlerMethod,這裏和尋找Handler
一個套路的,找到合適的直接返回,不會再去走下面的 HandlerAdapter

	protected HandlerAdapter getHandlerAdapter(Object handler) throws ServletException {
		if (this.handlerAdapters != null) {
			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");
	}

adapter.supports(handler)

看一下個方法,其實也沒什麼,只是判斷當前的HandlerAdapter是否支持處理 handlerMethod

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

supportsInternal((HandlerMethod) handler) 這個方法在 RequestMappingHandlerAdapter類中默認返回 true

執行前置攔截器

org.springframework.web.servlet.HandlerExecutionChain#applyPreHandle

	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;
			}
            // 存儲執行到了哪些攔截器,如果出現了前置攔截器返回false的情況,那麼最終攔截器也只執行到i下標的那一個
			this.interceptorIndex = i;
		}
		return true;
	}

這個沒啥好講的,大家看一下這個源碼,意思就是遍歷掃描的時候拿到的所有的攔截器(實現了 HandlerIntercepter接口的)
,拿出來全部調用其preHandle 方法

注意:如果前置攔截器返回了false
那麼意思就代表此請求被攔截掉了,要去執行最終攔截器,這個點放到流程最後講。也就是調用攔截器的afterCompletion方法

執行Handler--會去執行真正的方法

org.springframework.web.servlet.HandlerAdapter#handle

handle()->handleInternal,最紅會執行到下面的handleInternal方法

org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal

方法執行棧:

image-20220830163838408

方法執行鏈:

  • handle: 執行controller方法的的進入點
  • getMethodArgumentValue:解析方法入參,解析完就回去真正的執行HandlerMethod
protected ModelAndView handleInternal(HttpServletRequest request,
        HttpServletResponse response,HandlerMethod handlerMethod)throws Exception{

        ModelAndView mav;

        // 檢查當前請求的method是否爲支持的method(默認爲null,可以通過繼承AbstractController設置supportedMethod)
        // 檢查當前請求是夠必須 session(默認爲false,可以通過繼承AbstractController 設置requireSession)
        checkRequest(request);

        /**
         * 判斷當前是否需要支持在同一個session中只能線性地處理請求
         * 因爲鎖是通過 synchronized 是 JVM 進程級,所以在分佈式環境下,
         * 無法達到同步相同 Session 的功能。默認情況下,synchronizeOnSession 爲 false
         */
        // Execute invokeHandlerMethod in synchronized block if required.
        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進行適配
        // No HttpSession available -> no mutex necessary
        mav=invokeHandlerMethod(request,response,handlerMethod);
        }
        }
        else{
        // No synchronization on session demanded at all...
        // *如果當前不需要對session進行同步處理,則直接對HandlerMethod進行適配
        mav=invokeHandlerMethod(request,response,handlerMethod);
        }

        //判斷當前請求頭中是否包含Cache-Control請求頭,如果不包含,則對當前response進行處理
        if(!response.containsHeader(HEADER_CACHE_CONTROL)){
        if(getSessionAttributesHandler(handlerMethod).hasSessionAttributes()){
        applyCacheSeconds(response,this.cacheSecondsForSessionAttributeHandlers);
        }
        else{
        prepareResponse(response);
        }
        }

        return mav;
        }

在這個方法中我們主要看invokeHandlerMethod()方法,從方法名稱就能看出是去執行開始選出來的handlerMethod
方法,也就是我們自己寫的controller的方法,下面看一下該方法的源碼,方法有中文註釋,推薦自己debug看一遍

invokHandlerMethod

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

        // 將request response 包裝成 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();
        }
        }

上述方法重要點就在 invocableMethod.invokeAndHandle(webRequest, mavContainer);,這裏就回去執行方法並且處理返回的對象

invokeAndHandle

public void invokeAndHandle(ServletWebRequest webRequest,ModelAndViewContainer mavContainer,
        Object...providedArgs)throws Exception{
        /* 真正的調用目標方法。很重要、很重要*/
        Object returnValue=invokeForRequest(webRequest,mavContainer,providedArgs);
        // 設置相關的返回狀態
        setResponseStatus(webRequest);

        // 如果請求完成,則設置requestHandler 屬性
        if(returnValue==null){
        if(isRequestNotModified(webRequest)||getResponseStatus()!=null||mavContainer.isRequestHandled()){
        disableContentCachingIfNecessary(webRequest);
        mavContainer.setRequestHandled(true);
        return;
        }
        }
        // 如果請求失敗但是有錯誤原因,也會設置 requestHandler 屬性
        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){
        if(logger.isTraceEnabled()){
        logger.trace(formatErrorForReturnValue(returnValue),ex);
        }
        throw ex;
        }
        }

invokeForRequest方法中有一個很重要的方法 getMethodArgumentValues

getMethodArgumentValues

protected Object[]getMethodArgumentValues(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,
        Object...providedArgs)throws Exception{
        // 獲取目標方法參數的描述數組對象
        MethodParameter[]parameters=getMethodParameters();
        if(ObjectUtils.isEmpty(parameters)){
        return EMPTY_ARGS;
        }
        //用來初始化我們對應參數名稱的參數值得數組
        Object[]args=new Object[parameters.length];
        // 循環拿到參數名數組
        for(int i=0;i<parameters.length;i++){
        MethodParameter parameter=parameters[i];
        //爲我們得MethodParameter設置參數名稱探測器對象
        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;
        }

解析完方法後就會去調用doInvokle執行Controller的方法。

注意:invokAndHandle中有處理返回值的方法調用,也就是下面這個

// 遍歷當前容器中所有ReturnValueHandler,判斷哪種handler支持當前返回值的處理,
      // 如果支持,則使用該handler處理該返回值
      this.returnValueHandlers.handleReturnValue(
            returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

handleReturnValue

public void handleReturnValue(@Nullable Object returnValue,MethodParameter returnType,
        ModelAndViewContainer mavContainer,NativeWebRequest webRequest)throws Exception{
        // 根據返回的類型選擇返回值解析器
        HandlerMethodReturnValueHandler handler=selectHandler(returnValue,returnType);
        if(handler==null){
        throw new IllegalArgumentException("Unknown return value type: "+returnType.getParameterType().getName());
        }
        handler.handleReturnValue(returnValue,returnType,mavContainer,webRequest);
        }

這個selectHandler選擇返回值解析器和之前的是一個套路,找到了直接返回解析器,然後調用解析器的HandleReturnValue
進行處理,這裏是返回的ModelAndVierw也就是jsp,對應的處理器就是ModelAndViewMethodValueHandler,如果是Json
那麼對應的是ReqeustResponseBodyMethodProcessor

image-20220830165026940

image-20220830170312989

這裏ModelAndView的返回類型的處理,方法在下面一個類中,自己也能夠看懂,講到這裏返回值返回ModelAndView就結束了。下面就是查找視圖以及渲染視圖

org.springframework.web.servlet.mvc.method.annotation.ModelAndViewMethodReturnValueHandler#handleReturnValue

查找視圖

org.springframework.web.servlet.DispatcherServlet#processDispatchResult

在此方法中進行處理,根據方法名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(logger.isTraceEnabled()){
        logger.trace("No view rendering, null ModelAndView returned.");
        }
        }

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

        if(mappedHandler!=null){
        // Exception (if any) is already handled.. 攔截器:AfterCompletion
        mappedHandler.triggerAfterCompletion(request,response,null);
        }
        }

此方法中的重要點render()mappedHandler.triggerAfterCompletion(request, response, null);,可以看到如果出現了異常,會進入異常視圖

  • rendee:視圖進行渲染
  • triggerAfterCompletion: 執行最終攔截器 afterCompletion
    方法的調用,這裏是請求成功沒有被攔截,所以直接調用所有的攔截器的 afterCompletion方法

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){
        // 解析視圖名稱
        // We need to resolve the view name.
        view=resolveViewName(viewName,mv.getModelInternal(),locale,request);
        if(view==null){
        throw new ServletException("Could not resolve view with name '"+mv.getViewName()+
        "' in servlet with name '"+getServletName()+"'");
        }
        }
        else{
        // No need to lookup: the ModelAndView object contains the actual View object.
        view=mv.getView();
        if(view==null){
        throw new ServletException("ModelAndView ["+mv+"] neither contains a view name nor a "+
        "View object in servlet with name '"+getServletName()+"'");
        }
        }

        ...省略 log 代碼...
        try{
        if(mv.getStatus()!=null){
        request.setAttribute(View.RESPONSE_STATUS_ATTRIBUTE,mv.getStatus());
        response.setStatus(mv.getStatus().value());
        }
        view.render(mv.getModelInternal(),request,response);
        }
        catch(Exception ex){
        ...省略 log 代碼...
        throw ex;
        }
        }

可以看到上面的源碼中有一個解析視圖名稱的代碼,其實就是前綴+viewName+後綴,但是:
這裏獲取選擇對應的視圖解析器,和上面的選擇套路一樣,此處還解析了反正值得前綴以及看是redirect還是forward
,根據這個前綴的不同創建不同的視圖解析器

image-20220830172309426

此處使用的是InternalResourceViewResolver,

image-20220830172103211

視圖渲染

view.render(mv.getModelInternal(), request, response);

最終會走到

org.springframework.web.servlet.view.InternalResourceView#renderMergedOutputModel

protected void renderMergedOutputModel(
        Map<String, Object> model,HttpServletRequest request,HttpServletResponse response)throws Exception{

        // Expose the model object as request attributes.
        // 將model設置到request 的Attribute 中
        exposeModelAsRequestAttributes(model,request);

        // Expose helpers as request attributes, if any.
        // 設置國際化資源
        exposeHelpers(request);

        // Determine the path for the request dispatcher.
        // 防止死循環,就是請求路徑和轉發路徑一致
        String dispatcherPath=prepareForRendering(request,response);

        // Obtain a RequestDispatcher for the target resource (typically a JSP).
        // 通過 request 拿到 RequestDispatcher;request.getRequestDispacther("/test.jsp")
        RequestDispatcher rd=getRequestDispatcher(request,dispatcherPath);
        if(rd==null){
        throw new ServletException("Could not get RequestDispatcher for ["+getUrl()+
        "]: Check that the corresponding file exists within your web application archive!");
        }

        // If already included or response already committed, perform include, else forward.
        if(useInclude(request,response)){
        response.setContentType(getContentType());
        if(logger.isDebugEnabled()){
        logger.debug("Including ["+getUrl()+"]");
        }
        rd.include(request,response);
        }

        else{
        // Note: The forwarded resource is supposed to determine the content type itself.
        if(logger.isDebugEnabled()){
        logger.debug("Forwarding to ["+getUrl()+"]");
        }
        // RequestDispatcher.forward直接轉發
        rd.forward(request,response);
        }
        }

最後是直接走的Servlet
的轉發,至此響應客戶端完成,還得注意方法中進行了一次判斷,防止請求死循環的。就是判斷請求路徑與轉發路徑是否一直,如果一致的情況下就會造成請求死循環,例如:請求路徑:/user/info
、轉發路徑:/user/info,這種情況下就會造成死循環。

最終處理器

org.springframework.web.servlet.HandlerExecutionChain#triggerAfterCompletion

最後進入doDispatcher中的 tiggerAfterCompletion
方法,這個請求是成功的所以所有的攔截器都需要執行最終攔截,不同於前置攔截器preHandle攔截的時候

image-20220830173001255

這裏會去直接循環調用攔截器的afterCompletion方法

void triggerAfterCompletion(HttpServletRequest request,HttpServletResponse response,@Nullable Exception ex){
        for(int i=this.interceptorIndex;i>=0;i--){
        HandlerInterceptor interceptor=this.interceptorList.get(i);
        try{
        interceptor.afterCompletion(request,response,this.handler,ex);
        }
        catch(Throwable ex2){
        logger.error("HandlerInterceptor.afterCompletion threw exception",ex2);
        }
        }
        }

至此請求結束。

如文中又錯誤請指出或者聯繫我:[email protected]

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章