spring类型转换器(一)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了spring类型转换器(一)相关的知识,希望对你有一定的参考价值。

参考技术A

在spring容器初始化的时候,BeanDefinition中配置的bean的属性值一般都为String类型,如何将String类型转换为Bean中属性对应的类型呢,在这个过程中就需要用到类型转换器了。spring实例化bean过程中对属性值的转换主要是使用BeanWrapperImpl实现的。

首先来看下BeanWrapperImpl的使用

定义一个处理日期的转换器

创建一个BeanWrapperImpl用于包装目标bean(这里来模拟spring的内部实现)。然后注册Date类型的转换器,将值使用DatePropertyEditor转换为Date类型。调用setPropertyValue的时候,给testBean的birthday字段设置一个字符串类型的时间,在实际赋值的过程中会调用到类型转换器将字符串转换为日期类型。

同样可以实现一个String trim的转换器

BeanWrapper支持嵌套属性的赋值,当存在嵌套属性的时候需要设置 setAutoGrowNestedPaths=true。

接下来我们就来研究一下BeanWrapperImpl的实现过程

java.beans包中声明了一个PropertyEditor接口,用于提供对属性的访问和赋值。

PropertyEditor有个默认实现类PropertyEditorSupport,如果要自定义属性转换器,直接继承该类,然后覆写setAsText、getAsText、setValue、getValue方法。在类型转换的时候如果值是字符串会调用setAsText来赋值value,其他情况下会调用setValue来赋值value,然后再调用getValue获取改变后的value值完成类型转换。相当于将PropertyEditor当做一个加工作坊,传进去一个值,返回想要类型的值

注意 :PropertyEditor是线程不安全的,有状态的,因此每次使用时都需要创建一个,不可重用;

首先先看下BeanWrapperImpl的继承图。还有一个跟BeanWrapperImpl平级的实现类DirectFieldAccessor,用于处理getter和setter的字段访问

从图可以看出,BeanWrapperImpl实现了3个顶级接口,提供了对象属性访问、类型转换器的注册和查找、类型转换功能

从继承图可以看到PropertyEditorRegistrySupport实现了PropertyEditorRegistry接口,该类默认实现了类型转换器的注册和查找功能

在PropertyEditorRegistrySupport声明了多个存储结构用于存储不同的类型转换器来源。这里需要注意的是PropertyEditor是存在在Map中的,目标类型作为key,所以对于一个类型只能注册一个PropertyEditor,后面注册的会覆盖前面注册的

同时还看到一个conversionService变量,spring提供了另一种类型转换接口Converter,通过conversionService调用对应的Converter进行类型转换,在PropertyEditorRegistrySupport同样可以注册进来conversionService,用于使用Converter进行类型转换。conversionService的详细使用会在下篇文章中讲到。

接下来来看PropertyEditor的注册过程。

在上面提到过PropertyEditor存在在一个Map中,key是目标类型,那么这个参数propertyPath是干嘛的呢?这是为了给属性名指定专属的类型的转换器。因为一个目标类型只能有一个PropertyEditor的限制。但是有时候确实某个属性的类型转换比较特殊,这个时候就可以给这个属性名单独注册一个类型转换器,不会覆盖其他的哦。在类型转换的时候,会先根据属性名去customEditorsForPath中找可以用的PropertyEditor。

来看PropertyEditor的查找流程

首先根据属性名从customEditorsForPath查找特定的类型转换器

根据类型查找定义的类型转换器,如果没有对应的,则查找父类对应的类型转换器

PropertyEditorRegistrySupport 中默认添加了一些转换器,当调用 getDefaultEditor(requiredType)的时候会进行注册

TypeConverter接口定义了将一个值转换为目标类型的值的功能。在继承图中可以看出TypeConverterSupport对类型转换提供了默认实现。

TypeConverterSupport将类型转换功能委托给typeConverterDelegate实现

TypeConverterDelegate实现了类型转换的功能,创建TypeConverterDelegate的时候需要一个propertyEditorRegistry对象,用于查找匹配的类型转换器

首先通过propertyEditorRegistry查找自定义的类型转换器PropertyEditor和ConversionService

当该类型没有注册自定义的PropertyEditor,并且存在conversionService的时候,使用conversionService进行类型转换。如果conversionService中没有配置对应的converter,那么继续尝试使用默认注册的PropertyEditor。

如果目标类型存在自定义PropertyEditor 或者 目标类型和值类型不一样则需要进行类型转换(当没有找到ProperEditor的时候会尝试查找默认注册的PropertyEditor)

类型转换,主要分三种情况处理,值类型不是字符串,值类型是字符串数组,值类型是字符串

最后,需要对转换后的值和目标类型进行判断,是否符合要求,如果不符合继续处理。这里主要处理了集合类型还有Number类型、基本类型转String、枚举类型。

在attemptToConvertStringToEnum方法中自动根据枚举的名称转换为枚举的对象

PropertyAccessor接口定义了属性访问的功能。通过实现setPropertyValue 和 getPropertyValue方法实现对象属性的赋值和访问。

AbstractPropertyAccessor实现了PropertyAccessor接口,不过没有对setPropertyValue和getPropertyValue进行实现,而是单独提供了一个 setPropertyValues的方法, 用于批量设置属性值,同时可以通过 ignoreUnknown和ignoreInvalid参数忽略未知的属性

在AbstractNestablePropertyAccessor类中实现了setPropertyValue和getPropertyValue功能。

