首页 > 其他分享 >聊聊Spring中的数据绑定DataBinder

聊聊Spring中的数据绑定DataBinder

时间:2022-11-23 14:22:59浏览次数:44  
标签:target DataBinder Nullable Spring void 绑定 validators null public

数据绑定 这个概念在任何一个成型的框架中都是特别重要的(尤其是web框架),它能让框架更多的自动化,更好容错性以及更高的编码效率。它提供的能力是:把字符串形式的参数转换成服务端真正需要的类型的转换(当然可能还包含校验)。

DataBinder

此类所在的包是org.springframework.validation,所以可想而知,它不仅仅完成数据的绑定,还会和数据校验有关。

先看一个简单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);
}

源码分析

public class DataBinder implements PropertyEditorRegistry, TypeConverter {}

它是个实现类,直接实现了PropertyEditorRegistry和TypeConverter这两个接口,因此它可以注册java.beans.PropertyEditor,并且能完成类型转换(TypeConverter)。

具体源码如下:

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<Class<?>, 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<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));
    }
    // 效果同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<Validator> 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> 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.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)

需要注意的是:

  • initBeanPropertyAccess和initDirectFieldAccess两个初始化PropertyAccessor方法是互斥的
    • initBeanPropertyAccess()创建的是BeanPropertyBindingResult,内部依赖BeanWrapper
    • initDirectFieldAccess创建的是DirectFieldBindingResult,内部依赖DirectFieldAccessor
  • 这两个方法内部没有显示的调用,但是Spring内部默认使用的是initBeanPropertyAccess(),具体可以参照getInternalBindingResult()方法

WebDataBinder

 

标签:target,DataBinder,Nullable,Spring,void,绑定,validators,null,public
From: https://www.cnblogs.com/xfeiyun/p/15680377.html

相关文章

  • Java基础__Spring思想
    IoC控制反转对象的创建控制权由程序转移到外部(解耦),Spring对IoC思想进行了实现Spring提供了一个容器,IoC容器,用于充当IoC思想的外部,被创建的对象在Io......
  • Apache Solr 的 Spring Data (数据)
    版本4.3.15SpringDataforApacheSolr项目通过使用ApacheSolr搜索引擎将Spring的核心概念应用于解决方案的开发。我们提供了一个“模板”作为存储和查询文档的高级抽象......
  • SpringBatch 框架
    SpringBatch是一个轻量级的、完善的批处理框架,旨在帮助企业建立健壮、高效的批处理应用。它是一个批处理应用框架,不是调度框架,需要配合调度框架来完成批处理工作。这里......
  • WPF-简单数据绑定
    为了保持数据和控件内容的同步,我们通常的方法是在:数据类中创建事件,在属性修改时通知控件修改内容;在控件所属类中创建事件,在内容修改时通知数据对象更新属性,这叫做数据绑定......
  • Springboot 打包细节
    这里是打包关键备注一篇文章带你认识SpringBoot打包成的可执行jar,不能被其他项目依赖https://blog.csdn.net/nanhuaibeian/article/details/109310112需要注意,......
  • Spring Security(3)
    您好,我是湘王,这是我的博客园,欢迎您来,欢迎您再来~ 前面运行写好的代码之所以没有任何显示,是因为还没有对SpringSecurity进行配置,当然啥也不显示了。这就好比你坐在车上,却......
  • Spring面相切片编程的配置。
    AOP面向切面配置:1、context:component-scan:扫描包路径下的所有类注解。<!--指定扫描com.sfwu15.bean包下的所有类的注解注意:扫描包时,会扫描所有包下的子孙包--><......
  • Grafana+OpenSearch+Spring Boot集成(一) 【基础环境配置】
    先决条件1、环境Windows,内存最好在8GB以上已安装DockerDesktop(下载地址:https://www.docker.com/products/docker-desktop/)2、知识准备了解如何使用Docker了解Op......
  • Winform控件绑定数据
    目录简介绑定基类功能扩展简单控件绑定列表控件绑定绑定BindingList集合绑定DataTable表格绑定BindingSource源表格控件绑定绑定DataTable绑定BindingListUI线程全局类简......
  • Spring Cloud LoadBalancer 如何指定服务使用指定负载均衡策略
    当系统中有多个服务A,B,C时默认使用轮询策略当我们A服务需要使用指定IP策略时只需要在springboot代码中使用注解@LoadBalancerClients(value={@LoadBalancerClient......