《手写Spring渐进式源码实践》实践笔记(第十七章 数据类型转换)

ops/2024/11/13 22:09:17/

文章目录

  • 第十七章 数据类型转换工厂设计实现
    • 背景
      • 技术背景
        • Spring数据转换实现方式
        • 类型转换器(Converter)接口设计实现
      • 业务背景
    • 目标
    • 设计
    • 实现
      • 代码结构
      • 类图
      • 实现步骤
    • 测试
      • 事先准备
      • 属性配置文件
      • 转换器工厂Bean
      • 测试用例
      • 测试结果:
    • 总结


第十七章 数据类型转换工厂设计实现

背景

技术背景

Spring数据转换实现方式

Spring提供了多种机制来实现数据转换,以下是一些主要的方式:

  1. 类型转换器(Converter)接口: Spring提供了Converter<S, T>接口,允许开发者自定义转换逻辑。通过实现这个接口,你可以定义如何将源类型S转换为目标类型T。例如:

    java">@Component
    public class StringToIntegerConverter implements Converter<String, Integer> {@Overridepublic Integer convert(String source) {return Integer.valueOf(source);}
    }
    

    然后,你可以在配置中注册这个转换器,Spring会自动在需要的时候使用它。

  2. 属性编辑器(PropertyEditor)PropertyEditor是Java的一个老机制,Spring也支持它。通过实现PropertyEditor接口,你可以定义如何将字符串转换为其他类型。不过,从Spring 5开始,推荐使用Converter接口,因为PropertyEditor将在未来版本中被弃用。

  3. 格式化程序(Formatter)Formatter是Java的一个接口,用于格式化和解析对象。Spring通过FormattingConversionService支持Formatter,可以在Spring MVC中使用它来格式化和解析数据。

  4. 自动应用的转换器(Global Converters): 在Spring MVC中,可以通过实现WebMvcConfigurer接口的addFormatters方法来添加全局的转换器,这些转换器会对所有的控制器方法参数和返回值进行转换。

    java

    java">@Override
    public void addFormatters(FormatterRegistry registry) {registry.addConverter(new StringToIntegerConverter());
    }
    
  5. 局部转换器(Local Converters): 除了全局转换器,你还可以在方法级别使用@InitBinder注解来添加局部转换器,这些转换器只对特定的控制器或请求参数有效。

  6. Spring Expression Language(SpEL): SpEL也支持类型转换,当你在表达式中使用不同类型的值时,SpEL会尝试进行适当的类型转换。

  7. Java标准转换: Spring还会利用Java的自动装箱和拆箱特性,以及Number类的intValuelongValue等方法来进行基本的类型转换。

  8. 自定义转换服务: 你可以通过编程方式创建ConversionService的实例,并注册自定义的转换器,然后在你的应用程序中使用这个服务来进行类型转换。

类型转换器(Converter)接口设计实现

Spring 中的类型转换器(Converter)接口的底层生效思路主要涉及以下几个方面:

  1. 接口定义Converter<S, T> 是一个函数式接口,用于将类型 S 的对象转换为类型 T 的对象。这个接口定义了一个 convert 方法,该方法接受一个类型为 S 的源对象,并返回一个类型为 T 的目标对象。

  2. 转换器注册: 转换器可以通过实现 Converter 接口并注册到 ConversionService 中来生效。ConversionService 是 Spring 中的一个核心接口,负责管理所有的转换器,并根据需要调用适当的转换器来执行类型转换。

  3. 条件转换器ConditionalConverter 接口允许在转换过程中添加条件判断,以决定是否使用特定的转换器。这提供了更细粒度的控制,允许在运行时根据具体情况选择是否应用某个转换器。

  4. 转换器工厂ConverterFactory<S, R> 接口用于创建一系列相关转换器,它允许从类型 S 转换到类型 R 的子类型。这种设计模式支持了范围转换,即从一种类型到多种可能的子类型的转换。

  5. 泛型转换器GenericConverter 接口提供了在两个或多个类型之间进行转换的能力,它是最灵活但也是最为复杂的转换器SPI接口。GenericConverter 可以处理更复杂的转换场景,比如集合到集合的转换。

  6. 默认转换服务DefaultConversionServiceGenericConversionService 的一个实现,它提供了一个默认的转换服务,包含了许多内置的转换器,覆盖了大多数常用的类型转换场景。如果需要自定义转换逻辑,可以向 DefaultConversionService 添加自定义的转换器。

  7. 转换器查找和缓存: Spring 会根据源类型和目标类型查找合适的转换器。如果找到了匹配的转换器,Spring 会将其缓存起来以提高性能。如果缓存中没有找到匹配的转换器,Spring 会尝试查找并添加新的转换器,然后再次尝试转换。

  8. 转换器的链式调用Converter 接口中的 andThen 方法支持转换器的链式调用,允许将多个转换步骤串联起来,先应用一个转换器,然后将结果传递给下一个转换器。