由于存在嵌套属性赋值的情况,对于嵌套的处理,其实只需要对嵌套的最底层进行类型转换,上层每一层就创建默认的值然后set到再上层对象的属性中。在这里spring使用了递归解决这个问题,创建每一层属性的对象值,使用BeanWrapper包装该对象,那么又是一个BeanWrapperImpl的赋值流程。

来看这个递归的内部实现,在getNestedPropertyAccessor完成对外层属性的初始化和将该值赋值到所依赖的对象中。然后使用BeanWrapper封装属性对象,后续走属性对象的赋值流程

创建属性对象,并将该对象set到宿主对象。因为对象是指针引用的,所以在这步已经完成对宿主对象的属性赋值,接下来的流程只要对属性对象中的依赖属性进行赋值。

使用递归解决了嵌套赋值的问题,那么接下来就是针对最底层BeanWrapperImpl的属性赋值流程

在processLocalProperty方法中,首先通过子类获取属性处理器,通过PropertyHandler对属性赋值。在赋值之前再次判断属性是否已经进行了类型转换,如果没有再次调用类型转换器进行转换,如果已经完成类型转换,使用ConvertedValue

对属性的访问和设置spring进行了更小粒度的封装。提供了 PropertyHandler抽象类。为什么在这里进行抽象,看PropertyHandler的两个实现,可以看到一个是BeanPropertyHandler,一个是FieldPropertyHandler,不难想象,属性一种是由getter和setter方法进行访问,一种是没有getter和setter直接反射字段进行的。

如果要对map进行控制,我们可以再提供一个专门处理map的实现了类handler

BeanWrapperImpl提供了BeanPropertyHandler,将setter和getter传入

BeanPropertyHandler提供对setter和getter的访问

AbstractNestablePropertyAccessor的另一个实现类DirectFieldAccessor,专门用于给字段赋值,不依赖setter和getter,那么这个是怎么实现的,看源码发现是DirectFieldAccessor中提供了一个PropertyHandler的实现类,通过Field的反射实现了setValue和getValue

Spring--Spring类型转换

Spring类型转换的实现

  • 基于JavaBeans接口的类型转换实现:基于java.beans.PropertyEditor接口扩展
  • Spring 3.0+通用类型转换实现

使用场景

基于JavaBeans接口的类型转换

  • 核心职责: 将String类型的内容转换为目标类型的对象
  • 扩展原理

1.Spring框架将文本内容传递到PropertyEditor实现的setAsText(String)方法。
2.PropertyEditor#setAsText(String)方法实现将String类型转化为目标类型的对象。
3.将目标类型的对象传入PropertyEditor#setValue(Object)方法实现需要临时存储传入对象
4.Spring框架将通过PropertyEditor#getValue()获取类型转换后的对象

Spring内建PropertyEditor扩展

自定义PropertyEditor扩展

  • 扩展模式

1.扩展java.beans.PropertyEditorSupport类

  • 实现org.springframework.beans.PropertyEditorRegistrar

1.实现registerCustomEditors(org.springframework.beans.PropertyEditorRegistry)方法
2.将PropertyEditorRegistrar实现注册为SpringBean

  • 向org.springframework.beans.PropertyEditorRegistry注册自定义PropertyEditor实现

1.通用类型实现registerCustomEditor(Class<?>, PropertyEditor)
2.Java Bean属性类型实现:registerCustomEditor(Class<?>, String, PropertyEditor)

Spring3通用类型转换接口

  • 类型转换接口
@FunctionalInterface
public interface Converter<S, T> 
	@Nullable
	T convert(S source);

  • 通用类型转换接口
org.springframework.core.conert.converter.GenericConverter
核心方法: convert(Object, TypeDescriptor. TypeDescriptor)
配对类型: org.springframework.core.convert.converter.GenericConverter.ConvertiblePair 
类型描述: org.springframework.core.convert.TypeDescriptor

GenericConverter接口

使用场景:用于"复合"类型转换场景,比如Collection, Map数组等
转换范围: Set getConvertibleTypes()
配对类型:org.springframework.core.convert.converter.GenericConverter.ConvertiblePair
转换方法:convert(Object, TypeDescriptor, TypeDescriptor)
类型描述: org.springframework.core.convert.TypeDescriptor

优化GenericConverter接口

增加sourceType和TargetType前置判断,类型条件判断: ConditionalGenericConverter

public interface ConditionalConverter 

	boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);

扩展Spring类型转换器

  • 实现转换器接口,任一即可

1.org.sringframework.core.convert.convert.converter
2.org.springframework.core.convert.converter.ConverterFactory
3.org.springframework.core.convert.converter.GenericConerter

  • 注册转换器实现

1.通过ConversionServiceFactoryBean SpringBean
2.通过org.springframework.core.convert.ConversionService API

统一类型转换服务

使用ConversionService作为依赖

类型转换底层接口-org.springframework.beans.TypeConverter,Spring直接使用此接口实现作为转换实现。

  • 核心方法:convertIfNecessary重载方法
  • 抽象实现:org.springframework.beans.TypeConverterSupport
  • 简单实现: org.springframework.beans.SimpleTypeConverter


以上是关于spring类型转换器(一)的主要内容,如果未能解决你的问题,请参考以下文章

struts2中的类型转换,自定义类型转换器需要继承啥类

Spring MVC自定义类型转换器Converter参数解析器HandlerMethodArgumentResolver

spring类型转换器(三)

Spring--Spring类型转换

Spring--Spring类型转换

Spring读源码系列番外篇---05----类型转换---中---三种全新的类型转换器