當用戶傳送請求之後,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
這個方法中的內容:
Handler
,在此步驟中拿到請求地址 例如: /user/info
org.springframework.web.servlet.DispatcherServlet#getHandler
HandlerAdapter
對映介面卡org.springframework.web.servlet.HandlerInterceptor#preHandle
Handler
org.springframework.web.servlet.HandlerInterceptor#postHandle
org.springframework.web.servlet.HandlerInterceptor#afterCompletion
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);
}
}
}
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
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
,測試發現:找到一個合適處理器就會直接進行返回,意思就是可能不會遍歷完所有的處理器,就算後面又能夠適配的,但是如果開始又可以處理的就直接返回了:
這個方法沒什麼好講的,感興趣的可以自己去debug看看,這裡講重要的東西,多個HandlerMethod
同時匹配怎麼選擇的問題,按照spring
慣用肯定是會返回最合適的一個,就如同推斷構造方法進行分值計算一樣,下次有空再跟大家分享,推斷構造方法。
這裡Debug發現mapping
就是RequestMappingHandlerMapping
,並且直接匹配進行返回了,這裡回去匹配路徑,可能會匹配到多個路徑,這裡就回去選擇對應的處理的HandlerMethod
,在org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod
這個方法裡面,會經過方法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
中。如何所示:
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);
}
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());
String BEST_MATCHING_HANDLER_ATTRIBUTE = HandlerMapping.class.getName() + ".bestMatchingHandler";
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);
}
org.springframework.web.servlet.DispatcherServlet#getHandlerAdapter
這個點對應請求流程圖中的第二步,找到合適的HandlerAdapter
,我們看一下具體是怎麼找的
這裡傳進來的就是 在第一步中找到的: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");
}
看一下個方法,其實也沒什麼,只是判斷當前的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
方法
org.springframework.web.servlet.HandlerAdapter#handle
handle()->handleInternal
,最紅會執行到下面的handleInternal
方法
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter#handleInternal
方法執行棧:
方法執行鏈:
controller
方法的的進入點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看一遍
@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);
,這裡就回去執行方法並且處理返回的物件
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
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);
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
這裡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
方法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
,根據這個字首的不同建立不同的檢視解析器
此處使用的是InternalResourceViewResolver
,
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
攔截的時候
這裡會去直接回圈呼叫攔截器的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]
本文來自部落格園,作者:BaldHead,轉載請註明原文連結:https://www.cnblogs.com/strict/p/16640734.html