中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

SpringBoot項目中什么情況下需要添加@ResponseBody注解

發布時間:2020-11-11 14:36:02 來源:億速云 閱讀:382 作者:Leah 欄目:開發技術

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進行處理。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

而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);
}

SpringBoot項目中什么情況下需要添加@ResponseBody注解

可以看到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方法進行實際業務處理。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

【2】RequestMappingHandlerAdapter.invokeHandlerMethod核心處理

RequestMappingHandlerAdapter.invokeHandlerMethod

這個方法十分重要,是請求處理流程中的核心方法。這個方法會根據handlerMethod獲取一個ServletInvocableHandlerMethod 并對其進行各種屬性設置然后調用其invokeAndHandle方法進行處理。

 @Nullable
  protected ModelAndView invokeHandlerMethod(HttpServletRequest request, HttpServletResponse response, 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, mavContainer, 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&#63;(后續單獨分析)

SpringBoot項目中什么情況下需要添加@ResponseBody注解

② 此時的ServletWebRequest webRequest是什么?

這個倒是很簡單,如下圖所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

③ 此時的WebDataBinderFactory binderFactory

WebDataBinderFactory binderFactory = this.getDataBinderFactory(handlerMethod);

RequestMappingHandlerAdapter.getDataBinderFactory源碼如下:

private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
//獲取handlerType 
		Class<&#63;> 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<&#63;> handlerType = handlerMethod.getBeanType();通過handlerMethod獲取到handlerTYPE,handlerTYPE聲明了當前完整類路徑以及類上面的注解。其值如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

然后Set<Method> methods = this.initBinderCache.get(handlerType);嘗試先從initBinderCache這個ConcurrentHashMap中獲取當前類的使用了InitBinder注解的方法列表。如果methods為空,則從handlerType中獲取使用了@InitBinder注解的方法,然后放到initBinderCache中,對應代碼this.initBinderCache.put(handlerType, methods);

SpringBoot項目中什么情況下需要添加@ResponseBody注解

這個很關鍵。SpringBoot請求處理流程中最重要的一步就是數據綁定,即將參數寫到目標對象上面。那么這里就涉及到參數校驗、數據格式轉換、綁定結果對象、錯誤對象等。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

最后return createDataBinderFactory(initBinderMethods);其會拿到WebBindingInitializer創建數據綁定工廠,。

protected InitBinderDataBinderFactory createDataBinderFactory(List<InvocableHandlerMethod> binderMethods)
			throws Exception {
		return new ServletRequestDataBinderFactory(binderMethods, getWebBindingInitializer());
}

DataBinderFactory其屬性ConfigurableWebBindingInitializer對象提供了基礎功能,該對象中WebConversionService中轉換器實例如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

④ 根據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<&#63;> 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, bean, method));
		}
//根據attrMethods, binderFactory, sessionAttrHandler創建一個ModelFactory對象
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

可以看到modelFactory主要屬性modelMethods、dataBindFactory和sessionAttributeHandler都是為參數綁定數據服務的。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

④-① RequestMappingHandlerAdapter.getSessionAttributesHandler獲取給定類型的SessionAttributesHandler

方法源碼如下:

private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
//獲取當前handlerMethod對應的handlerType的SessionAttributesHandler 
//--如果沒有就創建一個new SessionAttributesHandler(handlerType, this.sessionAttributeStore)
//參考④-①
		SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
//獲取handlerType 
		Class<&#63;> 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, bean, method));
		}
//根據attrMethods, binderFactory, sessionAttrHandler創建一個ModelFactory對象
		return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
	}

④-①-①SessionAttributesHandler.SessionAttributesHandler

構造方法源碼如下:

