SpringBoot源码系列(6):参数解析之自定义参数绑定
前言
在SpringBoot/SpringMVC中,我们能在Controller中解析出请求的参数。除了一些基本类型之外,我们还可以使用一些复杂类型,诸如:
1 2 3 4
| @PostMapping("/hello") String hello(User user){ }
|
本文根据SpringBoot源码谈谈其背后的原理,文中SpringBoot版本号为2.7.5。
原文地址:https://xuedongyun.cn/post/44248/
自定义参数绑定原理
有关参数解析,及参数解析器的部分,可以查看我之前的博客:SpringBoot源码系列(5):参数解析。自定义类型参数使用的是ServletModelAttributeMethodProcessor
这个参数解析器。
其中类继承关系:HandlerMethodArgumentResolver
<- ModelAttributeMethodProcessor
<- ServletModelAttributeMethodProcessor
ServletModelAttributeMethodProcessor
其父类ModelAttributeMethodProcessor
实现了supportsParameter
方法
1 2 3 4 5 6 7 8 9
| @Override public boolean supportsParameter(MethodParameter parameter) {
return (parameter.hasParameterAnnotation(ModelAttribute.class) || (this.annotationNotRequired && !BeanUtils.isSimpleProperty(parameter.getParameterType()))); }
|
其父类ModelAttributeMethodProcessor
还实现了resolveArgument
方法。其中的核心点在于,将请求中的参数绑定到创建的空对象上。
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 32 33 34 35 36 37 38 39 40 41 42
| @Override @Nullable public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
String name = ModelFactory.getNameForParameter(parameter); ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class); if (ann != null) { mavContainer.setBinding(name, ann.binding()); }
Object attribute = null; BindingResult bindingResult = null; if (mavContainer.containsAttribute(name)) { attribute = mavContainer.getModel().get(name); } else { try { attribute = createAttribute(name, parameter, binderFactory, webRequest); } } if (bindingResult == null) { WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); if (binder.getTarget() != null) { if (!mavContainer.isBindingDisabled(name)) { bindRequestParameters(binder, webRequest); } validateIfApplicable(binder, parameter); } }
return attribute; }
|
ServletModelAttributeMethodProcessor
重写了bindRequestParameters
方法
1 2 3 4 5 6 7 8
| @Override protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) { ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class); ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder; servletBinder.bind(servletRequest); }
|
ServletRequestDataBinder
类继承关系:DataBinder
<-WebDataBinder
<-ServletRequestDataBinder
我们首先来看一下WebDataBinder
的基本结构。WebDataBinder
利用其中的各种Converter
,将请求数据转化成指定的数据类型。利用反射封装到JavaBean
中。未来,我们还可以放自定义的Converter
,将String转为想要的类型
1 2 3
| target: 我们的空Person对象 conversionService: converters: 124种转换器,负责将字符串转化为具体java对象
|
ServletRequestDataBinder
实现了bind
方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public void bind(ServletRequest request) { MutablePropertyValues mpvs = new ServletRequestParameterPropertyValues(request); MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class); if (multipartRequest != null) { bindMultipart(multipartRequest.getMultiFileMap(), mpvs); } else if (StringUtils.startsWithIgnoreCase(request.getContentType(), MediaType.MULTIPART_FORM_DATA_VALUE)) { HttpServletRequest httpServletRequest = WebUtils.getNativeRequest(request, HttpServletRequest.class); if (httpServletRequest != null && HttpMethod.POST.matches(httpServletRequest.getMethod())) { StandardServletPartUtils.bindParts(httpServletRequest, mpvs, isBindEmptyMultipartFiles()); } } addBindValues(mpvs, request); doBind(mpvs); }
|
1 2 3 4 5 6 7 8 9 10 11
| // mpvs包含请求中的所有key-value对 mpvs: propertyValueList: 0: name: "age" value: "23" converted: false convertedValue: null conversionNecessary: null ... ...
|
WebDataBinder
实现了doBind
方法
1 2 3 4 5 6 7
| @Override protected void doBind(MutablePropertyValues mpvs) { checkFieldDefaults(mpvs); checkFieldMarkers(mpvs); adaptEmptyArrayIndices(mpvs); super.doBind(mpvs); }
|
下面进入到父类DataBinder
中的doBind
方法
1 2 3 4 5 6
| protected void doBind(MutablePropertyValues mpvs) { checkAllowedFields(mpvs); checkRequiredFields(mpvs); applyPropertyValues(mpvs); }
|
DataBinder
中的applyPropertyValues
方法。这里通过getPropertyAccessor
方法返回了经过处理的bindingResult
(具体内容可以自行点开查看源码)
1 2 3 4 5 6 7
| protected void applyPropertyValues(MutablePropertyValues mpvs) { try { getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields()); } }
|
AbstractPropertyAccessor
这里通过getPropertyAccessor
方法返回了经过处理的bindingResult
后(其类别为AbstractPropertyBindingResult
),调用了其setPropertyValues
方法。我们可以看到核心为:遍历了mpvs
中的所有值
1 2 3 4 5 6 7 8 9 10 11
| @Override public void setPropertyValues(PropertyValues pvs, boolean ignoreUnknown, boolean ignoreInvalid) throws BeansException { for (PropertyValue pv : propertyValues) { try { setPropertyValue(pv); } } }
|
继续查看AbstractPropertyAccessor
的setPropertyValue
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| @Override public void setPropertyValue(PropertyValue pv) throws BeansException { String propertyName = pv.getName(); AbstractNestablePropertyAccessor nestedPa; try { nestedPa = getPropertyAccessorForPropertyPath(propertyName); } nestedPa.setPropertyValue(tokens, pv); }
|
在这里,nestedPa
是一个BeanWrapperImpl
(继承自AbstractNestablePropertyAccessor
),内部包装着原生对象
1 2
| nestedPa {BeanWrapperImpl}: rootObject: {User}
|
BeanWrapperImpl
类继承关系:BeanWrapperImpl
<-AbstractNestablePropertyAccessor
AbstractNestablePropertyAccessor
中,setPropertyValue
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { if (tokens.keys != null) {
processKeyedProperty(tokens, pv); } else {
processLocalProperty(tokens, pv); } }
|
AbstractNestablePropertyAccessor
中,processLocalProperty
方法
1 2 3 4 5 6 7 8 9
| private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { valueToApply = convertForProperty(tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); ph.setValue(valueToApply); }
|
AbstractNestablePropertyAccessor
中,convertForProperty
方法
1 2 3 4 5 6 7
| @Nullable protected Object convertForProperty( String propertyName, @Nullable Object oldValue, @Nullable Object newValue, TypeDescriptor td) throws TypeMismatchException {
return convertIfNecessary(propertyName, oldValue, newValue, td.getType(), td); }
|
AbstractNestablePropertyAccessor
中,convertIfNecessary
方法
1 2 3 4 5 6 7 8 9 10
| @Nullable private Object convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable TypeDescriptor td) throws TypeMismatchException { return this.typeConverterDelegate.convertIfNecessary(propertyName, oldValue, newValue, requiredType, td); }
|
TypeConverterDelegate
它是Spring的类型转换器委托类。convertIfNecessary
方法目的在于将给定的值转换为指定的目标类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| @Nullable public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { try { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } } }
return (T) convertedValue; }
|
GenericConversionService
类继承关系:ConversionService
<-ConfigurableConversionService
<-GenericConversionService
我们继续查看conversionService.convert
方法
1 2 3 4 5 6 7 8 9 10 11 12
| @Override @Nullable public Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType) { GenericConverter converter = getConverter(sourceType, targetType); if (converter != null) { Object result = ConversionUtils.invokeConverter(converter, source, sourceType, targetType); return handleResult(sourceType, targetType, result); } return handleConverterNotFound(source, sourceType, targetType); }
|
ConversionUtils
ConversionUtils
内的静态方法invokeConverter
1 2 3 4 5 6 7 8 9
| @Nullable public static Object invokeConverter(GenericConverter converter, @Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
try { return converter.convert(source, sourceType, targetType); } }
|
GenericConversionService
调用了converter.convert
方法,在当前例子中(User.userAge
)sourceType
是String
,targetType
是Integer
1 2 3 4 5 6 7 8
| @Override @Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { if (source == null) { return convertNullSource(sourceType, targetType); } return this.converterFactory.getConverter(targetType.getObjectType()).convert(source); }
|
StringToNumberConverterFactory
类继承关系:ConverterFactory
<-StringToNumberConverterFactory
StringToNumberConverterFactory
实现了ConverterFactory
接口。我们这里的this.converterFactory
是StringToNumberConverterFactory
类型的。
通过它,我们拿到了需要的StringToNumber
(实现自Converter<String, T>
)
StringToNumber
StringToNumber
类的convert
方法如下,其实非常简单
1 2 3 4 5 6 7 8
| @Override @Nullable public T convert(String source) { if (source.isEmpty()) { return null; } return NumberUtils.parseNumber(source, this.targetType); }
|
未来,我们可以给WebDataBinder
里放自己的Converter
自定义Converter
我们已经知道,当我们的参数标注了@ModelAttribute,或是一个复杂对象时,会使用ServletModelAttributeMethodProcessor
这个参数解析器。而该解析器会寻找合适的converter
来进行类型转换
假设我们的需求是:
1
| 用户: <input name="user" value="Tom,6">
|
1 2 3 4 5 6
| public class User {
private String userName;
private Integer userAge; }
|
1 2 3 4 5
| @PostMapping("/user") String addUser(User user) { System.out.println("user = " + user); return "hello"; }
|
我们可以通过WebMvcConfigurer
的addFormatters
方法,为容器中添加自定义的converter
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
| @Configuration(proxyBeanMethods = false) public class WebConfig { @Bean public WebMvcConfigurer webMvcConfigurer() { return new WebMvcConfigurer() {
@Override public void addFormatters(FormatterRegistry registry) { registry.addConverter(new Converter<String, User>() { @Override public User convert(String source) { if (!source.isEmpty()) { User user = new User(); String[] split = source.split(","); user.setUserName(split[0]); user.setUserAge(Integer.parseInt(split[1])); return user; } return null; } }); } }; } }
|