Spring学习笔记:Spring类型转换及源码分析

2021-04-14  本文已影响0人  大力papa

本文仅供学习交流使用,侵权必删。
不作商业用途,转载请注明出处

1. 概述

在Spring中,我们通常可以使用PropertyEditor将一个字符串类型的属性转换成指定类型的属性。而在Spring 3之后,Spring提供了一种更加通用的类型转换机制不仅限于字符串类型和对象类型之间的转换,同时也提供了多个扩展接口给开发者扩展类型转换功能。
下面将记录下如何在Spring环境中利用这些类型转换接口进行扩展以及类型转换的源码分析

当前使用版本是Spring Framework 5.2.2.RELEASE

2. 扩展类型转换器

2.1 PropertyEditor

这种方式是实现了Java Beans的PropertyEditor的一种实现。如何使用PropertyEditor进行类型转换,下面会做一个示例。
该示例是将一个String类型转换成一个Object类型的PropertyEditor扩展接口。

 public class User {
     private String name;
     private UserContext contextList;
 
     public User() {
     }
 
     public String getName() {
         return name;
     }
 
     public void setName(String name) {
         this.name = name;
     }
 
     public UserContext getContextList() {
         return contextList;
     }
 
     public void setContextList(UserContext contextList) {
         this.contextList = contextList;
     }
 }
 
 
 
 public class UserContext {
     private String[] context;
 
     public String[] getContext() {
         return context;
     }
     
     public void setContext(String[] context) {
         this.context = context;
     }
 }
public class StringToUserContextPropertyEditor extends PropertyEditorSupport implements PropertyEditor {

    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        String[] arr = text.split (",");
        UserContext userContext = new UserContext ();
        userContext.setContext (arr);
        setValue (userContext);
    }


    @Override
    public String getAsText() {
        UserContext userContext = (UserContext) getValue ();
        return Arrays.toString (userContext.getContext ());
    }


}
 public class CustomizedPropertyEditorRegistered implements PropertyEditorRegistrar {
     @Override
     public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) {
         propertyEditorRegistry.registerCustomEditor (UserContext.class, new StringToUserContextPropertyEditor ());
     }
 }
 <?xml version="1.0" encoding="UTF-8"?>
 <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:util="http://www.springframework.org/schema/util"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
 http://www.springframework.org/schema/util https://www.springframework.org/schema/utils/spring-util.xsd">
 
     <!-- 通过CustomEditorConfigurer这个BeanPostProcessor将我们的自定义注册器加载到容器中 -->
     <bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
         <property name="propertyEditorRegistrars">
             <list>
                 <ref bean="customizedPropertyEditorRegistered"/>
             </list>
         </property>
     </bean>
 
     
     <!-- 自定义注册器 -->
     <bean id="customizedPropertyEditorRegistered"   class="org.kgyam.spring.conversion.propertyEditor.CustomizedPropertyEditorRegistered"/>
 
   <!-- User bean信息 -->
     <bean id="user" class="org.kgyam.domain.User">
         <property name="name" value="dalipapa"/>
         <property name="contextList" value="v1,v2,v3,v4,v5"></property>
     </bean>
 
 </beans>
public class CustomizedPropertyEditorDemo {
    public static void main(String[] args) {
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("classpath:META-INF/conversion-context.xml");
        User user=applicationContext.getBean("user", User.class);
        System.out.println(user);
    }
}

利用PropertyEditor实现类型转换其实存在一定的缺陷:

  1. 首先这种方式只能支持String和Object之间的类型转换
  2. 其次setValue和getValue并没有支持泛型,在每次getValue的时候需要做类型强转,这种写法并不优雅
  3. PropertyEditor包含大量针对GUI的操作,因而从设计上违反了单一职责原则

所以在Spring 3.0之后添加了Converter实现类型转换。


2.2 Converter | ConverterFactory | GenericConverter

