Spring 核心特性之类型转换(PropertyEditor、ConversionService)

简介: 前言与数据绑定一样,类型转换同样是 Spring 的核心特性之一,Spring 最初的配置信息主要以 XML 的形式存在,这就要求 Spring 将字符串形式的配置转换为具体的 Java 类型,经过多个版本的演进,Spring 中的类型转换功能愈发成熟。

前言


与数据绑定一样,类型转换同样是 Spring 的核心特性之一,Spring 最初的配置信息主要以 XML 的形式存在,这就要求 Spring 将字符串形式的配置转换为具体的 Java 类型,经过多个版本的演进,Spring 中的类型转换功能愈发成熟。


PropertyEditor


Spring 类型转换的最初的目的是将字符串转换为 bean 的构造方法参数或属性对应的类型。java bean 规范中已经提供了一个 PropertyEditor 用作属性的设置及获取,因此 Spring 最初直接复用了 PropertyEditor 用于类型转换。


PropertyEditor 基本使用


PropertyEditor 接口定义如下。


public interface PropertyEditor {
    // 属性设置获取方法
    void setValue(Object value);
    Object getValue();
    String getAsText();
    void setAsText(String text) throws java.lang.IllegalArgumentException;
  ...省略支持 GUI 程序的其他方法
}


PropertyEditor 主要为 GUI 程序提供支持,因此除了属性设置获取方法,还有一些其他的方法。那么这个接口又是怎样使用的呢?假定我们需要将字符串转换为整数,则代码如下。


public class String2IntegerPropertyEditor extends PropertyEditorSupport {
    @Override
    public void setAsText(String text) throws IllegalArgumentException {
        this.setValue(Integer.valueOf(text));
    }   
}
public class App {
    public static void main(String[] args) {
        String text = "520";
        String2IntegerPropertyEditor editor = new String2IntegerPropertyEditor();
        editor.setAsText(text);
        Integer number = (Integer) editor.getValue();
        System.out.println(number);
    }
}


自定义 PropertyEditor 时,可以继承 PropertyEditorSupport 类,然后重写#setAsText方法,在这个方法中完成对字符串的转换,并调用#setValue存储转换后的值,完成转换后调用#getValue方法获取转换类型后的值。


Spring 默认的 PropertyEditor


Spring 默认的 PropertyEditor 位于包 org.springframework.beans.propertyeditors 中,在上篇《聊聊 Spring 核心特性中的数据绑定 (DataBinder)》我们已经提过,XML 配置的属性通过 BeanWrapper 设置到对象中,使用的实现类是 BeanWrapperImpl,实例化这个类时就会激活默认的 PropertyEditor。


如果 Spring 在进行类型转换时找不到内置的默认 PropertyEditor,Spring 还会根据一定的策略查找默认 PropertyEditor,具体是查找名称为 类型名Editor 的PropertyEditor,如果想要将 String 转换为 com.zzuhkp.User,则要查找的默认 PropertyEditor 全限定名为com.zzuhkp.UserEditor。


Spring 中自定义 PropertyEditor


添加 PropertyEditor 到 BeanFactory

Spring 解析 XML 中的配置设置 bean 的属性值时,实例化 BeanWrapperImpl 激活默认 PropertyEditor 后就会将 BeanFactory 中的自定义 PropertyEditor 初始化到 BeanWrapperImpl,在类型转换时自定义 PropertyEditor 将优先于默认 PropertyEditor。


那么如何将自定义的 PropertyEditor 添加到 BeanFactory 中呢?


如果可以获取到 ConfigurableBeanFactory 的实例,那么最直接的方法是调用ConfigurableBeanFactory#registerCustomEditor方法直接设置。

另一种方式是利用 ApplicationContext 生命周期提供的 BeanFactoryPostProcessor 回调获取到 BeanFactory 实例后注册自定义 PropertyEditor。


@Component
public class CustomEditorProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.registerCustomEditor(Integer.class, String2IntegerPropertyEditor.class);
    }
}


