前言
在講解PropertyEditorRegistrySupport
之前,我們有比較對jdk內(nèi)部自帶的PropertyEditor
進行說明。
PropertyEditor
,意為屬性編輯器,是JavaBean規(guī)范中定義的接口,最初的目的是為IDE的設(shè)計開發(fā)做準(zhǔn)備的雪侥,它可以很方便地讓IDE以可視化的方式設(shè)置JavaBean屬性。但是在Spring中精绎,尤其是IOC的xml配置速缨,我們往往需要將一個字符串靈活地轉(zhuǎn)換為各種其他類型。因此代乃,Spring借鑒了PropertyEditor
接口旬牲,在其基礎(chǔ)上做了增強和擴展,同時自帶了一些標(biāo)準(zhǔn)的實現(xiàn)襟己,讓我們可以比較快速而靈活地進行使用引谜。
PropertyEditor
PropertyEditor
是屬性編輯器的接口,其主要方法如下:
// 設(shè)置屬性值
void setValue(Object value);
// 獲取屬性值
Object getValue();
// 獲取屬性的初始值字符串
String getJavaInitializationString();
// 將數(shù)值已一個字符串表示
String getAsText();
// 用一個字符串去更新屬性值
void setAsText(String text) throws java.lang.IllegalArgumentException;
// 返回有效屬性值的可羅列值擎浴,例如boolean的有效值為true和false员咽。缺省為null,即:不可羅列的贮预。
String[] getTags();
// 其他...
PropertyEditorSupport
PropertyEditorSupport
是PropertyEditor
的標(biāo)準(zhǔn)實現(xiàn)贝室,大部分邏輯都在此類里面契讲。
public class PropertyEditorSupport implements PropertyEditor {
/**
* 構(gòu)造一個對象,源為自己
* Constructs a <code>PropertyEditorSupport</code> object.
*
* @since 1.5
*/
public PropertyEditorSupport() {
setSource(this);
}
/**
* 構(gòu)造一個對象滑频,源不為自己捡偏,而是外部傳入。<br/>
* 所謂源就是在事件觸發(fā)的時候可追溯的一個對象峡迷。
* Constructs a <code>PropertyEditorSupport</code> object.
*
* @param source the source used for event firing
* @since 1.5
*/
public PropertyEditorSupport(Object source) {
if (source == null) {
throw new NullPointerException();
}
setSource(source);
}
/**
* 獲取觸發(fā)事件對應(yīng)的源
* Returns the bean that is used as the
* source of events. If the source has not
* been explicitly set then this instance of
* <code>PropertyEditorSupport</code> is returned.
*
* @return the source object or this instance
* @since 1.5
*/
public Object getSource() {
return source;
}
/**
* 設(shè)置源
* Sets the source bean.
* <p>
* The source bean is used as the source of events
* for the property changes. This source should be used for information
* purposes only and should not be modified by the PropertyEditor.
*
* @param source source object to be used for events
* @since 1.5
*/
public void setSource(Object source) {
this.source = source;
}
/**
* 設(shè)置屬性值银伟,同時觸發(fā)屬性變更的事件
* Set (or change) the object that is to be edited.
*
* @param value The new target object to be edited. Note that this
* object should not be modified by the PropertyEditor, rather
* the PropertyEditor should create a new object to hold any
* modified value.
*/
public void setValue(Object value) {
this.value = value;
firePropertyChange();
}
/**
* 獲取屬性值
* Gets the value of the property.
*
* @return The value of the property.
*/
public Object getValue() {
return value;
}
//----------------------------------------------------------------------
/**
* 是否可繪制的,默認(rèn)為false
* Determines whether the class will honor the paintValue method.
*
* @return True if the class will honor the paintValue method.
*/
public boolean isPaintable() {
return false;
}
/**
* 繪制屬性绘搞,默認(rèn)為空實現(xiàn)
* Paint a representation of the value into a given area of screen
* real estate. Note that the propertyEditor is responsible for doing
* its own clipping so that it fits into the given rectangle.
* <p>
* If the PropertyEditor doesn't honor paint requests (see isPaintable)
* this method should be a silent noop.
*
* @param gfx Graphics object to paint into.
* @param box Rectangle within graphics object into which we should paint.
*/
public void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box) {
}
//----------------------------------------------------------------------
/**
* 獲取屬性初始的字符串彤避,默認(rèn)為三個英文問號
* This method is intended for use when generating Java code to set
* the value of the property. It should return a fragment of Java code
* that can be used to initialize a variable with the current property
* value.
* <p>
* Example results are "2", "new Color(127,127,34)", "Color.orange", etc.
*
* @return A fragment of Java code representing an initializer for the
* current value.
*/
public String getJavaInitializationString() {
return "???";
}
//----------------------------------------------------------------------
/**
* 獲取屬性對應(yīng)的字符串,默認(rèn)調(diào)用屬性對象的toString方法夯辖,通常情況下需要子類自行重寫琉预。
* Gets the property value as a string suitable for presentation
* to a human to edit.
*
* @return The property value as a string suitable for presentation
* to a human to edit.
* <p> Returns null if the value can't be expressed as a string.
* <p> If a non-null value is returned, then the PropertyEditor should
* be prepared to parse that string back in setAsText().
*/
public String getAsText() {
return (this.value != null)
? this.value.toString()
: null;
}
/**
* 用字符串去更新屬性,如果屬性本身就是字符串蒿褂,直接設(shè)置即可圆米,否則拋出異常。
* 子類可重寫此方法啄栓。
* Sets the property value by parsing a given String. May raise
* java.lang.IllegalArgumentException if either the String is
* badly formatted or if this kind of property can't be expressed
* as text.
*
* @param text The string to be parsed.
*/
public void setAsText(String text) throws java.lang.IllegalArgumentException {
if (value instanceof String) {
setValue(text);
return;
}
throw new java.lang.IllegalArgumentException(text);
}
//----------------------------------------------------------------------
/**
* 獲取標(biāo)簽娄帖,即:可羅列的字符串值。
* If the property value must be one of a set of known tagged values,
* then this method should return an array of the tag values. This can
* be used to represent (for example) enum values. If a PropertyEditor
* supports tags, then it should support the use of setAsText with
* a tag value as a way of setting the value.
*
* @return The tag values for this property. May be null if this
* property cannot be represented as a tagged value.
*
*/
public String[] getTags() {
return null;
}
//----------------------------------------------------------------------
/**
* A PropertyEditor may chose to make available a full custom Component
* that edits its property value. It is the responsibility of the
* PropertyEditor to hook itself up to its editor Component itself and
* to report property value changes by firing a PropertyChange event.
* <P>
* The higher-level code that calls getCustomEditor may either embed
* the Component in some larger property sheet, or it may put it in
* its own individual dialog, or ...
*
* @return A java.awt.Component that will allow a human to directly
* edit the current property value. May be null if this is
* not supported.
*/
public java.awt.Component getCustomEditor() {
return null;
}
/**
* Determines whether the propertyEditor can provide a custom editor.
*
* @return True if the propertyEditor can provide a custom editor.
*/
public boolean supportsCustomEditor() {
return false;
}
//----------------------------------------------------------------------
/**
* 設(shè)置監(jiān)聽事件
* Adds a listener for the value change.
* When the property editor changes its value
* it should fire a {@link PropertyChangeEvent}
* on all registered {@link PropertyChangeListener}s,
* specifying the {@code null} value for the property name.
* If the source property is set,
* it should be used as the source of the event.
* <p>
* The same listener object may be added more than once,
* and will be called as many times as it is added.
* If {@code listener} is {@code null},
* no exception is thrown and no action is taken.
*
* @param listener the {@link PropertyChangeListener} to add
*/
public synchronized void addPropertyChangeListener(
PropertyChangeListener listener) {
if (listeners == null) {
listeners = new java.util.Vector<>();
}
listeners.addElement(listener);
}
/**
* 移除監(jiān)聽事件
* Removes a listener for the value change.
* <p>
* If the same listener was added more than once,
* it will be notified one less time after being removed.
* If {@code listener} is {@code null}, or was never added,
* no exception is thrown and no action is taken.
*
* @param listener the {@link PropertyChangeListener} to remove
*/
public synchronized void removePropertyChangeListener(
PropertyChangeListener listener) {
if (listeners == null) {
return;
}
listeners.removeElement(listener);
}
/**
* 觸發(fā)監(jiān)聽事件
* Report that we have been modified to any interested listeners.
*/
public void firePropertyChange() {
java.util.Vector<PropertyChangeListener> targets;
synchronized (this) {
if (listeners == null) {
return;
}
targets = unsafeClone(listeners);
}
// Tell our listeners that "everything" has changed.
PropertyChangeEvent evt = new PropertyChangeEvent(source, null, null, null);
for (int i = 0; i < targets.size(); i++) {
PropertyChangeListener target = targets.elementAt(i);
target.propertyChange(evt);
}
}
@SuppressWarnings("unchecked")
private <T> java.util.Vector<T> unsafeClone(java.util.Vector<T> v) {
return (java.util.Vector<T>)v.clone();
}
//----------------------------------------------------------------------
private Object value;
private Object source;
private java.util.Vector<PropertyChangeListener> listeners;
}
PropertyEditorRegistry
PropertyEditorRegistry
是一個PropertyEditor
注冊器的接口昙楚,高度抽象了PropertyEditor
的注冊流程块茁。
public interface PropertyEditorRegistry {
/**
* 注冊自定義編輯器
* Register the given custom property editor for all properties of the given type.
* @param requiredType the type of the property
* @param propertyEditor the editor to register
*/
void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);
/**
* 注冊自定義編輯器,同時指定屬性路徑
*/
void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);
/**
* 根據(jù)類型和屬性路徑桂肌,查找屬性編輯器
*/
@Nullable
PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);
}
PropertyEditorRegistrySupport
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
// 轉(zhuǎn)換服務(wù)
@Nullable
private ConversionService conversionService;
// 默認(rèn)編輯器是否啟用
private boolean defaultEditorsActive = false;
// 配置屬性編輯器是否啟用
private boolean configValueEditorsActive = false;
// 默認(rèn)編輯器
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
// 重寫的默認(rèn)編輯器
@Nullable
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
// 自定義屬性編輯器
@Nullable
private Map<Class<?>, PropertyEditor> customEditors;
// 自定義路徑屬性編輯器
@Nullable
private Map<String, CustomEditorHolder> customEditorsForPath;
// 自定義屬性編輯器緩存,僅僅當(dāng)緩存過之后永淌,才會存在這里面
@Nullable
private Map<Class<?>, PropertyEditor> customEditorCache;
/**
* 設(shè)置轉(zhuǎn)換服務(wù)
* Specify a Spring 3.0 ConversionService to use for converting
* property values, as an alternative to JavaBeans PropertyEditors.
*/
public void setConversionService(@Nullable ConversionService conversionService) {
this.conversionService = conversionService;
}
/**
* 返回轉(zhuǎn)換服務(wù)
* Return the associated ConversionService, if any.
*/
@Nullable
public ConversionService getConversionService() {
return this.conversionService;
}
//---------------------------------------------------------------------
// Management of default editors
//---------------------------------------------------------------------
/**
* Activate the default editors for this registry instance,
* allowing for lazily registering default editors when needed.
*/
protected void registerDefaultEditors() {
this.defaultEditorsActive = true;
}
/**
* Activate config value editors which are only intended for configuration purposes,
* such as {@link org.springframework.beans.propertyeditors.StringArrayPropertyEditor}.
* <p>Those editors are not registered by default simply because they are in
* general inappropriate for data binding purposes. Of course, you may register
* them individually in any case, through {@link #registerCustomEditor}.
*/
public void useConfigValueEditors() {
this.configValueEditorsActive = true;
}
/**
* 重寫的默認(rèn)編輯器
* Override the default editor for the specified type with the given property editor.
* <p>Note that this is different from registering a custom editor in that the editor
* semantically still is a default editor. A ConversionService will override such a
* default editor, whereas custom editors usually override the ConversionService.
* @param requiredType the type of the property
* @param propertyEditor the editor to register
* @see #registerCustomEditor(Class, PropertyEditor)
*/
public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
if (this.overriddenDefaultEditors == null) {
this.overriddenDefaultEditors = new HashMap<>();
}
this.overriddenDefaultEditors.put(requiredType, propertyEditor);
}
/**
* 獲取特定類型的默認(rèn)編輯器
* 1. 當(dāng)默認(rèn)編輯器不啟用時崎场,返回null
* 2. 首先從重寫的默認(rèn)編輯器獲取
* 3. 其次從默認(rèn)編輯器獲取
* Retrieve the default editor for the given property type, if any.
* <p>Lazily registers the default editors, if they are active.
* @param requiredType type of the property
* @return the default editor, or {@code null} if none found
* @see #registerDefaultEditors
*/
@Nullable
public PropertyEditor getDefaultEditor(Class<?> requiredType) {
if (!this.defaultEditorsActive) {
return null;
}
if (this.overriddenDefaultEditors != null) {
PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
if (editor != null) {
return editor;
}
}
if (this.defaultEditors == null) {
createDefaultEditors();
}
return this.defaultEditors.get(requiredType);
}
/**
* 創(chuàng)建一系列的默認(rèn)編輯器
* Actually register the default editors for this registry instance.
*/
private void createDefaultEditors() {
this.defaultEditors = new HashMap<>(64);
// Simple editors, without parameterization capabilities.
// The JDK does not contain a default editor for any of these target types.
this.defaultEditors.put(Charset.class, new CharsetEditor());
this.defaultEditors.put(Class.class, new ClassEditor());
this.defaultEditors.put(Class[].class, new ClassArrayEditor());
this.defaultEditors.put(Currency.class, new CurrencyEditor());
this.defaultEditors.put(File.class, new FileEditor());
this.defaultEditors.put(InputStream.class, new InputStreamEditor());
this.defaultEditors.put(InputSource.class, new InputSourceEditor());
this.defaultEditors.put(Locale.class, new LocaleEditor());
this.defaultEditors.put(Path.class, new PathEditor());
this.defaultEditors.put(Pattern.class, new PatternEditor());
this.defaultEditors.put(Properties.class, new PropertiesEditor());
this.defaultEditors.put(Reader.class, new ReaderEditor());
this.defaultEditors.put(Resource[].class, new ResourceArrayPropertyEditor());
this.defaultEditors.put(TimeZone.class, new TimeZoneEditor());
this.defaultEditors.put(URI.class, new URIEditor());
this.defaultEditors.put(URL.class, new URLEditor());
this.defaultEditors.put(UUID.class, new UUIDEditor());
this.defaultEditors.put(ZoneId.class, new ZoneIdEditor());
// Default instances of collection editors.
// Can be overridden by registering custom instances of those as custom editors.
this.defaultEditors.put(Collection.class, new CustomCollectionEditor(Collection.class));
this.defaultEditors.put(Set.class, new CustomCollectionEditor(Set.class));
this.defaultEditors.put(SortedSet.class, new CustomCollectionEditor(SortedSet.class));
this.defaultEditors.put(List.class, new CustomCollectionEditor(List.class));
this.defaultEditors.put(SortedMap.class, new CustomMapEditor(SortedMap.class));
// Default editors for primitive arrays.
this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
// The JDK does not contain a default editor for char!
this.defaultEditors.put(char.class, new CharacterEditor(false));
this.defaultEditors.put(Character.class, new CharacterEditor(true));
// Spring's CustomBooleanEditor accepts more flag values than the JDK's default editor.
this.defaultEditors.put(boolean.class, new CustomBooleanEditor(false));
this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
// The JDK does not contain default editors for number wrapper types!
// Override JDK primitive number editors with our own CustomNumberEditor.
this.defaultEditors.put(byte.class, new CustomNumberEditor(Byte.class, false));
this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
this.defaultEditors.put(short.class, new CustomNumberEditor(Short.class, false));
this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
this.defaultEditors.put(int.class, new CustomNumberEditor(Integer.class, false));
this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
this.defaultEditors.put(long.class, new CustomNumberEditor(Long.class, false));
this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
this.defaultEditors.put(float.class, new CustomNumberEditor(Float.class, false));
this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
this.defaultEditors.put(double.class, new CustomNumberEditor(Double.class, false));
this.defaultEditors.put(Double.class, new CustomNumberEditor(Double.class, true));
this.defaultEditors.put(BigDecimal.class, new CustomNumberEditor(BigDecimal.class, true));
this.defaultEditors.put(BigInteger.class, new CustomNumberEditor(BigInteger.class, true));
// Only register config value editors if explicitly requested.
if (this.configValueEditorsActive) {
StringArrayPropertyEditor sae = new StringArrayPropertyEditor();
this.defaultEditors.put(String[].class, sae);
this.defaultEditors.put(short[].class, sae);
this.defaultEditors.put(int[].class, sae);
this.defaultEditors.put(long[].class, sae);
}
}
/**
* 拷貝默認(rèn)編輯器,包括參數(shù)等
* Copy the default editors registered in this instance to the given target registry.
* @param target the target registry to copy to
*/
protected void copyDefaultEditorsTo(PropertyEditorRegistrySupport target) {
target.defaultEditorsActive = this.defaultEditorsActive;
target.configValueEditorsActive = this.configValueEditorsActive;
target.defaultEditors = this.defaultEditors;
target.overriddenDefaultEditors = this.overriddenDefaultEditors;
}
//---------------------------------------------------------------------
// Management of custom editors
//---------------------------------------------------------------------
@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
registerCustomEditor(requiredType, null, propertyEditor);
}
// 注冊自定義編輯器
// 1. 類型和屬性路徑不能全為null
// 2. 屬性路徑不為null時遂蛀,默認(rèn)以屬性路徑的方式注冊
// 3. 當(dāng)屬性路徑為null時谭跨,以類型的方式注冊,并清空自定義緩存編輯器
@Override
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) {
if (requiredType == null && propertyPath == null) {
throw new IllegalArgumentException("Either requiredType or propertyPath is required");
}
if (propertyPath != null) {
if (this.customEditorsForPath == null) {
this.customEditorsForPath = new LinkedHashMap<>(16);
}
this.customEditorsForPath.put(propertyPath, new CustomEditorHolder(propertyEditor, requiredType));
}
else {
if (this.customEditors == null) {
this.customEditors = new LinkedHashMap<>(16);
}
this.customEditors.put(requiredType, propertyEditor);
this.customEditorCache = null;
}
}
/**
* 查找自定義編輯器李滴,同樣的螃宙。
* 1. 首先,以屬性路徑的方式進行查找
* 2. 其次所坯,其類型的方式進行查找
* 3. 特別需要注意的是`addStrippedPropertyPaths`方法谆扎,其目的是剝離掉其內(nèi)層的屬性。
* 比如:當(dāng)nestedPath為`a[b[c[d]]]]`時芹助,我們調(diào)用此方法堂湖,返回了`a]]]`
*/
@Override
@Nullable
public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
Class<?> requiredTypeToUse = requiredType;
if (propertyPath != null) {
if (this.customEditorsForPath != null) {
// Check property-specific editor first.
PropertyEditor editor = getCustomEditor(propertyPath, requiredType);
if (editor == null) {
List<String> strippedPaths = new ArrayList<>();
addStrippedPropertyPaths(strippedPaths, "", propertyPath);
for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editor == null;) {
String strippedPath = it.next();
editor = getCustomEditor(strippedPath, requiredType);
}
}
if (editor != null) {
return editor;
}
}
if (requiredType == null) {
requiredTypeToUse = getPropertyType(propertyPath);
}
}
// No property-specific editor -> check type-specific editor.
return getCustomEditor(requiredTypeToUse);
}
/**
* 確定是否存在特定類型或?qū)傩月窂降淖远x編輯器
* Determine whether this registry contains a custom editor
* for the specified array/collection element.
* @param elementType the target type of the element
* (can be {@code null} if not known)
* @param propertyPath the property path (typically of the array/collection;
* can be {@code null} if not known)
* @return whether a matching custom editor has been found
*/
public boolean hasCustomEditorForElement(@Nullable Class<?> elementType, @Nullable String propertyPath) {
if (propertyPath != null && this.customEditorsForPath != null) {
for (Map.Entry<String, CustomEditorHolder> entry : this.customEditorsForPath.entrySet()) {
if (PropertyAccessorUtils.matchesProperty(entry.getKey(), propertyPath) &&
entry.getValue().getPropertyEditor(elementType) != null) {
return true;
}
}
}
// No property-specific editor -> check type-specific editor.
return (elementType != null && this.customEditors != null && this.customEditors.containsKey(elementType));
}
/**
* Determine the property type for the given property path.
* <p>Called by {@link #findCustomEditor} if no required type has been specified,
* to be able to find a type-specific editor even if just given a property path.
* <p>The default implementation always returns {@code null}.
* BeanWrapperImpl overrides this with the standard {@code getPropertyType}
* method as defined by the BeanWrapper interface.
* @param propertyPath the property path to determine the type for
* @return the type of the property, or {@code null} if not determinable
* @see BeanWrapper#getPropertyType(String)
*/
@Nullable
protected Class<?> getPropertyType(String propertyPath) {
return null;
}
/**
* 獲取自定義屬性編輯器
* Get custom editor that has been registered for the given property.
* @param propertyName the property path to look for
* @param requiredType the type to look for
* @return the custom editor, or {@code null} if none specific for this property
*/
@Nullable
private PropertyEditor getCustomEditor(String propertyName, @Nullable Class<?> requiredType) {
CustomEditorHolder holder =
(this.customEditorsForPath != null ? this.customEditorsForPath.get(propertyName) : null);
return (holder != null ? holder.getPropertyEditor(requiredType) : null);
}
/**
* 獲取自定義屬性編輯器闲先,這兒為了加快檢索速度,做了以下兩件事情
* 1. 前置空校驗
* 2. 后置緩存處理和查找
* Get custom editor for the given type. If no direct match found,
* try custom editor for superclass (which will in any case be able
* to render a value as String via {@code getAsText}).
* @param requiredType the type to look for
* @return the custom editor, or {@code null} if none found for this type
* @see java.beans.PropertyEditor#getAsText()
*/
@Nullable
private PropertyEditor getCustomEditor(@Nullable Class<?> requiredType) {
if (requiredType == null || this.customEditors == null) {
return null;
}
// Check directly registered editor for type.
PropertyEditor editor = this.customEditors.get(requiredType);
if (editor == null) {
// Check cached editor for type, registered for superclass or interface.
if (this.customEditorCache != null) {
editor = this.customEditorCache.get(requiredType);
}
if (editor == null) {
// Find editor for superclass or interface.
for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) {
Class<?> key = it.next();
if (key.isAssignableFrom(requiredType)) {
editor = this.customEditors.get(key);
// Cache editor for search type, to avoid the overhead
// of repeated assignable-from checks.
if (this.customEditorCache == null) {
this.customEditorCache = new HashMap<>();
}
this.customEditorCache.put(requiredType, editor);
}
}
}
}
return editor;
}
/**
* 推測屬性對應(yīng)的類型无蜂,這個方法會遞歸和遍歷所有匹配的路徑伺糠,效率相對較低
* Guess the property type of the specified property from the registered
* custom editors (provided that they were registered for a specific type).
* @param propertyName the name of the property
* @return the property type, or {@code null} if not determinable
*/
@Nullable
protected Class<?> guessPropertyTypeFromEditors(String propertyName) {
if (this.customEditorsForPath != null) {
CustomEditorHolder editorHolder = this.customEditorsForPath.get(propertyName);
if (editorHolder == null) {
List<String> strippedPaths = new ArrayList<>();
addStrippedPropertyPaths(strippedPaths, "", propertyName);
for (Iterator<String> it = strippedPaths.iterator(); it.hasNext() && editorHolder == null;) {
String strippedName = it.next();
editorHolder = this.customEditorsForPath.get(strippedName);
}
}
if (editorHolder != null) {
return editorHolder.getRegisteredType();
}
}
return null;
}
/**
* 拷貝當(dāng)前對象,做的是淺拷貝的方式
* Copy the custom editors registered in this instance to the given target registry.
* @param target the target registry to copy to
* @param nestedProperty the nested property path of the target registry, if any.
* If this is non-null, only editors registered for a path below this nested property
* will be copied. If this is null, all editors will be copied.
*/
protected void copyCustomEditorsTo(PropertyEditorRegistry target, @Nullable String nestedProperty) {
String actualPropertyName =
(nestedProperty != null ? PropertyAccessorUtils.getPropertyName(nestedProperty) : null);
if (this.customEditors != null) {
this.customEditors.forEach(target::registerCustomEditor);
}
if (this.customEditorsForPath != null) {
this.customEditorsForPath.forEach((editorPath, editorHolder) -> {
if (nestedProperty != null) {
int pos = PropertyAccessorUtils.getFirstNestedPropertySeparatorIndex(editorPath);
if (pos != -1) {
String editorNestedProperty = editorPath.substring(0, pos);
String editorNestedPath = editorPath.substring(pos + 1);
if (editorNestedProperty.equals(nestedProperty) || editorNestedProperty.equals(actualPropertyName)) {
target.registerCustomEditor(
editorHolder.getRegisteredType(), editorNestedPath, editorHolder.getPropertyEditor());
}
}
}
else {
target.registerCustomEditor(
editorHolder.getRegisteredType(), editorPath, editorHolder.getPropertyEditor());
}
});
}
}
/**
* Add property paths with all variations of stripped keys and/or indexes.
* Invokes itself recursively with nested paths.
* @param strippedPaths the result list to add to
* @param nestedPath the current nested path
* @param propertyPath the property path to check for keys/indexes to strip
*/
private void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) {
int startIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_PREFIX_CHAR);
if (startIndex != -1) {
int endIndex = propertyPath.indexOf(PropertyAccessor.PROPERTY_KEY_SUFFIX_CHAR);
if (endIndex != -1) {
String prefix = propertyPath.substring(0, startIndex);
String key = propertyPath.substring(startIndex, endIndex + 1);
String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
// Strip the first key.
strippedPaths.add(nestedPath + prefix + suffix);
// Search for further keys to strip, with the first key stripped.
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
// Search for further keys to strip, with the first key not stripped.
addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
}
}
}
/**
* 自定義屬性持有器斥季,支持以類型繼承的方式進行查找
* Holder for a registered custom editor with property name.
* Keeps the PropertyEditor itself plus the type it was registered for.
*/
private static final class CustomEditorHolder {
private final PropertyEditor propertyEditor;
@Nullable
private final Class<?> registeredType;
private CustomEditorHolder(PropertyEditor propertyEditor, @Nullable Class<?> registeredType) {
this.propertyEditor = propertyEditor;
this.registeredType = registeredType;
}
private PropertyEditor getPropertyEditor() {
return this.propertyEditor;
}
@Nullable
private Class<?> getRegisteredType() {
return this.registeredType;
}
@Nullable
private PropertyEditor getPropertyEditor(@Nullable Class<?> requiredType) {
// Special case: If no required type specified, which usually only happens for
// Collection elements, or required type is not assignable to registered type,
// which usually only happens for generic properties of type Object -
// then return PropertyEditor if not registered for Collection or array type.
// (If not registered for Collection or array, it is assumed to be intended
// for elements.)
if (this.registeredType == null ||
(requiredType != null &&
(ClassUtils.isAssignable(this.registeredType, requiredType) ||
ClassUtils.isAssignable(requiredType, this.registeredType))) ||
(requiredType == null &&
(!Collection.class.isAssignableFrom(this.registeredType) && !this.registeredType.isArray()))) {
return this.propertyEditor;
}
else {
return null;
}
}
}
}
PropertyAccessorUtils
其內(nèi)部是一系列的工具方法训桶,在配合bean屬性解析時可考慮對比查看。