前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >深入分析Spring Type Conversion数据转换

深入分析Spring Type Conversion数据转换

作者头像
良辰美景TT
发布2018-09-11 14:27:50
5460
发布2018-09-11 14:27:50
举报

  Spring 3 推出了全新的数据类型之间的转换以替换PropertyEditor转换模式,但网上并没有很好的源码解读。Spring MVC是如何用这套转换体系将页面中的数据转成相应的业务对象的呢?首先让我们看看在不使用Spring 容器的情况下,如何使用Spring的类型转换功能。

简单使用

  • 首先我们定义一个类,Point,表示一个点,有横作标与纵作标两个属性。代码采用了lombok,他会在编译时帮我们生成setter/getter方法,这样代码显示的会更简洁。代码定义如下:
import lombok.Data;

@Data
public class Point {
int x, y;

}
  • 定义相应的转换器,用于将字符如 “1:2”这样的字符串转成Point对象,代码如下:
import org.springframework.core.convert.converter.Converter;

/**
* Point类型转换器,用于将如 “1:2”这样的字符串转成Point对象
* @author zhiminchen
*
*/
public class PointConverter implements Converter<String, Point> {
    @Override
    public Point convert(String source) {
      String[] splits = source.split(":");
      Point point = new Point();
      point.setX(Integer.parseInt(splits[0]));
      point.setY(Integer.parseInt(splits[1]));
      return point;
    }
}

上面代码我们自定义的转换器需要实现Converter接口,接口定义在org.springframework,core.convert包下,从这里也可以看出这个转换功能属于Spring的核心功能,并非只是用于Spring mvc。

  • 编写测试用例,看看自定义的转换器是否可用代码如下:
import org.junit.Test;
import org.springframework.core.convert.support.DefaultConversionService;

public class TestConverstion1 {

    @Test
    public void testConveter1() {
      DefaultConversionService service = new DefaultConversionService();
      service.addConverter(new PointConverter());
      Point point = service.convert("5:8", Point.class);
      Assert.assertEquals(5,point.getX());
      Assert.assertEquals(8,point.getY());
    }

}

执行效果如下图所示:

从执行效果看,程序已经达到了我们期望的效果, 也就是将输入为"5:8"的字符串转成了Point对像的X与Y属性。源码上我们也看出了只用到了两个Spring定义的类,一个为Converter另一个为DefaultConversionService类,下面我们就从这两个类入手看看Sprng是如何做的类型转换。

Converter接口定义

首先看看Converter类定义:

从类的定义上看还是相当简当的,就只定义了一个方法。下面我们看看Spring里默认的实现都有那些.

Spring还真定义了不少内部使用的Converter。同时Spring还定义了ConditionalConverter如下接口用于表示有条件的类型转换,通过转入的sourceType与targetType判断转换能否匹配,只有可匹配的转换才会调用convert方法进行转换。源码如下:

package org.springframework.core.convert.converter;
import org.springframework.core.convert.TypeDescriptor;
public interface ConditionalConverter {
  boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

Spring还定义了GenericConverter接口,来定义在多种类型进行转换,从接口的定义可以看出实现类会有个Set来存储ConvertiablePair,而ConvertiablePair包含了sourceType与targetType接口源码如下:

package org.springframework.core.convert.converter;

import java.util.Set;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.util.Assert;
public interface GenericConverter {
  Set<ConvertiblePair> getConvertibleTypes();
  Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

还有一个接口集GenericConverter接口与ConditionalConverter接口为一体,那就是ConditionalGenericConverter它的作用也就再清楚不过了就是有条件的在多种类型之间进行转换。

DefaultConversionService

  要了解DefaultConversionService的功能,我们先从DefaultConversionService的继承关系上入手,下图是DefaultConversionService类的继承关系图:

按从底向上的原则,我们看看ConvertRegistry里的类宝义。

package org.springframework.core.convert.converter;

/**
* For registering converters with a type conversion system.
*
* @author Keith Donald
* @author Juergen Hoeller
* @since 3.0
*/
public interface ConverterRegistry {
  void addConverter(Converter<?, ?> converter);
  void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
  void addConverter(GenericConverter converter);
  void addConverterFactory(ConverterFactory<?, ?> converterFactory);
  void removeConvertible(Class<?> sourceType, Class<?> targetType);

}

从接口定义中可以看出只定义了五个方法,四个add方法,与一个remove方法。从名字中我们也可以看出这是个注册类,实现类城需要提供了一个集中管理Converter的容器,通常是Map。上面定义的方法里有个特殊的方法,addConvertFactory,这个用来境加一个converterFactory, 而ConverterFactory用于返回一个Converter类型的实例。下面看看ConversionService接口的定义:

package org.springframework.core.convert;

/**
* A service interface for type conversion. This is the entry point into the convert system.
* Call {@link #convert(Object, Class)} to perform a thread-safe type conversion using this system.
*
* @author Keith Donald
* @author Phillip Webb
* @since 3.0
*/
public interface ConversionService {
  boolean canConvert(Class<?> sourceType, Class<?> targetType);

  boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

  <T> T convert(Object source, Class<T> targetType);

  Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

接口主要定义了两组方法,分别为canConvert与convert方法,从方法名就可以看出方法的作用,canConvert用于判断传入的sourceType能否转成targetType,而convert方法用于将source转成转入的TargetType类型实例。   ConfigurableConversionService接口的定义就更简单了,他同时继承了ConversionService与ConverterRegistry接口,代码定义如下:

package org.springframework.core.convert.support;

import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.converter.ConverterRegistry;

public interface ConfigurableConversionService extends ConversionService, ConverterRegistry {

}

下面来看看具体的实现类GenericConversionService,GenericConversionService实现了ConfigurableConversionService 接口,从上面的分析我们已经知道,GenericConversionService一定会有ConverterRegistry与ConversionService双从功能。由于源码量太多, 我还是用截图的方式进行描述吧,部分源码如下图所示:

DefaultConversionService的代码看起来也就更简单了,也就是把一些默认的转换器放到注册器里啦,部分源码截图如下:

现在我们还看看GenericConversionService内部是如何做转换的吧,从我们上面的分析已经很好猜到他是如何实现的了,从注册器里根据type拿到对应的转换器,然后调用Converter的convert方法就好了,很简单对吧,直接上源码吧。

getConverter方法如下所示:

Spring配置文件中使用Conversion

  我们需要增加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"
xsi:schemaLocation="http://www.springframework.org/schema/beans 
http://www.springframework.org/schema/beans/spring-beans.xsd">

<!-- 这里必须制定ID为conversionService -->
<bean class="org.springframework.context.support.ConversionServiceFactoryBean" 
id="conversionService">
    <property name="converters">
        <bean class="com.test.spring.validation.converter.PointConverter"/>
    </property>
</bean>
<!-- 这里定义的Circle里包括Point对象 -->
<bean class="com.test.spring.validation.domain.Circle" id="circle">
    <property name="point" value="1:2"/>
</bean> 

</beans>

加载上面的文件,然后通过beanFactory的getBean方法就能拿到Circle实例对像,现在我们来分析下Spring内部是如何处理将String"1:2"的Point对象的。为什么ConversionServiceFactoryBean的id必须是conversionService,如果是其它的值将无法完成类型的转换。ConversionServiceFactoryBean源码如下:

package org.springframework.context.support;

import java.util.Set;

import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.support.ConversionServiceFactory;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.core.convert.support.GenericConversionService;

/**
* 在大多数环境下,该工厂类用为了方便配置ConversionService的converters属性
*/
public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {

private Set<?> converters;

private GenericConversionService conversionService;

public void setConverters(Set<?> converters) {
this.converters = converters;
}

public void afterPropertiesSet() {
this.conversionService = createConversionService();
ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
}

protected GenericConversionService createConversionService() {
return new DefaultConversionService();
}

public ConversionService getObject() {
return this.conversionService;
}

public Class<? extends ConversionService> getObjectType() {
return GenericConversionService.class;
}

public boolean isSingleton() {
return true;
}

}

  从类的定义可以看出它需要实现两个接口,一个为FactoryBean接口,另一个为InitializingBean接口,FactoryBean接口表示这个类用于生厂一个Bean,也就是某个对象实例,需要实现最重要的getObject方法,返回的是生厂对象的实例。通过Spring容器的getBean方法将返回getObject方法的返回值,如果想要返回当前的factory实例的话,需要在id前面加上个“&”字符。下面我们来看看InitializingBean的接口定义:

了解了上面两个类的定义,再来看ConversionServiceFactoryBean也就一目了然了,其内部也就是一个DefaultConversionService,并把在配置文件里定义的convert放到DefaultConversionService这个对象里,还记得DefaultConversionService本身就是个ConverterRegistry吧。   我们从Spring容器的入口一步步来剖析Spring是如何实使化对象对进行类型转换的,所有的BeanFactory在初使化的时候都会调用AbstractApplicationContext的refresh方法,现把源码部分截图如下:

来看看finishBeanFactoryInitalization方法内部都做了些什么吧,源码如下图:

从源码中我们很容易看出来为什么需要在spring的xml配置文件里将org.springframework.context.support.ConversionServiceFactoryBean的id固定写成conversionService。我们也可以从源码推出,我们如果要自定义一个converstionService的话,只要实现ConverstionService接口,同时在配置 文件里将id写成conversionService就好了。getBean方法会最终调用到AbstractBeanFactory的doGetBean方法。在doGetBean方法内,会通过已经加载的BeanDefinition来创建相应的bean实例,具体的逻辑还是很复杂的,可以自行通过百度查看相关的文章。我们还是直接看看系统会在什么地方调用InitializingBean的afterPropertiesSet。具体的调用栈代码如下图:

现总结接口上面接口与类含义如下:

