springMVC适配器组件参数解析器HandlerMethodArgumentResolver

前言

在享受Spring MVC带给你便捷的时候,你是否曾经这样疑问过:Controller的handler方法参数能够自动完成参数封装(有时即使没有@PathVariable、@RequestParam、@RequestBody等注解都可),甚至在方法参数任意位置写HttpServletRequest、HttpSession、Writer…等类型的参数,它自动就有值了便可直接使用。对此你是否想问一句:Spring MVC它是怎么办到的?那么本文就揭开它的神秘面纱,还你一片"清白"。

一、参数解析器

HandlerMethodArgumentResolver策略接口:用于在给定请求的上下文中将方法参数解析为参数值。简单的理解为:它负责处理你Handler方法里的所有入参:包括自动封装、自动赋值、校验(底层是利用DataBinder)等等。有了它才能会让Spring MVC处理入参显得那么高级、那么自动化。

public interface HandlerMethodArgumentResolver {
    /**
	 * 给定的方法参数parameter是否受此解析程序支持。
	 * @param parameter:要检查的方法参数
	 **/
	boolean supportsParameter(MethodParameter parameter);
	/**
	 * 将方法参数从给定请求解析为参数值。
	 * @param parameter: 请求参数
	 * @param mavContainer: 容器
	 * @param webRequest: 请求
	 * @param binderFactory: 用于创建一个WebDataBinder用于数据绑定、校验
	 **/
	Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
}

因为子类众多,所以我分类进行说明。我把它分为:基于键值对,数据类型是Map,固定参数类型,基于ContentType的消息转换器
这四类进行分析

这些子类,先放在后面分析,我们先熟悉下参数解析器链。

二、参数解析器链

在springMVC的参数处理中,参数处理对处理器的顺序是敏感的,因此我们需要关注Spring MVC最终的执行顺序,这时候我们的聚合容器HandlerMethodArgumentResolverComposite就出场了:

public class HandlerMethodArgumentResolverComposite implements HandlerMethodArgumentResolver {

	private final List<HandlerMethodArgumentResolver> argumentResolvers = new LinkedList<>();
	// 具有缓存
	private final Map<MethodParameter, HandlerMethodArgumentResolver> argumentResolverCache = new ConcurrentHashMap<>(256);	
	...
	// @since 4.3  木有任何地方调用
	public void clear() {
		this.argumentResolvers.clear();
	}

	// getArgumentResolver()方法是本文的核心
	public boolean supportsParameter(MethodParameter parameter) {
	    //是否存在能解析parameter的解析器
		return getArgumentResolver(parameter) != null;
	}
	public Object resolveArgument(MethodParameter parameter,  ModelAndViewContainer mavContainer, NativeWebRequest webRequest,  WebDataBinderFactory binderFactory) throws Exception {
		// 这里是关键:每个参数最多只会被一个处理器处理
		HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
		if (resolver == null) {
			throw new IllegalArgumentException(" ");
		}
		//利用获取到的参数解析器解析参数
		return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
	}
	...	
	// 判断当前参数解析器是否能解析parameter,这块逻辑保证了每个parameter参数最多只会被一个处理器处理
	// 这个从缓存的数据结构中也能够看出来的
	@Nullable
	private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
		HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
		if (result == null) {
			for (HandlerMethodArgumentResolver methodArgumentResolver : this.argumentResolvers) {
				if (methodArgumentResolver.supportsParameter(parameter)) {
					result = methodArgumentResolver;
					this.argumentResolverCache.put(parameter, result);
					break;
				}
			}
		}
		return result;
	}
}

三、基于键值对的参数解析器

从URI(路径变量)、HttpServletRequest、HttpSession、Header、Cookie…等中根据名称key来获取值
这类处理器所有的都是基于抽象类AbstractNamedValueMethodArgumentResolver来实现,它是最为重要的分支(分类)

AbstractNamedValueMethodArgumentResolver

public final Object resolveArgument(MethodParameter parameter,  ModelAndViewContainer mavContainer,NativeWebRequest webRequest,  WebDataBinderFactory binderFactory) throws Exception {
   // 获取给定方法参数的命名值。
   NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
   MethodParameter nestedParameter = parameter.nestedIfOptional();
   //  namedValueInfo.name 是参数的名称字符串,不过该字符串可能是个表达式,需要进一步解析为
   // 最终的参数名称,下面的 resolveStringValue 语句就是对该名字进行表达式求值,从而得到解析后的
   // 控制器方法参数名称,此名称是从请求上下文中获取相应参数值的关键信息
   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()) {
         handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
      }
      //子类实现,参为null时但处理方法
      arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
   }
   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());

      }
   }
   handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
   return arg;
}

protected abstract Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception;

从上源码可以看出,抽象类已经定死了处理模版(方法为final的),留给子类需要做的事就不多了,大体还有如下三件事:

根据MethodParameter创建NameValueInfo(子类的实现可继承自NameValueInfo,就是对应注解的属性们)

根据方法参数名称name从HttpServletRequest, Http Headers, URI template variables等等中获取属性值

对arg == null这种情况的处理(非必须)

下面开始介绍他的子类

PathVariableMethodArgumentResolver

public boolean supportsParameter(MethodParameter parameter) {
   if (!parameter.hasParameterAnnotation(PathVariable.class)) {
      return false;
   }
   if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
      PathVariable pathVariable = parameter.getParameterAnnotation(PathVariable.class);
      return (pathVariable != null && StringUtils.hasText(pathVariable.value()));
   }
   return true;
}

protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
   Map<String, String> uriTemplateVars = (Map<String, String>) request.getAttribute( HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
   return (uriTemplateVars != null ? uriTemplateVars.get(name) : null);
}

它帮助Spring MVC实现restful风格的URL。它用于处理标注有@PathVariable注解的方法参数,用于从URL中获取值(并不是?后面的参数哦)

注意:如果不是使用@PathVariable进行显示定义属性名,那么属性名就是方法参数名

案例
key是name,如果不在@PathVariable中显示写,那么key就是string1 ,其value是PathVariableMethodArgumentResolver使用resolveName方法从URL中获取

@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") String string1){
    System.out.println(string1);
    return string1;
}

搜索路径:clyu/hello/test ,控制台输出如下:tset

如果我要接收的是一个list,那怎么办呢,情况如下:

@RequestMapping(value = "hello/{name}",method = RequestMethod.GET)
public String string( @PathVariable("name") List<String> string){
        System.out.println(string);
        return string.toString();
}

搜索路径:clyu/hello/test ,控制台输出如下:[test]
搜索路径:clyu/hello/test,age ,控制台输出如下:[test, age]

其底层使用了StringToCollectionConverter,其默认支持根据逗号分割字符串,在往里的说是使用了是使用StringUtils的commaDelimitedListToStringArray方法进行分割的

public static String[] commaDelimitedListToStringArray(@Nullable String str) {
		return delimitedListToStringArray(str, ",");
}

关于@PathVariable的required=false使用注意事项

@PathVariable的required=false使用较少,一般用于在用URL传多个值时,但有些值是非必传的时候使用。

比如这样的URL:/user/{id}/{name},/user/{id},/user

@ResponseBody
@GetMapping("/test/{id}")
public Person test(@PathVariable(required = false) Integer id) { ... }

以为这样写通过/test这个url就能访问到了,其实这样是不行的,会404。正确姿势如下:

@ResponseBody
@GetMapping({"/test/{id}", "/test"})
public Person test(@PathVariable(required = false) Integer id) { ... }

这样/test和/test/1这两个url就都能正常work了~

RequestParamMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	if (parameter.hasParameterAnnotation(RequestParam.class)) {
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
			return (requestParam != null && StringUtils.hasText(requestParam.name()));
		}
		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;
		}
	}
}

// 核心方法:根据Name 获取值(普通/文件上传)
// 并且还有集合、数组等情况
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);

		// 这块解析出来的是个MultipartFile或者其集合/数组
		if (servletRequest != null) {
			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);
			}
		}

		// 若解析出来值仍旧为null,那处理完文件上传里木有,那就去参数里取吧
		// 由此可见:文件上传的优先级是高于请求参数的
		if (arg == null) {
		
			//小知识点:getParameter()其实本质是getParameterNames()[0]的效果
			// 强调一遍:?ids=1,2,3 结果是["1,2,3"](兼容方式,不建议使用。注意:只能是逗号分隔)
			// ?ids=1&ids=2&ids=3  结果是[1,2,3](标准的传值方式,建议使用)
			// 但是Spring MVC这两种都能用List接收  请务必注意他们的区别~~~
			String[] paramValues = request.getParameterValues(name);
			if (paramValues != null) {
				arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
			}
		}
		return arg;
	}

1、处理器处理情况说明

所有标注有@RequestParam注解的类型(非Map.

注意如果是map,不是这个解析器,而RequestParamMapMethodArgumentResolver

下面都表示没有标注@RequestParam注解的情况

1、不能标注有@RequestPart注解,否则直接不处理了

2、MultipartFile类型或者对应的集合/数组类型或者javax.servlet.http.Part 对应结合/数组类型

3、useDefaultResolution=true情况下(useDefaultResolution在Spring中,普遍为true),"基本类型"也会处理

这里的普通类型不是java的概念,而是Spring的。其底层判断方法是BeanUtils.isSimpleProperty,如果参数类型是CharSequence,Number,Date,URI,URL,Locale,Class,或者是他们子类,或者是他们的数组类型,就可以认为是基本类型

2、参数解析过程

先处理文件信息,如果没有文件信息,在根据request.getParameterValues(name)获取属性值的集合

String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
		arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}

3、案例

因为可以根据名字获取其值的集合,测试如下:

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( String string){
    System.out.println(string);
    return string;
}

搜索路径:/clyu/hello?string=1,控制台输出 :1
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :1,3

如果接收对象是数组呢

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestParam  List<String> string){
    System.out.println(string);
    return string.toString();
}

搜索路径:/clyu/hello?string=1,控制台输出 :[1]
搜索路径:/clyu/hello?string=1&string=3,控制台输出 :[1,3]
搜索路径:/clyu/hello?string=1,3,控制台输出 :[1,3]

两个请求的URL不一样,但都能正确的达到效果。(@RequestParam Object[] objects这么写两种URL也能正常封装)
对此有如下这个细节你必须得注意:对于集合List而言@RequestParam注解是必须存在的,否则报错如下(因为交给ServletModelAttributeMethodProcessor处理了):但如果你这么写String[] objects,即使不写注解,也能够正常完成正确封装。因为它在spring中属于基本类型

RequestHeaderMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return (parameter.hasParameterAnnotation(RequestHeader.class) &&
			!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
}

@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
	String[] headerValues = request.getHeaderValues(name);
	if (headerValues != null) {
		return (headerValues.length == 1 ? headerValues[0] : headerValues);
	}
	else {
		return null;
	}
}