业务背景

  1. 其实实现到本章节,关于IOC、AOP在日常使用和面试中高频出现的技术点都该涵盖了。那么为了补全整个框架内容的结构,方便大家后续在阅读 Spring 时不至于对类型转换的知识体系陌生,这里添加数据类型转换中关于类型转换器的实现。
  2. 类型转换也叫做数据转换,比如从String到Integer、从String到Date、从Double到Long等等,但这些操作不能在已经使用框架的情况下还需要手动处理,所以我们要把这样的功能扩展到Spring框架中。

目标

基于当前实现的 Spring 框架,扩展类型转换功能,并熟悉spring组件服务的开发思路。

设计

如果我们来把只是看上去一个简单的类型转换操作抽象成框架,那么它就需要一个标准的接口,谁实现这个接口就具备类型转换的具体实现,提供类型转换的能力。

那么在有了这样接口后,还需要类型转换服务的注册、工厂等内容,才可以把类型转换抽象成一个组件服务。整体设计结构如下图:

image-20241107163620150
  • 首先从工厂出发我们需要实现一个 ConversionServiceFactoryBean 来对类型转换服务进行操作。

  • 而实现类型转换的服务,需要定义 Converter 转换类型、ConverterRegistry 注册类型转换功能,另外转换类型的操作较多,所以这里也会需要定义一个类型转换工厂 ConverterFactory 各个具体的转换操作来实现这个工厂接口。

实现

代码结构

image-20241107163702901

源码实现:https://github.com/swg209/spring-study/tree/main/step17-type-converter-factory

类图

image-20241107163812431

实现步骤

  1. 类型转换接口

    a. Converter 转换器,提供类型转换方法。

    java">public interface Converter<S, T> {/*** 将source从 S类型转换为T类型.*/T convert(S source);
    }

    b. ConverterFactory 转换工厂,用于获取转换器。

    java">public interface ConverterFactory<S, R> {/*** 获取转换器,将source从S类型转换为T类型,其中T也是R的实例。** @param targetType* @param <T>* @return*/<T extends R> Converter<S, T> getConverter(Class<T> targetType);}
    

    c. ConverterRegistry 转换注册中心,用于服务注册转换器、转换工厂。

    java">public interface ConverterRegistry {void addConverter(Converter<?, ?> converter);void addConverter(GenericConverter converter);void addConverterFactory(ConverterFactory<?, ?> converterFactory);}

    d. ConverterService 转换服务,提供 判断类型能否转换以及转换方法。

    java">public interface ConversionService {/*** 是否可以转换.** @param sourceType* @param targetType* @return*/boolean canConvert(Class<?> sourceType, Class<?> targetType);/*** 转换.** @param source* @param targetType* @param <T>* @return*/<T> T convert(Object source, Class<T> targetType);
    }
    

    e. GenericConverter 通用转换器,门面接口,并有一个内部类 ConvertiblePair,包装 源类型 与 目标类型。

    java">public interface GenericConverter {/*** Return the source and target types that this converter can convert between.* 返回此转换器可以在其之间转换的源和目标类型.*/Set<ConvertiblePair> getConvertibleTypes();/*** Convert the source object to the targetType described by the {@code TypeDescriptor}.* 将源对象转换为由{@code TypeDescriptor}描述的targetType.** @param source* @param sourceType* @param targetType* @return*/Object convert(Object source, Class sourceType, Class targetType);/*** Holder for a source-to-target class pair.* 源类型到目标类型的持有者.*/final class ConvertiblePair {private final Class<?> sourceType;private final Class<?> 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;}@Overridepublic boolean equals(Object obj) {if (this == obj) {return true;}if (obj == null || obj.getClass() != ConvertiblePair.class) {return false;}ConvertiblePair other = (ConvertiblePair) obj;return this.sourceType.equals(other.sourceType) && this.targetType.equals(other.targetType);}@Overridepublic int hashCode() {return this.sourceType.hashCode() * 31 + this.targetType.hashCode();}}
    }
  2. 类型转换服务

a. GenericConversionService , 实现 ConversionService ,ConverterRegistry, 并添加 根据 转换器或工厂 生成对应类型的 ConvertiblePair 以及 遍历源和目标一系列超类的所有组合,获取转换器 的方法辅助,设有 内部类 ConverterFactoryAdapter ,ConverterAdapter 实现 GenericConverter,用于 适配 通用转换器,方便注册及调用。

