Spring 3 推出了全新的数据类型之间的转换以替换PropertyEditor转换模式,但网上并没有很好的源码解读。Spring MVC是如何用这套转换体系将页面中的数据转成相应的业务对象的呢?首先让我们看看在不使用Spring 容器的情况下,如何使用Spring的类型转换功能。
import lombok.Data;
@Data
public class Point {
int x, y;
}
import org.springframework.core.convert.converter.Converter;
/**
* Point类型转换器,用于将如 “1:2”这样的字符串转成Point对象
* @author zhiminchen
*
*/
public class PointConverter implements Converter {
@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是如何做的类型转换。
从类的定义上看还是相当简当的,就只定义了一个方法。下面我们看看Spring里默认的实现都有那些.
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 getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
还有一个接口集GenericConverter接口与ConditionalConverter接口为一体,那就是ConditionalGenericConverter它的作用也就再清楚不过了就是有条件的在多种类型之间进行转换。
要了解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 convert(Object source, Class 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的代码看起来也就更简单了,也就是把一些默认的转换器放到注册器里啦,部分源码截图如下:
我们需要增加spring配置文件,内容如下所示:
加载上面的文件,然后通过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, 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 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吧。系统把conversionService注册到Spring容器后,又是如何在取得相应实例的时候利用convert来进行类型转换的呢,下面我们Spring具体是如何做到的。在Spring内部有个BeanWrapper它主要提供了分析和操作JavaBean的功能,单独或者批量设置属性或者获取属性的功能,属性的读写方法等。我们在使用BeanFactory容器方法中的getBean方法时,内部都会先构造一个BeanWrapperImpl对象,通过它来做一此属性的类型转换。我们先来看看BeanWrapperImpl的继承结构,如下图所示: