5. 穿过拥挤的人潮,Spring已为你制作好高级赛道(上)

简介: 5. 穿过拥挤的人潮,Spring已为你制作好高级赛道(上)

✍前言


你好,我是YourBatman。


上篇文章 大篇幅把Spring全新一代类型转换器介绍完了,已经至少能够考个及格分。在介绍Spring众多内建的转换器里,我故意留下一个尾巴,放在本文专门撰文讲解。


为了让自己能在“拥挤的人潮中”显得不(更)一(突)样(出),A哥特意准备了这几个特殊的转换器助你破局,穿越拥挤的人潮,踏上Spring已为你制作好的高级赛道。


版本约定


  • Spring Framework:5.3.1
  • Spring Boot:2.4.0

image.png





✍正文


本文的焦点将集中在上文留下的4个类型转换器上。


  • StreamConverter:将Stream流与集合/数组之间的转换,必要时转换元素类型


这三个比较特殊,属于“最后的”“兜底类”类型转换器:


  • ObjectToObjectConverter:通用的将原对象转换为目标对象(通过工厂方法or构造器)
  • IdToEntityConverter:本文重点。给个ID自动帮你兑换成一个Entity对象
  • FallbackObjectToStringConverter:将任何对象调用toString()转化为String类型。当匹配不到任何转换器时,它用于兜底


默认转换器注册情况


Spring新一代类型转换内建了非常多的实现,这些在初始化阶段大都被默认注册进去。注册点在DefaultConversionService提供的一个static静态工具方法里:


static静态方法具有与实例无关性,我个人觉得把该static方法放在一个xxxUtils里统一管理会更好,放在具体某个组件类里反倒容易产生语义上的误导性


DefaultConversionService:
  public static void addDefaultConverters(ConverterRegistry converterRegistry) {
    // 1、添加标量转换器(和数字相关)
    addScalarConverters(converterRegistry);
    // 2、添加处理集合的转换器
    addCollectionConverters(converterRegistry);
    // 3、添加对JSR310时间类型支持的转换器
    converterRegistry.addConverter(new ByteBufferConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new StringToTimeZoneConverter());
    converterRegistry.addConverter(new ZoneIdToTimeZoneConverter());
    converterRegistry.addConverter(new ZonedDateTimeToCalendarConverter());
    // 4、添加兜底转换器(上面处理不了的全交给这几个哥们处理)
    converterRegistry.addConverter(new ObjectToObjectConverter());
    converterRegistry.addConverter(new IdToEntityConverter((ConversionService) converterRegistry));
    converterRegistry.addConverter(new FallbackObjectToStringConverter());
    converterRegistry.addConverter(new ObjectToOptionalConverter((ConversionService) converterRegistry));
  }
  }


该静态方法用于注册全局的、默认的转换器们,从而让Spring有了基础的转换能力,进而完成绝大部分转换工作。为了方便记忆这个注册流程,我把它绘制成图供以你保存:


image.png


特别强调:转换器的注册顺序非常重要,这决定了通用转换器的匹配结果(谁在前,优先匹配谁)。


针对这幅图,你可能还会有疑问:


1.JSR310转换器只看到TimeZone、ZoneId等转换,怎么没看见更为常用的LocalDate、LocalDateTime等这些类型转换呢?难道Spring默认是不支持的?

1.答:当然不是。 这么常见的场景Spring怎能会不支持呢?不过与其说这是类型转换,倒不如说是格式化更合适。所以会在后3篇文章格式化章节在作为重中之重讲述


2.一般的Converter都见名之意,但StreamConverter有何作用呢?什么场景下会生效

 1.答:本文讲述


3.对于兜底的转换器,有何含义?这种极具通用性的转换器作用为何

 1.答:本文讲述


StreamConverter


用于实现集合/数组类型到Stream类型的互转,这从它支持的Set<ConvertiblePair> 集合也能看出来:


@Override
public Set<ConvertiblePair> getConvertibleTypes() {
  Set<ConvertiblePair> convertiblePairs = new HashSet<ConvertiblePair>();
  convertiblePairs.add(new ConvertiblePair(Stream.class, Collection.class));
  convertiblePairs.add(new ConvertiblePair(Stream.class, Object[].class));
  convertiblePairs.add(new ConvertiblePair(Collection.class, Stream.class));
  convertiblePairs.add(new ConvertiblePair(Object[].class, Stream.class));
  return convertiblePairs;
}


