前言

一直想开发一个功能比较强大的项目,但是一直没有动手,最近终于有点时间来折腾它了。由于时隔两年没有接触前端了,所以需要一个小项目先练练手感。等这个项目完工之后在着手搞一个大工程。都说好记星不如烂笔头,现在就将这一个过程记录下来,万一有什么踩坑的地方,也可以提示后来人。

背景
由于项目中需要使用到日期和字符串的一个转换,使用到了Converter,所以记录下来。
/**
 * 日期转换
 */
@Component
public class DateConverter implements Converter<String, LocalDate> {

    @Override
    public LocalDate convert(String source) {

        try {
            return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
            //return LocalDate.parse(source, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
        } catch (Exception e) {
           
        }
        return null;
    }
}
Spring提供了3种converter接口,分别是Converter、ConverterFactory和GenericConverter.一般用于1:1, 1:N, N:N的source->target类型转化

spring将一个对象转换成map spring类型转换器_类型转换

Converter
public interface Converter<S, T> {
  // 将S转换成T
T convert(S source);
}
GenericConverter

用于在两种或更多种类型之间转换的通用转换器接口

public interface GenericConverter {
    @Nullable
    Set<GenericConverter.ConvertiblePair> getConvertibleTypes();

    @Nullable
    Object convert(@Nullable Object var1, TypeDescriptor var2, TypeDescriptor var3);

    public static final class ConvertiblePair {
        private final Class<?> sourceType;
        private final Class<?> targetType;

        public ConvertiblePair(Class<?> sourceType, Class<?> targetType) {
            Assert.notNull(sourceType, "Source type must not be null");
            Assert.notNull(targetType, "Target type must not be null");
            this.sourceType = sourceType;
            this.targetType = targetType;
        }

        public Class<?> getSourceType() {
            return this.sourceType;
        }

        public Class<?> getTargetType() {
            return this.targetType;
        }

        public boolean equals(@Nullable Object other) {
            if (this == other) {
                return true;
            } else if (other != null && other.getClass() == GenericConverter.ConvertiblePair.class) {
                GenericConverter.ConvertiblePair otherPair = (GenericConverter.ConvertiblePair)other;
                return this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType;
            } else {
                return false;
            }
        }

        public int hashCode() {
            return this.sourceType.hashCode() * 31 + this.targetType.hashCode();
        }

        public String toString() {
            return this.sourceType.getName() + " -> " + this.targetType.getName();
        }
    }
}
ConverterFactory
public interface ConverterFactory<S, R> {
    <T extends R> Converter<S, T> getConverter(Class<T> var1);
}
TyperConverter
  • 定义类型转换方法的接口。通常(但不一定)与PropertyEditorRegistry接口一起实现,通常接口TypeConverter的实现是基于非线程安全的PropertyEditors类,因此也不是线程安全的
public interface TypeConverter {
	// 将参数中的var1转换成var2类型,从String到任何类型的转换通常使用	   	  		        PropertyEditor类的setAsText方法或ConversionService中的Spring Converter
    @Nullable
    <T> T convertIfNecessary(@Nullable Object var1, @Nullable Class<T> var2) throws TypeMismatchException;

	// 意义同上,增加了作为转换目标的方法参数,主要用于分析泛型类型,可能是null
    @Nullable
    <T> T convertIfNecessary(@Nullable Object var1, @Nullable Class<T> var2, @Nullable MethodParameter var3) throws TypeMismatchException;
	
	// 意义同上,增加了转换目标的反射field
    @Nullable
    <T> T convertIfNecessary(@Nullable Object var1, @Nullable Class<T> var2, @Nullable Field var3) throws TypeMismatchException;

    @Nullable
    default <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
        throw new UnsupportedOperationException("TypeDescriptor resolution not supported");
    }
}
TypeConverterSupport
  • TypeConverter的基本实现类,同时也是BeanWrapperImpl类的依赖类
public abstract class TypeConverterSupport extends PropertyEditorRegistrySupport implements TypeConverter {
   
    // 委托给TypeConverterDelegate来转换
    @Nullable
    TypeConverterDelegate typeConverterDelegate;