Spring 3之后提供了Converter和GenericConverter作为类型转换的扩展接口。

以下对于这三种类型接口编写一个自定义类型转换示例,该示例是实现一个Properties类型转换为String类型的扩展接口

 public class LocalProperties {
     private String propertiesStr;
 
     public String getPropertiesStr() {
         return propertiesStr;
     }
 
     public void setPropertiesStr(String propertiesStr) {
         this.propertiesStr = propertiesStr;
     }
 
     @Override
     public String toString() {
         return "LocalProperties{" +
                 "propertiesStr='" + propertiesStr + '\'' +
                 '}';
     }
 }
 public class PropertiesToStringConverter implements Converter<Properties, String>, ConditionalConverter {
     @Override
     public String convert(Properties properties) {
         System.out.println ("PropertiesToStringConverter#convert");
         StringBuilder stringBuilder = new StringBuilder ();
         for (Map.Entry<Object, Object> entry : properties.entrySet ()) {
             stringBuilder.append (entry.getKey () + ":" + entry.getValue ()).append (",");
         }
         return stringBuilder.deleteCharAt (stringBuilder.length () - 1).toString ();
     }
 
     @Override
     public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
         System.out.println ("PropertiesToStringConverter#matches");
         return Properties.class.equals (sourceType.getType ()) && String.class.equals (targetType.getType ());
     }
 }
 public class PropertiesToStringConverterFactory implements ConverterFactory<Properties, String> {
     @Override
     public <T extends String> Converter<Properties, T> getConverter(Class<T> aClass) {
         return new InnerPropertiesToStringConverter ();
     }
 
 
     private static final class InnerPropertiesToStringConverter<T extends String> implements Converter<Properties, String> {
 
         @Override
         public String convert(Properties properties) {
             StringBuilder stringBuilder = new StringBuilder ();
             for (Map.Entry<Object, Object> entry : properties.entrySet ()) {
                 stringBuilder.append (entry.getKey () + ":" + entry.getValue ()).append (",");
             }
             return stringBuilder.deleteCharAt (stringBuilder.length () - 1).toString ();
         }
     }
 }
 public class PropertiesToStringGenericConverter implements ConditionalGenericConverter {
 
     @Override
     public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
         return Properties.class.equals (sourceType.getObjectType ())
                 && String.class.equals (targetType.getObjectType ());
     }
 
     @Override
     public Set<ConvertiblePair> getConvertibleTypes() {
         return Collections.singleton (new ConvertiblePair (Properties.class, String.class));
     }
 
     @Override
     public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
         if (source == null) {
             return null;
         }
         Properties properties = (Properties) source;
         StringBuilder textBuilder = new StringBuilder ();
         for (Map.Entry<Object, Object> entry : properties.entrySet ()) {
             textBuilder.append (entry.getKey ()).append ("=").append (entry.getValue ()).append (System.getProperty ("line.separator"));
         }
         return textBuilder.toString ();
     }
 }
 <?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:util="http://www.springframework.org/schema/util"
        xsi:schemaLocation="
         http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
         http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">
 
 
     <util:properties id="default-properties">
         <prop key="id">pc1628</prop>
         <prop key="port">8080</prop>
         <prop key="ip">127.0.0.1</prop>
     </util:properties>
 
     <bean id="localProperties" class="org.kgyam.spring.conversion.converter.LocalProperties">
         <property name="propertiesStr" ref="default-properties"></property>
     </bean>
 
     <!-- 必须要用conversionService    -->
     <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
         <property name="converters">
             <util:list>
                 <bean class="org.kgyam.spring.conversion.converter.PropertiesToStringConverter"/>
 <!--                <bean class="org.kgyam.spring.conversion.converter.PropertiesToStringGenericConverter"/>-->
 
 <!--                <bean class="org.kgyam.spring.conversion.converter.CollectionToMapConditionalGenericConverter"/>-->
             </util:list>
         </property>
     </bean>
 </beans>
 public class ConverterDemo {
     public static void main(String[] args) {
         ApplicationContext applicationContext = new ClassPathXmlApplicationContext ("classpath:META-INF/conversion-context.xml");
         LocalProperties localProperties=applicationContext.getBean (LocalProperties.class);
         System.out.println (localProperties);
     }
 }

3. 关于Converter和GenericConverter的接口选择

对于单一类型的转换我们可以使用Converter接口,而对于复杂类型转换Spring官方文档更加推荐使用GenericConverter接口,尝试分析下这样选择的理由:

  1. 首先我们看下GenericConverter接口如图3-1所示,GenericConverter接口支持一组ConvertiblePair的类型转换,而ConvertiblePair可以定义一组来源类型和目标类型,如图图3-2。这种情况下我们能在一个GenericConverter接口下配置多个来源目标转换类型,这样接口针对转换类型能更多样更灵活。


    图3-1
图3-2
  1. 而Converter<S,T>接口因为用到的是泛型,如图3-3。所以仅建议用于实现来源单一类型转换成目标单一类型的转换功能,Converter<S,T>的从设计上来说更加符合单一职责原则,但灵活性来说却不如GenericConverter


    图3-3

所以针对不同的类型选择,我们可以有选择性地选用不同的类型转换接口进行扩展

4. Spring关于转换过程的源码分析

下面的源码分析是基于ConditionalGenericConverter的实现进行分析的,这个接口组合了GenericConverter和ConditionalConverter两个接口,如图4-1所示。所以这个组合接口的需要实现的方法有GenericConverter#getConvertibleTypes方法和GenericConverter#convert方法以及ConditionalConverter#matches

图4-1

bean属性的类型转换是在bean创建的时候进行的,接下来会根据上面这三个方法被调用的时机以及其调用堆栈分析类型转换过程。

在这里先给出一个结论:这些方法的调用顺序是getConvertibleTypes->matches->convert。

4.1 GenericConverter#getConvertibleTypes

图4-1-1 图4-1-2 图4-1-3 图4-1-4 图4-1-5 图4-1-6 图4-1-7 图4-1-8 图4-1-9 图4-1-10

4.2 ConditionalConverter#matches

图4-2-1 图4-2-2 图4-2-3 图4-2-4 图4-2-5

4.3 GenericConverter#convert

图4-3-1 图4-3-2 图4-3-3 图4-3-4

4.4 关于TypeConverter

上面提到TypeConverterDelegate,这个类是TypeConverterSupport的一个成员属性。而这个TypeConverterSupport是什么呢?接下来先展示BeanWrapperImpl的结构图,如图4-4-1所示。

图4-4-1 图4-4-2 图4-4-3 图4-4-4 图4-4-5 图4-4-6 图4-4-7

4.5 关于BeanFactory中的ConversionService

在这里看下BeanFactory中的ConversionService到底是什么时候构建的。然后同时解答下在一开始的代码示例中,有提到配置org.springframework.context.support.ConversionServiceFactoryBean这个bean配置的id必须是conversionService,否则会抛出异常。这里我们分析以下源码查找出原因所在。

图4-5-1 图4-5-2 图4-5-3 图4-5-4

5. 总结

Spring Framework的类型转换功能通过不断升级迭代从一开始使用Java Beans的PropertyEditor,到Spring 3之后升级成Converter接口和GenericConverter接口。可以看出Spring为了提高类型转换功能的扩展性下了很多工夫。

同时在配置ConversionService的bean时候我们要注意配置id需要是conversionService。虽然在spring官方文档的demo也是使用conversionService作为id,但是文档中貌似没有提醒用户配置时候需要注意这个id的命名,所以这里需要开发者对其源码有稍微了解或者遵循官方文档的demo。

参考文档

Spring Convert官方说明

上一篇 下一篇

猜你喜欢

热点阅读