SpringMVC源码阅读:属性编辑器、数据绑定
1.前言
SpringMVC是目前J2EE平台的主流Web框架,不熟悉的园友可以看SpringMVC源码阅读入门,它交代了SpringMVC的基础知识和源码阅读的技巧
本文将通过源码(基于Spring4.3.7)分析,弄清楚SpringMVC如何通过类型转换完成数据绑定和属性编辑器的原理,并自定义属性编辑器
2.源码分析
进入RequestMappingHandlerAdapter,该类支持参数解析和数据返回,进入invokeHandlerMethod方法
794行构造WebDataBinderFactory,传入HandlerMethod参数
点进去getDataBinderFactory方法,看看它做什么
886行获取@InitBinder方法
891行查找带有@ControllerAdvice注解支持的Controller
看下RequestParamMethodArgumentResolver的父类AbstractNamedValueMethodArgumentResolver的resolveArgument方法
117行获取到@InitBinder注解修饰的方法和@ControllerAdvice中的@InitBinder注解修饰的方法
118行创建一个ExtendedServletRequestDataBinder
120行arg获取参数转换结果
binderFactory变量是WebDataBinderFactory类型,打开WebDataBinderFactory,该类在Spring3.1引入,用来创建WebDataBinder
进入WebDataBinder,该类用于处理Web请求参数和JavaBean之间的数据绑定,ctrl+alt+h打开类继承图,WebDataBinder继承DataBinder
打开DataBinder类,该类允许在目标对象上设置属性值,支持数据验证和绑定,实现了PropertyEditorRegistry和TypeConverter
先打开PropertyEditorRegistry,该类给注册的JavaBean封装方法,注释提到被BeanWrapper继承,由BeanWrapperImpl实现
BeanWrappert接口提供操作JavaBean的方法,配置set/get方法
再打开TypeConverter,该类是定义类型转换方法的接口,和PropertyEditorRegistry组合使用
最后我们找到PropertyEditor,它是属性编辑的核心接口,看它的子类
稍后我们自定义属性编辑器要继承该类,重写setAsText方法
3.实例
3.1 测试BeanWrapper
创建实体类TestModel
public class TestModel { private int age; private Date birth; private String name; private boolean good; private long times; public int getAge() { return age; } public void setAge(int age) { this.age = age; } public Date getBirth() { return birth; } public void setBirth(Date birth) { this.birth = birth; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isGood() { return good; } public void setGood(boolean good) { this.good = good; } public long getTimes() { return times; } public void setTimes(long times) { this.times = times; } }
测试方法
@RequestMapping(value = "/testWrapper", produces={"application/json; charset=UTF-8"}) @ResponseBody public TestModel testWrapper() { TestModel tm = new TestModel(); BeanWrapper bw = new BeanWrapperImpl(tm); bw.setPropertyValue("good", "1"); return tm; }
浏览器输入http://localhost:8080/springmvcdemo/test/testWrapper
在PropertyEditorSupport(实现PropertyEditor)的子类CustomBooleanEditor中,setAsText方法对上述现象进行了处理
3.2 测试不使用BeanWrapper
@RequestMapping(value = "/testNotUseWrapper", produces={"application/json; charset=UTF-8"}) @ResponseBody public TestModel testNotUseWrapper() { TestModel tm = new TestModel(); BeanWrapperImpl bw = new BeanWrapperImpl(false); bw.setWrappedInstance(tm); bw.setPropertyValue("good", "1"); return tm; }
浏览器输入http://localhost:8080/springmvcdemo/test/testNotUseWrapper
因为没有对应的属性编辑器,导致String类型“1”无法转换成Boolean类型
3.3 测试无注解对象参数绑定
在SpringMVC源码阅读:Controller中参数解析我说过,ServletModelAttributeMethodProcessor处理无注解对象
@RequestMapping(value = "testObj", produces={"application/json; charset=UTF-8"}) @ResponseBody public Map testObj(Employee e) { Map resultMap = new HashMap(); resultMap.put("Employee",e); return resultMap; }
浏览器输入http://localhost:8080/springmvcdemo/test/testObj?id=1&name=s&age=12&dept.id=1&dept.name=20
resolveArgument方法在ServletModelAttributeMethodProcessor已废弃,在其父类ModelAttributeMethodProcessor被实现
99行获取参数别名
100行获取属性列表
110行创建ExtendedServletRequestDataBinder,前文已经说过
113行绑定请求参数,此时属性列表参数绑定完毕
4.编写自定义属性编辑器
自定义属性编辑器,实现PropertyEditorSupport
public class CustomDeptEditor extends PropertyEditorSupport { @Override public void setAsText(String text) throws IllegalArgumentException { if(text.indexOf(",") > 0) { Dept dept = new Dept(); String[] arr = text.split(","); dept.setId(Integer.parseInt(arr[0])); dept.setName(arr[1]); setValue(dept); } else { throw new IllegalArgumentException("dept param is error"); } } }
在TestController添加@InitBinder
@InitBinder public void initBinderDept(WebDataBinder binder) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor()); }
添加@ControllerAdvice,保证InitBinder应用到RequestMapping,就是说Controller里定义的@InitBinder和自定义的@ControllerAdvice里@InitBinder存在一个即可
@ControllerAdvice public class InitBinderControllerAdvice { @InitBinder public void initBinder(WebDataBinder binder) { binder.registerCustomEditor(Dept.class, new CustomDeptEditor()); } }
dispatcher-servlet需要配置component-scan,扫描到我们定义的ControllerAdvice
<context:component-scan base-package="org.format.demo.controlleradvice" />
浏览器输入http://localhost:8080/springmvcdemo/test/testObj?id=1&name=s&age=12&dept=1,research
5.总结
PropertyEditor是属性编辑器的接口,setAsText是核心方法,实现类PropertyEditorSupport
PropertyEditorRegistry接口给JavaBean注册对应的属性编辑器,实现类PropertyEditorRegistrySupport的createDefaultEditors创建默认的属性编辑器
TypeConverter接口,通过该接口,可以将value转换为指定类型对象,实现类TypeConverterSupport将类型转换委托给TypeConverterDelegate处理
BeanWrapper接口操作JavaBean,配置set/get方法和查询数据的可读可写性,实现类为BeanWrapperImpl
DataBinder用来set值和数据验证,WebDataBinder处理对Web请求参数到JavaBean的数据绑定
RequestMappingHandlerAdapter调用invokeHandlerMethod方法创建WebDataBinderFactory,WebDataBinderFactory创建WebDataBinder
最后HandlerMethodArgumentResolver解析参数
6.参考
https://docs.spring.io/spring/docs/current/javadoc-api/
http://www.cnblogs.com/fangjian0423/p/springMVC-databind-typeconvert.html
https://github.com/spring-projects/spring-framework
文中难免有不足,还望指出
年三十晚上完成了这篇文章,新年快乐
作者:Rest探路者
出处:http://www.cnblogs.com/Java-Starter/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意请保留此段声明,请在文章页面明显位置给出原文连接
Github:https://github.com/cjy513203427
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?