上一篇记录了处理请求的过程,这篇记录对请求参数与返回参数的处理
在前面分析《Spring MVC 源码笔记 @EnableWebMvc》中,提到了完全配置类WebMvcConfigurationSupport
,正是在这里声明的beanRequestMappingHandlerAdapter
,创建了默认的消息转换器和方法参数解析器。
WebMvcConfigurationSupport
@Bean
public RequestMappingHandlerAdapter requestMappingHandlerAdapter() {
RequestMappingHandlerAdapter adapter = createRequestMappingHandlerAdapter();
adapter.setContentNegotiationManager(mvcContentNegotiationManager());
adapter.setMessageConverters(getMessageConverters());
adapter.setWebBindingInitializer(getConfigurableWebBindingInitializer());
adapter.setCustomArgumentResolvers(getArgumentResolvers());
adapter.setCustomReturnValueHandlers(getReturnValueHandlers());
if (jackson2Present) {
adapter.setRequestBodyAdvice(
Collections.singletonList(new JsonViewRequestBodyAdvice()));
adapter.setResponseBodyAdvice(
Collections.>singletonList(new JsonViewResponseBodyAdvice()));
}
AsyncSupportConfigurer configurer = new AsyncSupportConfigurer();
configureAsyncSupport(configurer);
if (configurer.getTaskExecutor() != null) {
adapter.setTaskExecutor(configurer.getTaskExecutor());
}
if (configurer.getTimeout() != null) {
adapter.setAsyncRequestTimeout(configurer.getTimeout());
}
adapter.setCallableInterceptors(configurer.getCallableInterceptors());
adapter.setDeferredResultInterceptors(configurer.getDeferredResultInterceptors());
return adapter;
}
先看这里的getMessageConverters()
WebMvcConfigurationSupport
protected final List> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
...
protected final void addDefaultHttpMessageConverters(List> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter
可以看到,addDefaultHttpMessageConverters
内添加了默认的消息转换器。
RequestMappingHandlerAdapter
也实现了接口InitializingBean
,所以关注其afterPropertiesSet
方法
RequestMappingHandlerAdapter
@Override
public void afterPropertiesSet() {
// Do this first, it may add ResponseBody advice beans
initControllerAdviceCache();
if (this.argumentResolvers == null) {
List resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
...
private List getDefaultArgumentResolvers() {
List resolvers = new ArrayList();
// Annotation-based argument resolution
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), false));
resolvers.add(new RequestParamMapMethodArgumentResolver());
resolvers.add(new PathVariableMethodArgumentResolver());
resolvers.add(new PathVariableMapMethodArgumentResolver());
resolvers.add(new MatrixVariableMethodArgumentResolver());
resolvers.add(new MatrixVariableMapMethodArgumentResolver());
resolvers.add(new ServletModelAttributeMethodProcessor(false));
resolvers.add(new RequestResponseBodyMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestPartMethodArgumentResolver(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RequestHeaderMethodArgumentResolver(getBeanFactory()));
resolvers.add(new RequestHeaderMapMethodArgumentResolver());
resolvers.add(new ServletCookieValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new ExpressionValueMethodArgumentResolver(getBeanFactory()));
resolvers.add(new SessionAttributeMethodArgumentResolver());
resolvers.add(new RequestAttributeMethodArgumentResolver());
// Type-based argument resolution
resolvers.add(new ServletRequestMethodArgumentResolver());
resolvers.add(new ServletResponseMethodArgumentResolver());
resolvers.add(new HttpEntityMethodProcessor(getMessageConverters(), this.requestResponseBodyAdvice));
resolvers.add(new RedirectAttributesMethodArgumentResolver());
resolvers.add(new ModelMethodProcessor());
resolvers.add(new MapMethodProcessor());
resolvers.add(new ErrorsMethodArgumentResolver());
resolvers.add(new SessionStatusMethodArgumentResolver());
resolvers.add(new UriComponentsBuilderMethodArgumentResolver());
// Custom arguments
if (getCustomArgumentResolvers() != null) {
resolvers.addAll(getCustomArgumentResolvers());
}
// Catch-all
resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
resolvers.add(new ServletModelAttributeMethodProcessor(true));
return resolvers;
}
可以看到,在getDefaultArgumentResolvers
方法内,创建了多个方法参数解析器,(其中RequestParamMethodArgumentResolver
添加了两次,不同之处为最后添加的为默认的参数解析器)最后在HandlerMethodArgumentResolverComposite
的addResolvers
中将创建的方法参数解析器保存在其argumentResolvers
变量上。
- 记录完方法参数解析器的初始化,直接来到执行方法的片段。
方法的调用在InvocableHandlerMethod
的invokeForRequest
InvocableHandlerMethod
public Object invokeForRequest(NativeWebRequest request, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Invoking '" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"' with arguments " + Arrays.toString(args));
}
Object returnValue = doInvoke(args);
if (logger.isTraceEnabled()) {
logger.trace("Method [" + ClassUtils.getQualifiedMethodName(getMethod(), getBeanType()) +
"] returned [" + returnValue + "]");
}
return returnValue;
}
其中getMethodArgumentValues
方法用来获取请求参数
InvocableHandlerMethod
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;
}
catch (Exception ex) {
if (logger.isDebugEnabled()) {
logger.debug(getArgumentResolutionErrorMessage("Failed to resolve", i), ex);
}
throw ex;
}
}
if (args[i] == null) {
throw new IllegalStateException("Could not resolve method parameter at index " +
parameter.getParameterIndex() + " in " + parameter.getMethod().toGenericString() +
": " + getArgumentResolutionErrorMessage("No suitable resolver for", i));
}
}
return args;
}
getMethodParameters
方法是InvocableHandlerMethod
从HandlerMethod
继承而来
HandlerMethod
public MethodParameter[] getMethodParameters() {
return this.parameters;
}
而关于parameters
的初始化是在RequestMappingHandlerMapping
的afterPropertiesSet
方法执行时调用,只是简单的封装方法参数,不需要太过关注。parameters
数组的长度为方法的参数个数。
this.argumentResolvers
是InvocableHandlerMethod
默认创建的HandlerMethodArgumentResolverComposite
,关注其supportsParameter
方法
HandlerMethodArgumentResolverComposite
@Override
public boolean supportsParameter(MethodParameter parameter) {
return (getArgumentResolver(parameter) != null);
}
...
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
if (logger.isTraceEnabled()) {
logger.trace("Testing if argument resolver [" + methodArgumentResolver + "] supports [" +
parameter.getGenericParameterType() + "]");
}
if (methodArgumentResolver.supportsParameter(parameter)) {
result = methodArgumentResolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
这里的this.argumentResolvers
为内置的方法参数解析器,最开始已经说明。展示几个常用的方法参数解析器
RequestParamMethodArgumentResolver
@Override
public boolean supportsParameter(MethodParameter parameter) {
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
String paramName = parameter.getParameterAnnotation(RequestParam.class).name();
return StringUtils.hasText(paramName);
}
else {
return true;
}
}
else {
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
第一个是RequestParamMethodArgumentResolver
,可以看到,如果controller的method的参数列表有使用@RequestParam
注解,将会使用RequestParamMethodArgumentResolver
来解析参数。如果找不到合适的处理参数的解析器,也是用此解析器来处理,因为在内置的方法参数解析器中有一个RequestParamMethodArgumentResolver
的this.useDefaultResolution
为true,表示默认。
RequestResponseBodyMethodProcessor
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}
第二个是RequestResponseBodyMethodProcessor
,可以看到是判断方法上有没有@RequestBody
注解。
回到InvocableHandlerMethod
的getMethodArgumentValues
方法中,如果参数解析HandlerMethodArgumentResolverComposite
内的方法参数解析器能解析当前参数,会调用其resolveArgument
方法
HandlerMethodArgumentResolverComposite
@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unknown parameter type [" + parameter.getParameterType().getName() + "]");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
可以看到实际调用了选中的方法参数解析器的resolveArgument
方法。这里选用RequestResponseBodyMethodProcessor
来看下
`RequestResponseBodyMethodProcessor`
@Override
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);
}
...
@Override
protected 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);
if (arg == null) {
if (checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getMethod().toGenericString());
}
}
return arg;
}
...
关注readWithMessageConverters
方法,此方法在其基类AbstractMessageConverterMethodArgumentResolver
中
AbstractMessageConverterMethodArgumentResolver
protected Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
Class> contextClass = (parameter != null ? parameter.getContainingClass() : null);
Class targetClass = (targetType instanceof Class ? (Class) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = (parameter != null ?
ResolvableType.forMethodParameter(parameter) : ResolvableType.forType(targetType));
targetClass = (Class) resolvableType.resolve();
}
HttpMethod httpMethod = ((HttpRequest) inputMessage).getMethod();
Object body = NO_VALUE;
try {
inputMessage = new EmptyBodyCheckingHttpInputMessage(inputMessage);
for (HttpMessageConverter> converter : this.messageConverters) {
Class> converterType = (Class>) converter.getClass();
if (converter instanceof GenericHttpMessageConverter) {
GenericHttpMessageConverter> genericConverter = (GenericHttpMessageConverter>) converter;
if (genericConverter.canRead(targetType, contextClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
body = genericConverter.read(targetType, contextClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
}
break;
}
}
else if (targetClass != null) {
if (converter.canRead(targetClass, contentType)) {
if (logger.isDebugEnabled()) {
logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
}
if (inputMessage.getBody() != null) {
inputMessage = getAdvice().beforeBodyRead(inputMessage, parameter, targetType, converterType);
body = ((HttpMessageConverter) converter).read(targetClass, inputMessage);
body = getAdvice().afterBodyRead(body, inputMessage, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, inputMessage, parameter, targetType, converterType);
}
break;
}
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("Could not read document: " + ex.getMessage(), ex);
}
if (body == NO_VALUE) {
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && inputMessage.getBody() == null)) {
return null;
}
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
return body;
}
既然选择了RequestResponseBodyMethodProcessor
,@RequestBody
注解,那么传入的参数一定为json数据。for循环遍历的消息转换器this.messageConverters
在开始已经记录过,当遍历到GsonHttpMessageConverter
时,它是GenericHttpMessageConverter
的间接子类,通过instanceof判断后,关注GsonHttpMessageConverter
的父类AbstractGenericHttpMessageConverter
的canRead
方法
AbstractGenericHttpMessageConverter
@Override
public boolean canRead(Type type, Class> contextClass, MediaType mediaType) {
return (type instanceof Class ? canRead((Class>) type, mediaType) : canRead(mediaType));
}
再到父类AbstractHttpMessageConverter
的canRead
AbstractHttpMessageConverter
@Override
public boolean canRead(Class> clazz, MediaType mediaType) {
return supports(clazz) && canRead(mediaType);
}
...
protected boolean canRead(MediaType mediaType) {
if (mediaType == null) {
return true;
}
for (MediaType supportedMediaType : getSupportedMediaTypes()) {
if (supportedMediaType.includes(mediaType)) {
return true;
}
}
return false;
}
传入的mediaType
为发起请求时指定的'Content-Type
,getSupportedMediaTypes
返回的是当前消息转换器支持的MediaType,对于GsonHttpMessageConverter
来说,它是支持此Content-Type
的,这在它的构造器中很容易看到。
GsonHttpMessageConverter
public GsonHttpMessageConverter() {
super(MediaType.APPLICATION_JSON, new MediaType("application", "*+json"));
this.setDefaultCharset(DEFAULT_CHARSET);
}
选中消息转换器后,开始执行其read
方法
GsonHttpMessageConverter
@Override
@SuppressWarnings("deprecation")
public Object read(Type type, Class> contextClass, HttpInputMessage inputMessage)
throws IOException, HttpMessageNotReadableException {
TypeToken> token = getTypeToken(type);
return readTypeToken(token, inputMessage);
}
...
private Object readTypeToken(TypeToken> token, HttpInputMessage inputMessage) throws IOException {
Reader json = new InputStreamReader(inputMessage.getBody(), getCharset(inputMessage.getHeaders()));
try {
return this.gson.fromJson(json, token.getType());
}
catch (JsonParseException ex) {
throw new HttpMessageNotReadableException("Could not read JSON document: " + ex.getMessage(), ex);
}
}
type
为当前处理请求的方法的参数类型,TypeToken
是谷歌json包中的类,用来封装参数类型。在readTypeToken
方法中,可以看到使用this.gson.fromJson
将HttpInputMessage转化的流转化为java类的过程。(这里就是对controller请求的json格式的参数,是怎么被SpringMVC封装到我们指定的model里的过程)
重新回到AbstractMessageConverterMethodArgumentResolver
的readWithMessageConverters
方法,可以看到在消息转换器执行read
动作前后,执行了RequestResponseBodyAdviceChain
的环绕方法。
回到RequestResponseBodyMethodProcessor
的resolveArgument
方法,再回到InvocableHandlerMethod
的getMethodArgumentValues
方法中,方法参数解析完成。
总结:
-
WebMvcConfigurationSupport
配置的RequestMappingHandlerAdapter
负责初始化消息转换器和方法参数解析器 -
HandlerMethodArgumentResolverComposite
内遍历方法参数解析器以选中能处理当前对应的方法的参数解析器。 - 这里挑选了处理json格式参数的解析器
RequestResponseBodyMethodProcessor
- 在
RequestResponseBodyMethodProcessor
的间接父类AbstractMessageConverterMethodArgumentResolver
中选出了处理json格式参数的消息转换器GsonHttpMessageConverter
- 在消息转换器
GsonHttpMessageConverter
中使用谷歌的gson(默认jackson,这里因为引了gson的jar所以被替代)将HttpInputMessage转化的流转化为处理请求的方法参数中指定的java类并返回。