9. 细节见真章,Formatter注册中心的设计很讨巧(下)

简介: 9. 细节见真章,Formatter注册中心的设计很讨巧(下)

示例二:使用Printer,有中间转换


基于示例一,若要实现Person -> String的话,只需再给写一个Person -> Integer的转换器放进ConversionService里即可。


说明:一般来说ConversionService已经具备很多“能力”了的,拿来就用即可。本例为了帮你说明底层原理,所以用的是一个“干净的”ConversionService实例


@Test
public 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));
}


运行程序,输出:

true
11


完美。


针对本例,有如下关注点:


1.使用addFormatterForFieldType()方法注册了IntegerPrinter,并且明确指定了处理的类型:只处理Person类型

   1.说明:IntegerPrinter是可以注册多次分别用于处理不同类型。比如你依旧可以保留formatterRegistry.addPrinter(new IntegerPrinter());来处理Integer -> String是木问题的


2.因为IntegerPrinter 实际上 只能转换 Integer -> String,因此还必须注册一个转换器,用于Person -> Integer桥接一下,这样就串起来了Person -> Integer -> String。只是外部看起来这些都是IntegerPrinter做的一样,特别工整


3.强调:addConverter()注册转换器时请务必不要使用lambda表达式代替输入,否则会失去泛型类型,导致出错

   1.若想用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;
  }


转换步骤分为两步:


  1. 通过Parser将String转换为指定的类型结果result(若失败,则抛出异常)
  2. 判断若result属于目标类型的子类型,直接返回,否则调用ConversionService转换一把


image.png


可以看到它和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,无中间转换


书写测试用例:


@Test
public 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));
}


运行程序,输出:

true
1


完美。


示例二:使用Parser,有中间转换


下面示例输入一个“1”字符串,出来一个Person对象(因为有了上面例子的铺垫,这里就“直抒胸臆”了哈)。


@Test
public 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,期待能够被解决喽:


image.png



为了正常运行本例,这么改一下:


/

// 第二个参数不传null,用IntegerPrinter占位
formatterRegistry.addFormatterForFieldType(Person.class, new IntegerPrinter(), new IntegerParser()


再次运行程序,输出:

true
Person(id=1, name=YourBatman)


完美。


针对本例,有如下关注点:


  1. 使用addFormatterForFieldType()方法注册了IntegerParser,并且明确指定了处理的类型,用于处理Person类型
  2. 也就是说此IntegerParser专门用于转换目标类型为Person的属性
  3. 因为IntegerParser 实际上 只能转换 String -> Integer,因此还必须注册一个转换器,用于Integer -> Person桥接一下,这样就串起来了String -> Integer -> Person。外面看起来这些都是IntegerParser做的一样,非常工整
  4. 同样强调: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引用,从而到这样的效果:

image.png



通过能力组合协作,起到串联作用,从而扩大输入/输出“范围”,感觉就像起到了放大镜的效果一样,这个设计还是很讨巧的。


✍总结


本文以介绍FormatterRegistry接口为中心,重点研究了此接口的实现方式,发现即使小小的一枚注册中心实现,也蕴藏有丰富亮点供以学习、CV。


一般来说ConversionService 天生具备非常强悍的转换能力,因此实际情况是你若需要自定义一个Printer/Parser的话是大概率不需要自己再额外加个Converter转换器的,也就是说底层机制让你已然站在了“巨人”肩膀上。


♨本文思考题♨


看完了不一定懂,看懂了不一定会。来,文末3个思考题帮你复盘:


  1. FormatterRegistry作为注册中心只有添加方法,why?
  2. 示例中为何强调:addConverter()注册转换器时请务必不要使用lambda表达式代替输入,会有什么问题?
  3. 这种功能组合/桥接的巧妙设计方式,你脑中还能想到其它案例吗?
相关文章
|
10月前
|
人工智能 自然语言处理 测试技术
在PyCharm中提升编程效率:通义灵码(DeepSeek)助手全攻略(新版)
最近小栈在PyCharm中使用了阿里的 通义灵码 插件还不错,本次就再分享一个好用的AI代码助手,让编码过程更加方便!
3390 17
|
量子技术
量子雷达:隐身技术的挑战者与未来防御系统
【9月更文挑战第19天】量子雷达凭借其突破隐身技术、高灵敏度及抗干扰性的优势,正成为未来防御系统的关键组成部分。本文深入探讨了量子雷达如何挑战传统隐身技术,并介绍了其在反隐身作战、导弹防御及空间探测等领域的广阔应用前景。随着技术进步,量子雷达将彻底改变现代战争模式,提升防御体系的效能。中国在这一领域已取得显著进展,展现出量子雷达的强大潜力。
FFmpeg学习笔记(二):多线程rtsp推流和ffplay拉流操作,并储存为多路avi格式的视频
这篇博客主要介绍了如何使用FFmpeg进行多线程RTSP推流和ffplay拉流操作,以及如何将视频流保存为多路AVI格式的视频文件。
1479 0
|
网络协议 Linux
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
云服务器内部端口占用,9090端口已经存在了,如何关闭,Linux查询端口,查看端口,端口查询,关闭端口写法-netstat -tuln,​fuser -k 3306/tcp​
|
7天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
6天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1619 6
|
2天前
|
消息中间件 人工智能 Kubernetes
阿里云云原生应用平台岗位急招,加入我们,打造 AI 最强基础设施
云原生应用平台作为中国最大云计算公司的基石,现全面转向 AI,打造 AI 时代最强基础设施。寻找热爱技术、具备工程极致追求的架构师、极客与算法专家,共同重构计算、定义未来。杭州、北京、深圳、上海热招中,让我们一起在云端,重构 AI 的未来。
|
2天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
561 11
|
8天前
|
安全 数据可视化 网络安全
安全无小事|阿里云先知众测,为企业筑牢防线
专为企业打造的漏洞信息收集平台
1333 2
|
7天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
365 160