1、处理器处理情况说明
必须标注@RequestHeader注解,并且不能是Map类型

有的小伙伴会说:@RequestHeader Map headers这样可以接收到所有的请求头啊,其实这不是本类的功劳,
而是RequestHeaderMapMethodArgumentResolver的作用

2、参数解析过程
根据属性名,在请求体中查询值

String[] headerValues = request.getHeaderValues(name);
if (headerValues != null) {
            return (headerValues.length == 1 ? headerValues[0] : headerValues);
} else {
            return null;
}

3、案例

@RequestMapping(value = "hello",method = RequestMethod.GET)
public String string( @RequestHeader  String string){
    System.out.println(string);
    return string.toString();
}

在请求体中添加键值对string :123456,发送请求,控制台输出:123456

Tip:注解指定的value值(key值)是不区分大小写的,逗号分隔是可以被封装成集合/数组的
如果这个信息不是必须写的话,请标注@RequestHeader(required = false)

RequestAttributeMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(RequestAttribute.class);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request){
	return request.getAttribute(name, RequestAttributes.SCOPE_REQUEST);
}

1、处理器处理情况说明
必须标注有@RequestAttribute注解

2、参数解析过程

从请求中获取属性值: request.getAttribute(name, RequestAttributes.SCOPE_REQUEST)

SessionAttributeMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(SessionAttribute.class);
}
@Override
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) {
	return request.getAttribute(name, RequestAttributes.SCOPE_SESSION);
}

1、处理器处理情况说明
必须标注有@SessionAttribute注解

2、参数解析
从session中获取属性值: request.getAttribute(name, RequestAttributes.SCOPE_SESSION);

ServletCookieValueMethodArgumentResolver

//这个方法在其父类AbstractCookieValueMethodArgumentResolver中
public boolean supportsParameter(MethodParameter parameter) {
	return parameter.hasParameterAnnotation(CookieValue.class);
}


protected Object resolveName(String cookieName, MethodParameter parameter,
	NativeWebRequest webRequest) throws Exception {

	HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
	Assert.state(servletRequest != null, "No HttpServletRequest");

	Cookie cookieValue = WebUtils.getCookie(servletRequest, cookieName);
	if (Cookie.class.isAssignableFrom(parameter.getNestedParameterType())) {
		return cookieValue;
	}
	else if (cookieValue != null) {
		return this.urlPathHelper.decodeRequestString(servletRequest, cookieValue.getValue());
	}
	else {
		return null;
	}
}

1、处理器处理情况说明
必须标注有@CookieValue注解

2、参数解析过程
从请求Cookie中获取值,根据request.getCookies()

MatrixVariableMethodArgumentResolver

1、处理器处理情况说明
必须标注有@MatrixVariable注解

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (!parameter.hasParameterAnnotation(MatrixVariable.class)) {
			return false;
		}
		if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
			MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
			return (matrixVariable != null && StringUtils.hasText(matrixVariable.name()));
		}
		return true;
	}

四、基于map的接口参数解析

这类解析器我认为是对基于键值对接口参数解析的一种补充,它依赖基于键值对接口参数解析的相关注解。曾几何时你是否想过通过@RequestParam一次性全给封装进一个Map里,然后再自己分析?同样的本类处理器给@RequestHeader、@PathVariable、@MatrixVariable都赋予了这种能力~

PathVariableMapMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
    PathVariable ann = parameter.getParameterAnnotation(PathVariable.class);
    return (ann != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
            !StringUtils.hasText(ann.value()));
}

/**
 * Return a Map with all URI template variables or an empty map.
 */
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
        NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
    @SuppressWarnings("unchecked")
    Map<String, String> uriTemplateVars =
            (Map<String, String>) webRequest.getAttribute(
                    HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE, RequestAttributes.SCOPE_REQUEST);
    if (!CollectionUtils.isEmpty(uriTemplateVars)) {
        return new LinkedHashMap<>(uriTemplateVars);
    }
    else {
        return Collections.emptyMap();
    }
}

1、处理器处理情况说明
必须标注有@PathVariable注解,且其参数类型是map

2、参数解析过程
把所有的路径参数使用Map装着返回即可

RequestParamMapMethodArgumentResolver

 @Override
	public boolean supportsParameter(MethodParameter parameter) {
		RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
		return (requestParam != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(requestParam.name()));
	}

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

		ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
		if (MultiValueMap.class.isAssignableFrom(parameter.getParameterType())) {
			// MultiValueMap
			Class<?> valueType = resolvableType.as(MultiValueMap.class).getGeneric(1).resolve();
			if (valueType == MultipartFile.class) {
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getMultiFileMap() : new LinkedMultiValueMap<>(0));
			}
			else if (valueType == Part.class) {
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedMultiValueMap<String, Part> result = new LinkedMultiValueMap<>(parts.size());
					for (Part part : parts) {
						result.add(part.getName(), part);
					}
					return result;
				}
				return new LinkedMultiValueMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				MultiValueMap<String, String> result = new LinkedMultiValueMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					for (String value : values) {
						result.add(key, value);
					}
				});
				return result;
			}
		}

		else {
			// Regular Map
			Class<?> valueType = resolvableType.asMap().getGeneric(1).resolve();
			if (valueType == MultipartFile.class) {
				MultipartRequest multipartRequest = MultipartResolutionDelegate.resolveMultipartRequest(webRequest);
				return (multipartRequest != null ? multipartRequest.getFileMap() : new LinkedHashMap<>(0));
			}
			else if (valueType == Part.class) {
				HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
				if (servletRequest != null && MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
					Collection<Part> parts = servletRequest.getParts();
					LinkedHashMap<String, Part> result = new LinkedHashMap<>(parts.size());
					for (Part part : parts) {
						if (!result.containsKey(part.getName())) {
							result.put(part.getName(), part);
						}
					}
					return result;
				}
				return new LinkedHashMap<>(0);
			}
			else {
				Map<String, String[]> parameterMap = webRequest.getParameterMap();
				Map<String, String> result = new LinkedHashMap<>(parameterMap.size());
				parameterMap.forEach((key, values) -> {
					if (values.length > 0) {
						result.put(key, values[0]);
					}
				});
				return result;
			}
		}
	}

