基于SpringBoot 2.2.0.RELEASE
先看下Spring对HandlerMethodArgumentResolver
的接口定义
/**
*用于将请求上下文中的方法参数解析为参数值的策略接口
*/
public interface HandlerMethodArgumentResolver {
/**是否MethodParamter是否能被该resolver解析器支持
* @param parameter 待检查的方法参数
* @return {@code true} 如果解析器支持提供出来的参数 返回true
* 否则返回false
*/
boolean supportsParameter(MethodParameter parameter);
/**
* 将给定请求的方法参数解析为参数值
* @param parameter 要解析的方法参数。
* 此参数必须先前已传递给{supportsParameter},该必须已返回{@code true}。
* 返回已解析的参数值,如果无法解析,则返回{@code null}
* @throws Exception 如果出错则抛出异常
*/
@Nullable
Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}
因为SpringMVC/SpringBoot
中controller中方法入参对应多中情况,如@RequestParam类型的参数、@PathVariable类型的参数、@RequestBody类型的参数等等吧。相信HandlerMethodArgumentResolver
提供了不同的实现类来根据参数类型解析对应的参数。
下面是常见HandlerMethodArgumentResolver
实现类以及能处理的参数类型
带有@RequestParam注解的参数;(与MultipartResolver结合使用的)参数类型是MultipartFile;(与Servlet 3.0 multipart requests结合使用的)参数类型是javax.servlet.http.Part的参数以及一些没有被@RequestParam修饰的基本数据类型,如int/long等。
带有@RequestParam注解的类型是Map的参数
带有@PathVaribale注解类型的参数
借助HttpMessageConverter,这个类是来解析注解为@RequestBody的请求参数和注解为@ResponseBody的响应内容
下面以源码分析其中的一些类
public class RequestParamMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
//如果参数有被@RequestParam注解标识
if (parameter.hasParameterAnnotation(RequestParam.class)) {
//如果方法参数的类型是Map
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
//因为指定在@RequestParam注解中的name 是用来解析请求参数的value值的,
//所以下面是判断name是否存在,如果存在 则可以使用该解析器
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
//如果被注解标注的不是Map类型
return true;
}
}
else {
//如果参数有被@RequestPart注解标识
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
//如果请求参数类型是MultipartFile
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
//如果可以使用默认解析方案
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
//将给定的参数类型和值名称解析为参数值。
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
if (servletRequest != null) {
//如果方法中的参数是MultipartFile类型,则解析它的数据值
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
//这里的解析 与上面的解析出来的数据 不一样吗?为什么要写两遍
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
//这里是解析普通的参数,如字符串参数等
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
}
public class PathVariableMethodArgumentResolver extends AbstractNamedValueMethodArgumentResolver
implements UriComponentsContributor {
@Override
public boolean supportsParameter(MethodParameter parameter) {
//如果参数没有被@PathVariable标注
if (!parameter.hasParameterAnnotation(PathVariable.class)) {
return false;
}
//如果方法的参数类型是Map
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
//因为指定在@PathVariable注解中的value 是用来解析请求参数的value值的,
//所以下面是判断value是否存在,如果存在 则可以使用该解析器
return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
}
return true;
}
//AbstractNamedValueMethodArgumentResolver#resolveArgument会调用到
@Override
@SuppressWarnings("unchecked")
protected void handleResolvedValue(@Nullable Object arg, String name, MethodParameter parameter,
@Nullable ModelAndViewContainer mavContainer, NativeWebRequest request) {
//解析参数 并放到pathVars内
String key = View.PATH_VARIABLES;
int scope = RequestAttributes.SCOPE_REQUEST;
Map<String, Object> pathVars = (Map<String, Object>) request.getAttribute(key, scope);
if (pathVars == null) {
pathVars = new HashMap<>();
request.setAttribute(key, pathVars, scope);
}
pathVars.put(name, arg);
}
@Override
@SuppressWarnings("unchecked")
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
//在url中的属性已经被spring容器解析 并放入org.springframework.web.servlet.HandlerMapping.uriTemplateVariables的key里
//如果我的url为a/{id},且我请求的时候给id赋值为1
//那么这里获取到的 map形如{"id",1}
Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute(
HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
//如果map不为空 根据key(name)取出value值
return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}
}
经过发现,PathVariableMethodArgumentResolver
和RequestParamMethodArgumentResolver
的继承体系是一样的,而且resolveArgument
是写在父类的。下面来看下AbstractNamedValueMethodArgumentResolver
是如何解析参数的
//参数解析是一个参数一个参数的解析,
//如我controller中@RequestParam String a,@RequestParam String b
//则会执行两次参数解析
@Override
@Nullable
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//获取参数的NameValueInfo,这个NameValueInfo其实就是name/required/defaultValue,
//相信使用过@RequestParam/@PathVariable注解的,应该比较熟悉,因为这是注解内的属性
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
MethodParameter nestedParameter = parameter.nestedIfOptional();
//解析注解的名称,因为注解的name属性中可能会含有占位符和表达式
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
//解析获得参数的值
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
//如果参数为空,但是配置了默认值 则以默认值作为参数值
if (namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
//如果参数是必需的
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
//如果解析的参数值是空的,而且这个字段也是必须的,就会执行到这里。
//AbstractNamedValueMethodArgumentResolver这里是抛出异常处理
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
//如果某个参数不是必须的,并且解析出来的参数值是空
//这里方法的内部实现是:如果arg不为空 则返回arg;
//如果arg为空,如果方法类型为Boolean,则返回FALSE,
//如果是其他的基本数据类型 则抛出异常
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
//如果解析出的arg为"",且默认值不为空
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
//PathVariable注解时 会执行到这个方法
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
下面跟着RequestResponseBodyMethodProcessor
类来看下Spring如何对@RequestBody
和@ResponseBody
做处理的。
从类的继承关系可以看出,RequestResponseBodyMethodProcessor
既实现了HandlerMethodArgumentResolver
又实现了HandlerMethodReturnValueHandler
,因此具有了解析入参和响应结果的能力。
先从解析入参开始,老套路了,既然实现了HandlerMethodArgumentResolver
,那么肯定会先根据supportParameter
来判断当前参数解析器是否能够解析该注解的参数,然后再决定是否解析。
RequestResponseBodyMethodProcessor类
@Override
public boolean supportsParameter(MethodParameter parameter) {
//判断当前参数中是否有@RequestBody的注解,如果有则可以使用该参数解析器
return parameter.hasParameterAnnotation(RequestBody.class);
}
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
parameter = parameter.nestedIfOptional();
//根据MediaType选择合适的HTTPMessageConverter 来读取请求信息
//这个是解析参数的重要步骤,具体实现在其父类AbstractMessageConverterMethodArgumentResolver中做的操作
Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
String name = Conventions.getVariableNameForParameter(parameter);
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());
}
}
return adaptArgumentIfNecessary(arg, parameter);
}
@Override
protected <T> Object readWithMessageConverters(NativeWebRequest webRequest, MethodParameter parameter,
Type paramType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
Assert.state(servletRequest != null, "No HttpServletRequest");
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
//调用父类AbstractMessageConverterMethodArgumentResolver中的readWithMessageConverters
Object arg = readWithMessageConverters(inputMessage, parameter, paramType);
if (arg == null && checkRequired(parameter)) {
throw new HttpMessageNotReadableException("Required request body is missing: " +
parameter.getExecutable().toGenericString(), inputMessage);
}
return arg;
}
AbstractMessageConverterMethodArgumentResolver
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
@Nullable
protected <T> Object readWithMessageConverters(HttpInputMessage inputMessage, MethodParameter parameter,
Type targetType) throws IOException, HttpMediaTypeNotSupportedException, HttpMessageNotReadableException {
MediaType contentType;
boolean noContentType = false;
try {
//获取请求头的content-type
contentType = inputMessage.getHeaders().getContentType();
}
catch (InvalidMediaTypeException ex) {
throw new HttpMediaTypeNotSupportedException(ex.getMessage());
}
if (contentType == null) {
noContentType = true;
//如果contentType为空 为其设置默认值
contentType = MediaType.APPLICATION_OCTET_STREAM;
}
//解析方法中的参数类型
Class<?> contextClass = parameter.getContainingClass();
Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
if (targetClass == null) {
ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
targetClass = (Class<T>) resolvableType.resolve();
}
//获取Http请求方法
HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
Object body = NO_VALUE;
EmptyBodyCheckingHttpInputMessage message;
try {
message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
//messageConverters 是在SpringMVC上下文启动的加载进来的
//有框架提供的默认值,有根据classpath来决定加载某messageConverter的
for (HttpMessageConverter<?> converter : this.messageConverters) {
Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
GenericHttpMessageConverter<?> genericConverter =
(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
//根据具体messageConverter能处理的content-type与实际请求的content-type来判断能否被
//messageConverter处理。
if (genericConverter != null ? genericConverter.canRead(targetType, contextClass, contentType) :
(targetClass != null && converter.canRead(targetClass, contentType))) {
if (message.hasBody()) {
HttpInputMessage msgToUse =
getAdvice().beforeBodyRead(message, parameter, targetType, converterType);
//如果能被具体某httpmessageconverter处理,则进行处理
//比如 能被MappingJackson2HttpMessageConverter处理,能被其方法read处理
//将请求参数 转为json
body = (genericConverter != null ? genericConverter.read(targetType, contextClass, msgToUse) :
((HttpMessageConverter<T>) converter).read(targetClass, msgToUse));
body = getAdvice().afterBodyRead(body, msgToUse, parameter, targetType, converterType);
}
else {
body = getAdvice().handleEmptyBody(null, message, parameter, targetType, converterType);
}
break;
}
}
}
catch (IOException ex) {
throw new HttpMessageNotReadableException("I/O error while reading input message", ex, inputMessage);
}
//如果当前上下文中没有能够处理当前content-type类型参数的HTTPMessageConverter存在
if (body == NO_VALUE) {
//如果请求方法不存在或者不被支持或者请求参数没内容 那么返回null
if (httpMethod == null || !SUPPORTED_METHODS.contains(httpMethod) ||
(noContentType && !message.hasBody())) {
return null;
}
//否则这里抛出异常
throw new HttpMediaTypeNotSupportedException(contentType, this.allSupportedMediaTypes);
}
MediaType selectedContentType = contentType;
Object theBody = body;
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(theBody, !traceOn);
return "Read \"" + selectedContentType + "\" to [" + formatted + "]";
});
return body;
}
}
this.messageConverters如何被加载进上下文,可参考HttpMessageConverter
以上是常用注解的参数解析代码分析,但是由于SpringMVC框架 代码常用到很多设计模式,会出现 这一刻代码在这个类,下一刻就跑到了其某个实现类或父类等这样的情况。加之本人水平有限,所以会导致代码分析的时候,有种乱糟糟的感觉,望见谅。