public SessionAttributesHandler(Class<&#63;> 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> argumentResolversMap<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache兩個重要屬性。其中具體解析器值列表如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

我想你現在應該知道為什么方法參數使用@RequestBody就可以進行參數綁定了吧!

繼續看returnValueHandlers,也就是返回結果處理器。其中returnValueHandlers是HandlerMethodReturnValueHandlerComposite實例,就像HandlermethodArgumentResolverComposite一樣,它包含了所有HandlerMethodReturnValueHandler的列表,并在Spring啟動時完成注冊。其值列表如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

ok,我們的主題來了。就是這個RequestResponseBodyMethodProcessor后置處理器對@ResponseBody注解進行的處理!

繼續往下走,invocableMethod.setDataBinderFactory(binderFactory);給invocableMethod設置了DataBinderFactory。這個同上都是為數據參數綁定服務,繼續往下看invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);parameterNameDiscoverer這里值列表如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

⑥ 創建mavContainer進行數據的初步處理

//創建ModelAndViewContainer 實例對象
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//從請求中獲取InputFlashMap并把其數據放入defaultModel中,flashmap的作用是在redirect中傳遞參數
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//調用modelFactory對model進行初始化
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
//重定向時忽略默認Model
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);

首先看下ModelAndViewContainer,其核心有三個屬性view-視圖對象,defaultModel-默認的數據存放地方以及redirectModel-重定向時數據存放地方。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

modelFactory.initModel(webRequest, mavContainer, invocableMethod);,這里對model做了處理。也可以說是對目標方法實際調用前對數據做的最后一次處理:

public void initModel(NativeWebRequest request, ModelAndViewContainer container, HandlerMethod handlerMethod)
			throws Exception {
	//獲取會話屬性鍵值對
		Map<String, &#63;> 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, &#63;> 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, &#63;> 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<&#63;> 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

其類繼承示意圖如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

ServletInvocableHandlerMethod.invokeAndHandle方法源碼如下:

public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {
//調用目標方法并獲取返回值,這里對應 【3.1】 InvocableHandlerMethod.invokeForRequest調用目標方法
		Object returnValue = invokeForRequest(webRequest, mavContainer, 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), mavContainer, 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, mavContainer, providedArgs);
	if (logger.isTraceEnabled()) {
		logger.trace("Arguments: " + Arrays.toString(args));
	}
	//根據上面得到的參數值調用目標方法 這里對應 2 
	return doInvoke(args);
}

① 解析參數getMethodArgumentValues

protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
			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, 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;
	}

MethodParameter[] parameters = getMethodParameters();這里獲取的 MethodParameter[] parameters如下圖所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

參數解析器組合對象( this.resolvers)列表如下所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

為什么稱之為參數解析器組合對象?其實這里的this.resolvers并不是具體的參數解析器而是argumentResolvers、argumentResolverCache組合而成的HandlerMethodArgumentResolverComposite!

可以看到起還有argumentResolverCache屬性,其值列表如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

默認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, mavContainer, request, this.dataBinderFactory);

這里會調用HandlerMethodArgumentResolverComposite.resolveArgument方法:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			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, mavContainer, webRequest, binderFactory);
	}

獲取的實際的參數解析器如下所示(是RequestResponseBodyMethodProcessor):

SpringBoot項目中什么情況下需要添加@ResponseBody注解

調用RequestResponseBodyMethodProcessor.resolveArgument解析參數:

public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			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, mavContainer, webRequest, binderFactory);
	}

獲取方法參數變量名稱String name = Conventions.getVariableNameForParameter(parameter);Conventions.getVariableNameForParameter方法源碼如下:

public static String getVariableNameForParameter(MethodParameter parameter) {
		Assert.notNull(parameter, "MethodParameter must not be null");
		Class<&#63;> 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 &#63; 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, objectName, webRequest);,其會走到ServletRequestDataBinderFactory.createBinderInstance方法,如下所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

可以發現器創建了一個ExtendedServletRequestDataBinder實例對象,其類繼承圖如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

創建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對象:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

有了綁定結果后的binder:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

繼續往下走mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());把綁定結果放到model中:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

OK!回到HandlerMethodArgumentResolverComposite.resolveArgument!

SpringBoot項目中什么情況下需要添加@ResponseBody注解

