博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Spring COC TypeConverter
阅读量:5962 次
发布时间:2019-06-19

本文共 20024 字,大约阅读时间需要 66 分钟。

Spring的核心思想是IOC(Inversion of Control),DI其实就是IOC的另外一种说法。所谓IoC,对于spring框架来说,就是由spring来负责控制对象的生命周期和对象间的关系。当一个对象需要使用其它对象时,通过Spring容器动态的向这个对象提供它所需要的其他对象。这一点是通过DI(Dependency Injection,依赖注入)来实现的。

这里提到Spring IOC主要是为了说明Spring IOC中的(Convention over configuration) -- 约定优于配置的一个体现,那就是类型转换。Spring把它包装得太好了,可能大家都没有意识到。我下面简单的举一个例子:

1、User.java -- 实体类

public class User {        private String name;        private Integer age;    // getter and setter    }

2、beans.xml -- Spring配置文件

3、Main.java 测试类

public class Main {    public static void main(String[] args) {        ApplicationContext context = new ClassPathXmlApplicationContext("test-beans.xml");        User user = context.getBean(User.class);        System.out.println(user);    }    }

结果毫无疑问,控制台会把User类的name和age输出出来。但是大家有不有想过这样一个问题,在对象实体User中,我们的属性类型这里有String,也有Integer.当然这里举了2个简单的数据类型,Spring还支持更加复杂的数据类型。Spring是如何把我们配置在xml里面的属性转换成我们需要的类型呢?是不是之前没有想过这个问题,下面我们就来分析一下Spring内部是如何这个类型转换的。

1、缘由

其实我之前在看Spring 源码的时候,对于Spring IOC这块一直都看得不是很明白。直到之前看公司代码的时候让我看到了项目中使用了 FormattingConversionServiceFactoryBean这个对象。其实这个对象是一个Factory Bean,如果大家对于这个概念不太明白可以看我之前的blog -- 。通过对这个对象的源码分析让我明白了Spring的类型转换是如果实现的。

2、Type Conversion SPI

Spring从Spring 3开始新添加了一个包core.conver用来提供一般类型的转换系统。这个系统中定义了SPI在运行时期来实现类型转换逻辑。在Spring容器中,这个系统可以使用PropertyEditors把bean的属性值转换成需要的类型。同样的这个API同样会在你的应用中被使用到。下面我们来看一下Spring的类型转换API。

2.1 Converter SPI

这个SPI用于实现类型转换逻辑。

package org.springframework.core.convert.converter;public interface Converter
{ T convert(S source);}

2.2 Formatter SPI

Formatter SPI用于实现格式化逻辑。

package org.springframework.format;public interface Formatter
extends Printer
, Parser
{}

Formatter是继承自Printer,Parser接口

public interface Printer
{ String print(T fieldValue, Locale locale);}
import java.text.ParseException;public interface Parser
{ T parse(String clientValue, Locale locale) throws ParseException;}

不难看出虽然Format接口其实是Converter接口的一个子集,它只是类型转换的一种特例。

  • Format : Printer接口实现 T -> String,而Parser接口实现 String -> T.
  • Converter : 而Converter接口是实现 S -> T,从任意对象转换成任意对象。

这里只是简单的介绍了一下Spring关于的Spring Type Conversion与Spring Field Formatting接口方便后续的分析。如果大家想要了解更多详情可以查看的介绍。下面我们就来看看Spring类型转换的内部实现。

3、Type Converter Internal

我们还是首先来看看我们最开始提到的类,FormattingConversionServiceFactoryBean。最开始也说到这个类其实是一个FactoryBean。Spring IOC在进行容器初始的时候会通过它的getObject()获取到它想创建的对象。所以说我的目标就转换到了FormattingConversionService这个对象。其实Spring真正创建的对象是DefaultFormattingConversionService。下面我们就来看一下它的类继承体系。

这里写图片描述

3.1 相关接口与类

其实我们看类继承体系(e.g.:这里只画出了相关接口),主要还是看它实现的接口,这样就可以大概知道这个类干了哪些事。这个体系里面有4个接口。

