詳解SpringBoot中新增@ResponseBody註解會發生什麼
SpringBoot版本2.2.4.RELEASE。
【1】SpringBoot接收到請求
① springboot接收到一個請求返回json格式的列表,方法引數為JSONObject 格式,使用了註解@RequestBody
為什麼這裡要說明返回格式、方法引數、引數註解?因為方法引數與引數註解會影響你使用不同的引數解析器與後置處理器!通常使用WebDataBinder進行引數資料繫結結果也不同。
將要呼叫的目標方法如下:
@ApiOperation(value="分頁查詢") @RequestMapping(value = "/listPage",method = RequestMethod.POST) @ResponseBody public ResponseBean listPage(@RequestBody JSONObject params){ Integer pageNum = params.getInteger("pageNum"); Integer pageSize = params.getInteger("pageSize"); String vagueParam = params.getString("vagueParam"); IPage<TbSysGoodsCategory> indexPage = new Page<>(pageNum,pageSize); QueryWrapper<TbSysGoodsCategory> queryWrapper = new QueryWrapper<>(); if (!StringUtils.isEmpty(vagueParam)){ queryWrapper.like("name",vagueParam).or().like("code",vagueParam); } //排序 queryWrapper.orderByDesc("id"); indexPage = tbSysGoodsCategoryService.page(indexPage,queryWrapper); return new ResponseBean<>(true,indexPage,CommonEnum.SUCCESS_OPTION); }
如下所示,首先進入DispatcherServlet使用RequestMappingHandlerAdapter進行處理。
而RequestMappingHandlerAdapter (extends AbstractHandlerMethodAdapter)會呼叫父類AbstractHandlerMethodAdapter的handle方法進行處理。
AbstractHandlerMethodAdapter.handle方法原始碼如下:
@Override @Nullable public final ModelAndView handle(HttpServletRequest request,HttpServletResponse response,Object handler) throws Exception { return handleInternal(request,response,(HandlerMethod) handler); }
可以看到RequestMappingHandlerAdapter還實現了InitializingBean介面,該介面只有一個抽象方法afterPropertiesSet
用於在BeanFactory設定完bean屬性後執行,具體可參考博文:Spring - bean的初始化和銷燬幾種實現方式詳解
② RequestMappingHandlerAdapter.handleInternal
這裡首先在this.checkRequest(request)對請求進行了檢測,HttpRequestMethodNotSupportedException異常就是這裡丟擲的。
//1.檢測請求方法是否支援; //2.檢測是否需要session但是沒有獲取到 protected final void checkRequest(HttpServletRequest request) throws ServletException { // Check whether we should support the request method. String method = request.getMethod(); if (this.supportedMethods != null && !this.supportedMethods.contains(method)) { throw new HttpRequestMethodNotSupportedException(method,this.supportedMethods); } // Check whether a session is required. if (this.requireSession && request.getSession(false) == null) { throw new HttpSessionRequiredException("Pre-existing session required but none found"); } }
其他沒有什麼需要特殊說明的,然後直接呼叫了invokeHandlerMethod方法進行實際業務處理。
【2】RequestMappingHandlerAdapter.invokeHandlerMethod核心處理
RequestMappingHandlerAdapter.invokeHandlerMethod
這個方法十分重要,是請求處理流程中的核心方法。這個方法會根據handlerMethod獲取一個ServletInvocableHandlerMethod 並對其進行各種屬性設定然後呼叫其invokeAndHandle方法進行處理。
@Nullable protected ModelAndView invokeHandlerMethod(HttpServletRequest request,HandlerMethod handlerMethod) throws Exception { // 對應 2 ServletWebRequest webRequest = new ServletWebRequest(request,response); Object result; try { // 對應 3 WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod); // 對應 4 ModelFactory modelFactory = this.getModelFactory(handlerMethod,binderFactory); // 對應 5 ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer); // 對應 6 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); modelFactory.initModel(webRequest,mavContainer,invocableMethod); mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect); //對應 7 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()) { result = asyncManager.getConcurrentResult(); mavContainer = (ModelAndViewContainer)asyncManager.getConcurrentResultContext()[0]; asyncManager.clearConcurrentResult(); LogFormatUtils.traceDebug(this.logger,(traceOn) -> { String formatted = LogFormatUtils.formatValue(result,!traceOn); return "Resume with async result [" + formatted + "]"; }); invocableMethod = invocableMethod.wrapConcurrentResult(result); } //這裡會跳到【3】ServletInvocableHandlerMethod.invokeAndHandle invocableMethod.invokeAndHandle(webRequest,new Object[0]); if (!asyncManager.isConcurrentHandlingStarted()) { //這裡會跳到【4】RequestMappingHandlerAdapter.getModelAndView ModelAndView var15 = this.getModelAndView(mavContainer,modelFactory,webRequest); return var15; } result = null; } finally { //這裡會跳到【5】ServletWebRequest.requestCompleted webRequest.requestCompleted(); } return (ModelAndView)result; }
① 此時的handlerMethod是什麼?
如下圖所示,handlermethod裡面有bean、建立bean的工廠、bean的型別、原始方法method、橋接方法bridgedMethod以及引數物件parameters等關鍵屬性。
其他都容易理解,那麼什麼是bridgedMethod?(後續單獨分析)
② 此時的ServletWebRequest webRequest是什麼?
這個倒是很簡單,如下圖所示:
③ 此時的WebDataBinderFactory binderFactory
WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);
RequestMappingHandlerAdapter.getDataBinderFactory原始碼如下:
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception { //獲取handlerType Class<?> handlerType = handlerMethod.getBeanType(); //根據handlerType 從initBinderCache獲取到@InitBinder註解的方法 Set<Method> methods = this.initBinderCache.get(handlerType); //如果initBinderCache中沒有,就從handlerType查詢@InitBinder註解的方法 if (methods == null) { methods = MethodIntrospector.selectMethods(handlerType,INIT_BINDER_METHODS); this.initBinderCache.put(handlerType,methods); } List<InvocableHandlerMethod> initBinderMethods = new ArrayList<>(); //遍歷controllerAdviceBean的方法列表,從適合handlerType中拿到其方法列表 //然後封裝為一個個InvocableHandlerMethod放到initBinderMethods中 // 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)); } } }); for (Method method : methods) { Object bean = handlerMethod.getBean(); initBinderMethods.add(createInitBinderMethod(bean,method)); } return createDataBinderFactory(initBinderMethods); }
首先Class<?> handlerType = handlerMethod.getBeanType();
通過handlerMethod獲取到handlerTYPE,handlerTYPE聲明瞭當前完整類路徑以及類上面的註解。其值如下:
然後Set<Method> methods = this.initBinderCache.get(handlerType);
嘗試先從initBinderCache這個ConcurrentHashMap中獲取當前類的使用了InitBinder註解的方法列表。如果methods為空,則從handlerType中獲取使用了@InitBinder註解的方法,然後放到initBinderCache中,對應程式碼this.initBinderCache.put(handlerType,methods);
這個很關鍵。SpringBoot請求處理流程中最重要的一步就是資料繫結,即將引數寫到目標物件上面。那麼這裡就涉及到引數校驗、資料格式轉換、繫結結果物件、錯誤物件等。
最後return createDataBinderFactory(initBinderMethods);
其會拿到WebBindingInitializer建立資料繫結工廠,。
protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods) throws Exception { return new ServletRequestDataBinderFactory(binderMethods,getWebBindingInitializer()); }
DataBinderFactory其屬性ConfigurableWebBindingInitializer物件提供了基礎功能,該物件中WebConversionService中轉換器例項如下:
④ 根據handlerMethod和binderFactory獲取到ModelFactory modelFactory
ModelFactory modelFactory = this.getModelFactory(handlerMethod,binderFactory);
RequestMappingHandlerAdapter.getModelFactory方法原始碼如下:
private ModelFactory getModelFactory(HandlerMethod handlerMethod,WebDataBinderFactory binderFactory) { //獲取當前handlerMethod對應的handlerType的SessionAttributesHandler //--如果沒有就建立一個new SessionAttributesHandler(handlerType,this.sessionAttributeStore) //參考④-① SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod); //獲取handlerType Class<?> handlerType = handlerMethod.getBeanType(); //獲取添加了@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<>(); //從controllerAdviceBean中獲取適合當前handlerType的method, //並封裝為一個個InvocableHandlerMethod然後新增到attrMethods // Global methods first this.modelAttributeAdviceCache.forEach((controllerAdviceBean,methodSet) -> { if (controllerAdviceBean.isApplicableToBeanType(handlerType)) { Object bean = controllerAdviceBean.resolveBean(); for (Method method : methodSet) { attrMethods.add(createModelAttributeMethod(binderFactory,bean,method)); } } }); //遍歷methods並封裝為一個個InvocableHandlerMethod然後新增到attrMethods for (Method method : methods) { Object bean = handlerMethod.getBean(); attrMethods.add(createModelAttributeMethod(binderFactory,method)); } //根據attrMethods,binderFactory,sessionAttrHandler建立一個ModelFactory物件 return new ModelFactory(attrMethods,sessionAttrHandler); }
可以看到modelFactory主要屬性modelMethods、dataBindFactory和sessionAttributeHandler都是為引數繫結資料服務的。
④-① RequestMappingHandlerAdapter.getSessionAttributesHandler獲取給定型別的SessionAttributesHandler
方法原始碼如下:
private ModelFactory getModelFactory(HandlerMethod handlerMethod,sessionAttrHandler); }
④-①-①SessionAttributesHandler.SessionAttributesHandler
構造方法原始碼如下:
public SessionAttributesHandler(Class<?> handlerType,SessionAttributeStore sessionAttributeStore) { Assert.notNull(sessionAttributeStore,"SessionAttributeStore may not be null"); this.sessionAttributeStore = sessionAttributeStore; //嘗試從handlerType獲取@SessionAttributes註解 SessionAttributes ann = AnnotatedElementUtils.findMergedAnnotation(handlerType,SessionAttributes.class); if (ann != null) { //註解的name屬性值放入attributeNames中 Collections.addAll(this.attributeNames,ann.names()); //註解的type屬性值放入 attributeTypes Collections.addAll(this.attributeTypes,ann.types()); } //把所有的attributeNames放入knownAttributeNames //在初始化model方法initModel將會使用這些資料 this.knownAttributeNames.addAll(this.attributeNames); }
也就是經過③④兩步,建立binderFactory、modelFactory後就會拿到匹配當前handlerMethod的那些@InitBinder、@ModelAttribute的方法(HandlerMethod物件)以及SessionAttributesHandler !這三個東西能做什麼?當你為目標方法引數繫結資料的時候就會用到!
⑤ 建立核心處理物件ServletInvocableHandlerMethod invocableMethod併為其屬性賦值
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod); if (this.argumentResolvers != null) { invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers); } if (this.returnValueHandlers != null) { invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers); } invocableMethod.setDataBinderFactory(binderFactory); invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ServletInvocableHandlerMethod invocableMethod = this.createInvocableHandlerMethod(handlerMethod);
建立ServletInvocableHandlerMethod例項:
protected HandlerMethod(HandlerMethod handlerMethod) { Assert.notNull(handlerMethod,"HandlerMethod is required"); this.bean = handlerMethod.bean; this.beanFactory = handlerMethod.beanFactory; this.beanType = handlerMethod.beanType; this.method = handlerMethod.method; this.bridgedMethod = handlerMethod.bridgedMethod; this.parameters = handlerMethod.parameters; this.responseStatus = handlerMethod.responseStatus; this.responseStatusReason = handlerMethod.responseStatusReason; this.description = handlerMethod.description; this.resolvedFromHandlerMethod = handlerMethod.resolvedFromHandlerMethod; }
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
為invocableMethod設定引數解析器組合物件-HandlerMethodArgumentResolverComposite。其有List<HandlerMethodArgumentResolver> argumentResolvers
和Map<MethodParameter,HandlerMethodArgumentResolver> argumentResolverCache
兩個重要屬性。其中具體解析器值列表如下:
我想你現在應該知道為什麼方法引數使用@RequestBody就可以進行引數綁定了吧!
繼續看returnValueHandlers,也就是返回結果處理器。其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite例項,就像HandlermethodArgumentResolverComposite一樣,它包含了所有HandlerMethodReturnValueHandler的列表,並在Spring啟動時完成註冊。其值列表如下:
ok,我們的主題來了。就是這個RequestResponseBodyMethodProcessor後置處理器對@ResponseBody註解進行的處理!
繼續往下走,invocableMethod.setDataBinderFactory(binderFactory);
給invocableMethod設定了DataBinderFactory。這個同上都是為資料引數繫結服務,繼續往下看invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
parameterNameDiscoverer這裡值列表如下:
⑥ 建立mavContainer進行資料的初步處理
//建立ModelAndViewContainer 例項物件 ModelAndViewContainer mavContainer = new ModelAndViewContainer(); //從請求中獲取InputFlashMap並把其資料放入defaultModel中,flashmap的作用是在redirect中傳遞引數 mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request)); //呼叫modelFactory對model進行初始化 modelFactory.initModel(webRequest,invocableMethod); //重定向時忽略預設Model mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
首先看下ModelAndViewContainer,其核心有三個屬性view-檢視物件,defaultModel-預設的資料存放地方以及redirectModel-重定向時資料存放地方。
modelFactory.initModel(webRequest,invocableMethod);
,這裡對model做了處理。也可以說是對目標方法實際呼叫前對資料做的最後一次處理:
public void initModel(NativeWebRequest request,ModelAndViewContainer container,HandlerMethod handlerMethod) throws Exception { //獲取會話屬性鍵值對 Map<String,?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request); //對model中屬性-值進行合併處理:稱之為補缺更合適 //如果model中不存在,則放入---這個很重要 container.mergeAttributes(sessionAttributes); //呼叫標註了@ModelAttribute註解的方法 invokeModelAttributeMethods(request,container); //如果handlerMethod的方法引數標註了@ModelAttribute註解並且在sessionAttributetes存在/或型別匹配,則對其進行遍歷 //嘗試獲取值,如果獲取不到值就會丟擲異常;如果獲取到值就會放到model-defaultModel中 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); } } }
Map<String,?> sessionAttributes = this.sessionAttributesHandler.retrieveAttributes(request);
獲取會話屬性鍵值對,方法原始碼如下:
public Map<String,Object> retrieveAttributes(WebRequest request) { Map<String,Object> attributes = new HashMap<>(); //遍歷通過@SessionAttributes註解獲取的name for (String name : this.knownAttributeNames) { //從session中獲取name對應的值 Object value = this.sessionAttributeStore.retrieveAttribute(request,name); if (value != null) { //如果值存在,則放入attributes attributes.put(name,value); } } return attributes; }
container.mergeAttributes(sessionAttributes);
關於ModelMap.mergeAttributes合併屬性方法原始碼如下:
#也就是說遍歷sessionAttributes ,如果model中不存在,就放入。如果存在,就跳過!注意,不會進行值覆蓋 public ModelMap mergeAttributes(@Nullable Map<String,?> attributes) { if (attributes != null) { attributes.forEach((key,value) -> { if (!this.containsKey(key)) { this.put(key,value);//this這裡值的是modelMap,也就是defaultModel } }); } return this; }
這裡 invokeModelAttributeMethods(request,container);
呼叫了@ModelAttribute註解的方法,該方法通常會對model中的值進行更新。從另外一個方面來說呢,類裡面的@ModelAttribute方法會在目標方法呼叫前逐個進行呼叫!
,方法原始碼如下:
private void invokeModelAttributeMethods(NativeWebRequest request,ModelAndViewContainer container) throws Exception { //迴圈呼叫modelMethod while (!this.modelMethods.isEmpty()) { InvocableHandlerMethod modelMethod = getNextModelMethod(container).getHandlerMethod(); ModelAttribute ann = modelMethod.getMethodAnnotation(ModelAttribute.class); Assert.state(ann != null,"No ModelAttribute annotation"); if (container.containsAttribute(ann.name())) { if (!ann.binding()) { container.setBindingDisabled(ann.name()); } continue; } //反射呼叫方法並獲取返回值 Object returnValue = modelMethod.invokeForRequest(request,container); //如果返回值不為空,就放入model-(returnValueName,returnValue) if (!modelMethod.isVoid()){ String returnValueName = getNameForReturnValue(returnValue,modelMethod.getReturnType()); if (!ann.binding()) { container.setBindingDisabled(returnValueName); } if (!container.containsAttribute(returnValueName)) { container.addAttribute(returnValueName,returnValue); } } } }
關於findSessionAttributeArguments方法原始碼如下:
//從方法引數中找到在(@SessionAttributes註解的屬性/引數)中存在的或者型別匹配 // 且方法引數上標註了@ModelAttribute註解的屬性名集合 private List<String> findSessionAttributeArguments(HandlerMethod handlerMethod) { List<String> result = new ArrayList<>(); for (MethodParameter parameter : handlerMethod.getMethodParameters()) { if (parameter.hasParameterAnnotation(ModelAttribute.class)) { String name = getNameForParameter(parameter); Class<?> paramType = parameter.getParameterType(); if (this.sessionAttributesHandler.isHandlerSessionAttribute(name,paramType)) { result.add(name); } } } return result; }
⑦ 非同步請求
這一塊先不用管,後續分析
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); }
接下來呼叫invocableMethod.invokeAndHandle(webRequest,mavContainer);就到了ServletInvocableHandlerMethod.invokeAndHandle。
【3】呼叫目標方法並對返回值進行處理ServletInvocableHandlerMethod.invokeAndHandle
其類繼承示意圖如下:
ServletInvocableHandlerMethod.invokeAndHandle方法原始碼如下:
public void invokeAndHandle(ServletWebRequest webRequest,ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception { //呼叫目標方法並獲取返回值,這裡對應 【3.1】 InvocableHandlerMethod.invokeForRequest呼叫目標方法 Object returnValue = invokeForRequest(webRequest,providedArgs); //設定響應狀態 setResponseStatus(webRequest); //如果返回值為null,則將mavContainer.RequestHandled設定為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; } //將mavContainer.RequestHandled設定為false mavContainer.setRequestHandled(false); Assert.state(this.returnValueHandlers != null,"No return value handlers"); //返回值進行處理 ,這裡對應【3.2】 try { this.returnValueHandlers.handleReturnValue( returnValue,getReturnValueType(returnValue),webRequest); } catch (Exception ex) { if (logger.isTraceEnabled()) { logger.trace(formatErrorForReturnValue(returnValue),ex); } throw ex; } }
關於mavContainer.setRequestHandled(false);
原始碼如下:
public void setRequestHandled(boolean requestHandled) { this.requestHandled = requestHandled; }
檢驗請求添加了@ResponseBody註解的方法是否已經處理完,如果處理完則檢視解析不再需要。當方法引數有ServletResponse或者OutputStream型別時,同樣可以設定這個標識。requestHandled 預設值為false。
【3.1】 InvocableHandlerMethod.invokeForRequest呼叫目標方法
其方法原始碼如下所示,結構很清晰:獲取方法引數值然後呼叫目標方法:
public Object invokeForRequest(NativeWebRequest request,@Nullable ModelAndViewContainer mavContainer,Object... providedArgs) throws Exception { //解析引數--這裡對應 1 Object[] args = getMethodArgumentValues(request,providedArgs); if (logger.isTraceEnabled()) { logger.trace("Arguments: " + Arrays.toString(args)); } //根據上面得到的引數值呼叫目標方法 這裡對應 2 return doInvoke(args); }
① 解析引數getMethodArgumentValues
protected Object[] getMethodArgumentValues(NativeWebRequest request,Object... providedArgs) throws Exception { //獲取到方法的引數物件 MethodParameter[]陣列 MethodParameter[] parameters = getMethodParameters(); //如果為空,返回空引數組 if (ObjectUtils.isEmpty(parameters)) { return EMPTY_ARGS; } Object[] args = new Object[parameters.length]; //遍歷MethodParameter[] parameters,對每一個方法引數物件獲取到具體引數並解析得到引數值 for (int i = 0; i < parameters.length; i++) { MethodParameter parameter = parameters[i]; //繫結引數名稱發現器 parameter.initParameterNameDiscovery(this.parameterNameDiscoverer); //從providedArgs中嘗試獲取到引數名 args[i] = findProvidedArgument(parameter,providedArgs); if (args[i] != null) { continue; } //如果方法引數解析器不支援parameter,則丟擲異常 if (!this.resolvers.supportsParameter(parameter)) { throw new IllegalStateException(formatArgumentError(parameter,"No suitable resolver")); } try { //使用引數解析器解析引數獲取到值,下面會重點分析 args[i] = this.resolvers.resolveArgument(parameter,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; }
MethodParameter[] parameters = getMethodParameters();
這裡獲取的 MethodParameter[] parameters如下圖所示:
引數解析器組合物件( this.resolvers)列表如下所示:
為什麼稱之為引數解析器組合物件?其實這裡的this.resolvers並不是具體的引數解析器而是argumentResolvers、argumentResolverCache組合而成的HandlerMethodArgumentResolverComposite!
可以看到起還有argumentResolverCache屬性,其值列表如下:
預設argumentResolverCache是一個容量為256的ConcurrentHashMap,是HandlerMethodArgumentResolverComposite的成員變數:
private final Map<MethodParameter,HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);
這個argumentResolverCache是在動態改變,其在判斷是否支援paramter的方法中會改變,HandlerMethodArgumentResolverComposite.getArgumentResolver原始碼如下:
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) { //如果快取中有,則直接返回 HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter); //如果快取中沒有就嘗試從解析器列表中獲取一個支援parameter的,並將解析器 parameter放入快取 if (result == null) { for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) { if (resolver.supportsParameter(parameter)) { result = resolver; this.argumentResolverCache.put(parameter,result); break; } } } return result; }
為什麼要有argumentResolverCache ?你可以沒有,但是你就需要每次從argumentResolvers遍歷尋找支援當前MethodParameter的引數解析器!之所以儲存一份鍵值對資料到argumentResolverCache ,就是為了下次不用尋找,就是為了更快!
ok,引申多了。咱們繼續回去看如何解析引數獲取到引數值!
args[i] = this.resolvers.resolveArgument(parameter,this.dataBinderFactory);
這裡會呼叫HandlerMethodArgumentResolverComposite.resolveArgument
方法:
public Object resolveArgument(MethodParameter parameter,NativeWebRequest webRequest,@Nullable WebDataBinderFactory binderFactory) throws Exception { //這裡獲取具體的、實際的引數解析器! 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,webRequest,binderFactory); }
獲取的實際的引數解析器如下所示(是RequestResponseBodyMethodProcessor):
呼叫RequestResponseBodyMethodProcessor.resolveArgument解析引數:
public Object resolveArgument(MethodParameter parameter,binderFactory); }
獲取方法引數變數名稱String name = Conventions.getVariableNameForParameter(parameter);
Conventions.getVariableNameForParameter方法原始碼如下:
public static String getVariableNameForParameter(MethodParameter parameter) { Assert.notNull(parameter,"MethodParameter must not be null"); Class<?> valueClass; boolean pluralize = false; String reactiveSuffix = ""; //判斷引數型別是不是陣列 if (parameter.getParameterType().isArray()) { valueClass = parameter.getParameterType().getComponentType(); pluralize = true; } // 判斷是不是集合型別 else if (Collection.class.isAssignableFrom(parameter.getParameterType())) { valueClass = ResolvableType.forMethodParameter(parameter).asCollection().resolveGeneric(); if (valueClass == null) { throw new IllegalArgumentException( "Cannot generate variable name for non-typed Collection parameter type"); } pluralize = true; } else { //獲取引數型別,這裡是com.alibaba.fastjson.JSONObject valueClass = parameter.getParameterType(); ReactiveAdapter adapter = ReactiveAdapterRegistry.getSharedInstance().getAdapter(valueClass); if (adapter != null && !adapter.getDescriptor().isNoValue()) { reactiveSuffix = ClassUtils.getShortName(valueClass); valueClass = parameter.nested().getNestedParameterType(); } } String name = ClassUtils.getShortNameAsProperty(valueClass); return (pluralize ? pluralize(name) : name + reactiveSuffix); }
拿到引數變數名與引數值後,就會進行資料繫結過程。在這個過程中會使用binderFactory建立WebDataBinder物件,然後使用WebBindingInitializer對其進行初始化。
if (binderFactory != null) { WebDataBinder binder = binderFactory.createBinder(webRequest,arg,name); if (arg != null) { validateIfApplicable(binder,parameter); if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder,parameter)) { throw new MethodArgumentNotValidException(parameter,binder.getBindingResult()); } } if (mavContainer != null) { mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name,binder.getBindingResult()); } }
首先我們看一下WebDataBinder例項物件建立過程
。DefaultDataBinderFactory.createBinder方法原始碼如下:
public final WebDataBinder createBinder( NativeWebRequest webRequest,@Nullable Object target,String objectName) throws Exception { //建立WebDataBinder 例項 WebDataBinder dataBinder = createBinderInstance(target,objectName,webRequest); //如果初始化器不為null,進行初始化 if (this.initializer != null) { this.initializer.initBinder(dataBinder,webRequest); } //這是擴充套件介面,可以使用者自定義以進行更深入的初始化 initBinder(dataBinder,webRequest); return dataBinder; }
繼續跟createBinderInstance(target,webRequest);
,其會走到ServletRequestDataBinderFactory.createBinderInstance方法,如下所示:
可以發現器建立了一個ExtendedServletRequestDataBinder例項物件,其類繼承圖如下:
建立ExtendedServletRequestDataBinder例項物件時,一路呼叫父類的構造方法,最終跟到DataBinder類中:
public DataBinder(@Nullable Object target,String objectName) { this.ignoreUnknownFields = true; this.ignoreInvalidFields = false; this.autoGrowNestedPaths = true; this.autoGrowCollectionLimit = 256; this.bindingErrorProcessor = new DefaultBindingErrorProcessor(); this.validators = new ArrayList(); this.target = ObjectUtils.unwrapOptional(target); this.objectName = objectName; }
建立完資料繫結器後,就使用初始化器對其進行初始化,ConfigurableWebBindingInitializer.initBinder方法如下所示:
public void initBinder(WebDataBinder binder) { binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths); if (this.directFieldAccess) { binder.initDirectFieldAccess(); } if (this.messageCodesResolver != null) { binder.setMessageCodesResolver(this.messageCodesResolver); } if (this.bindingErrorProcessor != null) { binder.setBindingErrorProcessor(this.bindingErrorProcessor); } //如果target不為null且校驗器不為空,就繫結校驗器 if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) { binder.setValidator(this.validator); } //繫結型別轉換服務類 if (this.conversionService != null) { binder.setConversionService(this.conversionService); } if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { propertyEditorRegistrar.registerCustomEditors(binder); } } }
程式碼如下所示,初始化完WebDataBinder後,就嘗試使用binder的校驗器對parameter進行校驗(如果引數使用了@Valid註解或者以Valid開頭的註解)。校驗完後就會獲取org.springframework.validation.BeanPropertyBindingResult,如果BeanPropertyBindingResult有錯誤且你並沒有用一個Errors物件的引數接收異常,那麼就會丟擲MethodArgumentNotValidException異常!
public void initBinder(WebDataBinder binder) { binder.setAutoGrowNestedPaths(this.autoGrowNestedPaths); if (this.directFieldAccess) { binder.initDirectFieldAccess(); } if (this.messageCodesResolver != null) { binder.setMessageCodesResolver(this.messageCodesResolver); } if (this.bindingErrorProcessor != null) { binder.setBindingErrorProcessor(this.bindingErrorProcessor); } //如果target不為null且校驗器不為空,就繫結校驗器 if (this.validator != null && binder.getTarget() != null && this.validator.supports(binder.getTarget().getClass())) { binder.setValidator(this.validator); } //繫結型別轉換服務類 if (this.conversionService != null) { binder.setConversionService(this.conversionService); } if (this.propertyEditorRegistrars != null) { for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) { propertyEditorRegistrar.registerCustomEditors(binder); } } }
資料繫結這一塊就是引數從形參變為實參的最後一步!如何把請求中的引數值賦給方法的形參,就是通過WebDataBinder 這個物件實現的!可以看下此時binder物件:
有了繫結結果後的binder:
繼續往下走mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name,binder.getBindingResult());
把繫結結果放到model中:
OK!回到HandlerMethodArgumentResolverComposite.resolveArgument!
然後繼續回到InvocableHandlerMethod.getMethodArgumentValues
中:
因為本次請求的目標方法只有一個引數,則其會繼續返回到InvocableHandlerMethod.invokeForRequest
,也就是說到此,① 已經結束!
② 根據引數值反射呼叫目標方法
InvocableHandlerMethod.doInvoke方法原始碼如下:
protected Object doInvoke(Object... args) throws Exception { //獲取橋接方法並使方法可以呼叫 ReflectionUtils.makeAccessible(getBridgedMethod()); try { // 獲取橋接方法以及對應的bean 引數值,然後反射呼叫 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",targetException); } } }
這裡就會反射呼叫目標方法進行處理!處理完後會再次返回,一直返回到ServletInvocableHandlerMethod.invokeAndHandle!
到此【3.1】結束!已經呼叫了目標方法並獲取到了目標方法返回值!
【3.2】返回結果處理
ServletInvocableHandlerMethod.invokeAndHandle方法首先會反射呼叫目標方法,然後拿到方法返回值。最後會根據returnValueHandlers對返回結果進行處理!
this.returnValueHandlers.handleReturnValue( returnValue,webRequest);
這裡this.returnValueHandlers同樣是一個返回結果處理器組合物件,值列表如下:
① 獲取返回結果型別
HandlerMethod.getReturnValueType原始碼如下:
public MethodParameter getReturnValueType(@Nullable Object returnValue) { return new ReturnValueMethodParameter(returnValue); }
ReturnValueMethodParameter是HandlerMethod的內部類,並繼承自HandlerMethod.HandlerMethodParameter(沒錯,這貨也是HandlerMethod的內部類):
private class ReturnValueMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; public ReturnValueMethodParameter(@Nullable Object returnValue) { super(-1); this.returnValue = returnValue; } protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { super(original); this.returnValue = original.returnValue; } @Override public Class<?> getParameterType() { return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); } @Override public ReturnValueMethodParameter clone() { return new ReturnValueMethodParameter(this); } }
② 選擇HandlerMethodReturnValueHandler
HandlerMethodReturnValueHandlerComposite.handleReturnValue
方法原始碼如下:
private class ReturnValueMethodParameter extends HandlerMethodParameter { @Nullable private final Object returnValue; public ReturnValueMethodParameter(@Nullable Object returnValue) { super(-1); this.returnValue = returnValue; } protected ReturnValueMethodParameter(ReturnValueMethodParameter original) { super(original); this.returnValue = original.returnValue; } @Override public Class<?> getParameterType() { return (this.returnValue != null ? this.returnValue.getClass() : super.getParameterType()); } @Override public ReturnValueMethodParameter clone() { return new ReturnValueMethodParameter(this); } }
這裡returnType如下所示,其是HandlerMethod$ReturnValueMethodParameter
物件:
這裡尋找到的Handler是RequestResponseBodyMethodProcessor
:
還記得上面解析引數時,咱們獲取到的實際引數解析器也是這個RequestResponseBodyMethodProcessor!
也就是說RequestResponseBodyMethodProcessor就是用來處理@RequestBody和@ResponseBody的!它可以使用HttpMessageConverter從請求中讀資料賦給引數,並可以把返回結果扔給響應。HttpMessageConverter在這中間起到了什麼作用呢?顧名思義,資料格式轉換!
其類結構繼承圖如下:
③ 返回結果寫到outputMessage中
RequestResponseBodyMethodProcessor.handleReturnValue
原始碼如下:
@Override public void handleReturnValue(@Nullable Object returnValue,MethodParameter returnType,NativeWebRequest webRequest) throws IOException,HttpMediaTypeNotAcceptableException,HttpMessageNotWritableException { //設定請求已經被處理 mavContainer.setRequestHandled(true); //獲取一個ServletServerHttpRequest例項-建構函式引數為HttpServletRequest ServletServerHttpRequest inputMessage = createInputMessage(webRequest); //獲取一個 例項ServletServerHttpResponse ,建構函式引數為HttpServletResponse ServletServerHttpResponse outputMessage = createOutputMessage(webRequest); // Try even with null return value. ResponseBodyAdvice could get involved. writeWithMessageConverters(returnValue,returnType,inputMessage,outputMessage); }
記得在上面解析引數的時候呼叫過readWithMessageConverters方法,那時是從請求中獲取資料。這裡返回響應資訊需要把返回結果寫到響應體中。
AbstractMessageConverterMethodProcessor.writeWithMessageConverters
原始碼如下:
protected <T> void writeWithMessageConverters(@Nullable T value,ServletServerHttpRequest inputMessage,ServletServerHttpResponse outputMessage) throws IOException,HttpMessageNotWritableException { Object body; Class<?> valueType; Type targetType; if (value instanceof CharSequence) { body = value.toString(); valueType = String.class; targetType = String.class; } else { body = value; //值型別,這裡是class com.baby.healthcare.common.ResponseBean valueType = getReturnValueType(body,returnType); targetType = GenericTypeResolver.resolveType(getGenericType(returnType),returnType.getContainingClass()); } //判斷是否Resource 或InputStreamSource if (isResourceType(value,returnType)) { outputMessage.getHeaders().set(HttpHeaders.ACCEPT_RANGES,"bytes"); if (value != null && inputMessage.getHeaders().getFirst(HttpHeaders.RANGE) != null && outputMessage.getServletResponse().getStatus() == 200) { Resource resource = (Resource) value; try { List<HttpRange> httpRanges = inputMessage.getHeaders().getRange(); outputMessage.getServletResponse().setStatus(HttpStatus.PARTIAL_CONTENT.value()); body = HttpRange.toResourceRegions(httpRanges,resource); valueType = body.getClass(); targetType = RESOURCE_REGION_LIST_TYPE; } catch (IllegalArgumentException ex) { outputMessage.getHeaders().set(HttpHeaders.CONTENT_RANGE,"bytes */" + resource.contentLength()); outputMessage.getServletResponse().setStatus(HttpStatus.REQUESTED_RANGE_NOT_SATISFIABLE.value()); } } } MediaType selectedMediaType = null; //響應內容型別 MediaType contentType = outputMessage.getHeaders().getContentType(); boolean isContentTypePreset = contentType != null && contentType.isConcrete(); if (isContentTypePreset) { if (logger.isDebugEnabled()) { logger.debug("Found 'Content-Type:" + contentType + "' in response"); } selectedMediaType = contentType; } else { HttpServletRequest request = inputMessage.getServletRequest(); //獲取接收的MediaType List<MediaType> acceptableTypes = getAcceptableMediaTypes(request); //獲取返回結果的MediaType List<MediaType> producibleTypes = getProducibleMediaTypes(request,valueType,targetType); //如果body不為空,但是沒有合適的返回結果型別,則丟擲異常 if (body != null && producibleTypes.isEmpty()) { throw new HttpMessageNotWritableException( "No converter found for return value of type: " + valueType); } //迴圈比較,從acceptableTypes找到適配producibleTypes的 List<MediaType> mediaTypesToUse = new ArrayList<>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType,producibleType)); } } } if (mediaTypesToUse.isEmpty()) { if (body != null) { throw new HttpMediaTypeNotAcceptableException(producibleTypes); } if (logger.isDebugEnabled()) { logger.debug("No match for " + acceptableTypes + ",supported: " + producibleTypes); } return; } //對MediaType進行排序 MediaType.sortBySpecificityAndQuality(mediaTypesToUse); for (MediaType mediaType : mediaTypesToUse) { if (mediaType.isConcrete()) { selectedMediaType = mediaType; break; } else if (mediaType.isPresentIn(ALL_APPLICATION_MEDIA_TYPES)) { selectedMediaType = MediaType.APPLICATION_OCTET_STREAM; break; } } if (logger.isDebugEnabled()) { logger.debug("Using '" + selectedMediaType + "',given " + acceptableTypes + " and supported " + producibleTypes); } } if (selectedMediaType != null) { selectedMediaType = selectedMediaType.removeQualityValue(); for (HttpMessageConverter<?> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType,selectedMediaType) : converter.canWrite(valueType,selectedMediaType)) { body = getAdvice().beforeBodyWrite(body,selectedMediaType,(Class<? extends HttpMessageConverter<?>>) converter.getClass(),outputMessage); if (body != null) { Object theBody = body; LogFormatUtils.traceDebug(logger,traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody,!traceOn) + "]"); addContentDispositionHeader(inputMessage,outputMessage); if (genericConverter != null) { genericConverter.write(body,targetType,outputMessage); } else { ((HttpMessageConverter) converter).write(body,outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body"); } } return; } } } if (body != null) { Set<MediaType> producibleMediaTypes = (Set<MediaType>) inputMessage.getServletRequest() .getAttribute(HandlerMapping.PRODUCIBLE_MEDIA_TYPES_ATTRIBUTE); if (isContentTypePreset || !CollectionUtils.isEmpty(producibleMediaTypes)) { throw new HttpMessageNotWritableException( "No converter for [" + valueType + "] with preset Content-Type '" + contentType + "'"); } throw new HttpMediaTypeNotAcceptableException(this.allSupportedMediaTypes); } }
關於MediaType、MimeType與ContType對照表可以參考博文:ContentType與MIME對照表
迴圈比較,從acceptableTypes找到適配producibleTypes的:
//迴圈比較,從acceptableTypes找到適配producibleTypes的 List<MediaType> mediaTypesToUse = new ArrayList<>(); for (MediaType requestedType : acceptableTypes) { for (MediaType producibleType : producibleTypes) { if (requestedType.isCompatibleWith(producibleType)) { mediaTypesToUse.add(getMostSpecificMediaType(requestedType,producibleType)); } } }
請求接收的內容型別與返回響應的內容型別如下所示:
MediaType.sortBySpecificityAndQuality(mediaTypesToUse);
對mediaTypesToUse進行排序,排序後的效果如下所示:
尋找合適的轉換器把body寫到outputMessage中:
for (HttpMessageConverter<?> converter : this.messageConverters) { GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null); if (genericConverter != null ? ((GenericHttpMessageConverter) converter).canWrite(targetType,selectedMediaType) : converter.canWrite(valueType,selectedMediaType)) { body = getAdvice().beforeBodyWrite(body,outputMessage); if (body != null) { Object theBody = body; LogFormatUtils.traceDebug(logger,traceOn -> "Writing [" + LogFormatUtils.formatValue(theBody,!traceOn) + "]"); addContentDispositionHeader(inputMessage,outputMessage); if (genericConverter != null) { genericConverter.write(body,outputMessage); } else { ((HttpMessageConverter) converter).write(body,outputMessage); } } else { if (logger.isDebugEnabled()) { logger.debug("Nothing to write: null body"); } } return; } }
這裡遍歷的messageConverters如下所示:
genericConverter.write(body,outputMessage);
最後使用MappingJackson2HttpMessageConverter把body寫到outputMessage中。其類結構繼承示意圖如下:
往響應輸出流中寫完返回結果並flush後就會依次返回,此時【3.2】HandlerMethodReturnValueHandlerComposite.handleReturnValue返回結果處理執行完畢!
然後返回到ServletInvocableHandlerMethod.invokeAndHandle,此時【3】執行完畢!
【4】RequestMappingHandlerAdapter.getModelAndView嘗試獲取檢視物件
RequestMappingHandlerAdapter.getModelAndView方法原始碼如下:
@Nullable private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,ModelFactory modelFactory,NativeWebRequest webRequest) throws Exception { //更新model modelFactory.updateModel(webRequest,mavContainer); //如果請求已經處理完,則直接返回,不會再嘗試建立mav 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; }
① modelFactory.updateModel(webRequest,mavContainer);
更新model
- @SessionAttributes註解的屬性鍵值對放到session中;
- 如果請求沒有處理完畢,則嘗試更新model中的BindingResult
② 如果請求處理完畢,則直接返回null
如下圖所示,在【3.2】-③中handleReturnValue首先將requestHandled設定為true。那麼自然不會往下走去獲取檢視名並嘗試解析
【5】ServletWebRequest.requestCompleted
ServletWebRequest類繼承示意圖如下:
其會直接呼叫AbstractRequestAttributes.requestCompleted
,方法原始碼如下:
#標記這個請求已經被完成 ##呼叫所有的銷燬回撥方法 ##更新請求過程中訪問到的會話屬性 public void requestCompleted() { executeRequestDestructionCallbacks(); updateAccessedSessionAttributes(); this.requestActive = false; }
AbstractRequestAttributes.executeRequestDestructionCallbacks原始碼如下,其會遍歷requestDestructionCallbacks並依次執行每個Runnable。
private void executeRequestDestructionCallbacks() { //這裡使用synchronized 保證每個runnable 只被呼叫一次 synchronized (this.requestDestructionCallbacks) { for (Runnable runnable : this.requestDestructionCallbacks.values()) { runnable.run(); } this.requestDestructionCallbacks.clear(); } }
然後依次返回到RequestMappingHandlerAdapter.handleInternal也就是【1】-②:
如果響應頭中不包含快取控制Cache-Control,則嘗試對response進行Cache-Control設定:
if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response,this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } }
然後會返回到AbstractHandlerMethodAdapter.handle方法,然後回到DispatcherServlet.doDispatch,這是獲取到的MV為null。
【6】DispatcherServlet剩下的處理
① applyDefaultViewName(processedRequest,mv);嘗試獲取檢視名字
原始碼如下所示,這裡MV為null,自然不存在view name。
private void applyDefaultViewName(HttpServletRequest request,@Nullable ModelAndView mv) throws Exception { if (mv != null && !mv.hasView()) { String defaultViewName = getDefaultViewName(request); if (defaultViewName != null) { mv.setViewName(defaultViewName); } } }
② mappedHandler.applyPostHandle(processedRequest,mv);方法後置處理
其實就是執行攔截器的後置方法postHandle,HandlerExecutionChain.applyPostHandle原始碼如下:
void applyPostHandle(HttpServletRequest request,@Nullable ModelAndView mv) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = interceptors.length - 1; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; interceptor.postHandle(request,this.handler,mv); } } }
③ processDispatchResult(processedRequest,mappedHandler,mv,dispatchException);
如果存在異常,則MV為 ((ModelAndViewDefiningException) exception).getModelAndView();然後進行render(mv,response);
;
如果不存在異常,且MV不為null,則進行render(mv,response);
;
如果MV不存在,則不會進行render(mv,response);
;,其會直接呼叫mappedHandler.triggerAfterCompletion(request,null);
。
private void processDispatchResult(HttpServletRequest request,@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,handler,exception); errorView = (mv != null); } } // Did the handler return a view to render? if (mv != null && !mv.wasCleared()) { render(mv,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.. mappedHandler.triggerAfterCompletion(request,null); } }
攔截器的完成方法afterCompletion呼叫,HandlerExecutionChain.triggerAfterCompletion方法原始碼如下:
void triggerAfterCompletion(HttpServletRequest request,@Nullable Exception ex) throws Exception { HandlerInterceptor[] interceptors = getInterceptors(); if (!ObjectUtils.isEmpty(interceptors)) { for (int i = this.interceptorIndex; i >= 0; i--) { HandlerInterceptor interceptor = interceptors[i]; try { interceptor.afterCompletion(request,ex); } catch (Throwable ex2) { logger.error("HandlerInterceptor.afterCompletion threw exception",ex2); } } } }
最後執行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); } }
到此這篇關於詳解SpringBoot中新增@ResponseBody註解會發生什麼的文章就介紹到這了,更多相關SpringBoot新增@ResponseBody內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!