它支持的是双向的匹配规则:


image.png


代码示例


/**
 * {@link StreamConverter}
 */
@Test
public void test2() {
    System.out.println("----------------StreamConverter---------------");
    ConditionalGenericConverter converter = new StreamConverter(new DefaultConversionService());
    TypeDescriptor sourceTypeDesp = TypeDescriptor.valueOf(Set.class);
    TypeDescriptor targetTypeDesp = TypeDescriptor.valueOf(Stream.class);
    boolean matches = converter.matches(sourceTypeDesp, targetTypeDesp);
    System.out.println("是否能够转换:" + matches);
    // 执行转换
    Object convert = converter.convert(Collections.singleton(1), sourceTypeDesp, targetTypeDesp);
    System.out.println(convert);
    System.out.println(Stream.class.isAssignableFrom(convert.getClass()));
}


运行程序,输出:


----------------StreamConverter---------------
是否能够转换:true
java.util.stream.ReferencePipeline$Head@5a01ccaa
true

关注点:底层依旧依赖DefaultConversionService完成元素与元素之间的转换。譬如本例Set -> Stream的实际步骤为:


image.png


也就是说任何集合/数组类型是先转换为中间状态的List,最终调用list.stream()转换为Stream流的;若是逆向转换先调用source.collect(Collectors.<Object>toList())把Stream转为List后,再转为具体的集合or数组类型。


说明:若source是数组类型,那底层实际使用的就是ArrayToCollectionConverter,注意举一反三


使用场景


StreamConverter它的访问权限是default,我们并不能直接使用到它。通过上面介绍可知Spring默认把它注册进了注册中心里,因此面向使用者我们直接使用转换服务接口ConversionService便可。


@Test
public void test3() {
    System.out.println("----------------StreamConverter使用场景---------------");
    ConversionService conversionService = new DefaultConversionService();
    Stream<Integer> result = conversionService.convert(Collections.singleton(1), Stream.class);
    // 消费
    result.forEach(System.out::println);
    // result.forEach(System.out::println); //stream has already been operated upon or closed
}


运行程序,输出:

----------------StreamConverter使用场景---------------
1



再次特别强调:流只能被读(消费)一次。


因为有了ConversionService提供的强大能力,我们就可以在基于Spring/Spring Boot做二次开发时使用它,提高系统的通用性和容错性。如:当方法入参是Stream类型时,你既可以传入Stream类型,也可以是Collection类型、数组类型,是不是瞬间逼格高了起来。


image.png



相关文章
|
7月前
|
Java 开发者 Spring
Spring高级杂记
Spring高级杂记
43 0
|
7月前
|
存储 缓存 Java
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
【Spring原理高级进阶】有Redis为啥不用?深入剖析 Spring Cache:缓存的工作原理、缓存注解的使用方法与最佳实践
|
7月前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
|
7月前
|
Java 测试技术 数据库连接
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
【Spring源码解读!底层原理高级进阶】【下】探寻Spring内部:BeanFactory和ApplicationContext实现原理揭秘✨
|
6月前
|
前端开发 Java 开发者
深入理解 Spring Boot 注解:核心功能与高级用法详解
深入理解 Spring Boot 注解:核心功能与高级用法详解
331 1
|
7月前
|
存储 安全 Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(下)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
92 2
|
7月前
|
安全 Cloud Native Java
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)(上)
第10章 Spring Security 的未来趋势与高级话题(2024 最新版)
81 2
|
7月前
|
数据采集 前端开发 Java
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
数据塑造:Spring MVC中@ModelAttribute的高级数据预处理技巧
70 3
|
7月前
|
XML Java 数据格式
进阶注解探秘:深入Spring高级注解的精髓与实际运用
进阶注解探秘:深入Spring高级注解的精髓与实际运用
71 2
|
7月前
|
Java 关系型数据库 MySQL
高级对象装配:解析Spring创建复杂对象的秘诀
高级对象装配:解析Spring创建复杂对象的秘诀
62 0
高级对象装配:解析Spring创建复杂对象的秘诀