1、处理器处理情况说明
使用@RequestParam标注,且参数类型是map
2、参数解析过程
它不能传一key多值情况若出现相同的key,以在最前面的key的值为准。Map实例是一个LinkedHashMap<String,String>实例

3、案例

@ResponseBody
 @GetMapping("/test")
 public Object test(@RequestParam Map<String,Object> params) {
        System.out.println(params);
        return params;
 }

搜索路径:/test?name=fsx&age=18&age=28,控制台输出如下:{name=fsx, age=18}
age只能取其中一个值

RequestHeaderMapMethodArgumentResolver

一次性把请求头信息都拿到:数据类型支出写MultiValueMap(LinkedMultiValueMap)/HttpHeaders/Map

@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(RequestHeader.class) &&
				Map.class.isAssignableFrom(parameter.getParameterType()));
	}

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

		Class<?> paramType = parameter.getParameterType();
		if (MultiValueMap.class.isAssignableFrom(paramType)) {
			MultiValueMap<String, String> result;
			if (HttpHeaders.class.isAssignableFrom(paramType)) {
				result = new HttpHeaders();
			}
			else {
				result = new LinkedMultiValueMap<>();
			}
			for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
				String headerName = iterator.next();
				String[] headerValues = webRequest.getHeaderValues(headerName);
				if (headerValues != null) {
					for (String headerValue : headerValues) {
						result.add(headerName, headerValue);
					}
				}
			}
			return result;
		}
		else {
			Map<String, String> result = new LinkedHashMap<>();
			for (Iterator<String> iterator = webRequest.getHeaderNames(); iterator.hasNext();) {
				String headerName = iterator.next();
				String headerValue = webRequest.getHeader(headerName);
				if (headerValue != null) {
					result.put(headerName, headerValue);
				}
			}
			return result;
		}
	}

1、处理器处理情况说明
必须标注@RequestHeader,且参数是map类型
2、案例

@ResponseBody
@GetMapping("/test")
public Object test(@RequestHeader Map<String, Object> headers) {
     headers.forEach((k, v) -> System.out.println(k + "-->" + v));
      return headers;
}

控制台输出:

host-->localhost:8080
connection-->keep-alive
cache-control-->max-age=0
upgrade-insecure-requests-->1
。。。。

不过强烈不建议直接使用Map,而是使用HttpHeaders类型。这么写@RequestHeader HttpHeaders headers,获取的时候更为便捷。

MapMethodProcessor

它处理Map类型,但没有标注任何注解的情况,它的执行顺序是很靠后的,所以有点兜底的意思。
这个处理器同时也解释了:为何你方法入参上写个Map、HashMap、ModelMap等等就可以非常便捷的获取到模型的值的原因~

@Override
public boolean supportsParameter(MethodParameter parameter) {
	return Map.class.isAssignableFrom(parameter.getParameterType());
}

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

	Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
	return mavContainer.getModel();
}

1、处理器处理情况说明
此处理器,在spring的参数处理链中很靠后,是处理没有任何注解标注的map类型
2、参数解析过程
把Model直接返回

五、固定类型接口参数解析

在实际项目中,我们经常会在接口中获取如:HttpServletRequest, HttpServletResponse,我们会发现,我们不要做任何处理,就能获取到对象,这就是固定类型解析器的魅力 除了上面2个,参数比如是SessionStatus, ServletResponse, OutputStream, Writer, WebRequest, MultipartRequest, HttpSession, Principal, InputStream等源生的API对象也可以获取

ServletRequestMethodArgumentResolver

@Override
public boolean supportsParameter(MethodParameter parameter) {
	Class<?> paramType = parameter.getParameterType();
	return (WebRequest.class.isAssignableFrom(paramType) ||
			ServletRequest.class.isAssignableFrom(paramType) ||
			MultipartRequest.class.isAssignableFrom(paramType) ||
			HttpSession.class.isAssignableFrom(paramType) ||
			(pushBuilder != null && pushBuilder.isAssignableFrom(paramType)) ||
			Principal.class.isAssignableFrom(paramType) ||
			InputStream.class.isAssignableFrom(paramType) ||
			Reader.class.isAssignableFrom(paramType) ||
			HttpMethod.class == paramType ||
			Locale.class == paramType ||
			TimeZone.class == paramType ||
			ZoneId.class == paramType);
}

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

	Class<?> paramType = parameter.getParameterType();
	// WebRequest / NativeWebRequest / ServletWebRequest
	if (WebRequest.class.isAssignableFrom(paramType)) {
		if (!paramType.isInstance(webRequest)) {
			throw new IllegalStateException(
					"Current request is not of type [" + paramType.getName() + "]: " + webRequest);
		}
		return webRequest;
	}
	// ServletRequest / HttpServletRequest / MultipartRequest / MultipartHttpServletRequest
	if (ServletRequest.class.isAssignableFrom(paramType) || MultipartRequest.class.isAssignableFrom(paramType)) {
		return resolveNativeRequest(webRequest, paramType);
	}
	// HttpServletRequest required for all further argument types
	return resolveArgument(paramType, resolveNativeRequest(webRequest, HttpServletRequest.class));
}

