接口参数解析-基于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 {

	@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这个类,可以作为参考。

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

1、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 的入参无效的原因,人家都不用你的东西,当然没有效果啊

2、HttpEntityMethodProcessor

用于处理HttpEntityRequestEntity类型的入参的。

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

你可能感兴趣的:(收集站)