    public TypeConverterSupport() {
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType) throws TypeMismatchException {
        return this.convertIfNecessary(value, requiredType, TypeDescriptor.valueOf(requiredType));
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable MethodParameter methodParam) throws TypeMismatchException {
        return this.convertIfNecessary(value, requiredType, methodParam != null ? new TypeDescriptor(methodParam) : TypeDescriptor.valueOf(requiredType));
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable Field field) throws TypeMismatchException {
        return this.convertIfNecessary(value, requiredType, field != null ? new TypeDescriptor(field) : TypeDescriptor.valueOf(requiredType));
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable Object value, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws TypeMismatchException {
        Assert.state(this.typeConverterDelegate != null, "No TypeConverterDelegate");

        try {
            return this.typeConverterDelegate.convertIfNecessary((String)null, (Object)null, value, requiredType, typeDescriptor);
        } catch (IllegalStateException | ConverterNotFoundException var5) {
            throw new ConversionNotSupportedException(value, requiredType, var5);
        } catch (IllegalArgumentException | ConversionException var6) {
            throw new TypeMismatchException(value, requiredType, var6);
        }
    }
}
TypeConverterDelegate
  • 类型转换的委托类,所有类型转换的工作都由该类完成,即将属性转换为其他类型的Spring内部使用方法(内部实现: 先使用PropertyEditor转换器器转换,如果没找到对应的转换器器,会⽤ConversionService来进⾏行行对象转换
class TypeConverterDelegate {
    private static final Log logger = LogFactory.getLog(TypeConverterDelegate.class);
    private final PropertyEditorRegistrySupport propertyEditorRegistry;
    @Nullable
    private final Object targetObject;

    public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry) {
        this(propertyEditorRegistry, (Object)null);
    }

    public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, @Nullable Object targetObject) {
        this.propertyEditorRegistry = propertyEditorRegistry;
        this.targetObject = targetObject;
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, Object newValue, @Nullable Class<T> requiredType) throws IllegalArgumentException {
        return this.convertIfNecessary(propertyName, oldValue, newValue, requiredType, TypeDescriptor.valueOf(requiredType));
    }

    @Nullable
    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
        ConversionFailedException conversionAttemptEx = null;
        ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
        if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
            TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
            if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                try {
                    return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                } catch (ConversionFailedException var14) {
                    conversionAttemptEx = var14;
                }
            }
        }

        Object convertedValue = newValue;
        if (editor != null || requiredType != null && !ClassUtils.isAssignableValue(requiredType, newValue)) {
            if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && newValue instanceof String) {
                TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
                if (elementTypeDesc != null) {
                    Class<?> elementType = elementTypeDesc.getType();
                    if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String)newValue);
                    }
                }
            }

            if (editor == null) {
                editor = this.findDefaultEditor(requiredType);
            }

            convertedValue = this.doConvertValue(oldValue, convertedValue, requiredType, editor);
        }

        boolean standardConversion = false;
        if (requiredType != null) {
            if (convertedValue != null) {
                if (Object.class == requiredType) {
                    return convertedValue;
                }

                if (requiredType.isArray()) {
                    if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                        convertedValue = StringUtils.commaDelimitedListToStringArray((String)convertedValue);
                    }

                    return this.convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
                }

                if (convertedValue instanceof Collection) {
                    convertedValue = this.convertToTypedCollection((Collection)convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                } else if (convertedValue instanceof Map) {
                    convertedValue = this.convertToTypedMap((Map)convertedValue, propertyName, requiredType, typeDescriptor);
                    standardConversion = true;
                }

                if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                    convertedValue = Array.get(convertedValue, 0);
                    standardConversion = true;
                }

                if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                    return convertedValue.toString();
                }

                if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                    if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                        try {
                            Constructor<T> strCtor = requiredType.getConstructor(String.class);
                            return BeanUtils.instantiateClass(strCtor, new Object[]{convertedValue});
                        } catch (NoSuchMethodException var12) {
                            if (logger.isTraceEnabled()) {
                                logger.trace("No String constructor found on type [" + requiredType.getName() + "]", var12);
                            }
                        } catch (Exception var13) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", var13);
                            }
                        }
                    }

                    String trimmedValue = ((String)convertedValue).trim();
                    if (requiredType.isEnum() && trimmedValue.isEmpty()) {
                        return null;
                    }

                    convertedValue = this.attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                    standardConversion = true;
                } else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
                    convertedValue = NumberUtils.convertNumberToTargetClass((Number)convertedValue, requiredType);
                    standardConversion = true;
                }
            } else if (requiredType == Optional.class) {
                convertedValue = Optional.empty();
            }

            if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
                if (conversionAttemptEx != null) {
                    throw conversionAttemptEx;
                }

                if (conversionService != null && typeDescriptor != null) {
                    TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
                    if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                        return conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                    }
                }

                StringBuilder msg = new StringBuilder();
                msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue));
                msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'");
                if (propertyName != null) {
                    msg.append(" for property '").append(propertyName).append("'");
                }

                if (editor != null) {
                    msg.append(": PropertyEditor [").append(editor.getClass().getName()).append("] returned inappropriate value of type '").append(ClassUtils.getDescriptiveType(convertedValue)).append("'");
                    throw new IllegalArgumentException(msg.toString());
                }

                msg.append(": no matching editors or conversion strategy found");
                throw new IllegalStateException(msg.toString());
            }
        }

        if (conversionAttemptEx != null) {
            if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) {
                throw conversionAttemptEx;
            }

            logger.debug("Original ConversionService attempt failed - ignored since PropertyEditor based conversion eventually succeeded", conversionAttemptEx);
        }

        return convertedValue;
    }

    private Object attemptToConvertStringToEnum(Class<?> requiredType, String trimmedValue, Object currentConvertedValue) {
        Object convertedValue = currentConvertedValue;
        if (Enum.class == requiredType && this.targetObject != null) {
            int index = trimmedValue.lastIndexOf(46);
            if (index > -1) {
                String enumType = trimmedValue.substring(0, index);
                String fieldName = trimmedValue.substring(index + 1);
                ClassLoader cl = this.targetObject.getClass().getClassLoader();

                try {
                    Class<?> enumValueType = ClassUtils.forName(enumType, cl);
                    Field enumField = enumValueType.getField(fieldName);
                    convertedValue = enumField.get((Object)null);
                } catch (ClassNotFoundException var12) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Enum class [" + enumType + "] cannot be loaded", var12);
                    }
                } catch (Throwable var13) {
                    if (logger.isTraceEnabled()) {
                        logger.trace("Field [" + fieldName + "] isn't an enum value for type [" + enumType + "]", var13);
                    }
                }
            }
        }

        if (convertedValue == currentConvertedValue) {
            try {
                Field enumField = requiredType.getField(trimmedValue);
                ReflectionUtils.makeAccessible(enumField);
                convertedValue = enumField.get((Object)null);
            } catch (Throwable var11) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Field [" + convertedValue + "] isn't an enum value", var11);
                }
            }
        }

        return convertedValue;
    }

    @Nullable
    private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
        PropertyEditor editor = null;
        if (requiredType != null) {
            editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
            if (editor == null && String.class != requiredType) {
                editor = BeanUtils.findEditorByConvention(requiredType);
            }
        }

        return editor;
    }

    @Nullable
    private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue, @Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {
        Object convertedValue = newValue;
        Object returnValue;
        if (editor != null && !(newValue instanceof String)) {
            try {
                editor.setValue(convertedValue);
                returnValue = editor.getValue();
                if (returnValue != convertedValue) {
                    convertedValue = returnValue;
                    editor = null;
                }
            } catch (Exception var8) {
                if (logger.isDebugEnabled()) {
                    logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", var8);
                }
            }
        }

        returnValue = convertedValue;
        if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
            if (logger.isTraceEnabled()) {
                logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
            }

            convertedValue = StringUtils.arrayToCommaDelimitedString((String[])((String[])convertedValue));
        }

        if (convertedValue instanceof String) {
            if (editor != null) {
                if (logger.isTraceEnabled()) {
                    logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");
                }

                String newTextValue = (String)convertedValue;
                return this.doConvertTextValue(oldValue, newTextValue, editor);
            }

            if (String.class == requiredType) {
                returnValue = convertedValue;
            }
        }

        return returnValue;
    }

    private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
        try {
            editor.setValue(oldValue);
        } catch (Exception var5) {
            if (logger.isDebugEnabled()) {
                logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", var5);
            }
        }

        editor.setAsText(newTextValue);
        return editor.getValue();
    }

    private Object convertToTypedArray(Object input, @Nullable String propertyName, Class<?> componentType) {
        Object result;
        int i;
        if (input instanceof Collection) {
            Collection<?> coll = (Collection)input;
            result = Array.newInstance(componentType, coll.size());
            i = 0;

            for(Iterator it = coll.iterator(); it.hasNext(); ++i) {
                Object value = this.convertIfNecessary(this.buildIndexedPropertyName(propertyName, i), (Object)null, it.next(), componentType);
                Array.set(result, i, value);
            }

            return result;
        } else if (!input.getClass().isArray()) {
            Object result = Array.newInstance(componentType, 1);
            result = this.convertIfNecessary(this.buildIndexedPropertyName(propertyName, 0), (Object)null, input, componentType);
            Array.set(result, 0, result);
            return result;
        } else if (componentType.equals(input.getClass().getComponentType()) && !this.propertyEditorRegistry.hasCustomEditorForElement(componentType, propertyName)) {
            return input;
        } else {
            int arrayLength = Array.getLength(input);
            result = Array.newInstance(componentType, arrayLength);

            for(i = 0; i < arrayLength; ++i) {
                Object value = this.convertIfNecessary(this.buildIndexedPropertyName(propertyName, i), (Object)null, Array.get(input, i), componentType);
                Array.set(result, i, value);
            }

            return result;
        }
    }

    private Collection<?> convertToTypedCollection(Collection<?> original, @Nullable String propertyName, Class<?> requiredType, @Nullable TypeDescriptor typeDescriptor) {
        if (!Collection.class.isAssignableFrom(requiredType)) {
            return original;
        } else {
            boolean approximable = CollectionFactory.isApproximableCollectionType(requiredType);
            if (!approximable && !this.canCreateCopy(requiredType)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Custom Collection type [" + original.getClass().getName() + "] does not allow for creating a copy - injecting original Collection as-is");
                }

                return original;
            } else {
                boolean originalAllowed = requiredType.isInstance(original);
                TypeDescriptor elementType = typeDescriptor != null ? typeDescriptor.getElementTypeDescriptor() : null;
                if (elementType == null && originalAllowed && !this.propertyEditorRegistry.hasCustomEditorForElement((Class)null, propertyName)) {
                    return original;
                } else {
                    Iterator it;
                    try {
                        it = original.iterator();
                    } catch (Throwable var15) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cannot access Collection of type [" + original.getClass().getName() + "] - injecting original Collection as-is: " + var15);
                        }

                        return original;
                    }

                    Collection convertedCopy;
                    try {
                        if (approximable) {
                            convertedCopy = CollectionFactory.createApproximateCollection(original, original.size());
                        } else {
                            convertedCopy = (Collection)ReflectionUtils.accessibleConstructor(requiredType, new Class[0]).newInstance();
                        }
                    } catch (Throwable var17) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cannot create copy of Collection type [" + original.getClass().getName() + "] - injecting original Collection as-is: " + var17);
                        }

                        return original;
                    }

                    for(int i = 0; it.hasNext(); ++i) {
                        Object element = it.next();
                        String indexedPropertyName = this.buildIndexedPropertyName(propertyName, i);
                        Object convertedElement = this.convertIfNecessary(indexedPropertyName, (Object)null, element, elementType != null ? elementType.getType() : null, elementType);

                        try {
                            convertedCopy.add(convertedElement);
                        } catch (Throwable var16) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Collection type [" + original.getClass().getName() + "] seems to be read-only - injecting original Collection as-is: " + var16);
                            }

                            return original;
                        }

                        originalAllowed = originalAllowed && element == convertedElement;
                    }

                    return originalAllowed ? original : convertedCopy;
                }
            }
        }
    }

    private Map<?, ?> convertToTypedMap(Map<?, ?> original, @Nullable String propertyName, Class<?> requiredType, @Nullable TypeDescriptor typeDescriptor) {
        if (!Map.class.isAssignableFrom(requiredType)) {
            return original;
        } else {
            boolean approximable = CollectionFactory.isApproximableMapType(requiredType);
            if (!approximable && !this.canCreateCopy(requiredType)) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Custom Map type [" + original.getClass().getName() + "] does not allow for creating a copy - injecting original Map as-is");
                }

                return original;
            } else {
                boolean originalAllowed = requiredType.isInstance(original);
                TypeDescriptor keyType = typeDescriptor != null ? typeDescriptor.getMapKeyTypeDescriptor() : null;
                TypeDescriptor valueType = typeDescriptor != null ? typeDescriptor.getMapValueTypeDescriptor() : null;
                if (keyType == null && valueType == null && originalAllowed && !this.propertyEditorRegistry.hasCustomEditorForElement((Class)null, propertyName)) {
                    return original;
                } else {
                    Iterator it;
                    try {
                        it = original.entrySet().iterator();
                    } catch (Throwable var20) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cannot access Map of type [" + original.getClass().getName() + "] - injecting original Map as-is: " + var20);
                        }

                        return original;
                    }

                    Map convertedCopy;
                    try {
                        if (approximable) {
                            convertedCopy = CollectionFactory.createApproximateMap(original, original.size());
                        } else {
                            convertedCopy = (Map)ReflectionUtils.accessibleConstructor(requiredType, new Class[0]).newInstance();
                        }
                    } catch (Throwable var19) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Cannot create copy of Map type [" + original.getClass().getName() + "] - injecting original Map as-is: " + var19);
                        }

                        return original;
                    }

                    Object key;
                    Object value;
                    Object convertedKey;
                    Object convertedValue;
                    for(; it.hasNext(); originalAllowed = originalAllowed && key == convertedKey && value == convertedValue) {
                        Entry<?, ?> entry = (Entry)it.next();
                        key = entry.getKey();
                        value = entry.getValue();
                        String keyedPropertyName = this.buildKeyedPropertyName(propertyName, key);
                        convertedKey = this.convertIfNecessary(keyedPropertyName, (Object)null, key, keyType != null ? keyType.getType() : null, keyType);
                        convertedValue = this.convertIfNecessary(keyedPropertyName, (Object)null, value, valueType != null ? valueType.getType() : null, valueType);

                        try {
                            convertedCopy.put(convertedKey, convertedValue);
                        } catch (Throwable var18) {
                            if (logger.isDebugEnabled()) {
                                logger.debug("Map type [" + original.getClass().getName() + "] seems to be read-only - injecting original Map as-is: " + var18);
                            }

                            return original;
                        }
                    }

                    return originalAllowed ? original : convertedCopy;
                }
            }
        }
    }

    @Nullable
    private String buildIndexedPropertyName(@Nullable String propertyName, int index) {
        return propertyName != null ? propertyName + "[" + index + "]" : null;
    }

    @Nullable
    private String buildKeyedPropertyName(@Nullable String propertyName, Object key) {
        return propertyName != null ? propertyName + "[" + key + "]" : null;
    }

    private boolean canCreateCopy(Class<?> requiredType) {
        return !requiredType.isInterface() && !Modifier.isAbstract(requiredType.getModifiers()) && Modifier.isPublic(requiredType.getModifiers()) && ClassUtils.hasConstructor(requiredType, new Class[0]);
    }
}
SimpleTypeConverter
  • 不在特定目标对象上运行的TypeConverter接口的简单实现。这是使用完整的BeanWrapperImpl实例来实现任意类型转换需求的替代方法,同时使用相同的转换算法(包括委托给PropertyEditor和ConversionService)
public class SimpleTypeConverter extends TypeConverterSupport {
    public SimpleTypeConverter() {
        this.typeConverterDelegate = new TypeConverterDelegate(this);
        this.registerDefaultEditors();
    }
}
PropertyEditor(JDK)
  • 用于字符串到其它对象的转换,PropertyEditor是遵循javaBean规范的属性处理器,其通过set方法设置属性值,通过get方法获取属性值,相应的转换逻辑就隐藏于其中
public interface PropertyEditor {

    /**
     * Set (or change) the object that is to be edited.  Primitive types such
     * as "int" must be wrapped as the corresponding object type such as
     * "java.lang.Integer".
     *
     * @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.
     */
    void setValue(Object value);

    /**
     * Gets the property value.
     *
     * @return The value of the property.  Primitive types such as "int" will
     * be wrapped as the corresponding object type such as "java.lang.Integer".
     */

    Object getValue();

    //----------------------------------------------------------------------

    /**
     * Determines whether this property editor is paintable.
     *
     * @return  True if the class will honor the paintValue method.
     */

    boolean isPaintable();

    /**
     * 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.
     * <p>
     * The given Graphics object will have the default font, color, etc of
     * the parent container.  The PropertyEditor may change graphics attributes
     * such as font and color and doesn't need to restore the old values.
     *
     * @param gfx  Graphics object to paint into.
     * @param box  Rectangle within graphics object into which we should paint.
     */
    void paintValue(java.awt.Graphics gfx, java.awt.Rectangle box);