java">public class GenericConversionService implements ConversionService, ConverterRegistry {// 转换器映射表.private Map<ConvertiblePair, GenericConverter> converters = new HashMap<>();@Overridepublic boolean canConvert(Class<?> sourceType, Class<?> targetType) {GenericConverter converter = getConvert(sourceType, targetType);return converter != null;}@Overridepublic <T> T convert(Object source, Class<T> targetType) {Class<?> sourceType = source.getClass();GenericConverter converter = getConvert(sourceType, targetType);return (T) converter.convert(source, sourceType, targetType);}@Overridepublic void addConverter(Converter<?, ?> converter) {GenericConverter.ConvertiblePair typeInfo = getRequiredTypeInfo(converter);ConverterAdapter converterAdapter = new ConverterAdapter(typeInfo, converter);for (GenericConverter.ConvertiblePair convertibleType : converterAdapter.getConvertibleTypes()) {converters.put(convertibleType, converterAdapter);}}@Overridepublic void addConverter(GenericConverter converter) {// 遍历转换器支持的类型,将其添加到转换器映射表中.for (GenericConverter.ConvertiblePair convertibleType : converter.getConvertibleTypes()) {converters.put(convertibleType, converter);}}@Overridepublic void addConverterFactory(ConverterFactory<?, ?> converterFactory) {ConvertiblePair typeInfo = getRequiredTypeInfo(converterFactory);ConverterFactoryAdapter converterFactoryAdapter = new ConverterFactoryAdapter(typeInfo, converterFactory);for (ConvertiblePair convertibleType : converterFactoryAdapter.getConvertibleTypes()) {converters.put(convertibleType, converterFactoryAdapter);}}private GenericConverter getConvert(Class<?> sourceType, Class<?> targetType) {List<Class<?>> sourceCandidates = getClassHierarchy(sourceType);List<Class<?>> targetCandidates = getClassHierarchy(targetType);for (Class<?> sourceCandidate : sourceCandidates) {for (Class<?> targetCandidate : targetCandidates) {ConvertiblePair convertiblePair = new ConvertiblePair(sourceCandidate, targetCandidate);GenericConverter converter = converters.get(convertiblePair);if (converter != null) {return converter;}}}return null;}private List<Class<?>> getClassHierarchy(Class<?> clazz) {List<Class<?>> hierarchy = new ArrayList<>();while (clazz != null) {hierarchy.add(clazz);clazz = clazz.getSuperclass();}return hierarchy;}//因为要同时兼容 Converter、ConverterFactory,所以这里传参为Object.private ConvertiblePair getRequiredTypeInfo(Object object) {Type[] types = object.getClass().getGenericInterfaces();ParameterizedType parameterizedType = (ParameterizedType) types[0];Type[] actualTypeArguments = parameterizedType.getActualTypeArguments();Class sourceType = (Class) actualTypeArguments[0];Class targetType = (Class) actualTypeArguments[1];return new ConvertiblePair(sourceType, targetType);}private final class ConverterAdapter implements GenericConverter {private final ConvertiblePair typeInfo;private final Converter<Object, Object> converter;public ConverterAdapter(ConvertiblePair typeInfo, Converter<?, ?> converter) {this.typeInfo = typeInfo;this.converter = (Converter<Object, Object>) converter;}@Overridepublic Set<ConvertiblePair> getConvertibleTypes() {return Collections.singleton(typeInfo);}@Overridepublic Object convert(Object source, Class sourceType, Class targetType) {return converter.convert(source);}}private final class ConverterFactoryAdapter implements GenericConverter {private final ConvertiblePair typeInfo;private final ConverterFactory<Object, Object> converterFactory;public ConverterFactoryAdapter(ConvertiblePair typeInfo, ConverterFactory<?, ?> converterFactory) {this.typeInfo = typeInfo;this.converterFactory = (ConverterFactory<Object, Object>) converterFactory;}@Overridepublic Set<ConvertiblePair> getConvertibleTypes() {return Collections.singleton(typeInfo);}@Overridepublic Object convert(Object source, Class sourceType, Class targetType) {return converterFactory.getConverter(targetType).convert(source);}}
}

b. DefaultConversionService ,继承 GenericConversionService ,通过构造函数注册 内置的 类型转换器或工厂, 并对外作为一个 注册中心使用。