添加 PropertyEditorRegistrar 到 BeanFactory

Spring 除了将 BeanFacotory 中自定义的 PropertyEditor 添加到 BeanWrapperImpl,还会使用 BeanFactory 中的 PropertyEditorRegistrar 为 BeanWrapperImpl 添加新的 PropertyEditor。PropertyEditorRegistrar 接口定义如下。


public interface PropertyEditorRegistrar {
  void registerCustomEditors(PropertyEditorRegistry registry);
}


接口方法参数中的 registry 就是 BeanWrapperImpl 的实例,Spring 初始化 BeanWrapperImpl 后会回调 PropertyEditorRegistrar 中的方法。ApplicationContext 在 refresh 时就用了这种机制向 BeanFactory 中添加 PropertyEditorRegistrar 的实例 ResourceEditorRegistrar,自定义了一些和资源有关的 PropertyEditor。


为了向 BeanFactory 添加 PropertyEditorRegistrar,可以调用ConfigurableBeanFactory#addPropertyEditorRegistrar方法。按照这种方式自定义 PropertyEditor 代码如下。


@Component
public class CustomEditorRegistrar implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) throws BeansException {
        factory.addPropertyEditorRegistrar(new PropertyEditorRegistrar() {
            @Override
            public void registerCustomEditors(PropertyEditorRegistry propertyEditorRegistry) {
                propertyEditorRegistry.registerCustomEditor(Integer.class, new String2IntegerPropertyEditor());
            }
        });
    }
}


Spring 内置类 CustomEditorRegistrar


向 BeanFactory 中添加自定义的 ProprtyEditor 和 CustomEditorRegistrar,Spring 还内置了一个 BeanFactoryPostProcesor 的实现类 CustomEditorConfigurer,使用方式如下。


@Configuration
public class Config {
   @Bean
   public CustomEditorConfigurer customEditorConfigurer() {
       CustomEditorConfigurer configurer = new CustomEditorConfigurer();
       configurer.setCustomEditors(Collections.singletonMap(Integer.class, String2IntegerPropertyEditor.class));
       configurer.setPropertyEditorRegistrars(new PropertyEditorRegistrar[]{
               new PropertyEditorRegistrar() {
                   @Override
                   public void registerCustomEditors(PropertyEditorRegistry registry) {
                       registry.registerCustomEditor(Integer.class, new String2IntegerPropertyEditor());
                   }
               }
       });
       return configurer;
   }
}


ConversionService


虽然 PropertyEditor 已经可以支持类型转换,但是由于 PropertyEditor 职责不够单一(除了类型转换还包括 java bean 事件及 GUI 支持)、来源类型的局限性(只能为 String),Spring 在 3.0 版本提出了新的类型转换接口 ConversionService。


这个接口的定义如下。


public interface ConversionService {
  // 源类型是否能转换为目标类型
  boolean canConvert(@Nullable Class<?> sourceType, Class<?> targetType);
  boolean canConvert(@Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
  // 类型转换
  <T> T convert(@Nullable Object source, Class<T> targetType);
  Object convert(@Nullable Object source, @Nullable TypeDescriptor sourceType, TypeDescriptor targetType);
}


接口包含两类方法,一类用于判断源类型是否能够转换为目标类型,另一类用于将源类型转换为目标类型,其中 TypeDescriptor 用于描述转换的类型信息,以便支持泛型。


自定义 ConversionService


Spring 标准环境下默认没有配置 ConversionService,但是 Spring 提供了一个默认的实现 DefaultConversionService,如果需要使用 ConversionService 替换 PropertyEidtor 对 String 到 bean 属性类型的转换,可以使用 BeanFactory 直接设置 ConversionService ,也可以在应用上下文中配置名称为 conversionService 的 ConversionService 类型 bean,Spring 会在刷新上下文时将这个 bean 设置到 BeanFactory,最终会设置到 BeanWrapperImpl 实例中。


// 方法一:直接设置 ConversionService
@Component
public class ConversionServiceProcessor implements BeanFactoryPostProcessor {
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        beanFactory.setConversionService(new DefaultConversionService());
    }
}
// 方法二:注册名为 conversionService 的 ConversionService bean
@Configuration
public class Config {
    @Bean
    public ConversionService conversionService(){
        return new DefaultConversionService();
    }
}


