每篇一句

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

相关阅读

【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、PropertyEditor

【小家Spring】聊聊Spring中的数据绑定 --- 属性访问器PropertyAccessor和实现类DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的数据绑定 --- BeanWrapper以及Java内省Introspector和PropertyDescriptor


对Spring感兴趣可扫码加入wx群:`Java高工、架构师3群`(文末有二维码)


前言

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

总结

本文介绍了Spring用于数据绑定的最直接类DataBinder,它位于spring-context这个工程的org.springframework.validation包内,所以需要再次明确的是:它是Spring提供的能力而非web提供的~

虽然我们DataBinder是Spring提供,但其实把它发扬光大是发生在Web环境,也就是大名鼎鼎的WebDataBinder,毕竟我们知道一般只有进行web交互的时候,才会涉及到字符串 -> Java类型/对象的转换,这就是下个章节讲述的重中之重~

转载:https://www.cnblogs.com/fangshixiang/p/11212092.html

作者:YourBatman

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

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

    每篇一句 唯有热爱和坚持,才能让你在程序人生中屹立不倒,切忌跟风什么语言或就学什么去~ 前言 数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容 ...

  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. netty中的future和promise源码分析(二)

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

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

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

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

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

  8. jdk、spring、mybatis、线程的源码分析

    基础篇 从为什么String=String谈到StringBuilder和StringBuffer Java语法糖1:可变长度参数以及foreach循环原理 Java语法糖2:自动装箱和自动拆箱 集合 ...

  9. 详述 Spring MVC 启动流程及相关源码分析

    文章目录 Web 应用部署初始化过程(Web Application Deployement) Spring MVC 启动过程 Listener 的初始化过程 Filter 的初始化 Servlet ...

最新文章

  1. Flink State和容错机制
  2. python 在主线程开线程_Python开启线程,在函数中开线程的实例
  3. [家里蹲大学数学杂志]第254期第五届[2013年]全国大学生数学竞赛[数学类]试题
  4. phpstudy搭建网站使用php,教你用phpstudy搭建本地服务并建dedecms网站
  5. kotlin dsl_Spring Webflux – Kotlin DSL –实现的演练
  6. 微信公众号开发 重要 access_token {errcode:40164,errmsg:invalid ip 114.221.159.11, not in whitelist hint:
  7. 八、栈的操作、栈空间
  8. Flink万字干货:4大发展方向,美团、快手应用实践(PDF下载)
  9. 关于filter用户授权的例子
  10. Kali系统下载Thefatrat太慢怎么办?
  11. CMD下复制文件和文件夹
  12. 鸿蒙系统底部任务栏无响应,win10底部任务栏无响应两种修复方法
  13. 光纤交换机分为哪几种 光纤交换机连接图
  14. 数据分析案例——航空公司客户价值分析
  15. 信息化和信息系统知识点总结
  16. IDDD 实现领域驱动设计-SOA、REST 和六边形架构
  17. 【线上问题】线上故障分析-故障分级,原因,分类,混沌工程,排除方法
  18. 音响为什么要做CCC认证以及流程有哪些
  19. 【粉丝福利】赠《机器学习算法竞赛实战》10 本书
  20. cleanup(cleanup什么意思中文)

热门文章

  1. Dell服务器raid5的在线扩容
  2. 易云捷讯联合北京菜篮子配送有限公司共建“净菜进城”智慧云平台
  3. 微信公众号语音信息自动回复语音信息
  4. android实现远程控制
  5. 音频重采样原理及技术实现
  6. FCPX插件直接导入OBJ三维模型FCPX3D MODEL
  7. ffmpeg使用转码学习
  8. gitlab-ce搭建和数据迁移
  9. 计算机与教育教学,计算机网络技术与教育教学
  10. 电视上的腾讯会员和手机上的一样吗