处理器处理情况说明

如果形参类型是WebRequest,ServletRequest,MultipartRequest,HttpSession,pushBuilder,Principal,InputStream,Reader,HttpMethod,Locale,TimeZone,ZoneId
就会使用此处理器,看到这你应该明白,以后你需要使用这些参数的话,直接在方法上申明即可,不需要自己再去get了

ServletResponseMethodArgumentResolver

// @since 3.1
public class ServletResponseMethodArgumentResolver implements HandlerMethodArgumentResolver {
    // 它相对来说很比较简单
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        Class<?> paramType = parameter.getParameterType();
        return (ServletResponse.class.isAssignableFrom(paramType) || // webRequest.getNativeResponse(requiredType)
                OutputStream.class.isAssignableFrom(paramType) || //response.getOutputStream()
                Writer.class.isAssignableFrom(paramType)); //response.getWriter()
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
            NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
        // 这个判断放在这。。。
        if (mavContainer != null) {
            mavContainer.setRequestHandled(true);
        }
        ... 
    }
}

处理器处理情况说明
如果形参类型是ServletResponse,OutputStream,Writer就会使用此处理器

SessionStatusMethodArgumentResolver

public class SessionStatusMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return SessionStatus.class == parameter.getParameterType();
	}

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

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for session status exposure");
		return mavContainer.getSessionStatus();
	}

}

处理器处理情况说明
如果形参类型是SessionStatus就会使用此处理器

UriComponentsBuilderMethodArgumentResolver

public class UriComponentsBuilderMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> type = parameter.getParameterType();
		return (UriComponentsBuilder.class == type || ServletUriComponentsBuilder.class == type);
	}

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

		HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
		Assert.state(request != null, "No HttpServletRequest");
		return ServletUriComponentsBuilder.fromServletMapping(request);
	}

}

处理器处理情况说明
如果形参类型是UriComponentsBuilder,ServletUriComponentsBuilder就会使用此处理器
通过UriComponentsBuilder来得到URL的各个部分,以及构建URL都是非常的方便的。

RedirectAttributesMethodArgumentResolver

public class RedirectAttributesMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
	}

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

		Assert.state(mavContainer != null, "RedirectAttributes argument only supported on regular handler methods");

		ModelMap redirectAttributes;
		if (binderFactory != null) {
			DataBinder dataBinder = binderFactory.createBinder(webRequest, null, DataBinder.DEFAULT_OBJECT_NAME);
			redirectAttributes = new RedirectAttributesModelMap(dataBinder);
		}
		else {
			redirectAttributes  = new RedirectAttributesModelMap();
		}
		mavContainer.setRedirectModel(redirectAttributes);
		return redirectAttributes;
	}

}

处理器处理情况说明
如果形参类型是RedirectAttributes就会使用此处理器
RedirectAttributes是Spring mvc 3.1版本之后出来的一个功能,专门用于重定向之后还能带参数跳转的
如果涉及到重定向:多个视图见传值,使用它还是比较方便的。

ModelMethodProcessor

public class ModelMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Model.class.isAssignableFrom(parameter.getParameterType());
	}

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

		Assert.state(mavContainer != null, "ModelAndViewContainer is required for model exposure");
		return mavContainer.getModel();
	

处理器处理情况说明
如果形参类型是Model就会使用此处理器

ModelAttributeMethodProcessor

public class ModelAttributeMethodProcessor implements HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler {
	private final boolean annotationNotRequired;
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}
	@Override
	@Nullable
	public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		Assert.state(mavContainer != null, "ModelAttributeMethodProcessor requires ModelAndViewContainer");
		Assert.state(binderFactory != null, "ModelAttributeMethodProcessor requires WebDataBinderFactory");

		String name = ModelFactory.getNameForParameter(parameter);
		ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
		if (ann != null) {
			mavContainer.setBinding(name, ann.binding());
		}

		Object attribute = null;
		BindingResult bindingResult = null;

		if (mavContainer.containsAttribute(name)) {
			attribute = mavContainer.getModel().get(name);
		}
		else {
			// Create attribute instance
			try {
				attribute = createAttribute(name, parameter, binderFactory, webRequest);
			}
			catch (BindException ex) {
				if (isBindExceptionRequired(parameter)) {
					// No BindingResult parameter -> fail with BindException
					throw ex;
				}
				// Otherwise, expose null/empty value and associated BindingResult
				if (parameter.getParameterType() == Optional.class) {
					attribute = Optional.empty();
				}
				bindingResult = ex.getBindingResult();
			}
		}

		if (bindingResult == null) {
			// Bean property binding and validation;
			// skipped in case of binding failure on construction.
			WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
			if (binder.getTarget() != null) {
				if (!mavContainer.isBindingDisabled(name)) {
					bindRequestParameters(binder, webRequest);
				}
				validateIfApplicable(binder, parameter);
				if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
					throw new BindException(binder.getBindingResult());
				}
			}
			// Value type adaptation, also covering java.util.Optional
			if (!parameter.getParameterType().isInstance(attribute)) {
				attribute = binder.convertIfNecessary(binder.getTarget(), parameter.getParameterType(), parameter);
			}
			bindingResult = binder.getBindingResult();
		}

		// Add resolved attribute and BindingResult at the end of the model
		Map<String, Object> bindingResultModel = bindingResult.getModel();
		mavContainer.removeAttributes(bindingResultModel);
		mavContainer.addAllAttributes(bindingResultModel);

		return attribute;
	}