  • ConversionService:类型转换服务,提供判断类型之间是否可以转换,以及转换方法。
  • ConverterRegistry :类型转换服务注册接口,提供类型转换服务的注册接口。
  • ConfigurableConversionService:这个是个空接口,只是同时继承了ConversionService与ConverterRegistry接口。
  • FormatterRegistry:Formatter服务接口注册接口。

其实这里最需要关注的还转换服务的注册以及转换服务的获取。在解释这2个方法之前,再来介绍2个类:

1、GenericConverter

格式转换包装类,包装Formatter以及Converter.内部类ConvertiblePair提供这两种的索引。

public interface GenericConverter {    Set
getConvertibleTypes(); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); final class ConvertiblePair { private final Class
sourceType; private final Class
targetType; public ConvertiblePair(Class
sourceType, Class
targetType) { Assert.notNull(sourceType, "Source type must not be null"); Assert.notNull(targetType, "Target type must not be null"); this.sourceType = sourceType; this.targetType = targetType; } public Class
getSourceType() { return this.sourceType; } public Class
getTargetType() { return this.targetType; } @Override public boolean equals(Object other) { if (this == other) { return true; } if (other == null || other.getClass() != ConvertiblePair.class) { return false; } ConvertiblePair otherPair = (ConvertiblePair) other; return (this.sourceType == otherPair.sourceType && this.targetType == otherPair.targetType); } @Override public int hashCode() { return (this.sourceType.hashCode() * 31 + this.targetType.hashCode()); } @Override public String toString() { return (this.sourceType.getName() + " -> " + this.targetType.getName()); } }}

2、ConditionalConverter

转换条件类,判断这个GenericConverter对象是否可以进行转换。

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

3.2 注册

其实类型转换的具体实现是在分为Formatter与Converter的注册。

  • Converter的注册发生在GenericConversionService类中。也就是里面各种不同的重载方法addConverter().
  • Formatter的注册发生在FormattingConversionService类中。也就是里面各种不同的addFormatterXXX()方法。

它会把这两个接口的实现都会转换成上面提到的GenericConverter接口实现,并且注册到GenericConversionService.Converters对象中,里面有2个属性。converters与globalConverters这两个属性中。

private static class Converters {        private final Set
globalConverters = new LinkedHashSet
(); private final Map
converters = new LinkedHashMap
(36); public void add(GenericConverter converter) { Set
convertibleTypes = converter.getConvertibleTypes(); if (convertibleTypes == null) { Assert.state(converter instanceof ConditionalConverter, "Only conditional converters may return null convertible types"); this.globalConverters.add(converter); } else { for (ConvertiblePair convertiblePair : convertibleTypes) { ConvertersForPair convertersForPair = getMatchableConverters(convertiblePair); convertersForPair.add(converter); } } } public GenericConverter find(TypeDescriptor sourceType, TypeDescriptor targetType) { // Search the full type hierarchy List
> sourceCandidates = getClassHierarchy(sourceType.getType()); List
> targetCandidates = getClassHierarchy(targetType.getType()); for (Class
sourceCandidate : sourceCandidates) { for (Class
targetCandidate : targetCandidates) { ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate); GenericConverter converter = getRegisteredConverter(sourceType, targetType, convertiblePair); if (converter != null) { return converter; } } } return null; } }

当你实现Formatter、Converter接口时,它会把转换接口以转换源对象sourceType(Class<?>)与转换目标对象targetType(Class<?>)生成ConvertiblePair对象插入到一个converters属性中。如果你实现GenericConverter接口分为两种情况:

1) 如果实现的getConvertibleTypes()返回你需要转换的源对象与目标对象构成的Set<ConvertiblePair>不为空。它就会把转换对象添加到converters属性中。

2) 如果实现的getConvertibleTypes()返回你需要转换的源对象与目标对象构成的Set<ConvertiblePair>为空。它会检查它的类型是不是ConditionalConverter。所以如果你要实现GenericConverter并且实现getConvertibleTypes()方法返回为空,那么你同时需要实现ConditionalConverter。Spring提供了实现了这2个接口的接口ConditionalGenericConverter,你只需要实现它就行了。而且它会把这个转换器添加到globalConverters属性中。

3.3 查询

在Spring中的自定义转换中,当首先会查询GenericConversionService.Converters中的converters属性,然后才会查询GenericConversionService.Converters中的globalConverters属性。所以说实现ConditionalGenericConverter的方法getConvertibleTypes()如果返回为空,那么它就是一个备胎。

4、Spring IOC Type Converter

Spring IOC在进行类型转换的时候最终会调用在TypeConverterDelegate类的convertIfNecessary方法。下面我们来看一这个方法的具体实现。

