Spring 屬性注入(一)JavaBean 內省機制在 BeanWrapper 中的應用
Spring 屬性注入(一)JavaBean 內省機制在 BeanWrapper 中的應用
Spring 系列目錄(https://www.cnblogs.com/binarylei/p/10117436.html)
Spring 中的屬性注入也是基於 JDK 的 JavaBean 的內省,詳見《JDK 之 JavaBean 內省機制》:https://www.cnblogs.com/binarylei/p/10204208.html
一、BeanWrapper 的使用
@Test public void test() { // 1.1 beanWrapper BeanWrapper beanWrapper = new BeanWrapperImpl(new Company()); //BeanWrapper beanWrapper = new BeanWrapperImpl(Company.class); // 2.1 屬性注入 beanWrapper.setPropertyValue("name", "company"); // 2.2 也可以這樣,自動轉 int PropertyValue pv = new PropertyValue("total", "20"); beanWrapper.setPropertyValue(pv); // 2.3 巢狀注入,autoGrowNestedPaths=true 時當屬性為 null 時自動建立物件 beanWrapper.setAutoGrowNestedPaths(true); beanWrapper.setPropertyValue("director.name", "director"); beanWrapper.setPropertyValue("employees[0].name", "binarylei"); // 3.1 獲取例項 Company company = (Company) beanWrapper.getWrappedInstance(); // 3.2 獲取屬性 int total = (int) beanWrapper.getPropertyValue("total"); } // JavaBean 如下,省略 get/set 方法 public class Company { private String name; private int total; private Employee director; private Employee[] employees; public static class Employee{ private String name; private double salary; } }
那 Spring 是如何將一個字串轉化為 int 型別的呢?
二、BeanWrapper 屬性注入
跟蹤 setPropertyValue 程式碼到 AbstractNestablePropertyAccessor#processLocalProperty 方法
// 簡單屬性注入,而 Array, Collection, Map 則走 processKeyedProperty 方法 private void processLocalProperty(PropertyTokenHolder tokens, PropertyValue pv) { PropertyHandler ph = getLocalPropertyHandler(tokens.actualName); Object originalValue = pv.getValue(); Object valueToApply = originalValue; // 1. 型別轉換 alueToApply = convertForProperty( tokens.canonicalName, oldValue, originalValue, ph.toTypeDescriptor()); // 2. 利用 JavaBean 的內省機制設定屬性值 ph.setValue(valueToApply); }
processLocalProperty 主要完成了兩件事:一是型別轉換;二是設定屬性值。convertForProperty 利用 JDK 的 PropertyEditorSupport 進行型別轉換,Spring 中內建了一批轉換器,當然也可以自定義。而 setValue 則是使用反射進行賦值,關鍵程式碼如下:(BeanWrapperImpl#BeanPropertyHandler#setValue)
writeMethod.invoke(getWrappedInstance(), value)
我們再看一下 BeanPropertyHandler 是什麼,其實 BeanPropertyHandler 只是對 PropertyDescriptor 的簡單封裝。程式碼如下:
@Override
protected BeanPropertyHandler getLocalPropertyHandler(String propertyName) {
PropertyDescriptor pd = getCachedIntrospectionResults().getPropertyDescriptor(propertyName);
return (pd != null ? new BeanPropertyHandler(pd) : null);
}
三、PropertyEditorSupport 在 BeanWrapper 中的應用
BeanWrapper 將 JavaBean 型別轉換都委託給了 TypeConverterDelegate 元件,這個元件有一個重要的屬性 propertyEditorRegistry,可以通過這個註冊器獲取對應的屬性編輯器 PropertyEditor。
private final PropertyEditorRegistrySupport propertyEditorRegistry;
跟蹤 AbstractNestablePropertyAccessor#convertForProperty 到 TypeConverterDelegate#convertIfNecessary 方法中。Spring 提供了兩種型別的轉換方式:一是 JDK 的 PropertyEditor;二是 Spring 提供的 ConversionService,這裡只關注 PropertyEditor 方式。
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 1. 使用者自定義屬性編輯器
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
// 2. Spring 預設屬性編輯器
if (editor == null) {
editor = findDefaultEditor(requiredType);
}
// 3. 執行型別轉換
convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
}
convertIfNecessary 方法將只是匹配可用的 PropertyEditor 而執行則交給 doConvertValue 完成,很顯然 doConvertValue 會呼叫 PropertyEditor#setAsText 進行型別轉換,每個方法只做一件事。
// 判斷是否要進行型別轉換
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
@Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
// 省略...
Object convertedValue = newValue;
if (convertedValue instanceof String) {
if (editor != null) {
// Use PropertyEditor's setAsText in case of a String value.
String newTextValue = (String) convertedValue;
return doConvertTextValue(oldValue, newTextValue, editor);
} else if (String.class == requiredType) {
returnValue = convertedValue;
}
}
return returnValue;
}
// 呼叫 PropertyEditor 的 setAsText 進行型別轉換
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
try {
editor.setValue(oldValue);
} catch (Exception ex) {
}
editor.setAsText(newTextValue);
return editor.getValue();
}
每天用心記錄一點點。內容也許不重要,但習慣很重要!