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】, 方便在微信客户端阅读。

相关推荐
NiNg_1_2343 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
Chrikk4 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*4 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue4 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man4 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
Wx-bishekaifayuan6 小时前
django电商易购系统-计算机设计毕业源码61059
java·spring boot·spring·spring cloud·django·sqlite·guava
customer086 小时前
【开源免费】基于SpringBoot+Vue.JS周边产品销售网站(JAVA毕业设计)
java·vue.js·spring boot·后端·spring cloud·java-ee·开源
小白冲鸭7 小时前
【报错解决】使用@SpringJunitConfig时报空指针异常
spring·java后端开发