Spring 学习记录3 ConversionService
from:https://www.cnblogs.com/abcwt112/p/7447435.html
ConversionService与Environment的关系
通过之前的学习(Spring 学习记录2 Environment),我已经Environment主要是负责解析properties和profile...但是它虽然实现了相关的接口,但是具体工作并不是由它本身处理,而是委托了其他的类来帮忙..properties相关的接口方法最终主要是通过PropertySourcesPropertyResolver这个类来处理的..(它们实现了相同的接口)
在通过Environment使用properties相关的方法中,有一些方法是带泛型参数的,比如
1 org.springframework.core.env.PropertyResolver 2 3 /** 4 * Return the property value associated with the given key, or {@code null} 5 * if the key cannot be resolved. 6 * @param key the property name to resolve 7 * @param targetType the expected type of the property value 8 * @see #getRequiredProperty(String, Class) 9 */ 10 <T> T getProperty(String key, Class<T> targetType);
得到properties以后肯定要通过一些类型转换,才能从String类型得到T类型.那么这个类型转换.其实用的就是ConversionService以及其相关的一套类.
properties文件中的所有值都是String类型的,而java内存里都是对象.所以需要一些工具将String(或者其他类型)转化成我们需要的java类型..(ConversionService是一套通用的转化方案,并不只是在这里用到,任何需要类型转化的地方都可以用)
ConversionService
实验1
properties文件 list=a,b,c,1,2,3
1 /** 2 * 测试ConversionService 3 */ 4 @RunWith(SpringJUnit4ClassRunner.class) 5 @ContextConfiguration("classpath:test-application-context.xml") 6 public class PropertySourcesPropertyResolverTest implements EnvironmentAware { 7 8 StandardEnvironment standardEnvironment; 9 10 @Test 11 public void testPropertySourcesPropertyResolver() { 12 List<String> list = standardEnvironment.getProperty("list", List.class); 13 System.out.println(list); // [a, b, c, 1, 2, 3] 14 } 15 16 @Override 17 public void setEnvironment(Environment environment) { 18 standardEnvironment = (StandardEnvironment) environment; 19 } 20 }
通过Environment的相关properties方法获取属性值并转化成List对象.
追踪断点发现:
PropertySourcesPropertyResolver内部得到属性值a,b,c,1,2,3以后通过conversionService去convert成List类型.
所以让我们来研究下ConversionService吧
ConversionService的结构
public abstract class AbstractPropertyResolver implements ConfigurablePropertyResolver { protected final Log logger = LogFactory.getLog(getClass()); protected ConfigurableConversionService conversionService = new DefaultConversionService(); ............ ............. }
conversionService是定义在AbstractPropertyResolver中的.也就是PropertySourcesPropertyResolver的父抽象类中.
查看ConversionService接口里的方法得知,这个类主要就是判断是否能够类型转化,可以的话就转化.
查看ConverterRegistry接口里的方法得知,这个类主要就是增加Converter用的.
那么既实现了ConversionService又实现了ConverterRegistry的DefaultConversionService用处就是
1.允许添加类型转化器Converter.
2.允许调用相关方法进行类型转化.
查看DefaultConversionService的代码得知,它的构造方法里添加了一堆Converter,这些converter是Spring已经帮助我们实现的.通过这些Converter我们可以进行很多通用类型的转化.比如之前的string->list的类型转化.
Converter接口
Converter接口很简单,就是把S类型转化成T类型.
实验2
利用ConversionService进行类型转化
@Test public void testConversionService1() { String s = conversionService.convert(false, String.class); System.out.println(s); // false Boolean b = conversionService.convert("true", Boolean.class); System.out.println(b); // true } @Before public void setup() { conversionService = standardEnvironment.getConversionService(); }
boolean -> string 用到的是ObjectToStringConverter
string -> boolean 用到的是StringToBooleanConverter
这些都是内置的.同时我们也可以发现1个converter也可以进行N种转化.因为ObjectToStringConverter不止可以转化String.任何类型转化成String都可以用这个Converter..内部是直接调用toString()方法...
ConverterFactory和GenericConverter
Converter接口在绝大多数情况下可能都是专门进行S->T类型的转化.也就是1对1的.Spring还提供了一些其他接口来帮我们进行类型转化.比如ConverterFactory和GenericConverter
看源代码可以发现ConverterFactory更像是1对N的转化.
可以从S->各种R的各种子类型T..因为平时处理业务上面的各种转化基本上都是很特殊的1:1的专门的converter去转化.所以可能ConverterFactory和GenericConverter不太用得到.因此主要看看Spring是怎么用这些Converter的吧.
实验3
1 /** 2 * 测试ConverterFactory StringToNumberConverterFactory 3 */ 4 @Test 5 public void testConversionService2() { 6 double d = conversionService.convert("1.2", double.class); 7 System.out.println(d); //1.2 8 9 int i = conversionService.convert("2", int.class); 10 System.out.println(i); //2 11 12 Byte b = conversionService.convert("0x10", Byte.class); 13 System.out.println(Integer.toBinaryString(b)); //10000 14 }
这里用到了StringToNumberConverterFactory把String转化成了Number的各个子类型.
1 @Override 2 public T convert(String source) { 3 if (source.length() == 0) { 4 return null; 5 } 6 return NumberUtils.parseNumber(source, this.targetType); 7 }
StringToNumberConverterFactory通过NumberUtils的static方法进行转化
1 public static <T extends Number> T parseNumber(String text, Class<T> targetClass) { 2 Assert.notNull(text, "Text must not be null"); 3 Assert.notNull(targetClass, "Target class must not be null"); 4 String trimmed = StringUtils.trimAllWhitespace(text); 5 6 if (targetClass.equals(Byte.class)) { 7 return (T) (isHexNumber(trimmed) ? Byte.decode(trimmed) : Byte.valueOf(trimmed)); 8 } 9 else if (targetClass.equals(Short.class)) { 10 return (T) (isHexNumber(trimmed) ? Short.decode(trimmed) : Short.valueOf(trimmed)); 11 } 12 else if (targetClass.equals(Integer.class)) { 13 return (T) (isHexNumber(trimmed) ? Integer.decode(trimmed) : Integer.valueOf(trimmed)); 14 } 15 else if (targetClass.equals(Long.class)) { 16 return (T) (isHexNumber(trimmed) ? Long.decode(trimmed) : Long.valueOf(trimmed)); 17 } 18 else if (targetClass.equals(BigInteger.class)) { 19 return (T) (isHexNumber(trimmed) ? decodeBigInteger(trimmed) : new BigInteger(trimmed)); 20 } 21 else if (targetClass.equals(Float.class)) { 22 return (T) Float.valueOf(trimmed); 23 } 24 else if (targetClass.equals(Double.class)) { 25 return (T) Double.valueOf(trimmed); 26 } 27 else if (targetClass.equals(BigDecimal.class) || targetClass.equals(Number.class)) { 28 return (T) new BigDecimal(trimmed); 29 } 30 else { 31 throw new IllegalArgumentException( 32 "Cannot convert String [" + text + "] to target class [" + targetClass.getName() + "]"); 33 } 34 }
parseNumber方法里面各种ifelse判断需要的是哪种类型的Number然后再转化.
同理,GenericConverter应该是N:N的转化
1个GenericConverter支持转化的所有类型都写在了属性Set<ConvertiblePair>内.
实验四
/** * 测试GenericConverter CollectionToCollectionConverter */ @Test public void testConversionService3() { List<Integer> list1 = Arrays.asList(1, 2, 3, 4, 5); Set<String> set1 = conversionService.convert(list1, Set.class); // Set<Integer> System.out.println(set1); // [1, 2, 3, 4, 5] System.out.println(set1.toArray()[0].getClass()); // class java.lang.Integer }
这里用到了CollectionToCollectionConverter
conveter方法中如果source和target的collection是同一种类型的话是不需要转化的,直接返回source就OK了.
然后73行是我觉得很奇怪的一个地方
TypeDescriptor elementDesc = targetType.getElementTypeDescriptor();
因为泛型不同于数组,数组是协变的,泛型是编译期的功能,所以这行代码肯定返回的是null....不知道这里为什么还需要去判断是否是null....ArrayToCollection和其他一些converter都有自己的实现,似乎没走这个converter所以我这里也不是很懂什么时候elementDesc会不是null..看这个样子只有target是数组类才有可能,但是这样的话为什么会出现在CollectionToCollectionConverter中呢?很奇怪....
因为elementDesc是null,所以会进target.addAll(sourceCollection)这行,所以就是简单的把source的所有元素丢到target中了.因为没有对元素进行转化.所以Set之中仍然是Integer类型还不是String.
不过也可以理解.集合中的类型都不知道怎么能把每个元素转化成相应的其他类型呢...这是做不到的...这大概也是泛型的缺陷吧....
后面的操作
3种不同的converter在GenericConversionService类中都有对应的addConverter方法可以添加converter.通过ConverterAdapter或者ConverterFactoryAdapter最后都会转化成GenericConverter我想应该是因为这种converter是最通用的原因吧.
这些适配的GenericConverter会被添加到GenericConversionService的静态内部类Converters中,而不是List或者Map中去.可能是因为查找对应Converter方法的时候比较麻烦.
Converters中有属性converters
1 Map<ConvertiblePair, ConvertersForPair> converters = 2 new LinkedHashMap<ConvertiblePair, ConvertersForPair>(36);
ConvertiblePair是source的class与target的Class的封装
ConvertersForPair内部含有
1 LinkedList<GenericConverter> converters = new LinkedList<GenericConverter>();
所以是各种genericConvrter的封装.
因为GenericConverter可以转化N种source->target的配对.所以可以对应N个ConvertiblePair,也就是说N个ConvertiblePair对应的ConvertersForPair中的GenericConverter可以是同一个.(虽然我Spring中好像没有看到这样的..基本都是对应1个ConvertiblePair)
同样,多个GenericConverter也可以转化同一个source->target的配对,所以1个ConvertiblePair对应的ConvertersForPair中可以有多个GenericConverter.(虽然Spring中也很少出现我只发现了1个)
这样情况下如果要convert source->target是会使用前面的那个converter的...每次添加converter的时候都是向linkledlist调用addFirst方法..所以后面加的应该会放到最前面.
小结
1.Spring使用ConversionService来convert各种类型.默认提供的是DefaultConversionService.同时它实现了ConverterRegistry接口,所以也可以添加你自定义的converter.
2.Spring提供了3种converter接口,分别是Converter,ConverterFactory和GenericConverter.一般用于1:1, 1:N, N:N的source->target类型转化.
3.在DefaultConversionService内部3种converter都会转化成GenericConverter放到静态内部类Converters中.
4.接口ConvertiblePair是source的class与target的Class的封装.静态内部类ConvertersForPair是多个converter对应的LinkedList的封装..静态内部类Converters中含有1个Map<ConvertiblePair, ConvertersForPair>用来储存所有converter.
1个GenericConverter可以对应N个ConvertiblePair,1个ConvertiblePair对应的ConvertersForPair中也可以有N个GenericConverter.