你好,我是A哥(YourBatman)。
Spring设计了org.springframework.format.Formatter
格式化器接口形象,对格式化器进行了大一统,让你只须要关怀对立的API,而无需关注具体实现,相干议题上篇文章 有具体介绍。
Spring内建有不少格式化器实现,同时对它们的治理、调度应用也有专门的组件负责,堪称若明若暗,职责清晰。本文将围绕Formatter注册核心FormatterRegistry
开展,为你介绍Spring是如何优雅,奇妙的实现注册治理的。
学习编码是个模拟的过程,绝大多数时候你并不需要发明货色。当然这里指的模拟并非一般的CV模式,而是取精髓为己所用,本文所述奇妙设计便是精髓所在,任君提取。
这几天进入小寒天气,北京迎来最低-20℃,最高-11℃的冰点温度,外出留神保暖
本文提纲
版本约定
- Spring Framework:5.3.x
- Spring Boot:2.4.x
✍注释
对Spring的源码浏览、剖析这么多了,会发现对于组件治理大体思维都一样,离不开这几个组件:注册核心(注册员) + 散发器。
一龙生九子,九子各不同。尽管大体思路保持一致,但每个实现在其场景下都有本人的施展空间,值得咱们向而往之。
FormatterRegistry:格式化器注册核心
field属性格式化器的注册表(注册核心)。请留神:这里强调了field的存在,先混个眼生,前面你将能有较深领会。
public interface FormatterRegistry extends ConverterRegistry { void addPrinter(Printer<?> printer); void addParser(Parser<?> parser); void addFormatter(Formatter<?> formatter); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);}
此接口继承自类型转换器注册核心ConverterRegistry
,所以格式化注册核心是转换器注册核心的加强版,是其超集,性能更多更弱小。
对于类型转换器注册核心ConverterRegistry
的具体介绍,可翻阅本系列的这篇文章,看完后门清
尽管FormatterRegistry
提供的增加办法挺多,但其实根本都是在形容同一个事:为指定类型fieldType
增加格式化器(printer或parser),绘制成图如下所示:
阐明:最初一个接口办法除外,addFormatterForFieldAnnotation()
和格式化注解相干,因为它十分重要,因而放在下文专门撰文解说
FormatterRegistry接口的继承树如下:
有了学过ConverterRegistry的教训,这种设计套路很容易被看穿。这两个实现类按层级进行分工:
FormattingConversionService
:实现所有接口办法DefaultFormattingConversionService
:继承自下面的FormattingConversionService,在其根底上注册默认的格式化器
事实上,性能分类的确如此。本文重点介绍FormattingConversionService
,这个类的设计实现上有很多讨巧之处,只有你来,要你难看。
FormattingConversionService
它是FormatterRegistry
接口的实现类,实现其所有接口办法。
FormatterRegistry
是ConverterRegistry
的子接口,而ConverterRegistry接口的所有办法均已由GenericConversionService
全副实现了,所以能够通过继承它来间接实现 ConverterRegistry接口办法的实现,因而本类的继承构造是这样子的(请细品这个构造):
FormattingConversionService通过继承GenericConversionService搞定“左半边”(父接口ConverterRegistry
);只剩“右半边”待处理,也就是FormatterRegistry新增的接口办法。
FormattingConversionService: @Override public void addPrinter(Printer<?> printer) { Class<?> fieldType = getFieldType(printer, Printer.class); addConverter(new PrinterConverter(fieldType, printer, this)); } @Override public void addParser(Parser<?> parser) { Class<?> fieldType = getFieldType(parser, Parser.class); addConverter(new ParserConverter(fieldType, parser, this)); } @Override public void addFormatter(Formatter<?> formatter) { addFormatterForFieldType(getFieldType(formatter), formatter); } @Override public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { addConverter(new PrinterConverter(fieldType, formatter, this)); addConverter(new ParserConverter(fieldType, formatter, this)); } @Override public void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser) { addConverter(new PrinterConverter(fieldType, printer, this)); addConverter(new ParserConverter(fieldType, parser, this)); }
从接口的实现能够看到这个“惊天大机密”:所有的格式化器(含Printer、Parser、Formatter)都是被当作Converter
注册的,也就是说真正的注册核心只有一个,那就是ConverterRegistry
。
格式化器的注册治理远没有转换器那么简单,因为它是基于下层适配的思维,最终适配为Converter来实现注册的。所以最终注册进去的理论是个经由格式化器适配来的转换器,完满复用了那套简单的转换器治理逻辑。
这种设计思路,齐全能够“CV”到咱们本人的编程思维里吧
甭管是Printer还是Parser,都会被适配为GenericConverter从而被增加到ConverterRegistry
外面去,被当作转换器治理起来。当初你应该晓得为何FormatterRegistry
接口仅需提供增加办法而无需提供删除办法了吧。
当然喽,对于Printer/Parser的适配实现亦是本文本文关注的焦点,外面大有文章可为,let's go!
PrinterConverter:Printer接口适配器
把Printer<?>
适配为转换器,转换指标为fieldType -> String
。
private static class PrinterConverter implements GenericConverter { private final Class<?> fieldType; // 从Printer<?>泛型里解析进去的类型,有可能和fieldType一样,有可能不一样 private final TypeDescriptor printerObjectType; // 理论执行“转换”动作的组件 private final Printer printer; private final ConversionService conversionService; public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) { ... // 从类上解析出泛型类型,但不肯定是理论类型 this.printerObjectType = TypeDescriptor.valueOf(resolvePrinterObjectType(printer)); ... } // fieldType -> String @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(this.fieldType, String.class)); }}
既然是转换器,重点当然是它的convert转换方法:
PrinterConverter: @Override @SuppressWarnings("unchecked") public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { // 若sourceType不是printerObjectType的子类型 // 就尝试用conversionService转一下类型试试 // (也就是说:若是子类型是可间接解决的,无需转换一趟) if (!sourceType.isAssignableTo(this.printerObjectType)) { source = this.conversionService.convert(source, sourceType, this.printerObjectType); } if (source == null) { return ""; } // 执行理论转换逻辑 return this.printer.print(source, LocaleContextHolder.getLocale()); }
转换步骤分为两步:
若源类型(理论类型)不是该Printer类型的泛型类型的子类型的话,那就尝试应用conversionService转一趟
- 例如:Printer解决的是Number类型,然而你传入的是Person类型,这个时候conversionService就会发挥作用了
- 交由指标格式化器Printer执行理论的转换逻辑
能够说Printer它能够间接转,也能够是构建在conversionService 之上 的一个转换器:只有源类型是我能解决的,或者通过conversionService后能成为我能解决的类型,都能进行转换。有一次完满的能力复用。
说到这我预计有些小伙伴还不能了解啥意思,能解决什么问题,那么上面我别离给你用代码举例,加深你的理解。
筹备一个Java Bean:
@Data@NoArgsConstructor@AllArgsConstructorpublic class Person { private Integer id; private String name;}
筹备一个Printer:将Integer类型加10后,再转为String类型
private static class IntegerPrinter implements Printer<Integer> { @Override public String print(Integer object, Locale locale) { object += 10; return object.toString(); }}
示例一:应用Printer,无两头转换
测试用例:
@Testpublic void test2() { FormattingConversionService formattingConversionService = new FormattingConversionService(); FormatterRegistry formatterRegistry = formattingConversionService; // 阐明:这里不应用DefaultConversionService是为了防止默认注册的那些转换器对后果的“烦扰”,不不便看成果 // ConversionService conversionService = new DefaultConversionService(); ConversionService conversionService = formattingConversionService; // 注册格式化器 formatterRegistry.addPrinter(new IntegerPrinter()); // 最终均应用ConversionService对立提供服务转换 System.out.println(conversionService.canConvert(Integer.class, String.class)); System.out.println(conversionService.canConvert(Person.class, String.class)); System.out.println(conversionService.convert(1, String.class)); // 报错:No converter found capable of converting from type [cn.yourbatman.bean.Person] to type [java.lang.String] // System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class));}
运行程序,输入:
truefalse11
完满。
然而,它不能实现Person -> String
类型的转换。一般来说,咱们有两种路径来达到此目标:
间接形式:写一个Person转String的转换器,专用
- 毛病显著:多写一套代码
组合形式(举荐):如果目前曾经有
Person -> Integer
的了,那咱们就组合起来用就十分不便啦,上面这个例子将通知你应用这种形式实现“需要”- 毛病不显著:转换器个别要求与业务数据无关,因而通用性强,应最大可能的复用
上面示例二将帮你解决通过复用已有能力形式达到Person -> String
的目标。
示例二:应用Printer,有两头转换
基于示例一,若要实现Person -> String
的话,只需再给写一个Person -> Integer
的转换器放进ConversionService
里即可。
阐明:一般来说ConversionService曾经具备很多“能力”了的,拿来就用即可。本例为了帮你阐明底层原理,所以用的是一个“洁净的”ConversionService实例
@Testpublic void test2() { FormattingConversionService formattingConversionService = new FormattingConversionService(); FormatterRegistry formatterRegistry = formattingConversionService; // 阐明:这里不应用DefaultConversionService是为了防止默认注册的那些转换器对后果的“烦扰”,不不便看成果 // ConversionService conversionService = new DefaultConversionService(); ConversionService conversionService = formattingConversionService; // 注册格式化器 formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), null); // 强调:此处绝不能应用lambda表达式代替,否则泛型类型失落,后果将出错 formatterRegistry.addConverter(new Converter<Person, Integer>() { @Override public Integer convert(Person source) { return source.getId(); } }); // 最终均应用ConversionService对立提供服务转换 System.out.println(conversionService.canConvert(Person.class, String.class)); System.out.println(conversionService.convert(new Person(1, "YourBatman"), String.class));}
运行程序,输入:
true11
完满。
针对本例,有如下关注点:
应用
addFormatterForFieldType()
办法注册了IntegerPrinter,并且明确指定了解决的类型:只解决Person类型- 阐明:IntegerPrinter是能够注册屡次别离用于解决不同类型。比方你仍旧能够保留
formatterRegistry.addPrinter(new IntegerPrinter());
来解决Integer -> String是木问题的
- 阐明:IntegerPrinter是能够注册屡次别离用于解决不同类型。比方你仍旧能够保留
- 因为IntegerPrinter 实际上 只能转换
Integer -> String
,因而还必须注册一个转换器,用于Person -> Integer
桥接一下,这样就串起来了Person -> Integer -> String
。只是内部看起来这些都是IntegerPrinter做的一样,特地工整 强调:addConverter()注册转换器时请务必不要应用lambda表达式代替输出,否则会失去泛型类型,导致出错
- 若想用lambda表达式,请应用addConverter(Class,Class,Converter)这个重载办法实现注册
ParserConverter:Parser接口适配器
把Parser<?>
适配为转换器,转换指标为String -> fieldType
。
private static class ParserConverter implements GenericConverter { private final Class<?> fieldType; private final Parser<?> parser; private final ConversionService conversionService; ... // 省略结构器 // String -> fieldType @Override public Set<ConvertiblePair> getConvertibleTypes() { return Collections.singleton(new ConvertiblePair(String.class, this.fieldType)); } }
既然是转换器,重点当然是它的convert转换方法:
ParserConverter: @Override @Nullable public Object convert(@Nullable Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { // 空串当null解决 String text = (String) source; if (!StringUtils.hasText(text)) { return null; } ... Object result = this.parser.parse(text, LocaleContextHolder.getLocale()); ... // 解读/转换后果 TypeDescriptor resultType = TypeDescriptor.valueOf(result.getClass()); if (!resultType.isAssignableTo(targetType)) { result = this.conversionService.convert(result, resultType, targetType); } return result; }
转换步骤分为两步:
- 通过Parser将String转换为指定的类型后果result(若失败,则抛出异样)
- 判断若result属于指标类型的子类型,间接返回,否则调用ConversionService转换一把
能够看到它和Printer的“程序”是相同的,在返回值上做文章。同样的,上面将用两个例子来加深了解。
private static class IntegerParser implements Parser<Integer> { @Override public Integer parse(String text, Locale locale) throws ParseException { return NumberUtils.parseNumber(text, Integer.class); }}
示例一:应用Parser,无两头转换
书写测试用例:
@Testpublic void test3() { FormattingConversionService formattingConversionService = new FormattingConversionService(); FormatterRegistry formatterRegistry = formattingConversionService; ConversionService conversionService = formattingConversionService; // 注册格式化器 formatterRegistry.addParser(new IntegerParser()); System.out.println(conversionService.canConvert(String.class, Integer.class)); System.out.println(conversionService.convert("1", Integer.class));}
运行程序,输入:
true1
完满。
示例二:应用Parser,有两头转换
上面示例输出一个“1”字符串,进去一个Person对象(因为有了下面例子的铺垫,这里就“直抒胸臆”了哈)。
@Testpublic void test4() { FormattingConversionService formattingConversionService = new FormattingConversionService(); FormatterRegistry formatterRegistry = formattingConversionService; ConversionService conversionService = formattingConversionService; // 注册格式化器 formatterRegistry.addFormatterForFieldType(Person.class, null, new IntegerParser()); formatterRegistry.addConverter(new Converter<Integer, Person>() { @Override public Person convert(Integer source) { return new Person(source, "YourBatman"); } }); System.out.println(conversionService.canConvert(String.class, Person.class)); System.out.println(conversionService.convert("1", Person.class));}
运行程序,啪,空指针了:
java.lang.NullPointerException at org.springframework.format.support.FormattingConversionService$PrinterConverter.resolvePrinterObjectType(FormattingConversionService.java:179) at org.springframework.format.support.FormattingConversionService$PrinterConverter.<init>(FormattingConversionService.java:155) at org.springframework.format.support.FormattingConversionService.addFormatterForFieldType(FormattingConversionService.java:95) at cn.yourbatman.formatter.Demo.test4(Demo.java:86) ...
依据异样栈信息,可明确起因为:addFormatterForFieldType()
办法的第二个参数不能传null,否则空指针。这其实是Spring Framework
的bug,我已向社区提了issue,期待可能被解决喽:
为了失常运行本例,这么改一下:
// 第二个参数不传null,用IntegerPrinter占位formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), new IntegerParser());
再次运行程序,输入:
truePerson(id=1, name=YourBatman)
完满。
针对本例,有如下关注点:
应用
addFormatterForFieldType()
办法注册了IntegerParser,并且明确指定了解决的类型,用于解决Person类型- 也就是说此IntegerParser专门用于转换指标类型为Person的属性
- 因为IntegerParser 实际上 只能转换
String -> Integer
,因而还必须注册一个转换器,用于Integer -> Person
桥接一下,这样就串起来了String -> Integer -> Person
。里面看起来这些都是IntegerParser做的一样,十分工整 - 同样强调:addConverter()注册转换器时请务必不要应用lambda表达式代替输出,否则会失去泛型类型,导致出错
二者均持有ConversionService带来哪些加强?
阐明:对于如此重要的ConversionService你懂的,忘记了的可乘坐电梯到这温习
对于PrinterConverter和ParserConverter来讲,它们的源目标是实现 String <-> Object
,特点是:
- PrinterConverter:进口必须是String类型,入口类型也已确定,即
Printer<T>
的泛型类型,只能解决T(或T的子类型) -> String
- ParserConverter:入口必须是String类型,进口类型也已确定,即
Parser<T>
的泛型类型,只能解决String -> T(或T的子类型)
按既定“规定”,它俩的能力范畴还是蛮受限的。Spring厉害的中央就在于此,能够奇妙的通过组合的形式,扩充现有组件的能力边界。比方本利中它就在PrinterConverter/ParserConverter里别离放入了ConversionService援用,从而到这样的成果:
通过能力组合合作,起到串联作用,从而扩充输出/输入“范畴”,感觉就像起到了放大镜的成果一样,这个设计还是很讨巧的。
✍总结
本文以介绍FormatterRegistry
接口为核心,重点钻研了此接口的实现形式,发现即便小小的一枚注册核心实现,也蕴藏有丰盛亮点供以学习、CV。
一般来说ConversionService
天生具备十分强悍的转换能力,因而理论状况是你若须要自定义一个Printer/Parser的话是大概率不须要本人再额定加个Converter转换器的,也就是说底层机制让你未然站在了“伟人”肩膀上。
♨本文思考题♨
看完了不肯定懂,看懂了不肯定会。来,文末3个思考题帮你复盘:
- FormatterRegistry作为注册核心只有增加办法,why?
- 示例中为何强调:addConverter()注册转换器时请务必不要应用lambda表达式代替输出,会有什么问题?
- 这种性能组合/桥接的奇妙设计形式,你脑中还能想到其它案例吗?
☀举荐浏览☀
- ...
- 6. 抹平差别,对立类型转换服务ConversionService
- 7. JDK拍了拍你:字符串拼接肯定记得用MessageFormat#format
- 8. 格式化器大一统 -- Spring的Formatter形象
- ......
♚申明♚
本文所属专栏:Spring类型转换,公号后盾回复专栏名即可获取全部内容。
分享、成长,回绝浅藏辄止。关注【BAT的乌托邦】,回复关键字专栏有Spring技术栈、中间件等小而美的原创专栏供以收费学习。本文已被 https://www.yourbatman.cn 收录。
本文是 A哥(YourBatman)原创文章,未经作者容许/开白不得转载,谢谢合作。