向 ConversionService 中添加 Converter

虽然将 ConversionService 配置到 BeanFactory 能够达到替换 PropertyEditor 的目的,但是 ConversionService 并不是万能的,例如将一个 User 类实例转换为 Order 类肯定是不可能的。


Converter


默认的 ConversionService 的实现并非直接在内部直接进行类型转换,而是将类型转换的工作委托给了 Converter 接口,由这个接口的实现类完成具体的类型转换工作, DefultConversionService 已经内置了一些 Converter 实例,并且提供了一些添加自定义 Converter 接口的方法。


那么我们先来看这个 Converter 是如何定义的。


public interface Converter<S, T> {
  T convert(S source);
}


Converter 是一个函数式接口,内部只有一个用于类型转换的方法,其中泛型 S 表示源类型,泛型 T 表示目标类型。


ConverterRegistry


如何将自定义的 Converter 添加到 ConversionService 呢?DefultConversionService 实现了接口 ConverterRegistry,这个接口中定义了一些注册 Converter 的方法,具体如下。


public interface ConverterRegistry {
  void addConverter(Converter<?, ?> converter);
  <S, T> void addConverter(Class<S> sourceType, Class<T> targetType, Converter<? super S, ? extends T> converter);
  void addConverter(GenericConverter converter);
  void addConverterFactory(ConverterFactory<?, ?> factory);
  void removeConvertible(Class<?> sourceType, Class<?> targetType);
}


ConverterRegistry 接口提供了添加 Converter 和移除转换给定类型 Converter 的方法,这里出现了两个我们没提到过的接口,分别是 GenericConverter 和 ConverterFactory。


GenericConverter


Converter 只能将一种类型转换为另一个种类型,为了支持多种类型转换,GenericConverter 就出现了,这个接口定义如下。


public interface GenericConverter {
  // 获取可转换的类型
  Set<ConvertiblePair> getConvertibleTypes();
  // 类型转换
  Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
  final class ConvertiblePair {
    private final Class<?> sourceType;
    private final Class<?> targetType;
    ...省略部分代码
  }
}


GenericConverter 内部有一个持有源类型目标类型的 ConvertiblePair 类,除了类型转换方法还提供了获取可转换的类型对的方法。


ConverterFactory


至于 ConverterFactory,则是 Converter 的工厂,有了这个工厂,ConversionService 就可以获取到 Converter,ConverterFactory 接口定义如下。


public interface ConverterFactory<S, R> {
  <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}


接口中的泛型 S 表示源类型,T 表示目标类型。


ConditionalConverter


到了这里,我们已经可以添加自定义的 Converter 到 ConversionService 了,但是还要提到的是 ConditionalConverter 接口,这个接口用于在类型转换前判断是否可以进行类型转换。


ConditionalConverter 接口定义如下。


public interface ConditionalConverter {
  boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}


除此之外,Spring 还将 GenericConverter 和 ConditionalConverter 整合到一起,提供了一个 ConditionalGenericConverter 接口。


public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}


Spring 标准环境添加 Converter


了解各种不同的 Converter 之后,我们就可以将自定义的 Convter 添加到 ConversionServcie 了,示例代码如下。


