Spring读源码系列番外篇04----类型转换--上
Posted 大忽悠爱忽悠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring读源码系列番外篇04----类型转换--上相关的知识,希望对你有一定的参考价值。
Spring读源码系列番外篇04----类型转换--上
系列文章:
Spring读源码系列番外篇—01–PropertyValue相关类
Spring读源码系列番外篇—02—PropertyResolver的结构体系剖析—上
Spring读源码系列番外篇—03—PropertyResolver的结构体系剖析—下
之所以有这些番外篇,是因为在阅读spring主线源码的过程中,遇到了很多不熟悉的知识点,那么怎么办,只能属性它,本系列也只是带领大家粗浅认识spring中常会用到的一些工具类吧
本篇文章写作来源于spring中大量用到的类型转换,无论是pouplateBean中属性注入时用到的类型转换,还是dataBinder中的类型转换,无不让我们头疼,如果总是不搞懂,遇到看不懂的源码直接跳过,那么这部分就一直是搞不懂的地方了。
并且,就算我们以后自己尝试去写一些小框架,类型转换也是一个绕不开的话题,我们可以借鉴spring的设计手法,毕竟大佬的成功案例就在眼前,何不借鉴一下呢?
Spring类型转换器升级历史
本图借鉴于1. 揭秘Spring类型转换 - 框架设计的基石
古老的PropertyEditor
PropertyEditor是jdk提供服务于gui的类型转换器,但是由于和spring初期xml类型转换需求相同,因此spring就没有重复造轮子,而是复用了此接口,该接口我们只需要关注两个暴露出来的接口方法即可:
public interface PropertyEditor
...
// String -> Object
void setAsText(String text) throws java.lang.IllegalArgumentException;
// Object -> String
String getAsText();
...
缺陷:
职责不单一,类型不安全,只能实现String类型的转换等。虽然自Spring 3.0起提供了现代化的类型转换接口,但是此部分机制一直得以保留,保证了向下兼容性。
先进的Converter、GenericConverter
为了解决PropertyEditor
作为类型转换方式的设计缺陷,Spring 3.0版本重新设计了一套类型转换接口,其中主要包括:
- Converter<S, T>:Source -> Target类型转换接口,适用于1:1转换
- StringToPropertiesConverter:将String类型转换为Properties
- StringToBooleanConverter:将String类型转换为Boolean
- EnumToIntegerConverter:将Enum类型转换为Integer
- ConverterFactory<S, R>:Source -> R类型转换接口,适用于1:N转换
- StringToEnumConverterFactory:将String类型转任意Enum
- StringToNumberConverterFactory:将String类型转为任意数字(可以是int、long、double等等)
- NumberToNumberConverterFactory:数字类型转为数字类型(如int到long,long到double等等)
- GenericConverter:更为通用的类型转换接口,适用于N:N转换
- ObjectToCollectionConverter:任意集合类型转为任意集合类型(如List< String >转为List< Integer > / Set< Integer >都使用此转换器)
- CollectionToArrayConverter:解释基本同上
- MapToMapConverter:解释基本同上
- ConditionalConverter:条件转换接口。可跟上面3个接口组合使用,提供前置条件判断验证
先进的转换服务接口:ConversionService
Converter、ConverterFactory、GenericConverter它们三都着力于完成类型转换。对于使用者而言,如果做个类型转换需要了解到这三套体系无疑成本太高,因此就有了ConversionService用于整合它们三,统一化接口操作。
此接口也是Spring 3.0新增,用于统一化 底层类型转换实现的差异,对外提供统一服务,所以它也被称作类型转换的门面接口,从接口名称xxxService也能看出来其设计思路。它主要有两大实现:
- GenericConversionService:提供模版实现,如转换器的注册、删除、匹配查找等,但并不内置转换器实现
- DefaultConversionService:继承自GenericConversionService。在它基础上默认注册了非常多的内建的转换器实现,从而能够实现绝大部分的类型转换需求
ConversionService转换服务它贯穿于Spring上下文ApplicationContext的多项功能,包括但不限于:BeanWrapper处理Bean属性、DataBinder数据绑定、PropertySource外部化属性处理等等。因此想要进一步深入了解的话,ConversionService是你绕不过去的坎。
WebConversionService并非Spirng Framework的API,而属于Spring Boot提供的增强,且起始于2.x版本
类型转换整合格式化器Formatter
Spring 3.0还新增了一个Formatter< T >接口,作用为:将Object格式化为类型T。从语义上理解它也具有类型转换(数据转换的作用),相较于Converter<S,T>它强调的是格式化,因此一般用于时间/日期、数字(小数、分数、科学计数法等等)、货币等场景,举例它的实现:
- DurationFormatter:字符串和Duration类型的互转
- CurrencyUnitFormatter:字符串和javax.money.CurrencyUnit货币类型互转
- DateFormatter:字符串和java.util.Date类型互转。这个就使用得太多了,它默认支持什么格式?支持哪些输出方式,这将在后文详细描述
为了和类型转换服务ConversionService完成整合,对外只提供统一的API。Spring提供了FormattingConversionService专门用于整合Converter和Formatter,从而使得两者具有一致的编程体验,对开发者更加友好。
类型转换底层接口TypeConvert
定义类型转换方法的接口,它在Spring 2.0就已经存在。在还没有ConversionService之前,它的类型转换动作均委托给已注册的PropertyEditor来完成。但自3.0之后,这个转换动作可能被PropertyEditor来做,也可能交给ConversionService处理。
它一共提供三个重载方法:
// @since 2.0
public interface TypeConverter
// value:待转换的source源数据
// requiredType:目标类型targetType
// methodParam:转换的目标方法参数,主要为了分析泛型类型,可能为null
// field:目标的反射字段,为了泛型,可能为null
<T> T convertIfNecessary(Object value, Class<T> requiredType) throws TypeMismatchException;
<T> T convertIfNecessary(Object value, Class<T> requiredType, MethodParameter methodParam) throws TypeMismatchException;
<T> T convertIfNecessary(Object value, Class<T> requiredType, Field field) throws TypeMismatchException;
它是Spring内部使用类型转换的入口,最终委托给PropertyEditor或者注册到ConversionService里的转换器去完成。它的主要实现有:
- TypeConverterSupport:@since 3.2。继承自PropertyEditorRegistrySupport,它主要是为子类BeanWrapperImpl提供功能支撑。作用有如下两方面:
- 提供对默认编辑器(支持JDK内置类型的转换如:Charset、Class、Class[]、Properties、Collection等等)和自定义编辑器的管理(PropertyEditorRegistry#registerCustomEditor)
- 提供get/set方法,把ConversionService管理上(可选依赖,可为null)
- 数据绑定相关:因为数据绑定强依赖于类型转换,因此数据绑定涉及到的属性访问操作将会依赖于此组件,不管是直接访问属性的DirectFieldAccessor还是功能更强大的BeanWrapperImpl均是如此
总的来说,TypeConverter能把类型的各种实现、API收口于此,Spring把类型转换的能力都转嫁到TypeConverter这个API里面去了。虽然方便了使用,但其内部实现原理稍显复杂。
Spring Boot使用增强
在传统Spring Framework场景下,若想使用ConversionService还得手动档去配置,这对于不太了解其运行机制的同学无疑是有使用门槛的。而在Spring Boot场景下这一切都会变得简单许多,可谓使用起来愈发方便了。
另外,Spring Boot在内建转换器的基础上额外扩展了不少实用转换器,形如:
- StringToFileConverter:String -> File
- NumberToDurationConverter:
- DelimitedStringToCollectionConverter:
- …
PropertyEditor—旧的类型转换
下面是官方对该类的一个描述:
- PropertyEditor 类为希望允许用户编辑给定类型的属性值的 GUI 提供支持。
- PropertyEditor 支持各种不同的显示和更新属性值的方式。大多数 PropertyEditor 只需要支持此 API 中可用的不同选项的子集。
- 简单的 PropertyEditor 可能只支持 getAsText 和 setAsText 方法,不需要支持(比如)paintValue 或 getCustomEditor。更复杂的类型可能无法支持 getAsText 和 setAsText,但会支持 paintValue 和 getCustomEditor。
因为该类出现的比较早,当前java gui还很火,因此该类型转换大多服务于gui,但是我们关注的终点在于文本类型的转换,此接口提供的方法挺多的,和本文类型转换有关的最多只有4个:
//该类不是spring提供了----看导入的包名就明白了,这个是jdk提供的类型转换接口
package java.beans;
public interface PropertyEditor
//设置属性值
void setValue(Object value);
//获取属性值
Object getValue();
//输出。把属性值转换成String输出
String getAsText();
//输入。将String转换为属性值类型输入
void setAsText(String text) throws java.lang.IllegalArgumentException;--
/**
为值更改添加侦听器。当属性编辑器更改其值时,
它应在所有已注册的 PropertyChangeListener 上触发 PropertyChangeEvent,将属性名称和自身指定为空值作为源。
*/
void addPropertyChangeListener(PropertyChangeListener listener);
void removePropertyChangeListener(PropertyChangeListener listener);
//其余方法不需要关心,因为都是关于GUI的
....
JDK对PropertyEditor接口提供了一个默认实现java.beans.PropertyEditorSupport,因此我们若需扩展此接口,仅需继承此类,根据需要复写getAsText/setAsText这两个方法即可,Spring无一例外都是这么做的。
PropertyEditorSupport
PropertyEditor作为一个JDK原生接口,内置了一些基本实现来服务于GUI程序,如:
- BooleanEditor:将true/false字符串转换为Boolean类型
- IntegerEditor:将字符串转换为Integer类型
- 同类别的还有LongEditor、FloatEditor…
JDK内置的实现比较少(如上),功能简陋,但对于服务GUI程序来说已经够用,毕竟界面输入的只可能是字符串,并且还均是基础类型。但这对于复杂的Spring环境、以及富文本的web环境来说就不够用了,所以Spring在此基础上有所扩展,因此才有了本文来讨论。
public class PropertyEditorSupport implements PropertyEditor
//需要被修改的值
private Object value;
//用于事件触发的源
private Object source;
//监听器集合
private java.util.Vector<PropertyChangeListener> listeners;
public PropertyEditorSupport()
setSource(this);
public PropertyEditorSupport(Object source)
if (source == null)
throw new NullPointerException();
setSource(source);
public Object getSource()
return source;
public void setSource(Object source)
this.source = source;
public void setValue(Object value)
this.value = value;
//发出事件
firePropertyChange();
public Object getValue()
return value;
/**
* Gets the property value as a string suitable for presentation
* to a human to edit.
*
* @return The property value as a string suitable for presentation
* to a human to edit.
* Returns null if the value can't be expressed as a string.
*If a non-null value is returned, then the PropertyEditor should
* be prepared to parse that string back in setAsText().
*/
public String getAsText()
return (this.value != null)
? this.value.toString()
: null;
/**
* Sets the property value by parsing a given String. May raise
* java.lang.IllegalArgumentException if either the String is
* badly formatted or if this kind of property can't be expressed
* as text.
*
* @param text The string to be parsed.
*/
public void setAsText(String text) throws java.lang.IllegalArgumentException
if (value instanceof String)
setValue(text);
return;
throw new java.lang.IllegalArgumentException(text);
public synchronized void addPropertyChangeListener(
PropertyChangeListener listener)
//监听器集合是懒加载的,如果不需要监听器,那么就不会创建集合占用内存---大家学习这种懒加载思想
if (listeners == null)
listeners = new java.util.Vector<>();
listeners.addElement(listener);
public synchronized void removePropertyChangeListener(
PropertyChangeListener listener)
if (listeners == null)
return;
listeners.removeElement(listener);
public void firePropertyChange()
java.util.Vector<PropertyChangeListener> targets;
synchronized (this)
if (listeners == null)
return;
//拷贝一份?----其实我不太理解
targets = unsafeClone(listeners);
// PropertyChangeEvent 专门的一个属性改变的事件类
PropertyChangeEvent evt = new PropertyChangeEvent(source, null, null, null);
//将发生的事件告知所有的监听器
for (int i = 0; i < targets.size(); i++)
PropertyChangeListener target = targets.elementAt(i);
target.propertyChange(evt);
@SuppressWarnings("unchecked")
private <T> java.util.Vector<T> unsafeClone(java.util.Vector<T> v)
return (java.util.Vector<T>)v.clone();
大家可以学习一下该类中的懒加载思想和监听器机制
Spring为何基于它扩展?
官方的javadoc都说得很清楚:PropertyEditor设计是为GUI程序服务的,那么Spring为何看上它了呢?
试想一下:那会的Spring只能支持xml方式配置,而XML属于文本类型配置,但是此属性对应的类型却不一定是字符串,可能是任意类型,这种需求和gui其实是一样的。
为了实现这种需求,在PropertyEditorSupport的基础上只需要复写setAsText和getAsText这两个方法就行,然后Spring就这么干了。
Spring内建扩展实现有哪些?
Spring为了扩展自身功能,提高配置灵活性,扩展出了非常非常多的PropertyEditor实现,共计40余个,部分截图如下:
这边就拿一个举个例子,来看看spring的具体实现:
public class CustomBooleanEditor extends PropertyEditorSupport
/**
* Value of @code "true".
*/
public static final String VALUE_TRUE = "true";
/**
* Value of @code "false".
*/
public static final String VALUE_FALSE = "false";
/**
* Value of @code "on".
*/
public static final String VALUE_ON = "on";
/**
* Value of @code "off".
*/
public static final String VALUE_OFF = "off";
/**
* Value of @code "yes".
*/
public static final String VALUE_YES = "yes";
/**
* Value of @code "no".
*/
public static final String VALUE_NO = "no";
/**
* Value of @code "1".
*/
public static final String VALUE_1 = "1";
/**
* Value of @code "0".
*/
public static final String VALUE_0 = "0";
@Nullable
private final String trueString;
@Nullable
private final String falseString;
private final boolean allowEmpty;
public CustomBooleanEditor(boolean allowEmpty)
this(null, null, allowEmpty);
public CustomBooleanEditor(@Nullable String trueString, @Nullable String falseString, boolean allowEmpty)
this.trueString = trueString;
this.falseString = falseString;
this.allowEmpty = allowEmpty;
//如果自己设置了trueString或者falseString,那么就拿指定的字符串进行比较,来决定真假
//否则用默认的来进行比较
@Override
public void setAsText(@Nullable String text) throws IllegalArgumentException
String input = (text != null ? text.trim() : null);
if (this.allowEmpty && !StringUtils.hasLength(input))
// Treat empty String as null value.
setValue(null);
else if (this.trueString != null && this.trueString.equalsIgnoreCase(input))
setValue(Boolean.TRUE);
else if (this.falseString != null && this.falseString.equalsIgnoreCase(input))
setValue(Boolean.FALSE);
else if (this.trueString == null &&
(VALUE_TRUE.equalsIgnoreCase(input) || VALUE_ON.equalsIgnoreCase(input) ||
VALUE_YES.equalsIgnoreCase(input) || VALUE_1.equals(input)))
setValue(Boolean.TRUE);
else if (this.falseString == null &&
(VALUE_FALSE.equalsIgnoreCase(input) || VALUE_OFF.equalsIgnoreCase(input) ||
VALUE_NO.equalsIgnoreCase(input) || VALUE_0.equals(input)))
setValue(Boolean.FALSE);
else
throw new IllegalArgumentException("Invalid boolean value [" + text + "]");
@Override
public String getAsText()
if (Boolean.TRUE.equals(getValue()))
return (this.trueString != null ? this.trueString : VALUE_TRUE);
else if (Boolean.FALSE.equals(getValue()))
return (this.falseString != null ? this.falseString : VALUE_FALSE);
else
return "";
测试一下:
//允许为空
CustomBooleanEditor booleanEditor = new CustomBooleanEditor(true);
booleanEditor.setAsText("1");
String asText = booleanEditor.getAsText();
System.out.println("结果为: "+asText);
//自定义
CustomBooleanEditor customBooleanEditor = new CustomBooleanEditor("真的", "假的", true);
customBooleanEditor.setAsText("真的");
String asText1 = customBooleanEditor.getAsText();
System.out.println("结果为: "+asText1);
spring提供的其他类型转换器,大致思路差不多,这里不列举了
特殊实现
把没有放在org.springframework.beans.propertyeditors
包下的实现称作特殊实现(前者称为标准实现)。
-
MediaTypeEditor
:位于org.springframework.http。依赖的核心API是MediaType.parseMediaType(text),可以把诸如text/html、application/json转为MediaType对象 - 显然它属于spring-web包,使用在web环境下
-
FormatterPropertyEditorAdapter
:位于org.springframework.format.support。将3.0新增的Formatter接口适配为一个PropertyEditor:setAsText这种转换操作委托给formatter.parse()去完成,反向委托给formatter.print()去完成。- 此适配器在DataBinder#addCustomFormatter()得到应用
-
PropertyValuesEditor
:位于org.springframework.beans。将k-v字符串(Properties格式)转换为一个PropertyValues,从而方便放进Environment里 -
ResourceEditor
:位于org.springframework.core.io。此转换器将String转换为Resource资源,特别实用。作为基础设施,广泛被用到Spring的很多地方- 像什么标准的FileEditor、InputSourceEditor、InputStreamEditor、URLEditor等等与资源相关的转换器,均依赖它而实现
-
ConvertingPropertyEditorAdapter
:位于org.springframework.core.convert.support。将3.0新增的ConversionService转换服务适配为一个PropertyEditor,内部转换动作以上是关于Spring读源码系列番外篇04----类型转换--上的主要内容,如果未能解决你的问题,请参考以下文章
Spring读源码系列番外篇---05----类型转换---中---三种全新的类型转换器
Spring读源码系列番外篇08---BeanWrapper没有那么简单--上
Spring读源码系列番外篇08---BeanWrapper没有那么简单--中
Spring读源码系列番外篇---03---PropertyResolver的结构体系剖析---下