处理器处理情况说明
如果形参类型使用@ModelAttribute 或者不是普通类型(通过 !BeanUtils.isSimpleProperty来判断)的参数就会使用此处理器

ErrorsMethodArgumentResolver

它用于在方法参数可以写Errors类型,来拿到数据校验结果。

public class ErrorsMethodArgumentResolver implements HandlerMethodArgumentResolver {

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		Class<?> paramType = parameter.getParameterType();
		return Errors.class.isAssignableFrom(paramType);
	}

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

		Assert.state(mavContainer != null,
				"Errors/BindingResult argument only supported on regular handler methods");

		ModelMap model = mavContainer.getModel();
		String lastKey = CollectionUtils.lastElement(model.keySet());
		//接口入参中有BindingResult,那么其必须放在最后的位置,否则报错
		if (lastKey != null && lastKey.startsWith(BindingResult.MODEL_KEY_PREFIX)) {
			return model.get(lastKey);
		}

		throw new IllegalStateException("");
	}

}

处理器处理情况说明
如果是Errors类型就会被此处理器解析,准确的说是BindingResult。
注意如接口入参中有BindingResult,那么其必须放在最后的位置

六、基于ContentType消息转换器类型

基于ContentType消息转换器类型,其底层是利用HttpMessageConverter将输入流转换成对应的参数。
这类参数解析器的基类是AbstractMessageConverterMethodArgumentResolver:

AbstractMessageConverterMethodArgumentResolver

// @since 3.1
public abstract class AbstractMessageConverterMethodArgumentResolver implements HandlerMethodArgumentResolver {
                                 
	protected final List<HttpMessageConverter<?>> messageConverters;
	protected final List<MediaType> allSupportedMediaTypes;
	// 和RequestBodyAdvice和ResponseBodyAdvice有关的
	private final RequestResponseBodyAdviceChain advice;

	// 构造函数里指定HttpMessageConverter
	// 此一个参数的构造函数木人调用
	public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters) {
		this(converters, null);
	}

	// @since 4.2
	public AbstractMessageConverterMethodArgumentResolver(List<HttpMessageConverter<?>> converters, 
	                                                      @Nullable List<Object> requestResponseBodyAdvice) {
		Assert.notEmpty(converters, "'messageConverters' must not be empty");
		this.messageConverters = converters;
		// 它会把所有的消息转换器里支持的MediaType都全部拿出来汇聚起来~
		this.allSupportedMediaTypes = getAllSupportedMediaTypes(converters);
		this.advice = new RequestResponseBodyAdviceChain(requestResponseBodyAdvice);
	}

	// 提供一个defualt方法访问
	RequestResponseBodyAdviceChain getAdvice() {
		return this.advice;
	}

	// 统一了子类从请求中获取值的方式
	protected <T> 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.getContainingClass();
		Class<T> targetClass = (targetType instanceof Class ? (Class<T>) targetType : null);
		if (targetClass == null) {
			ResolvableType resolvableType = ResolvableType.forMethodParameter(parameter);
			targetClass = (Class<T>) resolvableType.resolve();
		}

		HttpMethod httpMethod = (inputMessage instanceof HttpRequest ? ((HttpRequest) inputMessage).getMethod() : null);
		Object body = NO_VALUE;
		EmptyBodyCheckingHttpInputMessage message;
		try {
			message = new EmptyBodyCheckingHttpInputMessage(inputMessage);
			//在这里进行请求体数据转换
			for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				GenericHttpMessageConverter<?> genericConverter =
						(converter instanceof GenericHttpMessageConverter ? (GenericHttpMessageConverter<?>) converter : null);
				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);
						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);
		}

		if (body == NO_VALUE) {
			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;
	}
	...
}

说明:此抽象类并没有实现resolveArgument()这个接口方法,而只是提供了一些protected方法,作为工具方法给子类调用,如统一了子类从请求中获取值的方式,底层是利用消息转换器HttpMessageConverter解析HttpInputMessage的核心。注意各种数据转换是在这里转换的,这和其他基于键值对参数解析器不同。

RequestPartMethodArgumentResolver

public class RequestPartMethodArgumentResolver extends AbstractMessageConverterMethodArgumentResolver {

