SpringMVC的入參轉換和響應參數轉換

在上一篇文章中講了大概的執行流程,這裏詳細講一下入參轉換和響應轉換的實現原理
入參轉換和響應轉換的流程都是在通過Adapter調用HandlerMethod時發生的。
如下是請求頭設置,SpringMVC會根據content-type和accept類型選擇合適的HttpMessageConverter來進行消息的轉換
在這裏插入圖片描述

參數轉化

private Object[] getMethodArgumentValues(NativeWebRequest request, ModelAndViewContainer mavContainer,
			Object... providedArgs) throws Exception {

		MethodParameter[] parameters = getMethodParameters();
		Object[] args = new Object[parameters.length];
		for (int i = 0; i < parameters.length; i++) {
			MethodParameter parameter = parameters[i];
			parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
			args[i] = resolveProvidedArgument(parameter, providedArgs);
			if (args[i] != null) {
				continue;
			}
			//這裏使用組合模式遍歷所有參數解析器是否支持
			if (this.argumentResolvers.supportsParameter(parameter)) {
				try {
					args[i] = this.argumentResolvers.resolveArgument(
							parameter, mavContainer, request, this.dataBinderFactory);
					continue;
				}
			}
		}
		return args;
	}

在使用組合模式遍歷所有參數解析器是否支持時,由於我們的的控制器參數使用@RequestBody修飾,所以在遍歷到RequestResponseBodyMethodProcessor時,其supportsParameter會返回True

public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

因此會使用RequestResponseBodyMethodProcessor來繼續進行參數轉化,其resolveArgument方法爲

public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		parameter = parameter.nestedIfOptional();
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		String name = Conventions.getVariableNameForParameter(parameter);

		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());
			}
		}
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		return adaptArgumentIfNecessary(arg, parameter);
	}

可以看到其是通過readWithMessageConverters來進行參數解析的

protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
			Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {

		HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
		ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
		Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
		
		return arg;
	}

readWithMessageConverters()會對我們請求頭中的content-type進行解析,並對目標參數對象進行實例化,之後對內置的所有HttpMessageConverter進行遍歷,根據轉換器的支持content-type類型來匹配處理
在這裏插入圖片描述
在這裏插入圖片描述
匹配的convert會對內容進行轉換處理

響應轉換

由於Rest應用存在@Response註解,在組合模式選擇返回值類型轉換器時,同樣選擇的是RequestResponseBodyMethodProcessor,其supportsReturnType方法內容如下,可以看到就是在判定是否存在@ResponseBody註解

public boolean supportsReturnType(MethodParameter returnType) {
		return (AnnotatedElementUtils.hasAnnotation(returnType.getContainingClass(), ResponseBody.class) ||
				returnType.hasMethodAnnotation(ResponseBody.class));
	}

其他部分與入參類型轉換基本相同,會解析請求支持的MediaType,通過ContentNegotiationManager來進行解析

public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
		for (ContentNegotiationStrategy strategy : this.strategies) {
			List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
			if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
				continue;
			}
			return mediaTypes;
		}
		return Collections.emptyList();
	}

HeaderContentNegotiationStrategy會解析請求頭中的Accept
在這裏插入圖片描述
拿到MediaType後,會再去拿controller方法所能產生的MediaType列表,並將accept的MediaType和這個MediaType列表進行兼容判斷,將兼容的MediaType列表排序後選擇最具體的(不包含通配符)作爲最終選擇的MediaType,然後尋找支持該MediaType的HttpMessageConverter進行消息轉換

//這裏拿到請求頭中的Accept內容,如果沒有則返回通配符 */* 代表所有類型列表
List<MediaType> requestedMediaTypes = getAcceptableMediaTypes(request);
// 這裏拿到controller方法produces描述的能夠產生的媒體類型
List<MediaType> producibleMediaTypes = getProducibleMediaTypes(request, valueType, declaredType);
// 這裏會對上面的倆進行兼容性判斷,剔除掉不兼容的產生類型
Set<MediaType> compatibleMediaTypes = new LinkedHashSet<MediaType>();
		for (MediaType requestedType : requestedMediaTypes) {
			for (MediaType producibleType : producibleMediaTypes) {
				if (requestedType.isCompatibleWith(producibleType)) {
					compatibleMediaTypes.add(getMostSpecificMediaType(requestedType, producibleType));
				}
			}
		}
//兼容的產生類型做排序
List<MediaType> mediaTypes = new ArrayList<MediaType>(compatibleMediaTypes);
		MediaType.sortBySpecificityAndQuality(mediaTypes);
// 選擇最具體的兼容性產生類型
		MediaType selectedMediaType = null;
		for (MediaType mediaType : mediaTypes) {
			if (mediaType.isConcrete()) {
				selectedMediaType = mediaType;
				break;
			}
			else if (mediaType.equals(MediaType.ALL) || mediaType.equals(MEDIA_TYPE_APPLICATION)) {
				selectedMediaType = MediaType.APPLICATION_OCTET_STREAM;
				break;
			}
		}

這裏牽扯到了producesconsumes,如下代碼

@RestController
public class TestController {

    @PostMapping(value = "/test",
                produces = "application/json;charset=utf-8",
                consumes = "application/json;charset=utf-8")
    public String test(@RequestBody MyRequest request){
        return "hello "+request.getName();

    }
}

該方法consumes指定了只能接受請求頭中content-type爲application/json;charset=utf-8的請求;produces表示該方法可以產生application/json;charset=utf-8的媒體類型;這倆都是數組可以寫多個,這倆的字符編碼保持一致否則會亂碼

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