然后繼續回到InvocableHandlerMethod.getMethodArgumentValues中:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

因為本次請求的目標方法只有一個參數,則其會繼續返回到InvocableHandlerMethod.invokeForRequest,也就是說到此,① 已經結束!

SpringBoot項目中什么情況下需要添加@ResponseBody注解

② 根據參數值反射調用目標方法

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 &#63; ex.getMessage() : "Illegal argument");
			throw new IllegalStateException(formatInvokeError(text, args), ex);
		}
		catch (InvocationTargetException ex) {
			// Unwrap for HandlerExceptionResolvers ...
			Throwable targetException = ex.getTargetException();
			if (targetException instanceof RuntimeException) {
				throw (RuntimeException) targetException;
			}
			else if (targetException instanceof Error) {
				throw (Error) targetException;
			}
			else if (targetException instanceof Exception) {
				throw (Exception) targetException;
			}
			else {
				throw new IllegalStateException(formatInvokeError("Invocation failure", args), targetException);
			}
		}
	}

這里就會反射調用目標方法進行處理!處理完后會再次返回,一直返回到ServletInvocableHandlerMethod.invokeAndHandle!

SpringBoot項目中什么情況下需要添加@ResponseBody注解

到此【3.1】結束!已經調用了目標方法并獲取到了目標方法返回值!

【3.2】返回結果處理

ServletInvocableHandlerMethod.invokeAndHandle方法首先會反射調用目標方法,然后拿到方法返回值。最后會根據returnValueHandlers對返回結果進行處理!

this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);

這里this.returnValueHandlers同樣是一個返回結果處理器組合對象,值列表如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

① 獲取返回結果類型

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<&#63;> getParameterType() {
			return (this.returnValue != null &#63; this.returnValue.getClass() : super.getParameterType());
		}

		@Override
		public ReturnValueMethodParameter clone() {
			return new ReturnValueMethodParameter(this);
		}
	}

SpringBoot項目中什么情況下需要添加@ResponseBody注解

② 選擇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<&#63;> getParameterType() {
			return (this.returnValue != null &#63; this.returnValue.getClass() : super.getParameterType());
		}

		@Override
		public ReturnValueMethodParameter clone() {
			return new ReturnValueMethodParameter(this);
		}
	}

這里returnType如下所示,其是HandlerMethod$ReturnValueMethodParameter對象:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

這里尋找到的Handler是RequestResponseBodyMethodProcessor

SpringBoot項目中什么情況下需要添加@ResponseBody注解

還記得上面解析參數時,咱們獲取到的實際參數解析器也是這個RequestResponseBodyMethodProcessor!

SpringBoot項目中什么情況下需要添加@ResponseBody注解

也就是說RequestResponseBodyMethodProcessor就是用來處理@RequestBody和@ResponseBody的!它可以使用HttpMessageConverter從請求中讀數據賦給參數,并可以把返回結果扔給響應。HttpMessageConverter在這中間起到了什么作用呢?顧名思義,數據格式轉換!

其類結構繼承圖如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

③ 返回結果寫到outputMessage中

RequestResponseBodyMethodProcessor.handleReturnValue源碼如下:

@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
	ModelAndViewContainer mavContainer, 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方法,那時是從請求中獲取數據。這里返回響應信息需要把返回結果寫到響應體中。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

AbstractMessageConverterMethodProcessor.writeWithMessageConverters源碼如下:

protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {

		Object body;
		Class<&#63;> 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<&#63;> converter : this.messageConverters) {
				GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter &#63;
						(GenericHttpMessageConverter<&#63;>) converter : null);
				if (genericConverter != null &#63;
						((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
						converter.canWrite(valueType, selectedMediaType)) {
					body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
							(Class<&#63; extends HttpMessageConverter<&#63;>>) converter.getClass(),
							inputMessage, 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, selectedMediaType, outputMessage);
						}
						else {
							((HttpMessageConverter) converter).write(body, selectedMediaType, 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對照表

SpringBoot項目中什么情況下需要添加@ResponseBody注解

循環比較,從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));
					}
				}
			}

請求接收的內容類型與返回響應的內容類型如下所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

MediaType.sortBySpecificityAndQuality(mediaTypesToUse);對mediaTypesToUse進行排序,排序后的效果如下所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

尋找合適的轉換器把body寫到outputMessage中:

for (HttpMessageConverter<&#63;> converter : this.messageConverters) {
GenericHttpMessageConverter genericConverter = (converter instanceof GenericHttpMessageConverter &#63;
			(GenericHttpMessageConverter<&#63;>) converter : null);
	if (genericConverter != null &#63;
			((GenericHttpMessageConverter) converter).canWrite(targetType, valueType, selectedMediaType) :
			converter.canWrite(valueType, selectedMediaType)) {
		body = getAdvice().beforeBodyWrite(body, returnType, selectedMediaType,
				(Class<&#63; extends HttpMessageConverter<&#63;>>) converter.getClass(),
				inputMessage, 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, selectedMediaType, outputMessage);
			}
			else {
				((HttpMessageConverter) converter).write(body, selectedMediaType, outputMessage);
			}
		}
		else {
			if (logger.isDebugEnabled()) {
				logger.debug("Nothing to write: null body");
			}
		}
		return;
	}
}

這里遍歷的messageConverters如下所示:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

genericConverter.write(body, targetType, selectedMediaType, outputMessage);最后使用MappingJackson2HttpMessageConverter把body寫到outputMessage中。其類結構繼承示意圖如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

往響應輸出流中寫完返回結果并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, &#63;> 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。那么自然不會往下走去獲取視圖名并嘗試解析

SpringBoot項目中什么情況下需要添加@ResponseBody注解

【5】ServletWebRequest.requestCompleted

ServletWebRequest類繼承示意圖如下:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

其會直接調用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】-②:

SpringBoot項目中什么情況下需要添加@ResponseBody注解

如果響應頭中不包含緩存控制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。

SpringBoot項目中什么情況下需要添加@ResponseBody注解

【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, response, mv);方法后置處理

其實就是執行攔截器的后置方法postHandle,HandlerExecutionChain.applyPostHandle源碼如下:

void applyPostHandle(HttpServletRequest request, HttpServletResponse response, @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, response, this.handler, mv);
			}
		}
	}

③ processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);

如果存在異常,則MV為 ((ModelAndViewDefiningException) exception).getModelAndView();然后進行render(mv, request, response);;

如果不存在異常,且MV不為null,則進行render(mv, request, response);;

如果MV不存在,則不會進行render(mv, request, response);;,其會直接調用mappedHandler.triggerAfterCompletion(request, response, null);

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 &#63; mappedHandler.getHandler() : null);
				mv = processHandlerException(request, response, handler, exception);
				errorView = (mv != null);
			}
		}

		// Did the handler return a view to render&#63;
		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..
			mappedHandler.triggerAfterCompletion(request, response, null);
		}
	}

攔截器的完成方法afterCompletion調用,HandlerExecutionChain.triggerAfterCompletion方法源碼如下:

void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @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, response, this.handler, 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注解問題的解答就分享到這里了,希望以上內容可以對大家有一定的幫助,如果你還有很多疑惑沒有解開,可以關注億速云行業資訊頻道了解更多相關知識。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

井陉县| 谢通门县| 凤台县| 团风县| 建昌县| 日土县| 莱阳市| 石阡县| 通辽市| 河源市| 霍州市| 饶河县| 平邑县| 定西市| 乌鲁木齐市| 乡宁县| 东丰县| 体育| 三江| 旬邑县| 澄江县| 子长县| 翁源县| 洛宁县| 孟津县| 时尚| 织金县| 东乡县| 陈巴尔虎旗| 宜阳县| 馆陶县| 安岳县| 土默特右旗| 贵南县| 安陆市| 葫芦岛市| 桃园县| 黑河市| 璧山县| 沛县| 平阴县|