Spring MVC学习—Spring数据类型转换机制全解一万字
Posted L-Java
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Spring MVC学习—Spring数据类型转换机制全解一万字相关的知识,希望对你有一定的参考价值。
基于最新Spring 5.x,详细介绍了Spring的类型转换机制,包括三种最常见的数据类型转换器PropertyEditor、Formatter、Converter、HttpMessageConverter、ConversionService等核心类。
在使用Spring以及使用Spring MVC的时候,Spring会通过一系列的类型转换机制将参数转换为我们指定的类型,这种转换对于使用者来说通常是无感的,我们只需要使用指定的类型接收即可!
下面我们来详细的了解Spring的类型转换机制,包括三种最常见的数据类型转换器PropertyEditor、Formatter、Converter,以及ConversionService等核心类。
Spring MVC学习 系列文章
Spring MVC学习(1)—MVC的介绍以及Spring MVC的入门案例
Spring MVC学习(2)—Spring MVC中容器的层次结构以及父子容器的概念
Spring MVC学习(3)—Spring MVC中的核心组件以及请求的执行流程
Spring MVC学习(4)—ViewSolvsolver视图解析器的详细介绍与使用案例
Spring MVC学习(5)—基于注解的Controller控制器的配置全解【一万字】
Spring MVC学习(6)—Spring数据类型转换机制全解【一万字】
Spring MVC学习(7)—Validation基于注解的声明式数据校验机制全解【一万字】
Spring MVC学习(8)—HandlerInterceptor处理器拦截器机制全解
Spring MVC学习(9)—项目统一异常处理机制详解与使用案例
Spring MVC学习(10)—文件上传配置、DispatcherServlet的路径配置、请求和响应内容编码
Spring MVC学习(11)—跨域的介绍以及使用CORS解决跨域问题
文章目录
1 Spring类型转换机制概述
BeanWrapper
是一个Spring的内部体系,主要用于在创建bean实例之后填充bean的依赖,我们在此前Spring IOC的源码
的部分已经讲过了,BeanWrapper
对于大部分开者这来说都是无感知的,被Spring内部使用,属于一个底层的对象。
Spring中的类型转换主要发生在两个地方:
Spring创建bean实例时
,在底层的BeanWrapper中注入Bean的属性依赖的时候,如果对于找到的依赖类型(给定的常量值或者找到依赖对象)如果不符合属性的具体类型,那么需要转换为对应的属性类型;- Spring MVC中,在执行处理器方法之前,可能需要把HTTP请求的数据通过DataBinder绑定到控制器
方法的给定参数
上,然而HTTP参数到后端时都是String类型,而方法参数可能是各种类型,这就可能涉及到从String到给定类型的转换。
Spring提供了三种最常见的数据类型转换器PropertyEditor、Formatter、Converter,无论是Spring MVC的 DataBinder和底层的BeanWrapper都支持使用这些转换器进行数据转换:
PropertyEditor
是JDK自带的类型转换接口。主要用于实现String到其他类型的转换。Spring已经提供了用于常见类型转换的PropertyEditor实现。Formatter
是Spring 3.0时提供的接口,只能转换String到其他类型,支持SPI机制。通常对于Spring MVC的参数绑定时的类型转换使用Formatter就可以了。Spring已经提供了用于常见类型转换的Formatter实现。Converter
是Spring 3.0时提供的接口,可以提供从一个对象类型到另一个对象类型的转换,支持SPI机制,当需要实现通用类型转换逻辑时应该使用Converter。Spring已经提供了用于常见类型转换的Converter实现。
2 PropertyEditor
2.1 PropertyEditor的概述
在最开始,Spring 使用PropertyEditor(属性编辑器)的概念来实现对象和字符串之间的转换,PropertyEditor接口并非来自于Spring,而是来自Java的rt.jar核心依赖包,是JDK自带的。属性编辑器的作用我们在此前就说过了:
- 在Spring创建bean时将数据转换为bean的属性对应的类型。
- 在 Spring MVC 框架中分析、转换HTTP 请求参数。
PropertyEditor接口的常见方法如下:
public interface PropertyEditor {
/**
* 设置(或更改)要编辑的对象。原始类型(如"int")必须包装为相应的对象类型,如"java.lang.integer"
*
* @param value 要编辑的新目标对象。属性编辑器不应修改此对象,而属性编辑器应创建一个新对象来保存任何修改的值
*/
void setValue(Object value);
/**
* 获取属性值。
*
* @return 属性的值。原始类型(如"int")将包装为相应的对象类型,如"java.lang.integer"
*/
Object getValue();
/**
* 为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值
*/
String getJavaInitializationString();
/**
* 获取属性值的可编辑的字符串表示形式
*
* @return 如果值不能表示为可编辑字符串,则返回 null。如果返回非 null 值,则属性编辑器应准备好在 setAsText()中分析该字符串
*/
String getAsText();
/**
* 通过分析给定字符串来设置属性值。如果字符串格式错误或此类属性不能以文本形式表示,可能会引发 java.lang.IllegalArgumentException
*
* @param text 要解析的字符串。
*/
void setAsText(String text) throws java.lang.IllegalArgumentException;
}
虽然经常看见有文章说PropertyEditor仅用于支持从String到对象的转换。但是实际上在目前的Spring版本中,早已支持通过PropertyEditor实现从对象到对象的转换,典型的实现就是来自于spring-data-redis的各种PropertyEditor实现,比如ValueOperationsEditor,我们可以直接依赖ValueOperations,并且对其注入redisTemplate,Spring 在检查到类型不一致时,最终会在ValueOperationsEditor中通过注入的redisTemplate获取ValueOperations并返回。
支持从对象转换为对象的核心方法就是PropertyEditor#setValue方法。
2.2 内置的PropertyEditor
尽管现在如果在需要自定义转换器时,PropertyEditor被推荐使用Converter替代,但是我们仍然能够配置并且正常使用自定义的PropertyEditor,并且Spring内部就是用了很多默认PropertyEditor。
Spring 拥有许多内置的PropertyEditor
实现。它们都位于 org.springframework.beans.propertyeditors包中
。默认情况下,大多数(但不包括全部)由 BeanWrapperImpl
来注册(位于AbstractBeanFactory#initBeanWrapper
方法中,注册之后被BeanWrapper用于创建和填充Bean实例的类型转换)。很多默认属性编辑器实现也都是可配置的,比如CustomDateEditor,就可以指定日期模式
。下表列出了Spring提供的常见PropertyEditor:
类型 | 描述 |
---|---|
ByteArrayPropertyEditor | 字节数组的编辑器。将String转换为相应的byte[]表示形式。默认情况下由 BeanWrapperImpl 注册。 |
ClassEditor | 支持表示类的字符串String解析与实际Class的相互转换。当找不到类时,将抛出IllegalArgumentException。默认情况下,由BeanWrapperImpl注册。 |
CustomBooleanEditor | boolean的可自定义属性编辑器,将指定的String解析为boolean值。默认情况下,由 BeanWrapperImpl 注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
CustomCollectionEditor | 集合的可自定义属性编辑器,将任何源字符串或者集合转换为给定的目标集合类型。默认情况下,由 BeanWrapperImpl 注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
CustomDateEditor | java.util.Date 的可自定义属性编辑器,支持自定义 DateFormat。默认情况下未注册。必须由用户根据需要使用适当的格式进行手动注册。 |
CustomNumberEditor | 任何Number的子类(如Integer、 Long、 Float或 Double)的可自定义属性编辑器。默认情况下,由BeanWrapperImpl注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。 |
FileEditor | 将字符串解析为 java.io.File 对象。默认情况下,由 BeanWrapperImpl 注册。 |
InputStreamEditor | 将一个String通过中间的ResourceEditor和Resource生成一个InputStream。默认用法不会关闭inputstream。默认情况下,由BeanWrapperImpl注册。 |
LocaleEditor | 可以实现String和Locale对象的相互转换,字符串格式为[国家/地区],与Locale的 toString()方法相同)。默认情况下,由 BeanWrapperImpl 注册。 |
PatternEditor | 可以实现String和java.util.regex.Pattern对象的相互转换 |
PropertiesEditor | 可以将字符串转换为Properties对象(使用java.util.Properties类的javadoc中定义的格式格式化)。默认情况下,由BeanWrapperImpl注册。 |
StringTrimmerEditor | 修剪字符串的属性编辑器,还可以(可选)将空字符串转换为null值。默认情况下未注册,必须是用户手动注册的。 |
URLEditor | 可以将URL字符串解析为实际 URL 对象。默认情况下,由 BeanWrapperImpl 注册。 |
BeanWrapperImpl自动注册的PropertyEditor位于PropertyEditorRegistrySupport#createDefaultEditors方法中,并且是在需要转类型但是其他自定义转换器中无法找到合适的转换器的时候才会注册的(convertIfNecessary方法内的findDefaultEditor方法),属于延迟初始化!
2.3 PropertyEditorManager
Spring使用java.beans.PropertyEditorManager
来注册、搜索可能的需要的PropertyEditor。搜索路径还包括rt.jar包中的sun.bean.editors,其中包括用于Font、Color和大多数基本类型的PropertyEditor实现。
另外,如果某个类型的类型转换器与该类型的Class位于同一个包路径,并且命名为ClassName+Editor,那么当需要转换为该类型时,将会自动发现该转换器,而无需手动注册,如下实例:
com
chank
pop
Something // Something类
SomethingEditor // 用于Something类的类型转换,将会自动发现
一个管理默认属性编辑器的管理器:PropertyEditorManager,该管理器内保存着一些常见类型的属性编辑器, 如果某个JavaBean的常见类型属性没有通过BeanInfo显式指定属性编辑器,IDE将自动使用PropertyEditorManager中注册的对应默认属性编辑器。
实际上我们前面说的spring-data-redis的各种PropertyEditor实现就是采用的这个机制取发现的,它们并没有手动注册:
当然,我们也可以使用标准的BeanInfo JavaBeans机制显式的指定某个类与某个属性编辑器的关系,如下实例:
com
chank
pop
Something
SomethingBeanInfo
2.4 注册自定义PropertyEditor
Spring 预注册了许多自定义属性编辑器实现(例如,将表示为字符串的类名转换为Class对象)。此外,Java 的标准 JavaBeans PropertyEditor查找机制允许对类的PropertyEditor进行适当命名,并放置在与它所支持的类相同的包中,以便可以自动找到它(上面讲过了)。
Spring提供了PropertyEditor的一个核心实现类PropertyEditorSupport
,如果我们要编写自定义的属性编辑器,只需要继承这个类即可,PropertyEditorSupport实现了PropertyEditor
接口的所有方法,我们继承PropertyEditorSupport之后只需要重写自己需要的方法即可,更加方便!
如果需要注册其他自定义PropertyEditors,可以使用几种机制:
- 最不建议、不方便是使用
ConfigurableBeanFactory接口的registerCustomEditor()方法
,因为这需要我们获取BeanFactory的引用。该方法将自定义的PropertyEditor直接注册到AbstractBeanFactory的customEditors
缓存中,等待后续BeanWrapper的获取。 - 另一种(稍微方便一点)的机制是使用
CustomEditorConfigurer
,它是一个特殊的BeanFactoryPostProcessor
,可以将自定义的PropertyEditor或者PropertyEditorRegistrar实现存入其内部的customEditors和propertyEditorRegistrars
属性中,启动项目之后,它的postProcessBeanFactory
方法会在所有普通bean实例化和初始化之前(创建BeanWrapper之前)调用beanFactory来将这些PropertyEditor和propertyEditorRegistrars
注册到AbstractBeanFactory的customEditors和propertyEditorRegistrars
缓存。
基于以上的配置,在Spring bean对应的BeanWrapper初始化时,会自动从AbstractBeanFactory的customEditors和propertyEditorRegistrars
缓存中将自定义的PropertyEditor注册到自己内部(位于AbstractBeanFactory#initBeanWrapper
方法中),之后被BeanWrapper用于创建和填充 Bean 实例的类型转换。
但是请注意,这种配置不适用于Spring MVC的数据绑定,因为DataBinder默认不会查找这里注册到AbstractBeanFactory中的customEditors和propertyEditorRegistrars缓存,数据绑定时需要的自定义Editor必须在org.springframework.validation.DataBinder中手动注册(通过Spring MVC的@InitBinder方法)。
如下案例,自定义了一个PropertyEditor,日期格式为“yyyy-MM-dd”:
/**
* @author lx
*/
public class DateEditor extends PropertyEditorSupport {
private String formatter = "yyyy-MM-dd";
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(formatter);
try {
Date date = simpleDateFormat.parse(text);
System.out.println("-----DateEditor-----");
//转换后的值设置给PropertyEditorSupport内部的value属性
setValue(date);
} catch (ParseException e) {
throw new IllegalArgumentException(e);
}
}
public DateEditor() {
}
public DateEditor(String formatter) {
this.formatter = formatter;
}
}
一个实体,需要将“2020-12-12”的字符串转换为Date类型的属性:
@Component
public class TestDate {
@Value("2020-12-12")
private Date date;
@PostConstruct
public void test() {
System.out.println(date);
}
}
将自定义的PropertyEditor注册到CustomEditorConfigurer的customEditors属性中,该属性是Map<Class<?>, Class<? extends PropertyEditor>类型,即都是Class类型:
<bean
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date" value="com.spring.mvc.config.DateEditor"/>
</map>
</property>
</bean>
启动项目,即可看到输出:
-----DateEditor-----
Sat Dec 12 00:00:00 CST 2020
通过在CustomEditorConfigurer的customEditors属性
中直接添加自定义编辑器的类型确实可以注册,但是这种方法的缺点是无法为自定义的PropertyEditor
指定初始化参数。
实际上在早期的Spring版本中,Map中的value类型是一个实例,因此它是支持自定义初始化参数的,但是因为PropertyEditor是有状态的,如果多个BeanWrapper共用同一个PropertyEditor实例,那么可能造成难以察觉的问题。因此,在新版本中customEditors属性的Map的Value是Class类型,并且每个BeanWrapper在设置转换器是都会创建属于自己的PropertyEditor实例。如果想要需要控制PropertyEditor的实例化过程,比如设置初始化参数,那么我们需要使用PropertyEditorRegistrar去注册它们。
还有一个缺点是,基于customEditors属性配置的PropertyEditor无法与Spring MVC的数据绑定共享同样的配置方式,即使它们都需要配置某个同样的PropertyEditor
。
2.4.1 使用PropertyEditorRegistrar
PropertyEditorRegistrar
,顾名思义,它是一个PropertyEditor的“注册表”,Spring中还有很多Registrar结尾的类,这种类通用作用就是用于注册类名去除“Registrar”之后的数据,比如PropertyEditorRegistrar就是用于注册PropertyEditor,它还有一个可选的特性就是,可以在一次方法调用中注册多个实例并且更加灵活!
另外,PropertyEditorRegistrar实例与名为PropertyEditorRegistry的接口配合使用,而该接口又被Spring的BeanWrapper和 DataBinder都实现了,因此PropertyEditorRegistrar中的PropertyEditor配置很容易的被BeanWrapper和 DataBinder共享!
Spring为我们提供了一个PropertyEditorRegistrar的实现ResourceEditorRegistrar,如果我们要实现自己的PropertyEditorRegistrar,那么可以参数该类,特别是它的registerCustomEditors方法。实际上ResourceEditorRegistrar将会被Spring自动默认注册到容器中(位于prepareBeanFactory方法中),因此该类中的PropertyEditor通常会被所有的beanWarpper使用!
下面是一个自己的PropertyEditorRegistrar:
/**
* @author lx
*/
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
private String formatter;
/**
* 传递一个PropertyEditorRegistry的实现,使用给定的PropertyEditorRegistry注册自定义PropertyEditor
* BeanWrapperImpl和DataBinder都实现了PropertyEditorRegistry接口,传递的通常是 BeanWrapper 或 DataBinder。
* <p>
* 该方法仅仅是定义了注册的流程,只有当某个BeanWrapper 或 DataBinder实际调用时才会真正的注册
*
* @param registry 将要注册自定义PropertyEditor的PropertyEditorRegistry
*/
@Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
// 预期将创建新的属性编辑器实例,可以自己控制创建流程
registry.registerCustomEditor(Date.class, new DateEditor(formatter));
// 可以在此处注册尽可能多的自定义属性编辑器...
}
public String getFormatter() {
return formatter;
}
public void setFormatter(String formatter) {
this.formatter = formatter;
}
}
下面是如何配置CustomEditorConfigurer并注入CustomPropertyEditorRegistrar实例:
<bean
class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<!--propertyEditorRegistrars是一个数组,可以传递多个自定义的PropertyEditorRegistrar-->
<property name="propertyEditorRegistrars">
<array>
<ref bean="customPropertyEditorRegistrar"/>
</array>
</property>
</bean>
<!--自定义的CustomPropertyEditorRegistrar-->
<bean id="customPropertyEditorRegistrar" class="com.spring.mvc.config.CustomPropertyEditorRegistrar">
<property name="formatter" value="yyyy-MM-dd"/>
</bean>
启动项目,同样成功转换:
-----DateEditor-----
Sat Dec 12 00:00:00 CST 2020
注意,Spring的目的就是让每个BeanWarpper和DataBinder都初始化自己的PropertyEditor实例,这是为了防止多个实例共用一个有状态的PropertyEditor导致数据异常,如果你确定没问题的话,也可以在PropertyEditorRegistrar中配置同一个PropertyEditor实例。
2.4.1.1 共享配置
配置PropertyEditorRegistrar之后,想要将这些PropertyEditor配置应用在Spring MVC的DataBinder中非常简单,如下案例:
@Controller
public class RegistrarController {
@Resource
private CustomPropertyEditorRegistrar customPropertyEditorRegistrar;
@InitBinder
public void init(WebDataBinder binder) {
//调用registerCustomEditors方法向当前DateBinder注册PropertyEditor
customPropertyEditorRegistrar.registerCustomEditors(binder);
}
//其他控制器方法
}
只需要在控制器中引入customPropertyEditorRegistrar
实例,然后在@ initBinder方法
中调用registerCustomEditors
方法并传入DataBinder
,即可将内部配置的PropertyEditor注册到当前DataBinder中。
这种类型的PropertyEditor注册方式可以产生简洁的代码(注册多个PropertyEditor的实现只有一行代码),并允许将公共的PropertyEditor注册代码封装在一个类中,然后根据需要在多个Controllers之间共享。
3 Converter
Spring 3.0引入了core.convert
包,它提供了一般类型的转换系统,作为JavaBeans PropertyEditors
属性编辑器的替代服务。
3.1 Converter SPI接口
相比于复杂的PropertyEditor接口,org.springframework.core.convert.converter.Converter
是一个用于类型转换的非常简单且强大的SPI接口,翻译成中文就是“转换器”。Converter
提供了核心的转换行为!
@FunctionalInterface
public interface Converter<S, T> {
/**
* 将 S 类型的源对象转换为目标类型 T 对象
*
* @param source 要转换的源对象,它必须是 S 类型的实例(从不为null)
* @return 转换后的对象,它必须是 T 类型的实例(可能为null)
* @throws IllegalArgumentException 如果源对象无法转换为所需的目标类型
*/
@Nullable
T convert(S source);
}
要想创建自己的Converter
,只需要实现Converter接口,其中S表示要转换的类型,T表示要转换到的类型。
convert(S)
方法的每次调用,都应该保证输入参数不为null。如果转换失败,转换器可能会引发任何非受检异常。在抛出异常时应该包裹在一个IllegalArgumentException中,同时我们必须保证Converter是线程安全
的!
和PropertyEditor类似,为了方便起见,Spring在core.convert.support
包中已经提供了非常多的Converter转换器实现
,其中包括从字符串到数字的转换器和其它常见类型。
下面是一个典型的Converter转换器实现:
public final class StringToInteger implements Converter<String, Integer> {
@Override
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
3.2 使用ConverterFactory
Converter的类型转换是明确的,倘若对有同一父类或接口的多个子类型需要进行类型转化,为每个类型都写一个Converter显然是十分不理智的,当需要集中管理整个类层次结构的转换逻辑时,可以实现ConverterFactory
接口:
/**
* @param <S> 源类型
* @param <R> 目标类型的超类型
*/
public interface ConverterFactory<S, R> {
/**
* 获取转换器从 S 转换为目标类型 T,其中 T 也是 R 的子类型。
*
* @param <T> 目标类型
* @param targetType 要转换为的目标类型的Class
* @return 从 S 到 T 的转换器
*/
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
参数S是需要转换的类型,R是要转换到的类的基类。然后实现getConverter(Class)
方法,其中T是R的子类型。ConverterFactory
用于实现一种类型到N种类型的转换。
Spring已经提供了基本的ConverterFactory的实现
:
3.3 使用GenericConverter
当需要定义复杂的Converter实现
时,可以使用GenericConverter
接口,GenericConverter并不是Converter的子接口,而是一个独立的顶级接口
,翻译成中文就是“通用转换器”!
与Converter相比,GenericConverter更灵活并且没有强类型的签名,它支持在多个源类型和目标类型之间进行转换,用于实现N种类型到N种类型的转换。此外,GenericConverter提供了可用的源和目标字段上下文(TypeDescriptor
),你可以在实现转换逻辑时使用它们。这样的上下文允许类型转换由字段注解或字段签名上声明的泛型信息驱动类型转换。
下面是GenericConverter的接口定义:
/**
* 用于在两种或多种类型之间转换的通用转换器接口。
* <p>
* 这是最灵活的转换器SPI接口,也是最复杂的。它的灵活性在于GenericConverter可能支持在多个源/目标类型对之间转换
* 此外,GenericConverter的实现在类型转换过程中可以访问源/目标字段上下文。
* 这允许解析源和目标字段元数据,如注解和泛型信息,这些元数据可用于影响转换逻辑。
*/
public interface GenericConverter {
/**
* 返回所有此转换器可以转换的源类型和目标类型的ConvertiblePair
* 每个ConvertiblePair都表示一组可转换的源类型以及目标类型。
* <p>
* 对于ConditionalConverter,此方法可能会返回 null 以指示应考虑所有ConvertiblePair
*/
@Nullable
Set<ConvertiblePair> getConvertibleTypes();
/**
* 将源对象转换为TypeDescriptor(类型描述符)描述的目标类型。
*
* @param source 要转换的源对象(可能是null)
* @param sourceType 正在转换的字段的类型描述符
* @param targetType 要转换为的字段的类型描述符
* @return 转换的对象
*/
@Nullable
Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
/**
* 源类型到目标类型对的持有者
*/
final class ConvertiblePair {
private final Class<?> sourceType;
private final Class<?> targetType;
/**
* 创建一个新的ConvertiblePair
*
* @param sourceType 源类型
* @param 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(@Nullable 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());
}
}
}
GenericConverter
中拥有一个内部类ConvertiblePair
,这个内部类用于封装一种可转换的源类型与目标类型对,一个GenericConverter可以拥有多个ConvertiblePair。
要实现GenericConverter,需要重写getConvertibleTypes()方法
以返回受转换支持的源类型到目标类型对,也就是ConvertiblePair。然后实现convert(Object, TypeDescriptor, TypeDescriptor)方法
,该方法包含转换的逻辑。源 TypeDescriptor(字段描述符)提供对保存了要转换值的源字段的访问。目标 TypeDescriptor提供对要设置转换值的目标字段的访问。
TypeDescriptor
作为类型描述符,保存了对应参数、字段的元数据,可以从其中获取对应参数、字段的名字、类型、注解、泛型的信息!
Spring已经提供了基本的GenericConverter的实现,一个很好的例子就是在Java数组和集合之间转换的转换器,比如ArrayToCollectionConverter
,首先线它会创建对应的集合类型,然后在将数组元素存入集合中时,如有必要,会尝试将数组元素类型转换为集合元素的泛型类型!
3.3.1 使用ConditionalGenericConverter
如果觉得只通过源类型和目标类型是否匹配来判断能够支持转换的方式太过简单了,还需要在特定的条件成立时才支持转换,比如可能希望在目标字段上存在指定的注解时才表示可以转换,或者可能希望仅在目标字段的类型上定义了特定方法(如静态的valueOf方法)时才表示可以转换,此时我们可以使用ConditionalGenericConverter
接口。
ConditionalGenericConverter是GenericConverter 和ConditionalConverter的结合
,允许自定义匹配条件来确定是否能执行转换!
/**
以上是关于Spring MVC学习—Spring数据类型转换机制全解一万字的主要内容,如果未能解决你的问题,请参考以下文章
Spring MVC 类型转换:PropertyEditor 还是 Converter?