class TypeConverterDelegate {    public 
T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class
requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException { // Custom editor for this type? PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName); ConversionFailedException conversionAttemptEx = null; // No custom editor but custom ConversionService specified? ConversionService conversionService = this.propertyEditorRegistry.getConversionService(); if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) { TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { try { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } catch (ConversionFailedException ex) { // fallback to default conversion logic below conversionAttemptEx = ex; } } } Object convertedValue = newValue; // Value not of required type? if (editor != null || (requiredType != null && !ClassUtils.isAssignableValue(requiredType, convertedValue))) { if (typeDescriptor != null && requiredType != null && Collection.class.isAssignableFrom(requiredType) && convertedValue instanceof String) { TypeDescriptor elementTypeDesc = typeDescriptor.getElementTypeDescriptor(); if (elementTypeDesc != null) { Class
elementType = elementTypeDesc.getType(); if (Class.class == elementType || Enum.class.isAssignableFrom(elementType)) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } } } if (editor == null) { editor = findDefaultEditor(requiredType); } convertedValue = doConvertValue(oldValue, convertedValue, requiredType, editor); } boolean standardConversion = false; if (requiredType != null) { // Try to apply some standard type conversion rules if appropriate. if (convertedValue != null) { if (Object.class == requiredType) { return (T) convertedValue; } else if (requiredType.isArray()) { // Array required -> apply appropriate conversion of elements. if (convertedValue instanceof String && Enum.class.isAssignableFrom(requiredType.getComponentType())) { convertedValue = StringUtils.commaDelimitedListToStringArray((String) convertedValue); } return (T) convertToTypedArray(convertedValue, propertyName, requiredType.getComponentType()); } else if (convertedValue instanceof Collection) { // Convert elements to target type, if determined. convertedValue = convertToTypedCollection( (Collection
) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } else if (convertedValue instanceof Map) { // Convert keys and values to respective target type, if determined. convertedValue = convertToTypedMap( (Map
) convertedValue, propertyName, requiredType, typeDescriptor); standardConversion = true; } if (convertedValue.getClass().isArray() && Array.getLength(convertedValue) == 1) { convertedValue = Array.get(convertedValue, 0); standardConversion = true; } if (String.class == requiredType && ClassUtils.isPrimitiveOrWrapper(convertedValue.getClass())) { // We can stringify any primitive value... return (T) convertedValue.toString(); } else if (convertedValue instanceof String && !requiredType.isInstance(convertedValue)) { if (conversionAttemptEx == null && !requiredType.isInterface() && !requiredType.isEnum()) { try { Constructor
strCtor = requiredType.getConstructor(String.class); return BeanUtils.instantiateClass(strCtor, convertedValue); } catch (NoSuchMethodException ex) { // proceed with field lookup if (logger.isTraceEnabled()) { logger.trace("No String constructor found on type [" + requiredType.getName() + "]", ex); } } catch (Exception ex) { if (logger.isDebugEnabled()) { logger.debug("Construction via String failed for type [" + requiredType.getName() + "]", ex); } } } String trimmedValue = ((String) convertedValue).trim(); if (requiredType.isEnum() && "".equals(trimmedValue)) { // It's an empty enum identifier: reset the enum value to null. return null; } convertedValue = attemptToConvertStringToEnum(requiredType, trimmedValue, convertedValue); standardConversion = true; } else if (convertedValue instanceof Number && Number.class.isAssignableFrom(requiredType)) { convertedValue = NumberUtils.convertNumberToTargetClass( (Number) convertedValue, (Class
) requiredType); standardConversion = true; } } else { // convertedValue == null if (javaUtilOptionalEmpty != null && requiredType == javaUtilOptionalEmpty.getClass()) { convertedValue = javaUtilOptionalEmpty; } } if (!ClassUtils.isAssignableValue(requiredType, convertedValue)) { if (conversionAttemptEx != null) { // Original exception from former ConversionService call above... throw conversionAttemptEx; } else if (conversionService != null) { // ConversionService not tried before, probably custom editor found // but editor couldn't produce the required type... TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue); if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) { return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor); } } // Definitely doesn't match: throw IllegalArgumentException/IllegalStateException StringBuilder msg = new StringBuilder(); msg.append("Cannot convert value of type '").append(ClassUtils.getDescriptiveType(newValue)); msg.append("' to required type '").append(ClassUtils.getQualifiedName(requiredType)).append("'"); if (propertyName != null) { msg.append(" for property '").append(propertyName).append("'"); } if (editor != null) { msg.append(": PropertyEditor [").append(editor.getClass().getName()).append( "] returned inappropriate value of type '").append( ClassUtils.getDescriptiveType(convertedValue)).append("'"); throw new IllegalArgumentException(msg.toString()); } else { msg.append(": no matching editors or conversion strategy found"); throw new IllegalStateException(msg.toString()); } } } if (conversionAttemptEx != null) { if (editor == null && !standardConversion && requiredType != null && Object.class != requiredType) { throw conversionAttemptEx; } logger.debug("Original ConversionService attempt failed - ignored since " + "PropertyEditor based conversion eventually succeeded", conversionAttemptEx); } return (T) convertedValue; }}

这个Spring IOC类型转换分为以下4个步骤:

  1. 通过Java中的PropertyEditor的内省机制对Spring的对象属性进行类型转换
  2. 通过Spring中的ConversionService的自定义类型转换实现对象属性进行类型转换
  3. 通过一般类型判断对对象的属性进行类型转换(Array, Collection, Map, String, Number, Optional)
  4. 报错(不遵循COC -- 约定大于配置)。

5、应用

在Spring通过它的约定大于配置,它帮助我们实现了一些默认的类型转换。具体的默认的类型转换在DefaultFormattingConversionService接口。可以如果你的包依赖中没有joda-time,Spring就不会提供String转换Date的转换服务。下面我们就来自定义类型转换服务:

5.1 Order.java -- 实体类

public class Order {    private Date createDt;    public Date getCreateDt() {        return createDt;    }    public void setCreateDt(Date createDt) {        this.createDt = createDt;    }    @Override    public String toString() {        return "Order{" +                "createDt=" + createDt +                '}';    }}

5.2 StringToDateConverter -- 实现Formatter接口

public class StringToDateConverter implements Formatter
{ private String pattern; public StringToDateConverter(String pattern) { this.pattern = pattern; } @Override public Date parse(String text, Locale locale) throws ParseException { DateFormat dateFormat = new SimpleDateFormat(pattern, locale); return dateFormat.parse(text); } @Override public String print(Date date, Locale locale) { DateFormat dateFormat = new SimpleDateFormat(pattern, locale); return dateFormat.format(date); }}

5.3 ConverterController.java

@RestControllerpublic class ConverterController {    @InitBinder    public void init(DataBinder dataBinder){        dataBinder.addCustomFormatter(new StringToDateConverter("yyyy-MM-dd"));    }    @RequestMapping("converter")    public Order converter(Order order){        return order;    }}

5.4 SpringBootMybatisApplication.java

@SpringBootApplicationpublic class SpringBootMybatisApplication {    public static void main(String[] args) {        SpringApplication.run(SpringBootMybatisApplication.class, args);    }}

5.4 测试

通过访问http://localhost:8080/conveter?createDt=2017-08-12,根据以上的测试代码就会返回以下的结果。

这里写图片描述

在Spring MVC中因为前端HttpServletRequest的传值只会涉及到String,所以在Spring MVC在进行数据绑定的时候只开放的Formatter接口,而没有开放Converter接口。

但是我们可以使用FormattingConversionServiceFactoryBean来注册Converter接口。

public class FormattingConversionServiceFactoryBean        implements FactoryBean
, EmbeddedValueResolverAware, InitializingBean { private Set
converters; private Set
formatters; private Set
formatterRegistrars;}

它可以注册Converter与Formatter接口.Spring会在容器开始依赖注入之前检测容器中是否有名称有conversionService,就会把conversionService设计到BeanFactory当中,当类型转换的时候就会把这个对象设置进去。

protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {        // Initialize conversion service for this context.        if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&                beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {            beanFactory.setConversionService(                    beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));        }        // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.        String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);        for (String weaverAwareName : weaverAwareNames) {            getBean(weaverAwareName);        }        // Stop using the temporary ClassLoader for type matching.        beanFactory.setTempClassLoader(null);        // Allow for caching all bean definition metadata, not expecting further changes.        beanFactory.freezeConfiguration();        // Instantiate all remaining (non-lazy-init) singletons.        beanFactory.preInstantiateSingletons();    }

可以看到在代码最开始的时候就是判断容器是否有这个对象。如果有就设置到BeanFactory里面。代码的最后面才是Spring容器初始化单例bean的逻辑。

beanFactory.preInstantiateSingletons();

转载地址:http://uejax.baihongyu.com/

你可能感兴趣的文章
力龙信息布局大数据领域
查看>>
大数据巧治职业差评师 生存空间锐减九成
查看>>
天津开展免费无线局域网建设
查看>>
提高信息安全意识对网络勒索病毒说不
查看>>
英国政府可能利用曼彻斯特自杀袭击要求互联网公司破解加密
查看>>
Mozilla 将大幅简化火狐浏览器的同步操作
查看>>
微软加大在 Edge/IE 浏览器上阻止 SHA-1 证书的力度
查看>>
龙芯将两款 CPU 核开源,这意味着什么?
查看>>
《51单片机应用开发从入门到精通》——导读
查看>>
iOS深拷贝与浅拷贝
查看>>
mysql5.6参数说明
查看>>
[转]线程安全的单例模式
查看>>
winfrom的打开窗口特效
查看>>
IOS(swift)-数据存储 · NSKeyedArchiver 归档
查看>>
TypeScript 入门指南
查看>>
mysql报错 Data truncated for column 'from' at row 1
查看>>
Monkey test确定package name的一种特殊方法
查看>>
ubuntu 12.04.4 安装 devstack
查看>>
windows10:一些虚拟化功能与vmware冲突
查看>>
JXL导出Excel数据表
查看>>