java">public class DefaultConversionService extends GenericConversionService {public DefaultConversionService() {addDefaultConverters(this);}public static void addDefaultConverters(ConverterRegistry converterRegistry) {// 添加各类类型转换工厂.converterRegistry.addConverterFactory(new StringToNumberConverterFactory());}
}
  1. 融入 Bean 的生命周期
    a. 类型转换工厂 ConversionServiceFactoryBean,实现 FactoryBean, InitializingBean,这样 在其初始化时就可以 注册类型转换器、工厂。

    java">public class ConversionServiceFactoryBean implements FactoryBean<ConversionService>, InitializingBean {@Nullableprivate Set<?> converters;@Nullableprivate GenericConversionService conversionService;@Overridepublic ConversionService getObject() throws Exception {return conversionService;}@Overridepublic Class<?> getObjectType() {return conversionService.getClass();}@Overridepublic boolean isSingleton() {return true;}@Overridepublic void afterPropertiesSet() throws BeansException {this.conversionService = new DefaultConversionService();registerConverters(converters, conversionService);}private void registerConverters(Set<?> converters, ConverterRegistry registry) {if (converters != null) {for (Object converter : converters) {if (converter instanceof GenericConverter) {registry.addConverter((GenericConverter) converter);} else if (converter instanceof Converter<?, ?>) {registry.addConverter((Converter<?, ?>) converter);} else if (converter instanceof ConverterFactory<?, ?>) {registry.addConverterFactory((ConverterFactory<?, ?>) converter);} else {throw new IllegalArgumentException("Each converter object must implement one of the "+ "Converter, ConverterFactory, or GenericConverter interfaces"+ "Unsupported converter type: " + converter.getClass());}}}}public void setConverters(Set<?> converters) {this.converters = converters;}
    }

b. 通过 ConfigurableBeanFactory 添加 设置、获取类型转换服务方法,由 AbstractBeanFactory 实现,使得 在刷新容器 实例化单例对象前,可以优先获取到 ConversionServiceFactoryBean ,并设置 beanfactory 的 类型转换服务;并在 AbstractAutowireCapableBeanFactory 的 createBean 属性填充前 增加 类型转换的操作。

测试

事先准备

多类型属性的类 Husband

Husband

java">public class Husband {private String wifeName;private LocalDate marriageDate;public String getWifeName() {return wifeName;}public void setWifeName(String wifeName) {this.wifeName = wifeName;}public LocalDate getMarriageDate() {return marriageDate;}public void setMarriageDate(LocalDate marriageDate) {this.marriageDate = marriageDate;}@Overridepublic String toString() {return "Husband{" +"wifeName='" + wifeName + '\'' +", marriageDate=" + marriageDate +'}';}
}

属性配置文件

spring.xml

  1. 配置husband,类型转换服务工厂Bean, 类型转换服务
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsd"><bean id="husband" class="cn.suwg.springframework.test.bean.Husband"><property name="wifeName" value="老老"/><property name="marriageDate" value="2022-04-14"/></bean><bean id="conversionService" class="cn.suwg.springframework.context.support.ConversionServiceFactoryBean"><property name="converters" ref="converters"/></bean><bean id="converters" class="cn.suwg.springframework.test.converter.ConvertersFactoryBean"/>
</beans>

转换器工厂Bean

ConvertersFactoryBean

java">public class ConvertersFactoryBean implements FactoryBean<Set<?>> {@Overridepublic Set<?> getObject() throws Exception {HashSet<Object> converters = new HashSet<>();StringToLocalDateConverter stringToLocalDateConverter = new StringToLocalDateConverter("yyyy-MM-dd");converters.add(stringToLocalDateConverter);return converters;}@Overridepublic Class<?> getObjectType() {return null;}@Overridepublic boolean isSingleton() {return true;}
}

StringToIntegerConverter

java">public class StringToIntegerConverter implements Converter<String, Integer> {@Overridepublic Integer convert(String source) {return Integer.valueOf(source);}
}

StringToLocalDateConverter

java">public class StringToLocalDateConverter implements Converter<String, LocalDate> {private final DateTimeFormatter DATE_TIME_FORMATTER;public StringToLocalDateConverter(String pattern) {DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(pattern);}@Overridepublic LocalDate convert(String source) {return LocalDate.parse(source, DATE_TIME_FORMATTER);}
}

测试用例

java">public class ApiTest {@Testpublic void testConvert() {ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:spring.xml");Husband husband = applicationContext.getBean("husband", Husband.class);System.out.println("测试结果: " + husband);}//返回转换好的日期. 年-月-日@Testpublic void testStringToLocalDateConverter() {StringToLocalDateConverter converter = new StringToLocalDateConverter("yyyy-MM-dd hh:mm:ss");LocalDate convert = converter.convert("2024-11-07 10:00:11");LocalDate expect = LocalDate.of(2024, 11, 7);Assert.assertEquals(expect, convert);System.out.println(convert);}// 测试字符串转换为整数.@Testpublic void testStringToIntegerConverter() {StringToIntegerConverter converter = new StringToIntegerConverter();Integer convert = converter.convert("12345");Assert.assertEquals(Integer.valueOf(12345), convert);System.out.println(convert);}
}

