Spring是如何将http请求参数转换为对象的

前言

spring有着很多转换机制, eg

  1. http请求中将json转换为vo对象
  2. jdbc将数据库column转换为entity对象
  3. spring-data-mongodb将entity对象转为document对象

源码分析

@RequestBody 参数转换

  1. controller定义如下
1
2
3
4
5
6
7
@ResponseStatus(HttpStatus.CREATED) // 201
@PostMapping
public Book create(@RequestBody Book book) {
String title = messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale());
book.setTitle(title);
return bookService.save(book);
}
  1. 函数入口 org.springframework.web.method.support.HandlerMethodArgumentResolver, 接口定义如下
1
2
3
4
5
6
7
8
9
public interface HandlerMethodArgumentResolver {

boolean supportsParameter(MethodParameter parameter);

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

}

发起请求后, 由于@RequestBody会命中如下实现 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#supportsParameter.

1
2
3
4
@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(RequestBody.class);
}

接着进入到 org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor#resolveArgument 进行参数转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {

parameter = parameter.nestedIfOptional();
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);
}
  1. 执行参数转换 Object arg = readWithMessageConverters(webRequest, parameter, parameter.getNestedGenericParameterType());

进入抽象类 org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodArgumentResolver#readWithMessageConverters(org.springframework.http.HttpInputMessage, org.springframework.core.MethodParameter, java.lang.reflect.Type)

关键实现如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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;
}
}

由于请求头包含 application/json;charset=UTF-8, 会命中 HttpMessageConverter 的实现类 org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter, 然后通过bean中的 ObjectMapper 和方法中的对象class类型进行反序列化, 得到vo对象

@RequestParam 参数转换

  1. controller定义如下
1
2
3
4
@GetMapping("/hello")
public Object hello(@RequestParam Boolean enable) {
return enable;
}
  1. 关键接口 org.springframework.core.convert.converter.Converter
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@FunctionalInterface
public interface Converter<S, T> {

@Nullable
T convert(S source);

default <U> Converter<S, U> andThen(Converter<? super T, ? extends U> after) {
Assert.notNull(after, "After Converter must not be null");
return (S s) -> {
T initialResult = convert(s);
return (initialResult != null ? after.convert(initialResult) : null);
};
}

}

http请求中 http://localhost:8080/books/hello?enable=true 参数是按String传递的, 但是接收是按boolean接收的. 所以会命中 org.springframework.core.convert.support.StringToBooleanConverter#convert

  1. debug溯源可以看到调用来自于 org.springframework.web.method.annotation.RequestParamMethodArgumentResolver#supportsParameter, 关键实现如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@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;
}
}
}

处理逻辑由抽象类 org.springframework.web.method.annotation.AbstractNamedValueMethodArgumentResolver#resolveArgument 实现。

然后代码进入到 org.springframework.validation.DataBinder#convertIfNecessary(java.lang.Object, java.lang.Class<T>, org.springframework.core.MethodParameter)

在代理类中查找匹配Converter org.springframework.beans.TypeConverterDelegate#convertIfNecessary(java.lang.String, java.lang.Object, java.lang.Object, java.lang.Class<T>, org.springframework.core.convert.TypeDescriptor) .

最后进入到我们的主角 org.springframework.core.convert.support.GenericConversionService#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) 中, 命中 org.springframework.core.convert.support.StringToBooleanConverter#convert 后. 类型转换结束

@RequestBody自定义实现

  1. 定义注解
1
2
3
4
5
6
7
8
9
/**
* @author cuishiying
* @date 2024-03-07
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CustomRequestBody {
}
  1. 实现自定义参数解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/**
* @author cuishiying
* @date 2024-03-07
*/
public class CustomRequestResponseBodyMethodProcessor implements HandlerMethodArgumentResolver {

@Override
public boolean supportsParameter(MethodParameter parameter) {
return parameter.hasParameterAnnotation(CustomRequestBody.class);
}

@Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
ServletServerHttpRequest inputMessage = new ServletServerHttpRequest(servletRequest);
InputStream inputStream = StreamUtils.nonClosing(inputMessage.getBody());

JavaTimeModule javaTimeModule = new JavaTimeModule();
// formatter
DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// deserializers
javaTimeModule.addDeserializer(LocalDate.class, new LocalDateDeserializer(dateFormatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(dateTimeFormatter));

ObjectMapper objectMapper = new ObjectMapper();
objectMapper.registerModule(javaTimeModule);
return objectMapper.readValue(inputStream, parameter.getParameterType());
}
}
  1. 注入自定义解析器
1
2
3
4
5
6
7
8
9
10
11
12
13
/**
* @author cuishiying
* @date 2024-03-07
*/

@Configuration
public class CustomWebMvcConfigurer implements WebMvcConfigurer {

@Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
resolvers.add(new CustomRequestResponseBodyMethodProcessor());
}
}
  1. 测试接口
1
2
3
4
5
6
7
@ResponseStatus(HttpStatus.CREATED) // 201
@PostMapping
public Book create(@CustomRequestBody Book book) {
String title = messageSource.getMessage("user.name", null, LocaleContextHolder.getLocale());
book.setTitle(title);
return bookService.save(book);
}