    //----------------------------------------------------------------------

    /**
     * Returns a fragment of Java code that can be used to set a property
     * to match the editors current state. This method is intended
     * for use when generating Java code to reflect changes made through the
     * property editor.
     * <p>
     * The code fragment should be context free and must be a legal Java
     * expression as specified by the JLS.
     * <p>
     * Specifically, if the expression represents a computation then all
     * classes and static members should be fully qualified. This rule
     * applies to constructors, static methods and non primitive arguments.
     * <p>
     * Caution should be used when evaluating the expression as it may throw
     * exceptions. In particular, code generators must ensure that generated
     * code will compile in the presence of an expression that can throw
     * checked exceptions.
     * <p>
     * Example results are:
     * <ul>
     * <li>Primitive expresssion: <code>2</code>
     * <li>Class constructor: <code>new java.awt.Color(127,127,34)</code>
     * <li>Static field: <code>java.awt.Color.orange</code>
     * <li>Static method: <code>javax.swing.Box.createRigidArea(new
     *                                   java.awt.Dimension(0, 5))</code>
     * </ul>
     *
     * @return a fragment of Java code representing an initializer for the
     *         current value. It should not contain a semi-colon
     *         ('<code>;</code>') to end the expression.
     */
    String getJavaInitializationString();

    //----------------------------------------------------------------------

    /**
     * Gets the property value as text.
     *
     * @return The property value as a human editable string.
     * <p>   Returns null if the value can't be expressed as an editable string.
     * <p>   If a non-null value is returned, then the PropertyEditor should
     *       be prepared to parse that string back in setAsText().
     */
    String getAsText();

    /**
     * Set 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.
     */
    void setAsText(String text) throws java.lang.IllegalArgumentException;

    //----------------------------------------------------------------------

    /**
     * If the property value must be one of a set of known tagged values,
     * then this method should return an array of the tags.  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 and the use of getAsText
     * to identify the current value.
     *
     * @return The tag values for this property.  May be null if this
     *   property cannot be represented as a tagged value.
     *
     */
    String[] getTags();

    //----------------------------------------------------------------------

    /**
     * A PropertyEditor may choose 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.
     */

    java.awt.Component getCustomEditor();

    /**
     * Determines whether this property editor supports a custom editor.
     *
     * @return  True if the propertyEditor can provide a custom editor.
     */
    boolean supportsCustomEditor();

    //----------------------------------------------------------------------

    /**
     * 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
     * and itself as the source.
     *
     * @param listener  the {@link PropertyChangeListener} to add
     */
    void addPropertyChangeListener(PropertyChangeListener listener);

    /**
     * Removes a listener for the value change.
     *
     * @param listener  the {@link PropertyChangeListener} to remove
     */
    void removePropertyChangeListener(PropertyChangeListener listener);

}
PropertyEditorSupport
  • 编写自己的PropertyEditor,通常是继承PropertyEditorSupport,而不用实现PropertyEditor,这样就不用重写PropertyEditor的所有方法了
public class PropertyEditorSupport implements PropertyEditor {

    /**
     * Constructs a <code>PropertyEditorSupport</code> object.
     *
     * @since 1.5
     */
    public PropertyEditorSupport() {
        setSource(this);
    }

    /**
     * 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);
    }

    /**
     * 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;
    }

    /**
     * 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;
    }

    /**
     * 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;
    }

    //----------------------------------------------------------------------

    /**
     * Determines whether the class will honor the paintValue method.
     *
     * @return  True if the class will honor the paintValue method.
     */

    public boolean isPaintable() {
        return false;
    }

    /**
     * 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) {
    }

    //----------------------------------------------------------------------

    /**
     * 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 "???";
    }

    //----------------------------------------------------------------------

    /**
     * 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;
    }

    /**
     * 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);
    }

    //----------------------------------------------------------------------

    /**
     * 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;
    }

    //----------------------------------------------------------------------

    /**
     * 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);
    }

    /**
     * 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);
    }

    /**
     * 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
  • 封装用于注册JavaBeans PropertyEditors的方法。这是PropertyEditorRegistrar操作的中央接口。内部提供了三个函数用于PropertyEditor的注册和查找。每个属性路径只支持一个已注册的自定义编辑器。对于集合/数组,不要同时为集合/数组和相同属性上的每个元素注册一个编辑器。例如,如果您想为“items[n].quantity”注册一个编辑器(对于所有值n),您应该使用“items.quantity”作为这个方法“propertyPath”参数的值。
    。propertyPath:属性的路径(名称或嵌套路径),如果为给定类型的所有属性注册一个编辑器,则为null
在这里插入代码片
PropertyEditorRegistrySupport
  • PropertyEditorRegistry接口的默认实现,并且提供了默认editors和自定义editors的管理,主要作为服务BeanWrapperImpl的基类
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
    @Nullable
    private ConversionService conversionService;
    private boolean defaultEditorsActive = false;
    private boolean configValueEditorsActive = false;
    @Nullable
    private Map<Class<?>, PropertyEditor> defaultEditors;
    @Nullable
    private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
    @Nullable
    private Map<Class<?>, PropertyEditor> customEditors;
    @Nullable
    private Map<String, PropertyEditorRegistrySupport.CustomEditorHolder> customEditorsForPath;
    @Nullable
    private Map<Class<?>, PropertyEditor> customEditorCache;

    public PropertyEditorRegistrySupport() {
    }

    public void setConversionService(@Nullable ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    @Nullable
    public ConversionService getConversionService() {
        return this.conversionService;
    }

    protected void registerDefaultEditors() {
        this.defaultEditorsActive = true;
    }

    public void useConfigValueEditors() {
        this.configValueEditorsActive = true;
    }

    public void overrideDefaultEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
        if (this.overriddenDefaultEditors == null) {
            this.overriddenDefaultEditors = new HashMap();
        }

        this.overriddenDefaultEditors.put(requiredType, propertyEditor);
    }

    @Nullable
    public PropertyEditor getDefaultEditor(Class<?> requiredType) {
        if (!this.defaultEditorsActive) {
            return null;
        } else {
            if (this.overriddenDefaultEditors != null) {
                PropertyEditor editor = (PropertyEditor)this.overriddenDefaultEditors.get(requiredType);
                if (editor != null) {
                    return editor;
                }
            }

            if (this.defaultEditors == null) {
                this.createDefaultEditors();
            }

            return (PropertyEditor)this.defaultEditors.get(requiredType);
        }
    }

    private void createDefaultEditors() {
        this.defaultEditors = new HashMap(64);
        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());
        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));
        this.defaultEditors.put(byte[].class, new ByteArrayPropertyEditor());
        this.defaultEditors.put(char[].class, new CharArrayPropertyEditor());
        this.defaultEditors.put(Character.TYPE, new CharacterEditor(false));
        this.defaultEditors.put(Character.class, new CharacterEditor(true));
        this.defaultEditors.put(Boolean.TYPE, new CustomBooleanEditor(false));
        this.defaultEditors.put(Boolean.class, new CustomBooleanEditor(true));
        this.defaultEditors.put(Byte.TYPE, new CustomNumberEditor(Byte.class, false));
        this.defaultEditors.put(Byte.class, new CustomNumberEditor(Byte.class, true));
        this.defaultEditors.put(Short.TYPE, new CustomNumberEditor(Short.class, false));
        this.defaultEditors.put(Short.class, new CustomNumberEditor(Short.class, true));
        this.defaultEditors.put(Integer.TYPE, new CustomNumberEditor(Integer.class, false));
        this.defaultEditors.put(Integer.class, new CustomNumberEditor(Integer.class, true));
        this.defaultEditors.put(Long.TYPE, new CustomNumberEditor(Long.class, false));
        this.defaultEditors.put(Long.class, new CustomNumberEditor(Long.class, true));
        this.defaultEditors.put(Float.TYPE, new CustomNumberEditor(Float.class, false));
        this.defaultEditors.put(Float.class, new CustomNumberEditor(Float.class, true));
        this.defaultEditors.put(Double.TYPE, 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));
        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);
        }

    }

    protected void copyDefaultEditorsTo(PropertyEditorRegistrySupport target) {
        target.defaultEditorsActive = this.defaultEditorsActive;
        target.configValueEditorsActive = this.configValueEditorsActive;
        target.defaultEditors = this.defaultEditors;
        target.overriddenDefaultEditors = this.overriddenDefaultEditors;
    }

    public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
        this.registerCustomEditor(requiredType, (String)null, propertyEditor);
    }

    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");
        } else {
            if (propertyPath != null) {
                if (this.customEditorsForPath == null) {
                    this.customEditorsForPath = new LinkedHashMap(16);
                }

                this.customEditorsForPath.put(propertyPath, new PropertyEditorRegistrySupport.CustomEditorHolder(propertyEditor, requiredType));
            } else {
                if (this.customEditors == null) {
                    this.customEditors = new LinkedHashMap(16);
                }

                this.customEditors.put(requiredType, propertyEditor);
                this.customEditorCache = null;
            }

        }
    }

    @Nullable
    public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
        Class<?> requiredTypeToUse = requiredType;
        if (propertyPath != null) {
            if (this.customEditorsForPath != null) {
                PropertyEditor editor = this.getCustomEditor(propertyPath, requiredType);
                if (editor == null) {
                    List<String> strippedPaths = new ArrayList();
                    this.addStrippedPropertyPaths(strippedPaths, "", propertyPath);

                    String strippedPath;
                    for(Iterator it = strippedPaths.iterator(); it.hasNext() && editor == null; editor = this.getCustomEditor(strippedPath, requiredType)) {
                        strippedPath = (String)it.next();
                    }
                }

                if (editor != null) {
                    return editor;
                }
            }

            if (requiredType == null) {
                requiredTypeToUse = this.getPropertyType(propertyPath);
            }
        }

        return this.getCustomEditor(requiredTypeToUse);
    }

    public boolean hasCustomEditorForElement(@Nullable Class<?> elementType, @Nullable String propertyPath) {
        if (propertyPath != null && this.customEditorsForPath != null) {
            Iterator var3 = this.customEditorsForPath.entrySet().iterator();

            while(var3.hasNext()) {
                Entry<String, PropertyEditorRegistrySupport.CustomEditorHolder> entry = (Entry)var3.next();
                if (PropertyAccessorUtils.matchesProperty((String)entry.getKey(), propertyPath) && ((PropertyEditorRegistrySupport.CustomEditorHolder)entry.getValue()).getPropertyEditor(elementType) != null) {
                    return true;
                }
            }
        }

        return elementType != null && this.customEditors != null && this.customEditors.containsKey(elementType);
    }

    @Nullable
    protected Class<?> getPropertyType(String propertyPath) {
        return null;
    }

    @Nullable
    private PropertyEditor getCustomEditor(String propertyName, @Nullable Class<?> requiredType) {
        PropertyEditorRegistrySupport.CustomEditorHolder holder = this.customEditorsForPath != null ? (PropertyEditorRegistrySupport.CustomEditorHolder)this.customEditorsForPath.get(propertyName) : null;
        return holder != null ? holder.getPropertyEditor(requiredType) : null;
    }

    @Nullable
    private PropertyEditor getCustomEditor(@Nullable Class<?> requiredType) {
        if (requiredType != null && this.customEditors != null) {
            PropertyEditor editor = (PropertyEditor)this.customEditors.get(requiredType);
            if (editor == null) {
                if (this.customEditorCache != null) {
                    editor = (PropertyEditor)this.customEditorCache.get(requiredType);
                }

                if (editor == null) {
                    Iterator it = this.customEditors.keySet().iterator();

                    while(it.hasNext() && editor == null) {
                        Class<?> key = (Class)it.next();
                        if (key.isAssignableFrom(requiredType)) {
                            editor = (PropertyEditor)this.customEditors.get(key);
                            if (this.customEditorCache == null) {
                                this.customEditorCache = new HashMap();
                            }

                            this.customEditorCache.put(requiredType, editor);
                        }
                    }
                }
            }

            return editor;
        } else {
            return null;
        }
    }

    @Nullable
    protected Class<?> guessPropertyTypeFromEditors(String propertyName) {
        if (this.customEditorsForPath != null) {
            PropertyEditorRegistrySupport.CustomEditorHolder editorHolder = (PropertyEditorRegistrySupport.CustomEditorHolder)this.customEditorsForPath.get(propertyName);
            if (editorHolder == null) {
                List<String> strippedPaths = new ArrayList();
                this.addStrippedPropertyPaths(strippedPaths, "", propertyName);

                String strippedName;
                for(Iterator it = strippedPaths.iterator(); it.hasNext() && editorHolder == null; editorHolder = (PropertyEditorRegistrySupport.CustomEditorHolder)this.customEditorsForPath.get(strippedName)) {
                    strippedName = (String)it.next();
                }
            }

            if (editorHolder != null) {
                return editorHolder.getRegisteredType();
            }
        }

        return null;
    }

    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());
                }

            });
        }

    }

    private void addStrippedPropertyPaths(List<String> strippedPaths, String nestedPath, String propertyPath) {
        int startIndex = propertyPath.indexOf(91);
        if (startIndex != -1) {
            int endIndex = propertyPath.indexOf(93);
            if (endIndex != -1) {
                String prefix = propertyPath.substring(0, startIndex);
                String key = propertyPath.substring(startIndex, endIndex + 1);
                String suffix = propertyPath.substring(endIndex + 1, propertyPath.length());
                strippedPaths.add(nestedPath + prefix + suffix);
                this.addStrippedPropertyPaths(strippedPaths, nestedPath + prefix, suffix);
                this.addStrippedPropertyPaths(strippedPaths, nestedPath + prefix + key, suffix);
            }
        }

    }

    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) {
            return 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()) ? null : this.propertyEditor;
        }
    }
}
PropertyEditorRegistrar
  • 用于使用属性编辑器注册表注册自定义属性编辑器的策略的接口,当需要在几种不同的情况下使用同一组属性编辑器时,这一点特别有用:编写相应的注册器并在每种情况下重用它
public interface PropertyEditorRegistrar {
    void registerCustomEditors(PropertyEditorRegistry var1);
}
PropertyEditor(JDK)用于字符串到其它对象的转换,由于其局限性,spring提供了converter接口,由ConversionService来调用对外提供服务,而TypeConverter综合了上述两种转换方式,交由TypeConverterDelegate来进行转换
TypeConverterDelegater先使用PropertyEditor转换器器转换,如果没找到对应的转换器器,会⽤ConversionService来进⾏行行对象转换