    public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters) {
		super(messageConverters);
	}
	public RequestPartMethodArgumentResolver(List<HttpMessageConverter<?>> messageConverters,
			List<Object> requestResponseBodyAdvice) {
		super(messageConverters, requestResponseBodyAdvice);
	}

	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		if (parameter.hasParameterAnnotation(RequestPart.class)) {
			return true;
		}
		else {
			if (parameter.hasParameterAnnotation(RequestParam.class)) {
				return false;
			}
			return MultipartResolutionDelegate.isMultipartArgument(parameter.nestedIfOptional());
		}
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest request, @Nullable WebDataBinderFactory binderFactory) throws Exception {

		HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
		Assert.state(servletRequest != null, "No HttpServletRequest");

		RequestPart requestPart = parameter.getParameterAnnotation(RequestPart.class);
		boolean isRequired = ((requestPart == null || requestPart.required()) && !parameter.isOptional());
        // 如果注解没有指定,就取形参名
		String name = getPartName(parameter, requestPart);
		parameter = parameter.nestedIfOptional();
		Object arg = null;

		Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
		if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
			arg = mpArg;
		}
		else {
			try {
				HttpInputMessage inputMessage = new RequestPartServletServerHttpRequest(servletRequest, name);
				arg = readWithMessageConverters(inputMessage, parameter, parameter.getNestedGenericParameterType());
				if (binderFactory != null) {
					WebDataBinder binder = binderFactory.createBinder(request, 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());
					}
				}
			}
			catch (MissingServletRequestPartException | MultipartException ex) {
				if (isRequired) {
					throw ex;
				}
			}
		}

		if (arg == null && isRequired) {
			if (!MultipartResolutionDelegate.isMultipartRequest(servletRequest)) {
				throw new MultipartException("Current request is not a multipart request");
			}
			else {
				throw new MissingServletRequestPartException(name);
			}
		}
		return adaptArgumentIfNecessary(arg, parameter);
	}
}

处理器处理情况说明
处理参数被@RequestPart修饰,或者参数类型是MultipartFile | Servlet 3.0提供的javax.servlet.http.Part类型(并且没有被@RequestParam修饰)
此处理器用于解析@RequestPart参数类型,它和多部分文件上传有关

AbstractMessageConverterMethodProcessor

命名为Processor说明它既能处理入参,也能处理返回值,当然本文的关注点是方法入参(和HttpMessageConverter相关)。

请求body体一般是一段字符串/字节流,查询参数可以看做URL的一部分,这两个是位于请求报文的不同地方。

表单参数可以按照一定格式放在请求体中,也可以放在url上作为查询参数。

响应body体则是response返回的具体内容,对于一个普通的html页面,body里面就是页面的源代码。对于HttpMessage响应体里可能就是个json串(但无强制要求)。

响应体一般都会结合Content-Type一起使用,告诉客户端只有知道这个头了才知道如何渲染。

AbstractMessageConverterMethodProcessor源码稍显复杂,它和Http协议、内容协商有很大的关联:

public abstract class AbstractMessageConverterMethodProcessor extends AbstractMessageConverterMethodArgumentResolver implements HandlerMethodReturnValueHandler {

	// 默认情况下:文件们后缀是这些就不弹窗下载
	private static final Set<String> WHITELISTED_EXTENSIONS = new HashSet<>(Arrays.asList("txt", "text", "yml", "properties", "csv","json", "xml", "atom", "rss", "png", "jpe", "jpeg", "jpg", "gif", "wbmp", "bmp"));
	private static final Set<String> WHITELISTED_MEDIA_BASE_TYPES = new HashSet<>(Arrays.asList("audio", "image", "video"));
	private static final List<MediaType> ALL_APPLICATION_MEDIA_TYPES = Arrays.asList(MediaType.ALL, new MediaType("application"));
	private static final Type RESOURCE_REGION_LIST_TYPE = new ParameterizedTypeReference<List<ResourceRegion>>() { }.getType();
	
	// 用于给URL解码 decodingUrlPathHelper.decodeRequestString(servletRequest, filename);
	private static final UrlPathHelper decodingUrlPathHelper = new UrlPathHelper();
	// rawUrlPathHelper.getOriginatingRequestUri(servletRequest);
	private static final UrlPathHelper rawUrlPathHelper = new UrlPathHelper();
	static {
		rawUrlPathHelper.setRemoveSemicolonContent(false);
		rawUrlPathHelper.setUrlDecode(false);
	}

	// 内容协商管理器
	private final ContentNegotiationManager contentNegotiationManager;
	// 扩展名的内容协商策略
	private final PathExtensionContentNegotiationStrategy pathStrategy;
	private final Set<String> safeExtensions = new HashSet<>();

	protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters) {
		this(converters, null, null);
	}
	// 可以指定内容协商管理器ContentNegotiationManager 
	protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager contentNegotiationManager) {
		this(converters, contentNegotiationManager, null);
	}
	// 这个构造器才是重点
	protected AbstractMessageConverterMethodProcessor(List<HttpMessageConverter<?>> converters, @Nullable ContentNegotiationManager manager, @Nullable List<Object> requestResponseBodyAdvice) {
		super(converters, requestResponseBodyAdvice);

		// 可以看到:默认情况下会直接new一个
		this.contentNegotiationManager = (manager != null ? manager : new ContentNegotiationManager());
		// 若管理器里有就用管理器里的,否则new PathExtensionContentNegotiationStrategy()
		this.pathStrategy = initPathStrategy(this.contentNegotiationManager);

		// 用safeExtensions装上内容协商所支持的所有后缀
		// 并且把后缀白名单也加上去(表示是默认支持的后缀)
		this.safeExtensions.addAll(this.contentNegotiationManager.getAllFileExtensions());
		this.safeExtensions.addAll(WHITELISTED_EXTENSIONS);
	}

	// ServletServerHttpResponse是对HttpServletResponse的包装,主要是对响应头进行处理
	// 主要是处理:setContentType、setCharacterEncoding等等
	// 所以子类若要写数据,就调用此方法来向输出流里写吧~~~
	protected ServletServerHttpResponse createOutputMessage(NativeWebRequest webRequest) {
		HttpServletResponse response = webRequest.getNativeResponse(HttpServletResponse.class);
		Assert.state(response != null, "No HttpServletResponse");
		return new ServletServerHttpResponse(response);
	}

	// 注意:createInputMessage()方法是父类提供的,对HttpServletRequest的包装
	// 主要处理了:getURI()、getHeaders()等方法
	// getHeaders()方法主要是处理了:getContentType()...


	protected <T> void writeWithMessageConverters(T value, MethodParameter returnType, NativeWebRequest webRequest) throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException {
		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		ServletServerHttpResponse outputMessage = createOutputMessage(webRequest);
		writeWithMessageConverters(value, returnType, inputMessage, outputMessage);
	}

	// 这个方法省略
	// 这个方法是消息处理的核心之核心:处理了contentType、消息转换、内容协商、下载等等
	// 注意:此处并且还会执行RequestResponseBodyAdviceChain,进行前后拦截
	protected <T> void writeWithMessageConverters(@Nullable T value, MethodParameter returnType,
			ServletServerHttpRequest inputMessage, ServletServerHttpResponse outputMessage)
			throws IOException, HttpMediaTypeNotAcceptableException, HttpMessageNotWritableException { ... }
}

