Spring-22 SpringMVC HTTP Message 转换 Java 对象

Spring-22 SpringMVC HTTP Message 转换 Java 对象

Spring 源码系列文章会遵循由浅入深,由易到难,由宏观到微观的原则,目标是尽量降低学习难度,而不是一上来就迷失在源码当中. 文章会从一个场景作为出发点,针对性的目的性极强的针对该场景对 Spring 的实现原理,源码进行探究学习。该系列文章会让你收获什么? 从对 Spring 的使用者成为 Spring 专家。该文章会同步在微信公众号 【DevXTalk】, 方便在微信客户端阅读。

上篇内容介绍了处理器适配器,它作为处理一次 HTTP 请求的入口,本篇内容进入到处理器适配器内部介绍 SpringMVC 是如何把 HTTP Message 转换成处理器最终能使用的 Java 对象。本篇内容主要针对 RequestMappingHandlerAdapter 处理器适配器来展开, RequestMappingHandlerAdapter 在我们日常工作使用到的频率很高,其他的适配器把 HTTP Message 转换 Java 对象的过程很简单,不需要我们关心。Servlet 容器例如 Tomcat 已经帮我们做了将 HTTP Message 转换为 Servlet API 的工作,在使用层面上我们是不需要关心的。 SpringMVCServlet API 的基础上做了更丰富的功能支持,可以将 Servlet API 中封装的请求相关信息封装为各种更丰富的 Java 对象。

核心接口 HandlerMethodArgumentResolver

HandlerMethodArgumentResolver 是完成转换过程的核心接口, supportsParameter 方法用来检测这个参数解析器能不能解析这个参数, 处理器方法上的每个参数都会被封装为 MethodParameter . 如果supportsParameter 方法返回 true 就去调用 resolveArgument 对参数进行解析, 把 Servlet API 解析成处理器方法上的参数需要的数据类型对象。我们可以随意的扩展 HandlerMethodArgumentResolver 接口的实现,理论上来说可以处理各种数据类型都没问题。

java 复制代码
public interface HandlerMethodArgumentResolver {

	
	boolean supportsParameter(MethodParameter parameter);

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

}

RequestMappingHandlerAdapter 默认的参数解析器

如果我们没有特别指定用哪些参数解析器, RequestMappingHandlerAdapter 就会使用默认的一系列参数解析器,相当于出厂配置,开箱即用。这里有 Resolver 结尾的, 也有 Processor 结尾的,Resolver 结尾是只实现了参数解析,Processor 结尾的是同时实现了参数解析和返回值处理。也就是实现了 HandlerMethodArgumentResolver 接口和 HandlerMethodReturnValueHandler 接口。 HandlerMethodReturnValueHandler 接口负责将处理器返回的 Java 对象处理成 HTTP Message 响应给客户端。

java 复制代码
	/**
	 * Return the list of argument resolvers to use including built-in resolvers
	 * and custom resolvers provided via {@link #setCustomArgumentResolvers}.
	 */
	private List<HandlerMethodArgumentResolver> getDefaultArgumentResolvers() {
		List<HandlerMethodArgumentResolver> resolvers = new ArrayList<>(30);

		// 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());
		if (KotlinDetector.isKotlinPresent()) {
			resolvers.add(new ContinuationHandlerMethodArgumentResolver());
		}

		// Custom arguments
		if (getCustomArgumentResolvers() != null) {
			resolvers.addAll(getCustomArgumentResolvers());
		}

		// Catch-all
		resolvers.add(new PrincipalMethodArgumentResolver());
		resolvers.add(new RequestParamMethodArgumentResolver(getBeanFactory(), true));
		resolvers.add(new ServletModelAttributeMethodProcessor(true));

		return resolvers;
	}

HandlerMethodArgumentResolver 参数解析器的实现

解析 key - value 形式的参数解析器 AbstractNamedValueMethodArgumentResolver 的子类

ExpressionValueMethodArgumentResolver 支持 @Value 注解参数和 SPEL 表达式

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(Value.class);
	}

MatrixVariableMapMethodArgumentResolver 支持 @MatrixVariable 注解 Map 类型参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		MatrixVariable matrixVariable = parameter.getParameterAnnotation(MatrixVariable.class);
		return (matrixVariable != null && Map.class.isAssignableFrom(parameter.getParameterType()) &&
				!StringUtils.hasText(matrixVariable.name()));
	}

PathVariableMapMethodArgumentResolver 支持被 @PathVariable 注解的 Map 类型 @PathVariable value() 属性必须为空

supportsParameter 方法:

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

PathVariableMethodArgumentResolver 支持被 @PathVariable 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	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;
	}

RequestAttributeMethodArgumentResolver 支持被 @RequestAttribute 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestAttribute.class);
	}

RequestHeaderMethodArgumentResolver 支持被 @RequestHeader 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(RequestHeader.class) &&
				!Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType()));
	}
···

#### `RequestParamMethodArgumentResolver` 支持被 @RequestParam 注解的参数

`supportsParameter` 方法:

