前言
spring有着很多转换机制, eg
http请求中将json转换为vo对象
jdbc将数据库column转换为entity对象
spring-data-mongodb将entity对象转为document对象
源码分析
@RequestBody 参数转换
controller定义如下
1 2 3 4 5 6 7 @ResponseStatus(HttpStatus.CREATED) @PostMapping public Book create (@RequestBody Book book) { String title = messageSource.getMessage("user.name" , null , LocaleContextHolder.getLocale()); book.setTitle(title); return bookService.save(book); }
函数入口 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); }
执行参数转换 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 参数转换
controller定义如下
1 2 3 4 @GetMapping("/hello") public Object hello (@RequestParam Boolean enable) { return enable; }
关键接口 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
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 2 3 4 5 6 7 8 9 @Target(ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface CustomRequestBody {}
实现自定义参数解析器
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 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(); DateTimeFormatter dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd" ); DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss" ); 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 2 3 4 5 6 7 8 9 10 11 12 13 @Configuration public class CustomWebMvcConfigurer implements WebMvcConfigurer { @Override public void addArgumentResolvers (List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new CustomRequestResponseBodyMethodProcessor()); } }
测试接口
1 2 3 4 5 6 7 @ResponseStatus(HttpStatus.CREATED) @PostMapping public Book create (@CustomRequestBody Book book) { String title = messageSource.getMessage("user.name" , null , LocaleContextHolder.getLocale()); book.setTitle(title); return bookService.save(book); }