自定义类型参数的数据绑定源码分析
自定义类型参数的数据绑定源码分析
关于自定义类型参数的参数解析场景,SpringMVC处理请求源码分析 —— part2 场景分析 > 2. 表单提交 > 2.1 源码分析 已经讲得非常清晰了,下面主要是对该博客中的内容做一些补充。
前端页面表单:
1 | <form action="/saveuser" method="post"> |
后端自定义类型 Person 以及 Pet 类定义:
1 |
|
后端处理器方法:
1 |
|
ServletModelAttributeMethodProcessor 的数据绑定原理
ServletModelAttributeMethodProcessor 参数解析实际调用的是其父类 ModelAttributeMethodProcessor#resolveArgument 方法,下面简化选出了其中的关键步骤:
1 | // ModelAttributeMethodProcessor.java |
当前 parameter 封装的是 Person saveuser(Person person) 中 Person 参数的信息,参数解析时,会先通过反射调用 Person 的无参构造器,创建 Person 壳对象/空对象。
接着,为了将请求 webRequest 中的请求参数数据,绑定到刚刚创建的 Person 壳对象的属性中,需要数据绑定工厂生产出一个数据绑定器 binder 来帮我们完成这项工作(数据绑定工厂在生产时,会将其内部的转换服务,装配给所生产出的数据绑定器,从而数据绑定器具备数据转换能力)。
然后,数据绑定器解析得到请求中的数据,并将数据转换为恰当的格式后,绑定到 Person 壳对象的属性中。
数据绑定工作完成后,还会将已完成绑定的 Person 对象,以及本次绑定结果,都放到 mavContainer 中。
所以重点在于 bindRequestParameters(binder, webRequest),往下看:
1 | // ServletModelAttributeMethodProcessor.java |
重点在于 servletBinder.bind(servletRequest),往下看:
1 | // ServletRequestDataBinder.java |
数据绑定器首先对请求参数数据进行解析,将其转换为 MutablePropertyValues。
MutablePropertyValues 内部保存了一个 List<PropertyValue>,PropertyValue 能够存储一个键值对,List 中每个 PropertyValue 就相应封装了请求参数数据中的一个 KV 对。
比如请求数据是 userName=tom&age=18,那么就会转换得到两个 PropertyValue:PropertyValue1(name="userName", value="tom"),PropertyValue2(name="age", value="18")。
底层是如何进行转换的呢?其实是 request.getParameterNames() 调用原生 API 拿到所有请求参数的参数名,然后遍历每个参数名,调用 request.getParameterValues() 来获取值,拿到参数名和参数值以后,就可以封装为一个 PropertyValue。
数据绑定操作重点在于 doBind(mpvs),往下看:
1 | // DataBinder.java |
重点在于 applyPropertyValues(mpvs),往下看:
1 | // DataBinder.java |
getPropertyAccessor() 会返回一个 BeanWrapperImpl。
BeanWrapperImpl 见名知义,它就是一个包装对象,其内部封装了一个 JavaBean 对象(这里封装的是 Person 壳对象),它主要有两个功能:
- 基于反射提供了一系列功能强大的函数,能够很方便地对被包装的 JavaBean 对象的属性进行各种访问和设置(比如支持级联属性的访问和设置)
- 内部包含了转换服务,在操作属性时,提供类型转换功能。
getPropertyAccessor() 函数所做的工作是,找到 this.target 所指向的 Person 壳对象,使用 BeanWrapperImpl 对其进行包装,将数据绑定器内部的转换服务 conversionService 装配给 BeanWrapperImpl。
这样后续我们就能直接调用 BeanWrapperImpl#setPropertyValues 来对被包装的 Person 壳对象的属性赋值了。
我们深入 setPropertyValues 看看:
1 |
|
可以看到是取出了之前准备好的 List<PropertyValue>,然后逐个 PropertyValue 调用 setPropertyValue(pv) 进行 Person 壳对象的属性绑定。
setPropertyValue(pv) 底层无非就是对值进行类型转换,然后通过反射调用 setter 进行属性值的设置。
进入 setPropertyValue 看看:
1 |
|
getPropertyAccessorForPropertyPath(propertyName) 见名知义,就是根据属性名路径,确定对应的属性访问器,也即确定对应的 BeanWrapperImpl。
当 propertyName 值为 userName 时,该方法获取到的 BeanWrapperImpl 就是 this!注意,当前 setPropertyValue 就是封装了 Person 壳对象的 BeanWrapperImpl 内部的方法,Person 壳对象中有 userName 属性。
当 propertyName 值为级联属性 pet.age 时,该方法获取到的 BeanWrapperImpl 就不是 this 了,而是一个 Pet 壳对象的 BeanWrapperImpl。无论是 Pet 壳对象,还是包装了 Pet 壳对象的 BeanWrapperImpl,都是在该方法底层刚刚处理 pet.age 时创建的。
有兴趣的话,可以深入 getPropertyAccessorForPropertyPath 源码底层进行学习。
根据属性路径,拿到对应的属性访问器 nestedPa 后,就可以执行 nestedPa.setPropertyValue(tokens, pv) 来做属性绑定了:
1 | protected void setPropertyValue(PropertyTokenHolder tokens, PropertyValue pv) throws BeansException { |
数据绑定器相关的 SpringMVC 自动配置
数据绑定器是数据绑定器工厂生产出来的。
1 | WebDataBinder binder = binderFactory.createBinder(webRequest, attribute, name); |
数据绑定器工厂是在 RequestMappingHandlerAdapter#invokeHandlerMethod 中构造出来的,该工厂封装了 RequestMappingHandlerAdapter 中的 WebBindingInitializer。
1 |
|
WebBindingInitializer 是一个接口,它只有一个实现类 ConfigurableWebBindingInitializer,实现类内部封装了转换服务 private ConversionService conversionService,数据绑定器中的转换服务就是一路从这里拿到的。
ConfigurableWebBindingInitializer 并没有直接注册在容器中!SpringMVC 的自动配置类中,是在注册 RequestMappingHandlerAdapter 时,创建了 ConfigurableWebBindingInitializer 对象并设置到 RequestMappingHandlerAdapter 中。
此外,SpringMVC 自动配置类还注册了转换服务 ConversionService 到容器中。