【小家Spring】聊聊Spring中的數據綁定 --- DataBinder本尊(源碼分析)


每篇一句

唯有熱愛和堅持,才能讓你在程序人生中屹立不倒,切忌跟風什么語言或就學什么去~

相關閱讀

【小家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<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它提供了如下能力:

  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類型/對象的轉換,這就是下個章節講述的重中之重~

知識交流

若文章格式混亂,可點擊原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接

The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~

若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。並且備注:"java入群" 字樣,會手動邀請入群

![在這里插入圖片描述](https://img-blog.csdnimg.cn/20190703175020107.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2Y2NDEzODU3MTI=,size_16,color_FFFFFF,t_70,pic_center =300x)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM