spring--9--类型转换PropertyEditor

spring–类型转换PropertyEditor


PropertyEditor位于 JDKjava.beans包下,是 JDK自带的类型转换接口,其设计目的主要为 GUI提供支持,它可以将 GUI界面上输入的 String类型转化为我们需要的类型。

1 一个小例子

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.16</version>
</dependency>

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.11.RELEASE</version>
</dependency>

然后提供两个类UserRole

@Data
@ToString
public class User {

    private String name;
    private String username;
    private String password;
    //User中有Role对象
    private Role role;
}

@Data
@ToString
public class Role {
    private Integer id;
    private String rolename;
    private String description;
}

接下来我们再编写一个属性编辑器PropertyEditor,重写里面的setAsText方法。

继承PropertyEditorSupport类的原因是PropertyEditor中有很多方法,而且那些方法都没有默认实现,如果我们实现它,那么就需要实现所有的方法。

public class StringToRolePropertyEditor extends PropertyEditorSupport {

    /**
     * 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.
     * 我们需要将"12,lx,中国"格式字符串转化为一个Role对象
     */
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] split = text.split(",");
        Role role = new Role();
        role.setId(Integer.valueOf(split[0]));
        role.setRolename(split[1]);
        role.setDescription(split[2]);
        setValue(role);
    }
}

对应的spring配置文件如下

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

    <!--向容器中放入一个User对象
        并填充role属性
        需要实现将字符串转化为Role对象
	-->
    <bean id="user" class="com.lx.converter.domain.User">
        <property name="role" value="12,lx,中国"></property>
    </bean>

    <!--CustomEditorConfigurer是一个BeanFactoryPostProcessor-->
    <bean id="customEditorConfigurer"
          class="org.springframework.beans.factory.config.CustomEditorConfigurer">
        <!--将我们自己写的PropertyEditor放入CustomEditorConfigurer中,再由它注册到工厂中-->
        <property name="customEditors">
            <map>
                <entry key="com.lx.converter.domain.Role"
                       value="com.lx.converter.propertyEditor.StringToRolePropertyEditor"></entry>
            </map>
        </property>
    </bean>

</beans>

最后编写一个启动类,测试

public class Main {
    public static void main(String[] args) {

        //声明一个可以读取xml配置文件的容器
        ClassPathXmlApplicationContext classPathXmlApplicationContext = new
                ClassPathXmlApplicationContext();

        //设置配置文件
        classPathXmlApplicationContext.setConfigLocation("spring-context.xml");

        //刷新容器,这是spring中最重要的方法
        classPathXmlApplicationContext.refresh();

        User user = (User) classPathXmlApplicationContext.getBean("user");

        System.out.println(user);

    }
}

控制台打印如下所示

User(name=null, username=null, password=null, role=Role(id=12, rolename=lx, description=中国))

自动的将"12,lx,中国"转化为了Role对象

2 原理

从上篇文章中我们已经知道了类型转换工作被委托给TypeConverterDelegate类的convertIfNecessary方法,下面我们就来详细的看一下该方法的源码,看看它是怎么调用PropertyEditor"12,lx,中国"转化为了Role对象的

2.1 TypeConverterDelegate的构造方法

TypeConverterDelegateTypeConverterSupport类(BeanWrapperImpl是它的子类)的一个字段类型,spring创建BeanWrapperImpl对象的时候会自动使用下面这个构造方法初始化TypeConverterDelegate

//PropertyEditor注册中心
private final PropertyEditorRegistrySupport propertyEditorRegistry;

//BeanWrapperImpl包装的对象
@Nullable
private final Object targetObject;


/**
 * Create a new TypeConverterDelegate for the given editor registry and bean instance.
 * @param propertyEditorRegistry the editor registry to use
 * @param targetObject the target object to work on (as context that can be passed to editors)
 */
public TypeConverterDelegate(PropertyEditorRegistrySupport propertyEditorRegistry, @Nullable Object targetObject) {
    //实际上就是BeanWrapperImpl
    this.propertyEditorRegistry = propertyEditorRegistry;
    //BeanWrapperImpl包装的对象
    this.targetObject = targetObject;
}

2.2 convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor)方法,类型转换

