✍前言
你好,我是YourBatman。
上篇文章 介绍完了Spring类型转换早期使用的PropertyEditor详细介绍,关于PropertyEditor现存的资料其实还蛮少的,希望这几篇文章能弥补这块空白,贡献一份微薄之力。
如果你也吐槽过PropertyEditor不好用,那么本文将对会有帮助。Spring自3.0版本开始自建了一套全新类型转换接口,这就是本文的主要内容,接下来逐步展开。
说明:Spring自3.0后笑傲群雄,进入大一统。Java从此步入Spring的时代
版本约定
- Spring Framework:5.3.1
- Spring Boot:2.4.0
✍正文
在了解新一代的转换接口之前,先思考一个问题:Spring为何要自己造一套轮子呢? 一向秉承不重复造轮子原则的Spring,不是迫不得已的话是不会去动他人奶酪的,毕竟互利共生才能长久。类型转换,作为Spring框架的基石,扮演着异常重要的角色,因此对其可扩展性、可维护性、高效性均有很高要求。
基于此,我们先来了解下PropertyEditor设计上到底有哪些缺陷/不足(不能满足现代化需求),让Spring“被迫”走上了自建道路。
PropertyEditor设计缺陷
前提说明:本文指出它的设计缺陷,只讨论把它当做类型转换器在转换场景下存在的一些缺陷。
- 职责不单一:该接口有非常多的方法,但只用到2个而已
- 类型不安全:setValue()方法入参是Object,getValue()返回值是Object,依赖于约定好的类型强转,不安全
- 线程不安全:依赖于setValue()后getValue(),实例是线程不安全的
- 语义不清晰:从语义上根本不能知道它是用于类型转换的组件
- 只能用于String类型:它只能进行String <-> 其它类型的转换,而非更灵活的Object <-> Object
PropertyEditor存在这五宗“罪”,让Spring决定自己设计一套全新API用于专门服务于类型转换,这就是本文标题所述:新一代类型转换Converter、ConverterFactory、GenericConverter。
关于PropertyEditor在Spring中的详情介绍,请参见文章:3. 搞定收工,PropertyEditor就到这
新一代类型转换
为了解决PropertyEditor作为类型转换方式的设计缺陷,Spring 3.0版本重新设计了一套类型转换接口,有3个核心接口:
- Converter<S, T>:Source -> Target类型转换接口,适用于1:1转换
- ConverterFactory<S, R>:Source -> R类型转换接口,适用于1:N转换
- GenericConverter:更为通用的类型转换接口,适用于N:N转换
1.注意:就它没有泛型约束,因为是通用
另外,还有一个条件接口ConditionalConverter,可跟上面3个接口搭配组合使用,提供前置条件判断验证。
这套接口,解决了PropertyEditor做类型转换存在的所有缺陷,且具有非常高的灵活性和可扩展性。下面进入详细了解。
Converter
将源类型S转换为目标类型T。
@FunctionalInterface public interface Converter<S, T> { T convert(S source); }
它是个函数式接口,接口定义非常简单。适合1:1转换场景:可以将任意类型 转换为 任意类型。它的实现类非常多,部分截图如下:
值得注意的是:几乎所有实现类的访问权限都是default/private
,只有少数几个是public公开的,下面我用代码示例来“近距离”感受一下。
代码示例
/** * Converter:1:1 */ @Test public void test() { System.out.println("----------------StringToBooleanConverter---------------"); Converter<String, Boolean> converter = new StringToBooleanConverter(); // trueValues.add("true"); // trueValues.add("on"); // trueValues.add("yes"); // trueValues.add("1"); System.out.println(converter.convert("true")); System.out.println(converter.convert("1")); // falseValues.add("false"); // falseValues.add("off"); // falseValues.add("no"); // falseValues.add("0"); System.out.println(converter.convert("FalSe")); System.out.println(converter.convert("off")); // 注意:空串返回的是null System.out.println(converter.convert("")); System.out.println("----------------StringToCharsetConverter---------------"); Converter<String, Charset> converter2 = new StringToCharsetConverter(); // 中间横杠非必须,但强烈建议写上 不区分大小写 System.out.println(converter2.convert("uTf-8")); System.out.println(converter2.convert("utF8")); }
运行程序,正常输出:
----------------StringToBooleanConverter--------------- true true false false null ----------------StringToCharsetConverter--------------- UTF-8 UTF-8
说明:StringToBooleanConverter/StringToCharsetConverter访问权限都是default,外部不可直接使用。此处为了做示例用到一个小技巧 -> 将Demo的报名调整为和转换器的一样,这样就可以直接访问。
关注点:true/on/yes/1都能被正确转换为true的,且对于英文字母来说一般都不区分大小写,增加了容错性(包括Charset的转换)。
不足
Converter用于解决1:1的任意类型转换,因此它必然存在一个不足:解决1:N转换问题需要写N遍,造成重复冗余代码。
譬如:输入是字符串,它可以转为任意数字类型,包括byte、short、int、long、double等等,如果用Converter来转换的话每个类型都得写个转换器,想想都麻烦有木有。
Spring早早就考虑到了该场景,提供了相应的接口来处理,它就是ConverterFactory<S, R>。