SpringBoot源码系列(6):参数解析之自定义参数绑定

布鸽不鸽 Lv4

前言

在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) {
/*
参数标ModelAttribute注解 or 参数不是简单属性
我们的参数User user肯定不是简单类型
*/
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注解,绑定一大堆东西。我们这里没有,暂时不用管
ModelAttribute ann = parameter.getParameterAnnotation(ModelAttribute.class);
if (ann != null) {
mavContainer.setBinding(name, ann.binding());
}

Object attribute = null;
BindingResult bindingResult = null;

// 查看mav中有没有对应的参数。如果以前modelAndView中已经有了,就直接返回。也不用管。
if (mavContainer.containsAttribute(name)) {
attribute = mavContainer.getModel().get(name);
} else {
// 创建一个空实例
try {
attribute = createAttribute(name, parameter, binderFactory, webRequest);
}
}

// 我们还没有绑定参数到对象
if (bindingResult == null) {
// 将请求封装到WebDataBinder对象中,WebDataBinder对象负责将值绑定到bean中
WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name);
if (binder.getTarget() != null) {
if (!mavContainer.isBindingDisabled(name)) {
// 最关键的一步,将请求中的参数绑定到target对象
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;

// 先对请求做了下类型转换,然后使用WebDataBinder,将request传入
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);
// 上述操作绑定了所需的参数,mvps保存了请求中所有的key-value对
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);
}
}
}

继续查看AbstractPropertyAccessorsetPropertyValue方法

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 {
// 利用反射工具PropertyAccessor,访问属性,返回
nestedPa = getPropertyAccessorForPropertyPath(propertyName);
}

// 利用wrapper设置属性的值
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) {
/*
用于处理键值对属性
例如,当属性名是person.address.street时,tokens.keys将包含[person, address]作为键的标记
*/
processKeyedProperty(tokens, pv);
}
else {
/*
用于处理本地属性。本地属性是指不涉及键值对的普通属性
例如,当属性名是name时,tokens.keys将为null,这个方法可以直接获取或设置属性值。
*/
processLocalProperty(tokens, pv); // 步入
}
}

AbstractNestablePropertyAccessor中,processLocalProperty方法

1
2
3
4
5
6
7
8
9
// 代码有删改
private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) {

// 类型转换,比如将"23"转化为Integer
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,某种转换工具。
// 里面有converters,包含124种转换器。
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) {
// 拿到converter后进行处理
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.userAgesourceTypeStringtargetTypeInteger

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.converterFactoryStringToNumberConverterFactory类型的。

通过它,我们拿到了需要的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";
}

我们可以通过WebMvcConfigureraddFormatters方法,为容器中添加自定义的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
// 在配置类中配置自定义webMvcConfigurer,利用其中addFormatters添加自定义Converter
@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;
}
});
}
};
}
}
  • 标题: SpringBoot源码系列(6):参数解析之自定义参数绑定
  • 作者: 布鸽不鸽
  • 创建于 : 2023-05-17 16:53:50
  • 更新于 : 2023-06-26 09:30:46
  • 链接: https://xuedongyun.cn//post/44248/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论