/**
 * Convert the value to the required type (if necessary from a String),
 * for the specified property.
 * @param propertyName name of the property
 * @param oldValue the previous value, if available (may be {@code null})
 * @param newValue the proposed new value
 * @param requiredType the type we must convert to
 * (or {@code null} if not known, for example in case of a collection element)
 * @param typeDescriptor the descriptor for the target property or field
 * @return the new value, possibly the result of type conversion
 * @throws IllegalArgumentException if type conversion failed
 */
@SuppressWarnings("unchecked")
@Nullable
public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
                                @Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {

    // Custom editor for this type?
    //从用户自定义的PropertyEditor中找到可以将String->requiredType的PropertyEditor,见3.4
    PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);

    ConversionFailedException conversionAttemptEx = null;

    /*****************************使用conversionService转换*****************************/
    // No custom editor but custom ConversionService specified?
    //获取统一转换服务
    ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
    /**
     * 只有从用户自定义的PropertyEditor找不到合适的
     * 才会尝试使用ConversionService进行类型转换
     */
    if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
        //得到待转换值的属性描述
        TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
        /**
         * 判断统一转换服务能否实现sourceTypeDesc->typeDescriptor的转换
         * sourceTypeDesc:待转换值的类型描述
         * typeDescriptor:要求类型的类型描述
         */
        if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
            try {
                return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
            }
            catch (ConversionFailedException ex) {
                // fallback to default conversion logic below
                conversionAttemptEx = ex;
            }
        }
    }

    Object convertedValue = newValue;

    /********************************使用PropertyEditor转换*****************************/
    // Value not of required type?
    /**
     * 此处两种情况进入类型转换过程
     * 1.用户自定义了String->requiredType转换的PropertyEditor
     * 2.requiredType和现有的值的类型不匹配
     */
    if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
        //String->Collection
        if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) &&
            convertedValue instanceof String) {
            //获取类型的元素类型(array,collection,stream中存的元素类型)
            TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor();
            if (elementTypeDesc != null) {
                Class<?> elementType = elementTypeDesc.getType();
                //元素类型为clazz或枚举
                if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) {
                    //将逗号分隔的字符串转化为字符串数组
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
            }
        }
        if (editor == null) {
            /**
             * 从默认的PropertyEditor中找到可以将String->requiredType转换的PropertyEditor,
             * 见2.2.1
             */
            editor = findDefaultEditor(requiredType);
        }
        //使用PropertyEditor转换String->requiredType,见2.2.2
        convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
    }
    /*******************************************************************************/

    boolean standardConversion = false;

    if (requiredType != null) {

        /******************************标准类型转换**************************/
        // Try to apply some standard type conversion rules if appropriate.
        if (convertedValue != null) {
            //属性是Object类型,则无需类型转换
            if (Object.class == requiredType) {
                return (T) convertedValue;
            }
            //属性是数组类型
            else if (requiredType.isArray()) {
                // Array required -> apply appropriate conversion of elements.
                if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) {
                    convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue);
                }
                return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType());
            }
            //属性是集合类型
            else if (convertedValue instanceof Collection) {
                // Convert elements to target type, if determined.
                convertedValue = convertToTypedCollection(
                    (Collection<?>) convertedValue, propertyName, requiredType, typeDescriptor);
                standardConversion = true;
            }
            /**
             * 属性是Map集合类型,此处只会转化Map集合key和value的类型,
             * 并不是将别的集合转为Map,见2.2.3
             */
            else if (convertedValue instanceof Map) {
                // Convert keys and values to respective target type, if determined.
                convertedValue = convertToTypedMap(
                    (Map<?, ?>) convertedValue, propertyName, requiredType, typeDescriptor);
                standardConversion = true;
            }
            //属性为非数组类型,但填充值为数组类型,那么就取数组的第一个值进行类型转换之后填充
            if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) {
                convertedValue = Array.get(convertedValue, 0);
                standardConversion = true;
            }
            /**
             * isPrimitiveOrWrapper()判断这个类是不是一个原始类型
             * ( boolean, byte, char, short, int, long, float, or double,void)
             * 或者包装类型
             * (Boolean, Byte, Character, Short, Integer, Long, Float,Double, or Void)
             * 这个分支处理原始或包装类型->String类型的转换
             */
            if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) {
                // We can stringify any primitive value...
                return (T) convertedValue.toString();
            }
            /**
             * 通过构造方法进行类型转换,见2.2.4
             * 直接获取转换类型类的单String参数的构造器,传入需要转换的值
             * 用户在这个构造器中编写转换逻辑,spring通过这个构造器构造转换成功的对象
             * 这种方式只能实现String->requiredType的转换
             */
            else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) {
                //requiredType不是接口,不是枚举
                if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) {
                    try {
                        //得到单String参数的构造器
                        Constructor<T> strCtor = requiredType.getConstructor(String.class);
                        /**
                         * 使用这个构造器实例化一个requiredType对象
                         * 如此也实现了String->requiredType的转换
                         */
                        return BeanUtils.instantiateClass(strCtor, convertedValue);
                    }
                    catch (NoSuchMethodException ex) {
                        // proceed with field lookup
                        if (logger.isTraceEnabled()) {
                            logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex);
                        }
                    }
                    catch (Exception ex) {
                        if (logger.isDebugEnabled()) {
                            logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex);
                        }
                    }
                }
                //requiredType是枚举,String->enum
                String trimmedValue = ((String) convertedValue).trim();
                if (requiredType.isEnum() && trimmedValue.isEmpty()) {
                    // It's an empty enum identifier: reset the enum value to null.
                    return null;
                }
                convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue);
                standardConversion = true;
            }
            //Number->Number
            else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) {
                convertedValue = NumberUtils.convertNumberToTargetClass(
                    (Number) convertedValue, (Class<Number>) requiredType);
                standardConversion = true;
            }
        }
        //待转换的值为null,此时创建一个空容器代表已转化的值
        else {
            // convertedValue == null
            if (requiredType == Optional.class) {
                convertedValue = Optional.empty();
            }
        }

        /***********************未转换成功才会运行下面代码**************************/
        //convertedValue是不是requiredType类型
        if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) {
            //抛出前面转换过程产生的异常
            if (conversionAttemptEx != null) {
                // Original exception from former ConversionService call above...
                throw conversionAttemptEx;
            }
            //用户自定义的PropertyEditor未转换成功,此时使用conversionService进行转换
            else if (conversionService != null && typeDescriptor != null) {
                // ConversionService not tried before, probably custom editor found
                // but editor couldn't produce the required type...
                //得到待转换值的类型描述
                TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
                /**
                 * 判断统一转换服务能否实现sourceTypeDesc->typeDescriptor的转换
                 * sourceTypeDesc:待转换值的类型描述
                 * typeDescriptor:要求类型的类型描述
                 */
                if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
                    //统一转换服务转换类型
                    return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
                }
            }
            //后面都是异常相关处理
            // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException
            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());
            }
            else {
                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 (T) convertedValue;
}