public class String2IntegerConverter implements ConditionalGenericConverter {
    @Override
    public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) {
        return sourceType.getType().equals(String.class) && targetType.getType().equals(Integer.class);
    }
    @Override
    public Set<ConvertiblePair> getConvertibleTypes() {
        return Collections.singleton(new ConvertiblePair(String.class, Integer.class));
    }
    @Override
    public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
        return Integer.valueOf(source.toString());
    }
}
@Configuration
public class Config {
    @Bean
    public ConversionService conversionService() {
        DefaultConversionService conversionService = new DefaultConversionService();
        conversionService.addConverter(new String2IntegerConverter());
        return conversionService;
    }
}


Spring Web 环境下添加 Converter


标准环境下名为 conversionService 的 ConversionService bean 只能用于 bean 实例化后设置 bean 的属性,Web 环境下 Spring 需要将 request 中的信息转换为 controller 方法参数,同样需要进行类型转换。


Web 环境自定义 Converter 的配置和标准环境有所不同,需要我们定义一个实现 WebMvcConfigurationSupport 的配置类,然后重写#addFormatters 方法。具体如下。


@Configuration
public class Config extends WebMvcConfigurationSupport {
    @Override
    protected void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new String2IntegerConverter());
    }
}

其中 FormatterRegistry 是继承了 ConverterRegistry 接口的一个接口。


Spring 类型转换流程


标准环境 bean 属性设置类型转换流程

前面已经陆陆续续总结了一些 Spring 类型转换的流程,这里根据流程画了一张图便于直观的了解 Spring 类型转换流程,见下图。


6.png


Web 环境 controller 方法参数类型转换流程


Web 环境类型转换主要用于将 request 转换为 contoller 方法参数,这里的类型转换只用到了 ConversionService,不过 Spring 已经在 WebMvcConfigurationSupport 类中为我们配置了一个,并且提供了一个添加 Converter 的#addFormatters 方法,跟踪代码可以看到 Web 环境类型转换具有如下的流程。


9f53315787df4f4cabfa5e1007959974.png

目录
相关文章
|
2月前
|
Java 数据库 Spring
Spring事务的传播机制(行为、特性)
Spring事务的传播机制(行为、特性)
40 0
|
5月前
|
存储 监控 Java
|
4月前
|
JSON Dubbo Java
微服务框架(二十)Dubbo Spring Boot 生产就绪特性
  此系列文章将会描述Java框架Spring Boot、服务治理框架Dubbo、应用容器引擎Docker,及使用Spring Boot集成Dubbo、Mybatis等开源框架,其中穿插着Spring Boot中日志切面等技术的实现,然后通过gitlab-CI以持续集成为Docker镜像。   本文为Dubbo Spring Boot 生产就绪特性
|
3月前
|
设计模式 前端开发 Java
玩转Spring—Spring5新特性之Reactive响应式编程实战
玩转Spring—Spring5新特性之Reactive响应式编程实战
70 0
|
11天前
|
安全 Java 程序员
Spring框架的核心特性是什么?
【4月更文挑战第30天】Spring 的特性
17 0
|
4月前
|
前端开发 Java 开发者
Spring 6 的新特性:HTTP Interface
【1月更文挑战第11天】Spring 6 带来了一个新的特性——HTTP Interface。这个新特性,可以让开发者将 HTTP 服务,定义成一个包含特定注解标记的方法的 Java 接口,然后通过对接口方法的调用,完成 HTTP 请求。
116 2
|
5月前
|
网络协议 Java Spring
java spring boot2.5的新特性
java spring boot2.5的新特性
52 0
|
2月前
|
消息中间件 运维 监控
|
5月前
|
XML Java 数据格式
深入了解 Spring Boot 核心特性、注解和 Bean 作用域
Spring Boot 是基于 Spring Framework 构建应用程序的框架,Spring Framework 是一个广泛使用的用于构建基于 Java 的企业应用程序的开源框架。Spring Boot 旨在使创建独立的、生产级别的 Spring 应用程序变得容易,您可以"只是运行"这些应用程序。
67 0
|
5月前
|
设计模式 Java 程序员
深入理解spring的两大特性 ioc 和aop
深入理解spring的两大特性 ioc 和aop
60 0