```java
	@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;
			}
		}
	}

SessionAttributeMethodArgumentResolver 支持被 @SessionAttribute 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(SessionAttribute.class);
	}

ServletCookieValueMethodArgumentResolver 支持 @CookieValue 注解参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(CookieValue.class);
	}

支持字节流形式的参数解析器

AbstractMessageConverterMethodArgumentResolver 类和 AbstractMessageConverterMethodProcessor 的子类。其实现主要是依赖于 HttpMessageConverter 组件,之后的内容中会介绍到。

HttpEntityMethodProcessor 支持 HttpEntity 和 RequestEntity 类型参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (HttpEntity.class == parameter.getParameterType() ||
				RequestEntity.class == parameter.getParameterType());
	}

RequestResponseBodyMethodProcessor 支持 @RequestBody 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return parameter.hasParameterAnnotation(RequestBody.class);
	}

RequestPartMethodArgumentResolver 支持 @RequestPart 注解的参数或 MultipartFile 类型参数或 MultipartFile 类型的集合参数

supportsParameter 方法:

java 复制代码
	@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());
		}
	}

支持一些特定类型和 SpringMVC 传参规则的参数解析器

ErrorsMethodArgumentResolver 支持 org.springframework.validation.Errors 类型参数

supportsParameter 方法:

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

MapMethodProcessor 支持 Map 类型没有注解标注的

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (Map.class.isAssignableFrom(parameter.getParameterType()) &&
				parameter.getParameterAnnotations().length == 0);
	}

SessionStatusMethodArgumentResolver 支持 org.springframework.web.bind.support.SessionStatus 类型的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return SessionStatus.class == parameter.getParameterType();
	}

RedirectAttributesMethodArgumentResolver 支持 org.springframework.web.servlet.mvc.support.RedirectAttributes 类型的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return RedirectAttributes.class.isAssignableFrom(parameter.getParameterType());
	}

ModelAttributeMethodProcessor 支持被 @ModelAttribute 注解的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return (parameter.hasParameterAnnotation(ModelAttribute.class) ||
				(this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType())));
	}

ModelMethodProcessor 支持 org.springframework.ui.Model 类型的参数

supportsParameter 方法:

java 复制代码
	@Override
	public boolean supportsParameter(MethodParameter parameter) {
		return Model.class.isAssignableFrom(parameter.getParameterType());
	}

UriComponentsBuilderMethodArgumentResolver 支持 UriComponentsBuilder 或ServletUriComponentsBuilder 类型的参数

supportsParameter 方法:

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

ServletRequestMethodArgumentResolver 支持一些 Servlet API 参数类型

这个参数解析器支持的类型比较多主要是一些 Servlet API 和一些 JDK 中的数据类型和 SpringMVC 自定义的类型,WebRequest , ServletRequest , MultipartRequest , HttpSession , Principal , InputStream , Reader , HttpMethod , Locale , TimeZone , ZoneId

supportsParameter 方法:

java 复制代码
	@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) && !parameter.hasParameterAnnotations()) ||
				InputStream.class.isAssignableFrom(paramType) ||
				Reader.class.isAssignableFrom(paramType) ||
				HttpMethod.class == paramType ||
				Locale.class == paramType ||
				TimeZone.class == paramType ||
				ZoneId.class == paramType);
	}

有读者对内容中画的示意图感兴趣的话可以使用 www.drawio.com/ , 完全免费支持多种存储, 我是存储在 github 上。也可以使用 idea 内的 Diagrams.net Integration 插件,这样就可以在 idea 内作图了,完成后也可以直接提交到 github


DevXTalk 会持续分享有趣的技术和见闻,如果你觉得本文对你有帮助希望你可以分享给更多的朋友看到。该文章会同步在微信公众号 【DevXTalk】, 方便在微信客户端阅读。

相关推荐
骆晨学长9 分钟前
基于springboot的智慧社区微信小程序
java·数据库·spring boot·后端·微信小程序·小程序
AskHarries14 分钟前
利用反射实现动态代理
java·后端·reflect
Flying_Fish_roe38 分钟前
Spring Boot-Session管理问题
java·spring boot·后端
hai405871 小时前
Spring Boot中的响应与分层解耦架构
spring boot·后端·架构
哈喽,树先生2 小时前
1.Seata 1.5.2 seata-server搭建
spring·springcloud
Adolf_19933 小时前
Flask-JWT-Extended登录验证, 不用自定义
后端·python·flask
叫我:松哥3 小时前
基于Python flask的医院管理学院,医生能够增加/删除/修改/删除病人的数据信息,有可视化分析
javascript·后端·python·mysql·信息可视化·flask·bootstrap
海里真的有鱼3 小时前
Spring Boot 项目中整合 RabbitMQ,使用死信队列(Dead Letter Exchange, DLX)实现延迟队列功能
开发语言·后端·rabbitmq
工业甲酰苯胺3 小时前
Spring Boot 整合 MyBatis 的详细步骤(两种方式)
spring boot·后端·mybatis