2.2.1 根据要求类型requiredType获取默认的PropertyEditor

/**
 * Find a default editor for the given type.
 * @param requiredType the type to find an editor for
 * @return the corresponding editor, or {@code null} if none
 */
@Nullable
private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
    PropertyEditor editor = null;
    if (requiredType != null) {
        // No custom editor -> check BeanWrapperImpl's default editors.
        //根据类型获取BeanWrapperImpl中默认的PropertyEditor,见3.5
        editor = this.propertyEditorRegistry.getDefaultEditor(requiredType);
        if (editor == null && String.class != requiredType) {
            // No BeanWrapper default editor -> check standard JavaBean editor.
            /**
             * 获取后缀约定的PropertyEditor
             * 什么叫后缀约定,举个例子,现在要将String->Role,那么我们可以直接将这个PropertyEditor
             * 命名为RoleEditor,不需要自己注册到容器中,容器会自动根据约定获取到这个PropertyEditor
             * 在这个方法里面会自动的将requiredType拼接上Editor,
             * 然后反射实例化得到这个PropertyEditor,要求它们在同一个包下
             */
            editor = BeanUtils.findEditorByConvention(requiredType);
        }
    }
    return editor;
}

2.2.2 进行String->requiredType的类型转换

/**
 * Convert the value to the required type (if necessary from a String),
 * using the given property editor.
 * @param oldValue the previous value, if available (may be {@code null})
 * @param newValue the proposed new value
 * @param requiredType the type we must convert to
 * (or {@code null} if not known, for example in case of a collection element)
 * @param editor the PropertyEditor to use
 * @return the new value, possibly the result of type conversion
 * @throws IllegalArgumentException if type conversion failed
 */