本类的核心是各式各样的HttpMessageConverter消息转换器,因为最终的write都是交给它们去完成。
此抽象类里,它完成了内容协商~

关于内容协商的详解,强烈建议你点击 这里 。另外 这篇文章也深入的分析了AbstractMessageConverterMethodProcessor这个类,可以作为参考。

既然父类都已经完成了这么多事,那么子类自然就非常的简单的。看看它的两个具体实现子类:

RequestResponseBodyMethodProcessor

顾名思义,它负责处理@RequestBody这个注解的参数

public class RequestResponseBodyMethodProcessor extends AbstractMessageConverterMethodProcessor {
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

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

		parameter = parameter.nestedIfOptional();
		// 所以核心逻辑:读取流、消息换换等都在父类里已经完成。子类直接调用就可以拿到转换后的值arg 
		// arg 一般都是个类对象。比如Person实例
		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());
		// 若是POJO,就是类名首字母小写(并不是形参名)
		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());
				}
			}
			// 把校验结果放进Model里,方便页面里获取
			if (mavContainer != null) {
				mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());
			}
		}
		// 适配:支持到Optional类型的参数
		return adaptArgumentIfNecessary(arg, parameter);
	}
}

参数解析过程

1、先利用HttpMessageConverter消息转换器解析数据解析,方法如下:

Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

2、在利用WebDataBinder解析数据数据校验,注意这里只要数据校验,没有进行数据转化,和其他3解析器不一样

RequestResponseBodyMethodProcessor利用WebDataBinder参数解析的代码
validateIfApplicable(binder, parameter);
if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
			throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
}

AbstractNamedValueMethodArgumentResolver 利用WebDataBinder参数解析的代码

arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);

这也是@DateTimeFormat 对于标注@RequestBody 的入参无效的原因,人家都不用你的东西,当然没有效果啊

HttpEntityMethodProcessor

用于处理HttpEntity和RequestEntity类型的入参的。

public class HttpEntityMethodProcessor extends AbstractMessageConverterMethodProcessor {
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (HttpEntity.class == parameter.getParameterType() || RequestEntity.class == parameter.getParameterType());
	}

	@Override
	@Nullable
	public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws IOException, HttpMediaTypeNotSupportedException {

		ServletServerHttpRequest inputMessage = createInputMessage(webRequest);
		// 拿到HttpEntity的泛型类型
		Type paramType = getHttpEntityType(parameter);
		if (paramType == null) {
			// 注意:这个泛型类型是必须指定的,必须的
			throw new IllegalArgumentException("HttpEntity parameter '" + parameter.getParameterName() + "' in method " + parameter.getMethod() + " is not parameterized");
		}

		// 调用父类方法拿到body的值(把泛型类型传进去了,所以返回的是个实例)
		Object body = readWithMessageConverters(webRequest, parameter, paramType);
		// 注意步操作:new了一个RequestEntity进去,持有实例即可
		if (RequestEntity.class == parameter.getParameterType()) {
			return new RequestEntity<>(body, inputMessage.getHeaders(), inputMessage.getMethod(), inputMessage.getURI());
		} else { // 用的父类HttpEntity,那就会丢失掉Method等信息(因此建议入参用RequestEntity类型,更加强大些)
			return new HttpEntity<>(body, inputMessage.getHeaders());
		}
	}
}

注意:这里可没有validate校验了,这也是经常被面试问到的:使用HttpEntity和@RequestBody有什么区别呢?

从代码里可以直观的看到:有了抽象父类后,子类需要做的事情已经很少了,只需要匹配参数类型、做不同的返回而已。
关于它俩的使用案例,此处不用再展示了,因为各位平时工作中都在使用,再熟悉不过了。但针对他俩的使用,我总结出如下几个小细节,供以参考:

@RequestBody/HttpEntity它的参数(泛型)类型允许是Map
方法上的和类上的@ResponseBody都可以被继承,但@RequestBody不可以
@RequestBody它自带有Bean Validation校验能力(当然需要启用),HttpEntity更加的轻量和方便
HttpEntity/RequestEntity所在包是:org.springframework.http,属于spring-web
@RequestBody位于org.springframework.web.bind.annotation,同样属于spring-web

发布了34 篇原创文章 · 获赞 0 · 访问量 1360

猜你喜欢

转载自blog.csdn.net/qq_41071876/article/details/104209630