摘要:對中的數(shù)據(jù)綁定場景,小伙伴們就再熟悉不過了。比如包下大名鼎鼎的源碼分析的源碼相對來說還是頗為復雜的,它提供的能力非常強大,也注定了它的方法非常多屬性也非常多。并且備注入群字樣,會手動邀請入群
每篇一句
唯有熱愛和堅持,才能讓你在程序人生中屹立不倒,切忌跟風什么語言或就學什么去~相關閱讀
【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- 屬性訪問器PropertyAccessor和實現(xiàn)類DirectFieldAccessor的使用
【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptor
數(shù)據(jù)綁定 這個概念在任何一個成型的框架中都是特別重要的(尤其是web框架),它能讓框架更多的自動化,更好容錯性以及更高的編碼效率。它提供的能力是:把字符串形式的參數(shù)轉換成服務端真正需要的類型的轉換(當然可能還包含校驗)。
對Spring中的數(shù)據(jù)綁定場景,小伙伴們就再熟悉不過了。比如我們Controller中只需要使用Model對象就能完成request到Model對象的自動數(shù)據(jù)自動綁定,使用起來著實非常方便~(完全屏蔽了Servlet的API)
既然數(shù)據(jù)綁定這么重要,但是為何鮮有人提起呢?我也上網(wǎng)搜了搜關于DataBinder的相關資料,相對來說還是寥寥無幾的~
我們不提起并不代表它不重要,這些都是Spring它幫我們默默的干了。這也印證了那句名言嘛:我們的安好是因為有人替我們負重前行
查到網(wǎng)上的資料,大都停留在如何使用WebDataBinder的說明上,并且?guī)缀鯖]有文章是專門分析核心部件DataBinder的,本文作為此方面的一股清流,在此把我結合官方文檔、源碼的所獲分享給大家~
DataBinder注意,此類所在的包是org.springframework.validation,所以可想而知,它不僅僅完成數(shù)據(jù)的綁定,還會和數(shù)據(jù)校驗有關~
注意:我看到有的文章說DataBinder在綁定的時候還會進行數(shù)據(jù)校驗Validation,其實這個是不準確的,容易誤導人(校驗動作不發(fā)生在DataBinder本類)DataBinder使用Demo還有說DataBinder數(shù)據(jù)綁定最終依賴的是BeanWrapper,其實這個也是不準確的,實際上依賴的是PropertyAccessor。
先看一個簡單Demo,體驗一把直接使用DataBinder進行數(shù)據(jù)綁定吧:
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一直是在弱化數(shù)據(jù)綁定對使用者的接觸(這就是為何鮮有人提起的原因),所以之前博文也說到Spring并不推薦直接使用BeanWrapper去自己綁定數(shù)據(jù)(而是都讓框架自己來完成吧~)。
BeanWrapper不推薦直接使用,但是DataBinder是一個更為成熟、完整些的數(shù)據(jù)綁定器,若實在有需求使用它是比使用BeanWrapper是個更好的選擇~
其實直接使用頂層的DataBinder也是一般不會的,而是使用它的子類。比如web包下大名鼎鼎的WebDataBinder~源碼分析
DataBinder的源碼相對來說還是頗為復雜的,它提供的能力非常強大,也注定了它的方法非常多、屬性也非常多。
首先看看類聲明:
public class DataBinder implements PropertyEditorRegistry, TypeConverter {}
它是個實現(xiàn)類,直接實現(xiàn)了PropertyEditorRegistry和TypeConverter 這兩個接口,因此它可以注冊java.beans.PropertyEditor,并且能完成類型轉換(TypeConverter)。
關于數(shù)據(jù)轉換這塊內容,有興趣的可參見:【小家Spring】聊聊Spring中的數(shù)據(jù)轉換: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; // 默認是支持級聯(lián)的~~~ private boolean autoGrowNestedPaths = true; private int autoGrowCollectionLimit = DEFAULT_AUTO_GROW_COLLECTION_LIMIT; // 這三個參數(shù) 都可以自己指定~~ 允許的字段、不允許的、必須的 @Nullable private String[] allowedFields; @Nullable private String[] disallowedFields; @Nullable private String[] requiredFields; // 轉換器ConversionService @Nullable private ConversionService conversionService; // 狀態(tài)碼處理器~ @Nullable private MessageCodesResolver messageCodesResolver; // 綁定出現(xiàn)錯誤的處理器~ 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; } // 你會發(fā)現(xiàn),初始化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請求參數(shù)時,限制這一點以避免惡意用戶進行不必要的修改。 // 簡單的說:我可以控制只有指定的一些屬性才允許你修改~~~~ // 注意:它支持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); // 因為自己手動設置了,所以先清空 再加進來~~~ // 這步你會發(fā)現(xiàn),即使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); // 實現(xiàn)接口方法 public void registerCustomEditor(Class> requiredType, PropertyEditor propertyEditor); public void registerCustomEditor(@Nullable Class> requiredType, @Nullable String field, PropertyEditor propertyEditor); ... // 實現(xiàn)接口方法 // 統(tǒng)一委托給持有的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()); } } } // 執(zhí)行校驗,此處就和BindingResult 關聯(lián)上了,校驗失敗的消息都會放進去(不是直接拋出異常哦~ ) public void validate() { Object target = getTarget(); Assert.state(target != null, "No target to validate"); BindingResult bindingResult = getBindingResult(); // 每個Validator都會執(zhí)行~~~~ 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實現(xiàn)~)
提供校驗的能力:提供了public方法validate()對各個屬性使用Validator執(zhí)行校驗~
提供了注冊屬性編輯器(PropertyEditor)和對類型進行轉換的能力(TypeConverter)
總結本文介紹了Spring用于數(shù)據(jù)綁定的最直接類DataBinder,它位于spring-context這個工程的org.springframework.validation包內,所以需要再次明確的是:它是Spring提供的能力而非web提供的~
雖然我們DataBinder是Spring提供,但其實把它發(fā)揚光大是發(fā)生在Web環(huán)境,也就是大名鼎鼎的WebDataBinder,畢竟我們知道一般只有進行web交互的時候,才會涉及到字符串 -> Java類型/對象的轉換,這就是下個章節(jié)講述的重中之重~
知識交流若文章格式混亂,可點擊:原文鏈接-原文鏈接-原文鏈接-原文鏈接-原文鏈接
==The last:如果覺得本文對你有幫助,不妨點個贊唄。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的~==
**若對技術內容感興趣可以加入wx群交流:Java高工、架構師3群。
若群二維碼失效,請加wx號:fsx641385712(或者掃描下方wx二維碼)。并且備注:"java入群" 字樣,會手動邀請入群**
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://www.ezyhdfw.cn/yun/75484.html
摘要:畢竟永遠相信本文能給你帶來意想不到的收獲使用示例關于數(shù)據(jù)校驗這一塊在中的使用案例,我相信但凡有點經(jīng)驗的程序員應該沒有不會使用的,并且還不乏熟練的選手。 每篇一句 NBA里有兩大笑話:一是科比沒天賦,二是詹姆斯沒技術 相關閱讀 【小家Java】深入了解數(shù)據(jù)校驗:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validati...
摘要:每篇一句不要總問低級的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點獨立思考的能力都沒有相關閱讀小家聊聊中的數(shù)據(jù)綁定本尊源碼分析小家聊聊中的數(shù)據(jù)綁定屬性訪問器和實現(xiàn)類的使用小家聊聊中的數(shù)據(jù)綁定以及內省和對感興趣可掃碼加 每篇一句 不要總問低級的問題,這樣的人要么懶,不愿意上網(wǎng)搜索,要么笨,一點獨立思考的能力都沒有 相關閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)綁定 --- ...
摘要:從層層委托的依賴關系可以看出,的依賴注入給屬性賦值是層層委托的最終給了內省機制,這是框架設計精妙處之一。當然分享到你的朋友圈讓更多小伙伴看到也是被作者本人許可的若對技術內容感興趣可以加入群交流高工架構師群。 每篇一句 具備了技術深度,遇到問題可以快速定位并從根本上解決。有了技術深度之后,學習其它技術可以更快,再深入其它技術也就不會害怕 相關閱讀 【小家Spring】聊聊Spring中的...
摘要:關于它的數(shù)據(jù)轉換使用了如下兩種機制隸屬于規(guī)范。這種類中的方法主要用于訪問私有的字段,且方法名符合某種命名規(guī)則。如果在兩個模塊之間傳遞信息,可以將信息封裝進中,這種對象稱為值對象,或。 每篇一句 千古以來要飯的沒有要早飯的,知道為什么嗎? 相關閱讀 【小家Spring】聊聊Spring中的數(shù)據(jù)轉換:Converter、ConversionService、TypeConverter、Pro...
摘要:方案一借助對方法級別數(shù)據(jù)校驗的能力首先必須明確一點此能力屬于框架的,而部分框架。 每篇一句 在金字塔塔尖的是實踐,學而不思則罔,思而不學則殆(現(xiàn)在很多編程框架都只是教你碎片化的實踐) 相關閱讀 【小家Java】深入了解數(shù)據(jù)校驗:Java Bean Validation 2.0(JSR303、JSR349、JSR380)Hibernate-Validation 6.x使用案例【小家Spr...
閱讀 2788·2021-09-22 15:58
閱讀 2314·2019-08-29 16:06
閱讀 979·2019-08-29 14:14
閱讀 2876·2019-08-29 13:48
閱讀 2515·2019-08-28 18:01
閱讀 1625·2019-08-28 17:52
閱讀 3398·2019-08-26 14:05
閱讀 1721·2019-08-26 13:50