测试结果:

image-20241107170138185

  • 从测试结果可以看到,字符串转日期转换器功能正常。

image-20241107170120413

  • 从测试结果可以看到,字符串转数字转换器功能正常。

image-20241107172248023

  • 从测试结果可以看到,对象属性值可以正常输出到日志。这个测试内容还是比较简单的,可以自行验证结果,虽然最终的结果看上去比较简单,但整个框架结构实现设计还是蛮复杂的,把这么一个转换操作抽象为接口适配、工厂模型等方式,还是很值得借鉴的。

总结

  • 本章节实现的类型转换操作如果只是功能性的开发,就像你自己承接的需求那样,可能只是简单的if判断就搞定了,但放在一个成熟的框架要中要考虑的是可复用性、可扩展性,所以会看到接口的定义、工厂的使用等等设计模式在这里体现。
  • 恭喜你,能够坚持学习到这一章节。如果你在学习过程中,每一章节都是通过阅读文章、编写代码、调试错误以及理解设计来完成的,那么一定会在这个过程中获得很多宝贵的经验和知识。以后再次阅读Spring源码时,就不会觉得那么困难了。

参考书籍:《手写Spring渐进式源码实践》

书籍源代码:https://github.com/fuzhengwei/small-spring


http://www.ppmy.cn/ops/132943.html

相关文章

数据中心类DataCenter(三)

数据中心类DataCenter&#xff08;三&#xff09; 前言 在上一集&#xff0c;我们就完成了整个数据中心类的构造函数以及析构函数&#xff0c;我们讨论了我们的数据持久化的相关事宜&#xff0c;那么我们这一集就要来完成这一个内容。 需求分析 我们需要暂且就规定我们的路…

springboot快递物流管理系统-计算机设计毕业源码85178

目 录 摘要 1 绪论 1.1 选题背景与意义 1.2国内外研究现状 1.3论文结构与章节安排 2 快递物流管理系统分析 2.1 可行性分析 2.1.1 技术可行性分析 2.1.2 经济可行性分析 2.1.3 操作可行性分析 2.2 系统流程分析 2.2.1数据增加流程 2.2.2 数据修改流程 2.2.3 数据…

PCD文件(Point Cloud Data)详细解析

PCD文件&#xff08;Point Cloud Data&#xff09;详细解析 PCD文件格式是专为存储和处理三维点云数据而设计的&#xff0c;由开源项目Point Cloud Library&#xff08;PCL&#xff09;引入。该格式广泛应用于机器人视觉、计算机视觉以及三维建模等领域&#xff0c;支持多种数…

一文详解@Async注解

背景 Java中的Async注解是Spring框架提供的一个功能强大的异步执行注解&#xff0c;它允许开发者将某些方法的执行异步化&#xff0c;从而提高应用程序的响应性能和吞吐量。 使用方法 要使用Async注解&#xff0c;首先需要在配置类上添加EnableAsync注解&#xff0c;以启用Spri…

github信息收集规则

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 github信息收集 github信息收集 •site:Github.com password•site:Github.com ftp ftppassword •site:Github.com 密码 •site:Github.com 内部 •in:name test …

深度学习之卷积问题

1 卷积在图像中有什么直观作用 ​ 在卷积神经网络中&#xff0c;卷积常用来提取图像的特征&#xff0c;但不同层次的卷积操作提取到的特征类型是不相同的&#xff0c;特征类型粗分如表1所示。 ​ 表1 卷积提取的特征类型 卷积层次特征类型浅层卷积边缘特征中层卷积局部特征深…

react-router-dom中的几个hook

useLocation useLocation钩子返回当前的location对象。 useLocation相关的属性&#xff1a; pathname:当前URL的路径部分&#xff08;如/about&#xff09;search:URL的查询字符串&#xff08;如?nameJohn&#xff09;hash:URL的哈希片段&#xff08;如#section1&#xff09…

【MySQL】深度学习与解析 : 库的操作知识整合

MySQL是一种开源的关系型数据库管理系统&#xff0c;被广泛应用于各种应用软件中。在深度学习中&#xff0c;MySQL可以用于存储和管理大量的数据&#xff0c;如训练数据、模型参数等。以下整理了一些MySQL库的操作知识&#xff1a; 1. 创建数据库&#xff1a; CREATE DATABA…