@Nullable
private Object doConvertValue(@Nullable Object oldValue, @Nullable Object newValue,
                              @Nullable Class<?> requiredType, @Nullable PropertyEditor editor) {

    Object convertedValue = newValue;

    /**
     * 非String类型->requiredType
     * 调用PropertyEditor的setValue方法和getValue方法
     * 也就是说我们可以重写这两个方法对这个非String类型干点啥
     */
    if (editor != null && !(convertedValue instanceof String)) {
        // Not a String -> use PropertyEditor's setValue.
        // With standard PropertyEditors, this will return the very same object;
        // we just want to allow special PropertyEditors to override setValue
        // for type conversion from non-String values to the required type.
        try {
            editor.setValue(convertedValue);
            Object newConvertedValue = editor.getValue();
            if (newConvertedValue != convertedValue) {
                convertedValue = newConvertedValue;
                // Reset PropertyEditor: It already did a proper conversion.
                // Don't use it again for a setAsText call.
                editor = null;
            }
        }
        catch (Exception ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
            }
            // Swallow and proceed.
        }
    }

    Object returnValue = convertedValue;

    /**
     * String数组类型->requiredType(String类型)
     * 仅适用于之前未经PropertyEditor转换的数组
     */
    if (requiredType != null && !requiredType.isArray() && convertedValue instanceof String[]) {
        // Convert String array to a comma-separated String.
        // Only applies if no PropertyEditor converted the String array before.
        // The CSV String will be passed into a PropertyEditor's setAsText method, if any.
        if (logger.isTraceEnabled()) {
            logger.trace("Converting String array to comma-delimited String [" + convertedValue + "]");
        }
        //字符串数组转化为逗号分隔的字符串
        convertedValue = StringUtils.arrayToCommaDelimitedString((String[]) convertedValue);
    }

    /**
     * String类型->requiredType
     * 使用PropertyEditor的setAsText方法进行类型转换
     */
    if (convertedValue instanceof String) {
        if (editor != null) {
            // Use PropertyEditor's setAsText in case of a String value.
            if (logger.isTraceEnabled()) {
                logger.trace("Converting String to [" + requiredType + "] using property editor [" + editor + "]");
            }
            String newTextValue = (String) convertedValue;
            //调用setAsText方法进行类型转换
            return doConvertTextValue(oldValue, newTextValue, editor);
        }
        //要求类型就是String,无需转换,直接使用
        else if (String.class == requiredType) {
            returnValue = convertedValue;
        }
    }

    return returnValue;
}


/**
 * Convert the given text value using the given property editor.
 * @param oldValue the previous value, if available (may be {@code null})
 * @param newTextValue the proposed text value
 * @param editor the PropertyEditor to use
 * @return the converted value
 */
private Object doConvertTextValue(@Nullable Object oldValue, String newTextValue, PropertyEditor editor) {
    try {
        /**
         * 先保存旧值,旧值是用属性的getter方法获取的
         * 相当于属性的默认值
         * newTextValue则是用户在xml中定义的经过BeanDefinitionValueResolver初步处理的值
         */
        editor.setValue(oldValue);
    }
    catch (Exception ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("PropertyEditor [" + editor.getClass().getName() + "] does not support setValue call", ex);
        }
        // Swallow and proceed.
    }
    //最终在此处调用了setAsText()方法进行类型转换
    editor.setAsText(newTextValue);
    //调用getValue()方法将转换成功的值取出来
    return editor.getValue();
}

spring底层使用PropertyEditorsetAsText()方法进行类型转换,然后再调用getValue()方法将转换成功的值取出。

2.2.3 Map集合的类型转换

此种情况针对用户在xml中配置的<map>标签处理

