惟有熱愛和堅持,才能讓你在程序人生中屹立不倒,切忌跟風什麼語言或就學什麼去~java
【小家Spring】聊聊Spring中的數據綁定 --- 屬性訪問器PropertyAccessor和實現類DirectFieldAccessor的使用【小家Spring】聊聊Spring中的數據綁定 --- BeanWrapper以及Java內省Introspector和PropertyDescriptorweb
Java高工、架構師3羣
(文末有二維碼)
數據綁定 這個概念在任何一個成型的框架中都是特別重要的(尤爲是web框架),它能讓框架更多的自動化,更好容錯性以及更高的編碼效率。它提供的能力是:把字符串形式的參數轉換成服務端真正須要的類型的轉換(固然可能還包含校驗)。spring
對Spring
中的數據綁定場景,小夥伴們就再熟悉不過了。好比咱們Controller
中只須要使用Model對象就能完成request
到Model對象的自動數據自動綁定,使用起來着實很是方便~(徹底屏蔽了Servlet的API)架構
既然數據綁定這麼重要,可是爲什麼鮮有人提起呢?我也上網搜了搜關於DataBinder
的相關資料,相對來講仍是寥寥無幾的~咱們不提起並不表明它不重要,這些都是Spring它幫咱們默默的幹了。這也印證了那句名言嘛:咱們的安好是由於有人替咱們負重前行app
查到網上的資料,大都停留在如何使用WebDataBinder
的說明上,而且幾乎沒有文章是專門分析核心部件DataBinder
的,本文做爲此方面的一股清流,在此把我結合官方文檔、源碼的所獲分享給你們~框架
注意,此類所在的包是org.springframework.validation
,因此可想而知,它不只僅完成數據的綁定,還會和數據校驗有關~編輯器
注意:我看到有的文章說
DataBinder
在綁定的時候還會進行數據校驗Validation,其實這個是不許確的,容易誤導人(校驗動做不發生在DataBinder
本類)ide還有說
DataBinder
數據綁定最終依賴的是BeanWrapper
,其實這個也是不許確的,實際上依賴的是PropertyAccessor
。源碼分析
先看一個簡單Demo
,體驗一把直接使用DataBinder
進行數據綁定吧:ui
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 {}複製代碼
它是個實現類,直接實現了PropertyEditorRegistry
和TypeConverter
這兩個接口,所以它能夠註冊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
它提供了以下能力:
PropertyValues
綁定到target上(bind()
方法,依賴於PropertyAccessor
實現~) 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入羣"
字樣,會手動邀請入羣**