  • Converter:类型转换器,用于转换S类型到T类型,此接口的实现必须是线程安全的且可以被共享。
  • ConditionalConverter 接口定义了有条件的类型之间进行转换。
  • GenericConverter 接口定义了能在多种类型之间进行转换。
  • ConditionalGenericConverter 集成了ConditionalConverter与GenericConverter。
  • ConverterRegistry:类型转换器注册支持,可以注册/删除相应的类型转换器。
  • ConversionService:运行时类型转换服务接口,提供运行期类型转换的支持。这个接口的实现类可以使用类型转换器进行类型转换。
  • ConfigurableConversionService 集成了ConverterRegistry与ConversionService的功能。
  • ConversionServiceFactoryBean 用于方便配置ConversionService的converters属性

Spring容器里的对象如何应用Conversion

  系统把conversionService注册到Spring容器后,又是如何在取得相应实例的时候利用convert来进行类型转换的呢,下面我们Spring具体是如何做到的。在Spring内部有个BeanWrapper它主要提供了分析和操作JavaBean的功能,单独或者批量设置属性或者获取属性的功能,属性的读写方法等。我们在使用BeanFactory容器方法中的getBean方法时,内部都会先构造一个BeanWrapperImpl对象,通过它来做一此属性的类型转换。我们先来看看BeanWrapperImpl的继承结构,如下图所示:

从上图我们知道BeanWrapperImpl继承了PropertyEditorRegistrySupport,则PropertyEditorRegistrySupport这个类里包含了我们已经熟悉的ConversionService。源码如下图所示:

实际上BeanWrapperImpl内部做类型转换的是TypeConverterSupport,他实现了doConvert方法,同时TypeConverterSupport内部拥有TypeConverterDelegate对象,最终的转换逻辑最终就放到了这个TypeConverterDelegate对象上啦,我们来看看源代码:

我们来看看TypeConverterDelegate类里定义的方法,到低是如何处理类型转换逻辑的源代码如下:

下面来看看BeanWrapperImpl是如何构造出来并如何将convertService对象传给它,代码如下图所示:

通过上面代码结构的分析,我们知道了Spring是如何进行类型转换的,现在把流程总结如下:

  • Spring通过读取配置文件,发现系统有配置id为conversionService对像的实例是,会将它当成conversionService注入到Spring容器中,而conversionService内部实现包括了对converter的管理。
  • 外部应用通过Spring容器的getBean方法或都由隐式触发的getBean方法时会先创建一个BeanWrapperImpl对象。而BeanWrapperImpl对像在初使化的时候会将容器的conversionService放入到BeanWrapperImpl对象中,这样在实例化具体对象的时候就可以采用我们自定义的convert进行类型转换了。具体在使用getBean时的程序调用栈结构如下。

总结如下: 1:Spring conversionService 是独立的类型转换模块,不但可以应用于Spring mvc模块中浏览器到业务对象的转换,也可以应用于Spring 容器管理的bean对像的类型转换。 2:Spring converstionService 体系支持任意类型之间的转换。而PropertyEditor只支持Stirng到Object类型的转换。 3:PropertyEditor的处理过程为:调用setAsText把待处理的text传递到PropertyEditor,PropertyEditor把text转换成相应类型的值并且保存到PropertyEditor。调用getValue把获取上面步骤转换后的value通过上面的过程可以发现PropertyEditor保存了value,所以是非线程安全的,因此在值转换的过程中需要做同步。而converter体系的类型转换因为没有保存相应的值,而以直接在接口中返回相应的值,所以是线程安全的。 4:Spring 同时支特PropertyEditor与Converter,而Converter体系是从3.0之后才有的。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.07.24 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 简单使用
  • Converter接口定义
  • DefaultConversionService
  • Spring配置文件中使用Conversion
    • Spring容器里的对象如何应用Conversion
    相关产品与服务
    容器服务
    腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
    领券
    问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档