private Map<?, ?> convertToTypedMap(Map<?, ?> original, @Nullable String propertyName,
                                    Class<?> requiredType, @Nullable TypeDescriptor typeDescriptor) {

    //要转换的类型非Map集合,跳过转换,因为此处只能进行key和value的转换
    if (!Map.class.isAssignableFrom(requiredType)) {
        return original;
    }

    /**
     * 判断类型requiredType的类型是不是下面7种之一
     * Map、SortedMap、NavigableMap、HashMap、LinkedHashMap、TreeMap、EnumMap
     */
    boolean approximable = CollectionFactory.isApproximableMapType(requiredType);
    if (!approximable && !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;
    }

    //判断经过BeanDefinitionValueResolver初步解析之后的值是不是requiredType的实例
    boolean originalAllowed = requiredType.isInstance(original);
    //获取Map集合key的TypeDescriptor,未指定就返回null
    TypeDescriptor keyType = (typeDescriptor != null ? typeDescriptor.getMapKeyTypeDescriptor() : null);
    //获取Map集合value的TypeDescriptor,未指定就返回null
    TypeDescriptor valueType = (typeDescriptor != null ? typeDescriptor.getMapValueTypeDescriptor() : null);
    if (keyType == null && valueType == null && originalAllowed &&
        /**
         * 判断是否为propertyName指定了PropertyEditor,此时只根据属性名匹配,
         * 因为该方法第一个参数为null,见3.6
         */
        !this.propertyEditorRegistry.hasCustomEditorForElement(null, propertyName)) {
        return original;
    }

    Iterator<?> it;
    try {
        it = original.entrySet().iterator();
    }
    catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Cannot access Map of type [" + original.getClass().getName() +
                         "] - injecting original Map as-is: " + ex);
        }
        return original;
    }

    //用来存放经过类型转换的键值对的集合
    Map<Object, Object> convertedCopy;
    try {
        if (approximable) {
            /**
             * 如果original的类型为EnumMap,那么就创建一个空的和original大小相同的EnumMap
             * 如果original的类型为SortedMap,那么就创建一个空的和original大小相同的SortedMap
             * 如果original是其他类型的Map,那么就创建一个空的和original大小相同的LinkedHashMap
             */
            convertedCopy = CollectionFactory.createApproximateMap(original, original.size());
        }
        else {
            //反射创建一个requiredType类型的map集合
            convertedCopy = (Map<Object, Object>)
                ReflectionUtils.accessibleConstructor(requiredType).newInstance();
        }
    }
    catch (Throwable ex) {
        if (logger.isDebugEnabled()) {
            logger.debug("Cannot create copy of Map type [" + original.getClass().getName() +
                         "] - injecting original Map as-is: " + ex);
        }
        return original;
    }

    /**********************迭代器遍历map,转换每一个key、value*************************/
    while (it.hasNext()) {
        Map.Entry<?, ?> entry = (Map.Entry<?, ?>) it.next();
        Object key = entry.getKey();
        Object value = entry.getValue();
        //生成键属性名
        String keyedPropertyName = buildKeyedPropertyName(propertyName, key);
        
        //递归调用convertIfNecessary()方法转换key和value
        Object convertedKey = convertIfNecessary(keyedPropertyName, null, key,
                                                 (keyType != null ? keyType.getType() : null), keyType);
        Object convertedValue = convertIfNecessary(keyedPropertyName, null, value,
                                                   (valueType!= null ? valueType.getType() : null), valueType);
        
        try {
            //转换成功的值放入convertedCopy集合中
            convertedCopy.put(convertedKey, convertedValue);
        }
        catch (Throwable ex) {
            if (logger.isDebugEnabled()) {
                logger.debug("Map type [" + original.getClass().getName() +
                             "] seems to be read-only - injecting original Map as-is: " + ex);
            }
            return original;
        }
        //转换前和转换后的值相同为true,表示original可以直接使用,否则使用转换的值
        originalAllowed = originalAllowed && (key == convertedKey) && (value == convertedValue);
    }
    return (originalAllowed ? original : convertedCopy);
}

生成键属性名(类似customEditors[com.lx.converter.domain.Role])

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

2.2.4 通过构造方法进行类型转换

//获取String参数的构造器
Constructor<T> strCtor = requiredType.getConstructor(String.class);
//使用这个构造器实例化对象
return BeanUtils.instantiateClass(strCtor, convertedValue);

如何使用呢?下面是一个小例子

@Data
@ToString
@NoArgsConstructor
public class Role {
    private Integer id;
    private String rolename;
    private String description;

    /**
     * 定义了这个构造方法,就能通过requiredType.getConstructor(String.class)获取到这个构造器了
     * BeanUtils.instantiateClass(strCtor, convertedValue);实例化这个对象的时候,
     * 将convertedValue作为构造器参数实例化对象
     */
    public Role(String text) {
        //转换逻辑在这里写了
        String[] split = text.split(",");
        this.id = Integer.valueOf(split[0]);
        this.rolename = split[1];
        this.description = split[2];
    }
}

注释掉配置文件中下面bean定义,我们自定义的PropertyEditor就不会生效了

<!--CustomEditorConfigurer是一个BeanFactoryPostProcessor-->
<bean id="customEditorConfigurer"
      class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <!--将我们自己写的PropertyEditor放入CustomEditorConfigurer中,再由它注册到工厂中-->
    <property name="customEditors">

        <map>
            <entry key="com.lx.converter.domain.Role"
                   value="com.lx.converter.propertyEditor.StringToRolePropertyEditor"></entry>
        </map>
    </property>
</bean>

测试

User(name=null, username=null, password=null, role=Role(id=12, rolename=lx, description=中国))

仍能正常的进行类型转换。

3 PropertyEditorRegistrySupport

该类非常重要,某种程度上说它是PropertyEditorRegistry接口的唯一实现,提供PropertyEditor注册和查找的功能。并且该类还是BeanWrapperImpl的父类

先来看PropertyEditorRegistry接口,该接口提供了三个方法

public interface PropertyEditorRegistry {

    //注册String->requiredType转换的PropertyEditor
    void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor);

    //注册指定属性的PropertyEditor
    void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor);

    //获取指定条件的PropertyEditor
    @Nullable
    PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath);

}

接下来看一下PropertyEditorRegistrySupport的属性

public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {

    //统一转换服务,spring自建的一套类型转换
    @Nullable
    private ConversionService conversionService;

    //是否允许使用默认的PropertyEditor
    private boolean defaultEditorsActive = false;

    private boolean configValueEditorsActive = false;
    
    //默认的PropertyEditor
    @Nullable
    private Map<Class<?>, PropertyEditor> defaultEditors;

    //覆盖的PropertyEditor,优先级比defaultEditors的高
    @Nullable
    private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;

    //用户注册的String->requiredType的PropertyEditor
    @Nullable
    private Map<Class<?>, PropertyEditor> customEditors;

    /**
     * 映射关系为propertyName->PropertyEditor
     * 保存用户单独为某个属性的类型转换注册的PropertyEditor
     */
    @Nullable
    private Map<String, CustomEditorHolder> customEditorsForPath;

    /**
     * 缓存
     * 当完成String->xxxType的类型转换后,就会将xxxType和其使用的PropertyEditor缓存到这里面
     */
    @Nullable
    private Map<Class<?>, PropertyEditor> customEditorCache;
}

3.1 CustomEditorHolder

CustomEditorHolderPropertyEditorRegistrySupport的嵌套类

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

    /**
     * 该方法会获取保存的propertyEditor对象,只不过要遵循如下条件
     * 1.如果用户未指定注册类型,就直接返回保存的propertyEditor对象
     * 2.如果用户指定了注册类型,也指定了要求类型,那么只有当这两个类型之间有继承关系时才会返回保存的propertyEditor对象
     * 3.如果用户指定了注册类型,但是未指定要求类型,那么只有当注册类型不是Collection和数组时才会返回保存的propertyEditor对象
     * 4.其他情况均返回null
     */
    @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;
        }
    }
}

3.2 registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor)方法,注册String->requiredTypePropertyEditor

@Override
public void registerCustomEditor(Class<?> requiredType, PropertyEditor propertyEditor) {
   registerCustomEditor(requiredType, null, propertyEditor);
}

该方法中调用3.3中的方法来完成注册功能

3.3registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor)方法,注册指定属性的PropertyEditor

@Override
public void registerCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath, PropertyEditor propertyEditor) {
    //两个条件不能都为null
    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;
    }
}
  • 如果指定了属性名,直接注册到customEditorsForPath集合中
  • 如果未指定属性名,直接注册到customEditors集合中

3.4 findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath)方法, 获取指定条件的用户定义的PropertyEditor

