在上一篇文章中講了大概的執行流程,這裏詳細講一下入參轉換和響應轉換的實現原理
入參轉換和響應轉換的流程都是在通過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;
}
}
這裏牽扯到了produces
和consumes
,如下代碼
@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
的媒體類型;這倆都是數組可以寫多個,這倆的字符編碼保持一致否則會亂碼