每篇一句

唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~

前言

数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是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 {}

它是个实现类,直接实现了PropertyEditorRegistryTypeConverter这两个接口,因此它可以注册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它提供了如下能力:

  1. 把属性值PropertyValues绑定到target上(bind()方法,依赖于PropertyAccessor实现~)
  2. 提供校验的能力:提供了public方法validate()对各个属性使用Validator执行校验~
  3. 提供了注册属性编辑器(PropertyEditor)和对类型进行转换的能力(TypeConverter

还需要注意的是:

  1. initBeanPropertyAccessinitDirectFieldAccess两个初始化PropertyAccessor方法是互斥的
    1. initBeanPropertyAccess()创建的是BeanPropertyBindingResult,内部依赖BeanWrapper
    2. initDirectFieldAccess创建的是DirectFieldBindingResult,内部依赖DirectFieldAccessor
  2. 这两个方法内部没有显示的调用,但是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
E-mail yourbatman@qq.com
微 信 fsx641385712
活跃平台
公众号 BAT的乌托邦(ID:BAT-utopia)
知识星球 BAT的乌托邦
每日文章推荐 每日文章推荐

聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)【享学Spring】相关推荐

  1. 聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据转换:Converter.ConversionServic ...

  2. 【小家Spring】聊聊Spring中的数据绑定 --- DataBinder本尊(源码分析)

    每篇一句 > 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 相关阅读 [小家Spring]聊聊Spring中的数据绑定 --- 属性访问器PropertyAcce ...

  3. Spring Boot Dubbo 应用启停源码分析

    作者:张乎兴 来源:Dubbo官方博客 背景介绍 Dubbo Spring Boot 工程致力于简化 Dubbo RPC 框架在Spring Boot应用场景的开发.同时也整合了 Spring Boo ...

  4. Spring Boot 2.x 启动全过程源码分析(上)入口类剖析

    转载自   Spring Boot 2.x 启动全过程源码分析(上)入口类剖析 Spring Boot 的应用教程我们已经分享过很多了,今天来通过源码来分析下它的启动过程,探究下 Spring Boo ...

  5. 源码通透-mybatis源码分析以及整合spring过程

    源码通透-mybatis源码分析以及整合spring过程 mybatis源码分析版本:mybaits3 (3.5.0-SNAPSHOT) mybatis源码下载地址:https://github.co ...

  6. netty中的future和promise源码分析(二)

    前面一篇netty中的future和promise源码分析(一)中对future进行了重点分析,接下来讲一讲promise. promise是可写的future,从future的分析中可以发现在其中没 ...

  7. Spring Boot 2.x 启动全过程源码分析(全)

    上篇<Spring Boot 2.x 启动全过程源码分析(一)入口类剖析>我们分析了 Spring Boot 入口类 SpringApplication 的源码,并知道了其构造原理,这篇我 ...

  8. WebRTC[1]-WebRTC中h264解码过程的源码分析

    目录 前言 正文 <WebRTC工作原理精讲>系列-总览_liuzhen007的专栏-CSDN博客_webrtc 原理前言欢迎大家订阅Data-Mining 的<WebRTC工作原理 ...

  9. Spring Security源码分析八:Spring Security 退出

    为什么80%的码农都做不了架构师?>>> Spring Security是一个能够为基于Spring的企业应用系统提供声明式的安全访问控制解决方案的安全框架.它提供了一组可以在Spr ...

最新文章

  1. python爬取电影和美食数据实战
  2. Ubutun重启网卡
  3. 挑战程序猿---三角形
  4. Stack Overflow上最热门问题是什么?
  5. Codeforces 821C - Okabe and Boxes
  6. 【在路上2】快递的运单轨迹
  7. 2020蓝天杯论文评比系统_教师写作:专业表达的快乐旅行 2020梅小暑期教师教育论文、教育随笔、读书心得评比结果...
  8. 在Vs.net中集成 NDoc生成的 Html Help 2帮助文档
  9. python如何执行代码漏洞_命令执行与代码执行漏洞原理
  10. c语言fread malloc,流操作之读写(fread、fwrite、fopen、malloc)
  11. Python+OpenCV:基于KNN手写数据OCR(OCR of Hand-written Data using kNN)
  12. Delphi中怎么结束线程(这个线程是定时执行的)(方案一)
  13. Codeforces Round #442 (Div. 2) D. Olya and Energy Drinks
  14. 移动政企Java线上测评_(重要)如何锻炼训练,确保通过企业线上测评 在线测评和职业性格测评...
  15. RMON学习笔记(二)
  16. 中国移动号码手机开机以及注册gprs流程(转载)
  17. Win10自带杀毒功能如何打开
  18. 请插入多卷集的最后一张磁盘,然后单击”确认“继续
  19. 特殊监管区解决方案,高等学历继续教育及高职扩招综合管理平台服务技术功能详解
  20. linux fixup段和__ex_table段

热门文章

  1. JSP四大作用域的范围
  2. 软件协作工具Trello
  3. C语言-入门-必备基础知识(九)
  4. 逻辑,问题到底在哪里?
  5. 批量邮箱注册申请,邮箱注册申请这样才靠谱!
  6. Android创意——多功能智能播放器
  7. 分类变量回归——Probit和Logit(附代码)
  8. 从Mt.Gox的破产来看业务安全的重要性
  9. 服务器硬件、部署LNMP动态网站 案例
  10. 输电线路导线舞动预警装置