在上一篇文章中讲了大概的执行流程,这里详细讲一下入参转换和响应转换的实现原理
入参转换和响应转换的流程都是在通过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
的媒体类型;这俩都是数组可以写多个,这俩的字符编码保持一致否则会乱码