@Override
@Nullable
public PropertyEditor findCustomEditor(@Nullable Class<?> requiredType, @Nullable String propertyPath) {
    Class<?> requiredTypeToUse = requiredType;

    /***************************属性名匹配PropertyEditor********************************/
    //根据属性名查找PropertyEditor
    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) {
            /**
             * 这个方法会被子类重写
             * BeanWrapperImpl可以通过内省找到属性名对应的类型
             */
            requiredTypeToUse = getPropertyType(propertyPath);
        }
    }

    /************************类型匹配PropertyEditor*******************************/
    // No property-specific editor -> check type-specific editor.
    //找不到就根据类型匹配PropertyEditor
    return getCustomEditor(requiredTypeToUse);
}

属性名匹配是优先的

3.4.1 根据属性名获取PropertyEditor

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

通过该方法获取PropertyEditor会经过两次筛选

3.4.2 根据要求类型获取PropertyEditor

/**
 * 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) {
    //用户自定义的PropertyEditor会被放到customEditors属性中
    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.
            //遍历用户自定义的PropertyEditor
            for (Iterator<Class<?>> it = this.customEditors.keySet().iterator(); it.hasNext() && editor == null;) {
                Class<?> key = it.next();
                //能够转换的类型是要求类型父类或父接口
                if (key.isAssignableFrom(requiredType)) {
                    //得到这个PropertyEditor
                    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;
}

方法流程:

  • 首先尝试直接根据要求类型获取PropertyEditor
  • 如果获取不到,再尝试从缓存中获取PropertyEditor
  • 如果缓存中也获取不到,就用户自定义的PropertyEditor,只要这个PropertyEditor能够转换的类型是要求类型父类或父接口,就使用当前PropertyEditor,并缓存,方便下次快速得到该要求类型的PropertyEditor

3.5 getDefaultEditor(Class<?> requiredType)方法,根据要求类型获取默认的PropertyEditor

/**
 * 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) {
    //如果这个字段值为false,表明不使用默认的PropertyEditor
    if (!this.defaultEditorsActive) {
        return null;
    }
    /**
     * 重写的默认的PropertyEditor,优先级大于默认PropertyEditor
     * BeanWrapper初始化的时候,会调用ResourceEditorRegistrar的registerCustomEditors方法,
     * 将spring定义的默认的PropertyEditor注册到BeanWrapper的overriddenDefaultEditors中
     * ResourceEditorRegistrar是spring默认属性编辑器注册商,实现了PropertyEditorRegistrar接口
     */
    if (this.overriddenDefaultEditors != null) {
        PropertyEditor editor = this.overriddenDefaultEditors.get(requiredType);
        //找到了符合条件的PropertyEditor,直接返回
        if (editor != null) {
            return editor;
        }
    }
    //未找到就创建默认的PropertyEditor
    if (this.defaultEditors == null) {
        createDefaultEditors();
    }
    return this.defaultEditors.get(requiredType);
}


/**
 * Actually register the default editors for this registry instance.
 * 这些是常见的基本的PropertyEditor,非常多,里面包括了基本数据类型的转换
 */
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);
    }
}

3.6 hasCustomEditorForElement(@Nullable Class<?> elementType, @Nullable String propertyPath)方法,判断是否为propertyName指定了PropertyEditor(属性专用的PropertyEditor

/**
 * 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) {
    //this.customEditorsForPath里面保存了用户指定的属性专用的PropertyEditor
    if (propertyPath != null && this.customEditorsForPath != null) {
        //遍历属性专用的PropertyEditor
        for (Map.Entry<String, CustomEditorHolder> entry : this.customEditorsForPath.entrySet()) {
            /**
             * 先根据属性名匹配
             * 然后调用getPropertyEditor(elementType)方法根据类型匹配,见3.1
             */
            if (PropertyAccessorUtils.matchesProperty(entry.getKey(), propertyPath) &&
                entry.getValue().getPropertyEditor(elementType) != null) {
                return true;
            }
        }
    }
    // No property-specific editor -> check type-specific editor.
    //否则就从类型专用的PropertyEditor查询
    return (elementType != null && this.customEditors != null && this.customEditors.containsKey(elementType));
}

该方法先根据属性匹配PropertyEditor,如果能找到,直接返回true。如果找不到在根据类型匹配

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值