• 17. Spring类型转换之PropertyEditor


    简介

    我们之所以使用spring如此的简单,并且功能比较强大,比较人性化,是因为很多重复且繁琐的以及能想到的可以简化业务的功能都给我们默认提供了,类型转化就是典型的例子

    spring提供了两种类型转化的方式,一种是spring提供的ConversionService,后面一篇文章再讲,另外一种就是本文要介绍的
    PropertyEditor,它是jdk提供的 java.beans.PropertyEditor,spring自然也要支持

    快速使用

    一般的转换spring都提供了,但也不排除依然不满足我们的需要,假如现在有一个User类,使用@Value注入一个字符串,就能得到一个User,并且User的name属性就是注入的值,显然spring不知道我们有一个User类,那么就只能我们自己去实现

    先看怎么使用,在分析源码

    定义一个要转换的类

    public class User {
    	private String name;
    
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    定义转换器

    public class String2UserPropertyEditor extends PropertyEditorSupport implements PropertyEditor {
    	@Override
    	public void setAsText(String text) throws IllegalArgumentException {
    		User user = new User(); // 直接实例化一个User
    		user.setName(text); // 设置到Name
    		this.setValue(user);
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    告诉spring我们的转换器

    @Configuration
    public class ConvertConfig {
    	@Bean
    	public static CustomEditorConfigurer customEditorConfigurer() {
    		CustomEditorConfigurer configurer = new CustomEditorConfigurer();
    		Map<Class<?>, Class<? extends PropertyEditor>> customEditors = new HashMap<>();
    		customEditors.put(User.class, String2UserPropertyEditor.class);
    		configurer.setCustomEditors(customEditors);
    		return configurer;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    测试

    
    @Component
    public class UserBean implements UserGenerics {
    
    	@Value("xx")
    	private User user;
    
    	public User getUser() {
    		return user;
    	}
    }
    
    
    
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserBean userBean = context.getBean("userBean", UserBean.class);
        System.out.println(userBean);
        System.out.println(userBean.getUser());
        System.out.println(userBean.getUser().getName());
    }
    
    
    输出
    
    com.shura.beans.UserBean@668bc3d5
    com.shura.convert.User@3cda1055
    xx
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    从上面结果可以看出确实spring给我们自动转换了。

    下面就从源码看看,如何告诉spring我们需要的转换器以及spring提供的默认转换器

    首先看 CustomEditorConfigurer

    源码分析

    public class CustomEditorConfigurer implements BeanFactoryPostProcessor, Ordered {
    
    	protected final Log logger = LogFactory.getLog(getClass());
    
    	private int order = Ordered.LOWEST_PRECEDENCE;
    
    	@Nullable
    	private PropertyEditorRegistrar[] propertyEditorRegistrars;
    
    	@Nullable
    	private Map<Class<?>, Class<? extends PropertyEditor>> customEditors;
    
    
    	public void setOrder(int order) {
    		this.order = order;
    	}
    
    	@Override
    	public int getOrder() {
    		return this.order;
    	}
    
        // 添加类型转换注册器 先忽略
    	public void setPropertyEditorRegistrars(PropertyEditorRegistrar[] propertyEditorRegistrars) {
    		this.propertyEditorRegistrars = propertyEditorRegistrars;
    	}
    
        // 添加类型转换器
    	public void setCustomEditors(Map<Class<?>, Class<? extends PropertyEditor>> customEditors) {
    		this.customEditors = customEditors;
    	}
    
        
        // BeanFactory后置处理
    	@Override
    	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    		if (this.propertyEditorRegistrars != null) {
    			for (PropertyEditorRegistrar propertyEditorRegistrar : this.propertyEditorRegistrars) {
    				beanFactory.addPropertyEditorRegistrar(propertyEditorRegistrar);
    			}
    		}
    		// 将类型转换器设置到beanFactory
    		if (this.customEditors != null) {
    			this.customEditors.forEach(beanFactory::registerCustomEditor);
    		}
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48

    CustomEditorConfigurer的代码比较简单,就是将我们自定义的类型转换器设置到beanFactory中,知道它是一个BeanFactoryPostProcessor就好理解了,肯定在Bean实例化之前BeanFactory就有了各种类型转换器了

    spring实例化Bean的时候将每个Bean都先实例化一个 BeanWrapperImpl
    org.springframework.beans.factory.support.ConstructorResolver#instantiateUsingFactoryMethod

    这个类很重要,我们需要记住BeanWrapperImpl是 TypeConverterSupport PropertyEditorRegistry PropertyEditorRegistrySupport
    TypeConverter的子类,并且有一个属性 TypeConverterDelegate 用来真正做转换用的

    BeanWrapperImpl -> TypeConverterSupport (一个属性 TypeConverterDelegate)

    
    // ConstructorResolver类
    public BeanWrapper instantiateUsingFactoryMethod({
        BeanWrapperImpl bw = new BeanWrapperImpl();
    	this.beanFactory.initBeanWrapper(bw);
    	...
    }
    
    // AbstractBeanFactory类
    protected void initBeanWrapper(BeanWrapper bw) {
        bw.setConversionService(getConversionService()); // 下节分析
        registerCustomEditors(bw); // 注册PropertyEditor 
    }
    
    protected void registerCustomEditors(PropertyEditorRegistry registry) {
        if (registry instanceof PropertyEditorRegistrySupport) {
            ((PropertyEditorRegistrySupport) registry).useConfigValueEditors();
        }
        
        
        if (!this.customEditors.isEmpty()) {
            this.customEditors.forEach((requiredType, editorClass) ->
                    registry.registerCustomEditor(requiredType, BeanUtils.instantiateClass(editorClass))); // 添加到了PropertyEditorRegistrySupport
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    上面源码简单来说就是将beanFactory中自定义的PropertyEditor添加到了BeanWrapper

    上面提到了BeanWrapper里面会有一个TypeConverterDelegate属性,他就是真正做转换的类,中间细节不讲了,直接看怎么转的

    public <T> T convertIfNecessary(@Nullable String propertyName, @Nullable Object oldValue, @Nullable Object newValue,
    			@Nullable Class<T> requiredType, @Nullable TypeDescriptor typeDescriptor) throws IllegalArgumentException {
    
        // 在这一步根据requiredType=User 就可以找到我们定义的 String2UserPropertyEditor 
        PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
    
        // ... 跳过 ConversionService ...
    
        Object convertedValue = newValue;
    
        if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) {
            // 如果没有找到就从默认的propertyEditor去找
            if (editor == null) {
                editor = findDefaultEditor(requiredType);
            }
            // 转换
            convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor);
        }
    
        return (T) convertedValue;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    从上面代码可以看出就是找出前面注册的PropertyEditor,然后进行转换,转换的逻辑很简单,setText()执行一下就有了对象,然后执行getText拿到对象就行

    如果没有自定义的就从默认的去找,下面就看看有哪些默认的

    private PropertyEditor findDefaultEditor(@Nullable Class<?> requiredType) {
        return this.propertyEditorRegistry.getDefaultEditor(requiredType);;
    }
    
    
    public PropertyEditor getDefaultEditor(Class<?> requiredType) {
        if (this.defaultEditors == null) {
            createDefaultEditors();
        }
        return 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());
        if (!shouldIgnoreXml) {
            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));
    
        // 提供了string转数组的逻辑,我们通过配置地址的时候就会使用到
        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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83

    以上就是默认PropertyEditor的逻辑

    下一篇我们介绍另外一种类型转换器 ConversionService


    欢迎关注,学习不迷路!

  • 相关阅读:
    msql 查询json类型数据
    具有柔性结构的孤岛直流微电网的分级控制(Malab代码实现)
    Apache Doris 数据建模之 Aggregate Key 模型
    2023年Android Gradle、Gradle插件以及Kotlin版本升级记录
    基于机器学习的 ICU 脑血管疾病死亡风险智能预测系统
    Java高级应用——泛型
    Linux~常用命令的使用
    Git 笔记
    08 ARM Cortex-A7汇编语言和指令介绍,ARM汇编语言名为UAL,由编译器指定指令集是ARM还是Thumb,不同指令集的汇编指令是一样的
    【工具篇】Unity翻书效果的三种方式
  • 原文地址:https://blog.csdn.net/weixin_44412085/article/details/134496662