资讯专栏INFORMATION COLUMN

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

charles_paul / 3549人阅读

摘要:对中的数据绑定场景,小伙伴们就再熟悉不过了。比如包下大名鼎鼎的源码分析的源码相对来说还是颇为复杂的,它提供的能力非常强大,也注定了它的方法非常多属性也非常多。并且备注入群字样,会手动邀请入群

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

【小家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;

    @Nullable
    private final Object target;
    private final String objectName; // 默认值是target

    // BindingResult:绑定错误、失败的时候会放进这里来~
    @Nullable
    private AbstractPropertyBindingResult bindingResult;

    //类型转换器,会注册最为常用的那么多类型转换Map, PropertyEditor> defaultEditors
    @Nullable
    private SimpleTypeConverter typeConverter;

    // 默认忽略不能识别的字段~
    private boolean ignoreUnknownFields = true;
    // 不能忽略非法的字段(比如我要Integer,你给传aaa,那肯定就不让绑定了,抛错)
    private boolean ignoreInvalidFields = false;
    // 默认是支持级联的~~~
    private boolean autoGrowNestedPaths = true;

    private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT;

    // 这三个参数  都可以自己指定~~ 允许的字段、不允许的、必须的
    @Nullable
    private String[] allowedFields;
    @Nullable
    private String[] disallowedFields;
    @Nullable
    private String[] requiredFields;

    // 转换器ConversionService
    @Nullable
    private ConversionService conversionService;
    // 状态码处理器~
    @Nullable
    private MessageCodesResolver messageCodesResolver;
    // 绑定出现错误的处理器~
    private BindingErrorProcessor bindingErrorProcessor = new DefaultBindingErrorProcessor();
    // 校验器(这个非常重要)
    private final List 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));
    }
    // 效果同set
    public void replaceValidators(Validator... validators) {
        assertValidators(validators);
        this.validators.clear();
        this.validators.addAll(Arrays.asList(validators));
    }
    
    // 返回一个,也就是primary默认的校验器
    @Nullable
    public Validator getValidator() {
        return (!this.validators.isEmpty() ? this.validators.get(0) : null);
    }
    // 只读视图
    public List getValidators() {
        return Collections.unmodifiableList(this.validators);
    }

    // since Spring 3.0
    public 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
    @Nullable
    public  T convertIfNecessary(@Nullable Object value, @Nullable Class 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.1
    public 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

总结

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

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

知识交流
若文章格式混乱,可点击:原文链接-原文链接-原文链接-原文链接-原文链接

==The last:如果觉得本文对你有帮助,不妨点个赞呗。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的~==

**若对技术内容感兴趣可以加入wx群交流:Java高工、架构师3群
若群二维码失效,请加wx号:fsx641385712(或者扫描下方wx二维码)。并且备注:"java入群" 字样,会手动邀请入群**

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75484.html

相关文章

  • @Validated和@Valid的区别?校验级联属性(内部类)

    摘要:毕竟永远相信本文能给你带来意想不到的收获使用示例关于数据校验这一块在中的使用案例,我相信但凡有点经验的程序员应该没有不会使用的,并且还不乏熟练的选手。 每篇一句 NBA里有两大笑话:一是科比没天赋,二是詹姆斯没技术 相关阅读 【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validati...

    Winer 评论0 收藏0
  • 最全面阐述WebDataBinder理解Spring数据绑定

    摘要:每篇一句不要总问低级的问题,这样的人要么懒,不愿意上网搜索,要么笨,一点独立思考的能力都没有相关阅读小家聊聊中的数据绑定本尊源码分析小家聊聊中的数据绑定属性访问器和实现类的使用小家聊聊中的数据绑定以及内省和对感兴趣可扫码加 每篇一句 不要总问低级的问题,这样的人要么懒,不愿意上网搜索,要么笨,一点独立思考的能力都没有 相关阅读 【小家Spring】聊聊Spring中的数据绑定 --- ...

    cgspine 评论0 收藏0
  • 小家SpringSpring IoC是如何使用BeanWrapper和Java内省结合起来给Be

    摘要:从层层委托的依赖关系可以看出,的依赖注入给属性赋值是层层委托的最终给了内省机制,这是框架设计精妙处之一。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的若对技术内容感兴趣可以加入群交流高工架构师群。 每篇一句 具备了技术深度,遇到问题可以快速定位并从根本上解决。有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 相关阅读 【小家Spring】聊聊Spring中的...

    waruqi 评论0 收藏0
  • 详叙BeanWrapper和PropertyDescriptor

    摘要:关于它的数据转换使用了如下两种机制隶属于规范。这种类中的方法主要用于访问私有的字段,且方法名符合某种命名规则。如果在两个模块之间传递信息,可以将信息封装进中,这种对象称为值对象,或。 每篇一句 千古以来要饭的没有要早饭的,知道为什么吗? 相关阅读 【小家Spring】聊聊Spring中的数据转换:Converter、ConversionService、TypeConverter、Pro...

    APICloud 评论0 收藏0
  • 让Controller支持对平铺参数执行@Valid数据校验

    摘要:方案一借助对方法级别数据校验的能力首先必须明确一点此能力属于框架的,而部分框架。 每篇一句 在金字塔塔尖的是实践,学而不思则罔,思而不学则殆(现在很多编程框架都只是教你碎片化的实践) 相关阅读 【小家Java】深入了解数据校验:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【小家Spr...

    printempw 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<