聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)【享学Spring】
每篇一句
唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~
前言
数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容错性以及更高的编码效率。它提供的能力是:把字符串形式的参数转换成服务端真正需要的类型的转换(当然可能还包含校验)。
对Spring
中的数据绑定场景,小伙伴们就再熟悉不过了。比如我们Controller
中只需要使用Model对象就能完成request
到Model对象的自动数据自动绑定,使用起来着实非常方便~(完全屏蔽了Servlet的API)
既然数据绑定这么重要,但是为何鲜有人提起呢?我也上网搜了搜关于DataBinder
的相关资料,相对来说还是寥寥无几的~
我们不提起并不代表它不重要,这些都是Spring它帮我们默默的干了。这也印证了那句名言嘛:我们的安好是因为有人替我们负重前行
查到网上的资料,大都停留在如何使用WebDataBinder
的说明上,并且几乎没有文章是专门分析核心部件DataBinder
的,本文作为此方面的一股清流,在此把我结合官方文档、源码的所获分享给大家~
DataBinder
注意,此类所在的包是org.springframework.validation
,所以可想而知,它不仅仅完成数据的绑定,还会和数据校验有关~
注意:我看到有的文章说
DataBinder
在绑定的时候还会进行数据校验Validation,其实这个是不准确的,容易误导人(校验动作不发生在DataBinder
本类)
还有说
DataBinder
数据绑定最终依赖的是BeanWrapper
,其实这个也是不准确的,实际上依赖的是PropertyAccessor
。
DataBinder使用Demo
先看一个简单Demo
,体验一把直接使用DataBinder
进行数据绑定吧:
public static void main(String[] args) throws BindException {Person person = new Person();DataBinder binder = new DataBinder(person, "person");MutablePropertyValues pvs = new MutablePropertyValues();pvs.add("name", "fsx");pvs.add("age", 18);binder.bind(pvs);Map<?, ?> close = binder.close();System.out.println(person);System.out.println(close);}
输出:
Person{name='fsx', age=18}
{person=Person{name='fsx', age=18}, org.springframework.validation.BindingResult.person=org.springframework.validation.BeanPropertyBindingResult: 0 errors}
其实Spring
一直是在弱化数据绑定对使用者的接触(这就是为何鲜有人提起的原因),所以之前博文也说到Spring
并不推荐直接使用BeanWrapper
去自己绑定数据(而是都让框架自己来完成吧~)。
BeanWrapper
不推荐直接使用,但是DataBinder
是一个更为成熟、完整
些的数据绑定器,若实在有需求使用它是比使用BeanWrapper
是个更好的选择~
其实直接使用顶层的
DataBinder
也是一般不会的,而是使用它的子类。比如web包下大名鼎鼎的WebDataBinder
~
源码分析
DataBinder
的源码相对来说还是颇为复杂的,它提供的能力非常强大,也注定了它的方法非常多、属性也非常多。
首先看看类声明:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {}
它是个实现类,直接实现了PropertyEditorRegistry
和TypeConverter
这两个接口,因此它可以注册java.beans.PropertyEditor
,并且能完成类型转换(TypeConverter
)。
关于数据转换这块内容,有兴趣的可参见:【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor
接下里分析具体源码(需要解释说明都写在源码处了):
public class DataBinder implements PropertyEditorRegistry, TypeConverter {/** Default object name used for binding: "target". */public static final String DEFAULT_OBJECT_NAME = "target";/** Default limit for array and collection growing: 256. */public static final int DEFAULT_AUTO_GROW_COLLECTION_LIMIT = 256;@Nullableprivate final Object target;private final String objectName; // 默认值是target// BindingResult:绑定错误、失败的时候会放进这里来~@Nullableprivate AbstractPropertyBindingResult bindingResult;//类型转换器,会注册最为常用的那么多类型转换Map<Class<?>, PropertyEditor> defaultEditors@Nullableprivate SimpleTypeConverter typeConverter;// 默认忽略不能识别的字段~private boolean ignoreUnknownFields = true;// 不能忽略非法的字段(比如我要Integer,你给传aaa,那肯定就不让绑定了,抛错)private boolean ignoreInvalidFields = false;// 默认是支持级联的~~~private boolean autoGrowNestedPaths = true;private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;// 这三个参数 都可以自己指定~~ 允许的字段、不允许的、必须的@Nullableprivate String[] allowedFields;@Nullableprivate String[] disallowedFields;@Nullableprivate String[] requiredFields;// 转换器ConversionService@Nullableprivate ConversionService conversionService;// 状态码处理器~@Nullableprivate MessageCodesResolver messageCodesResolver;// 绑定出现错误的处理器~private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();// 校验器(这个非常重要)private final List<Validator> validators = new ArrayList<>();// objectName没有指定,就用默认的public DataBinder(@Nullable Object target) {this(target, DEFAULT_OBJECT_NAME);}public DataBinder(@Nullable Object target, String objectName) {this.target = ObjectUtils.unwrapOptional(target);this.objectName = objectName;}... // 省略所有属性的get/set方法// 提供一些列的初始化方法,供给子类使用 或者外部使用 下面两个初始化方法是互斥的public void initBeanPropertyAccess() {Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initBeanPropertyAccess before other configuration methods");this.bindingResult = createBeanPropertyBindingResult();}protected AbstractPropertyBindingResult createBeanPropertyBindingResult() {BeanPropertyBindingResult result = new BeanPropertyBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths(), getAutoGrowCollectionLimit());if (this.conversionService != null) {result.initConversion(this.conversionService);}if (this.messageCodesResolver != null) {result.setMessageCodesResolver(this.messageCodesResolver);}return result;}// 你会发现,初始化DirectFieldAccess的时候,校验的也是bindingResult ~~~~public void initDirectFieldAccess() {Assert.state(this.bindingResult == null, "DataBinder is already initialized - call initDirectFieldAccess before other configuration methods");this.bindingResult = createDirectFieldBindingResult();}protected AbstractPropertyBindingResult createDirectFieldBindingResult() {DirectFieldBindingResult result = new DirectFieldBindingResult(getTarget(), getObjectName(), isAutoGrowNestedPaths());if (this.conversionService != null) {result.initConversion(this.conversionService);}if (this.messageCodesResolver != null) {result.setMessageCodesResolver(this.messageCodesResolver);}return result;}...// 把属性访问器返回,PropertyAccessor(默认直接从结果里拿),子类MapDataBinder有复写protected ConfigurablePropertyAccessor getPropertyAccessor() {return getInternalBindingResult().getPropertyAccessor();}// 可以看到简单的转换器也是使用到了conversionService的,可见conversionService它的效用protected SimpleTypeConverter getSimpleTypeConverter() {if (this.typeConverter == null) {this.typeConverter = new SimpleTypeConverter();if (this.conversionService != null) {this.typeConverter.setConversionService(this.conversionService);}}return this.typeConverter;}... // 省略众多get方法// 设置指定的可以绑定的字段,默认是所有字段~~~// 例如,在绑定HTTP请求参数时,限制这一点以避免恶意用户进行不必要的修改。// 简单的说:我可以控制只有指定的一些属性才允许你修改~~~~// 注意:它支持xxx*,*xxx,*xxx*这样的通配符 支持[]这样子来写~public void setAllowedFields(@Nullable String... allowedFields) {this.allowedFields = PropertyAccessorUtils.canonicalPropertyNames(allowedFields);}public void setDisallowedFields(@Nullable String... disallowedFields) {this.disallowedFields = PropertyAccessorUtils.canonicalPropertyNames(disallowedFields);}// 注册每个绑定进程所必须的字段。public void setRequiredFields(@Nullable String... requiredFields) {this.requiredFields = PropertyAccessorUtils.canonicalPropertyNames(requiredFields);if (logger.isDebugEnabled()) {logger.debug("DataBinder requires binding of required fields [" + StringUtils.arrayToCommaDelimitedString(requiredFields) + "]");}}...// 注意:这个是set方法,后面是有add方法的~// 注意:虽然是set,但是引用是木有变的~~~~public void setValidator(@Nullable Validator validator) {// 判断逻辑在下面:你的validator至少得支持这种类型呀 哈哈assertValidators(validator);// 因为自己手动设置了,所以先清空 再加进来~~~// 这步你会发现,即使validator是null,也是会clear的哦~ 符合语意this.validators.clear();if (validator != null) {this.validators.add(validator);}}private void assertValidators(Validator... validators) {Object target = getTarget();for (Validator validator : validators) {if (validator != null && (target != null && !validator.supports(target.getClass()))) {throw new IllegalStateException("Invalid target for Validator [" + validator + "]: " + target);}}}public void addValidators(Validator... validators) {assertValidators(validators);this.validators.addAll(Arrays.asList(validators));}// 效果同setpublic void replaceValidators(Validator... validators) {assertValidators(validators);this.validators.clear();this.validators.addAll(Arrays.asList(validators));}// 返回一个,也就是primary默认的校验器@Nullablepublic Validator getValidator() {return (!this.validators.isEmpty() ? this.validators.get(0) : null);}// 只读视图public List<Validator> getValidators() {return Collections.unmodifiableList(this.validators);}// since Spring 3.0public void setConversionService(@Nullable ConversionService conversionService) {Assert.state(this.conversionService == null, "DataBinder is already initialized with ConversionService");this.conversionService = conversionService;if (this.bindingResult != null && conversionService != null) {this.bindingResult.initConversion(conversionService);}}// =============下面它提供了非常多的addCustomFormatter()方法 注册进PropertyEditorRegistry里=====================public void addCustomFormatter(Formatter<?> formatter);public void addCustomFormatter(Formatter<?> formatter, String... fields);public void addCustomFormatter(Formatter<?> formatter, Class<?>... fieldTypes);// 实现接口方法public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String field, PropertyEditor propertyEditor);...// 实现接口方法// 统一委托给持有的TypeConverter~~或者是getInternalBindingResult().getPropertyAccessor();这里面的@Override@Nullablepublic <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType,@Nullable MethodParameter methodParam) throws TypeMismatchException {return getTypeConverter().convertIfNecessary(value, requiredType, methodParam);}// ===========上面的方法都是开胃小菜,下面才是本类最重要的方法==============// 该方法就是把提供的属性值们,绑定到目标对象target里去~~~public void bind(PropertyValues pvs) {MutablePropertyValues mpvs = (pvs instanceof MutablePropertyValues ? (MutablePropertyValues) pvs : new MutablePropertyValues(pvs));doBind(mpvs);}// 此方法是protected的,子类WebDataBinder有复写~~~加强了一下protected void doBind(MutablePropertyValues mpvs) {// 前面两个check就不解释了,重点看看applyPropertyValues(mpvs)这个方法~checkAllowedFields(mpvs);checkRequiredFields(mpvs);applyPropertyValues(mpvs);}// allowe允许的 并且还是没有在disallowed里面的 这个字段就是被允许的protected boolean isAllowed(String field) {String[] allowed = getAllowedFields();String[] disallowed = getDisallowedFields();return ((ObjectUtils.isEmpty(allowed) || PatternMatchUtils.simpleMatch(allowed, field)) &&(ObjectUtils.isEmpty(disallowed) || !PatternMatchUtils.simpleMatch(disallowed, field)));}...// protected 方法,给target赋值~~~~protected void applyPropertyValues(MutablePropertyValues mpvs) {try {// 可以看到最终赋值 是委托给PropertyAccessor去完成的getPropertyAccessor().setPropertyValues(mpvs, isIgnoreUnknownFields(), isIgnoreInvalidFields());// 抛出异常,交给BindingErrorProcessor一个个处理~~~} catch (PropertyBatchUpdateException ex) {for (PropertyAccessException pae : ex.getPropertyAccessExceptions()) {getBindingErrorProcessor().processPropertyAccessException(pae, getInternalBindingResult());}}}// 执行校验,此处就和BindingResult 关联上了,校验失败的消息都会放进去(不是直接抛出异常哦~ )public void validate() {Object target = getTarget();Assert.state(target != null, "No target to validate");BindingResult bindingResult = getBindingResult();// 每个Validator都会执行~~~~for (Validator validator : getValidators()) {validator.validate(target, bindingResult);}}// 带有校验提示的校验器。SmartValidator// @since 3.1public void validate(Object... validationHints) { ... }// 这一步也挺有意思:实际上就是若有错误,就抛出异常// 若没错误 就把绑定的Model返回~~~(可以看到BindingResult里也能拿到最终值哦~~~)// 此方法可以调用,但一般较少使用~public Map<?, ?> close() throws BindException {if (getBindingResult().hasErrors()) {throw new BindException(getBindingResult());}return getBindingResult().getModel();}
}
从源源码的分析中,大概能总结到DataBinder
它提供了如下能力:
- 把属性值
PropertyValues
绑定到target上(bind()
方法,依赖于PropertyAccessor
实现~) - 提供校验的能力:提供了public方法
validate()
对各个属性使用Validator
执行校验~ - 提供了注册属性编辑器(
PropertyEditor
)和对类型进行转换的能力(TypeConverter
)
还需要注意的是:
initBeanPropertyAccess
和initDirectFieldAccess
两个初始化PropertyAccessor
方法是互斥的
1.initBeanPropertyAccess()
创建的是BeanPropertyBindingResult
,内部依赖BeanWrapper
2.initDirectFieldAccess
创建的是DirectFieldBindingResult
,内部依赖DirectFieldAccessor
- 这两个方法内部没有显示的调用,但是Spring内部默认使用的是
initBeanPropertyAccess()
,具体可以参照getInternalBindingResult()
方法~
总结
本文介绍了Spring
用于数据绑定的最直接类DataBinder
,它位于spring-context
这个工程的org.springframework.validation
包内,所以需要再次明确的是:它是Spring提供的能力而非web提供的~
虽然我们DataBinder
是Spring提供,但其实把它发扬光大是发生在Web环境,也就是大名鼎鼎的WebDataBinder
,毕竟我们知道一般只有进行web交互的时候,才会涉及到字符串 -> Java类型/对象的转换,这就是下个章节讲述的重中之重~
相关阅读
聊聊Spring中的数据绑定 — 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用【享学Spring】
聊聊Spring中的数据绑定 — BeanWrapper以及Java内省Introspector和PropertyDescriptor【享学Spring】
关注A哥
Author | A哥(YourBatman) |
---|---|
个人站点 | www.yourbatman.cn |
yourbatman@qq.com | |
微 信 | fsx641385712 |
活跃平台
|
|
公众号 | BAT的乌托邦(ID:BAT-utopia) |
知识星球 | BAT的乌托邦 |
每日文章推荐 | 每日文章推荐 |
聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)【享学Spring】相关推荐
- 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)
每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionServic ...
- 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)
每篇一句 > 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAcce ...
- Spring Boot Dubbo 应用启停源码分析
作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发.同时也整合了 Spring Boo ...
- Spring Boot 2.x 启动全过程源码分析(上)入口类剖析
转载自 Spring Boot 2.x 启动全过程源码分析(上)入口类剖析 Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boo ...
- 源码通透-mybatis源码分析以及整合spring过程
源码通透-mybatis源码分析以及整合spring过程 mybatis源码分析版本:mybaits3 (3.5.0-SNAPSHOT) mybatis源码下载地址:https://github.co ...
- netty中的future和promise源码分析(二)
前面一篇netty中的future和promise源码分析(一)中对future进行了重点分析,接下来讲一讲promise. promise是可写的future,从future的分析中可以发现在其中没 ...
- Spring Boot 2.x 启动全过程源码分析(全)
上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入口类 SpringApplication 的源码,并知道了其构造原理,这篇我 ...
- WebRTC[1]-WebRTC中h264解码过程的源码分析
目录 前言 正文 <WebRTC工作原理精讲>系列-总览_liuzhen007的专栏-CSDN博客_webrtc 原理前言欢迎大家订阅Data-Mining 的<WebRTC工作原理 ...
- Spring Security源码分析八:Spring Security 退出
为什么80%的码农都做不了架构师?>>> Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spr ...
最新文章
- python爬取电影和美食数据实战
- Ubutun重启网卡
- 挑战程序猿---三角形
- Stack Overflow上最热门问题是什么?
- Codeforces 821C - Okabe and Boxes
- 【在路上2】快递的运单轨迹
- 2020蓝天杯论文评比系统_教师写作:专业表达的快乐旅行 2020梅小暑期教师教育论文、教育随笔、读书心得评比结果...
- 在Vs.net中集成 NDoc生成的 Html Help 2帮助文档
- python如何执行代码漏洞_命令执行与代码执行漏洞原理
- c语言fread malloc,流操作之读写(fread、fwrite、fopen、malloc)
- Python+OpenCV:基于KNN手写数据OCR(OCR of Hand-written Data using kNN)
- Delphi中怎么结束线程(这个线程是定时执行的)(方案一)
- Codeforces Round #442 (Div. 2) D. Olya and Energy Drinks
- 移动政企Java线上测评_(重要)如何锻炼训练,确保通过企业线上测评 在线测评和职业性格测评...
- RMON学习笔记(二)
- 中国移动号码手机开机以及注册gprs流程(转载)
- Win10自带杀毒功能如何打开
- 请插入多卷集的最后一张磁盘,然后单击”确认“继续
- 特殊监管区解决方案,高等学历继续教育及高职扩招综合管理平台服务技术功能详解
- linux fixup段和__ex_table段