第07篇:巧用Spring类型转换, ConverterFormatter知识点学习。
创始人
2024-01-16 05:18:06
0

公众号: 西魏陶渊明

CSDN: https://springlearn.blog.csdn.net

天下代码一大抄, 抄来抄去有提高, 看你会抄不会抄!

文章目录

    • 一、前言
    • 1.1 类型转换
    • 1.2 格式化输出
    • 二、Converter 类型转换
    • 2.1 Converter
      • 2.1.1 接口定义
      • 2.1.2 接口功能
    • 2.2 ConverterFactory
      • 2.2.1 接口定义
      • 2.2.2 接口功能
    • 2.3 GenericConverter
      • 2.3.1 接口定义
      • 2.3.2 接口功能
    • 2.4 ConditionalGenericConverter
      • 2.4.1 接口定义
    • 2.5 Spring 实践
      • 2.5.1 ConversionService
      • 2.5.2 硬编码使用
      • 2.5.3 整合Spring
    • 三、Formatter 格式化输出
    • 3.1 自定义Formatter
    • 3.1 注解驱动Formatter

一、前言

本篇文章中的内容,非常的小众,虽然在实际开发中,基本上不会有使用的场景,但是在Spring中却无处不在的知识点。因为我们是学习Spring,所以我们最好了解一下。希望对你有用,最终能运用在Spring框架的扩展上。

本篇文章,主要学习两个东西。第一个是类型转换, 第二个是格式化输出(支持国际化)。

1.1 类型转换

类型转换,比如说Long类型转换Date、String类型转换Long类型。
在实际的开发中我们可能直接使用 BeanUtils.copy() 或者其他三方工具来实现,但其实Spring已经提供了这种的接口能力了。我们只需要下面这样就可以了。

如下演示,将Long类型转Date。

@SpringBootApplication
public class Application {// 注册一个转换器,目标由Long转Datepublic static class LongToDateConvert implements Converter {@Overridepublic Date convert(Long source) {return new Date(source);}}@Bean("customerConvert")public ConversionServiceFactoryBean customerConvert() {ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();conversionServiceFactoryBean.setConverters(Collections.singleton(new LongToDateConvert()));return conversionServiceFactoryBean;}
}
@SpringBootTest
@TestConfiguration
public class SpringConvertTest {@Autowired@Qualifier("customerConvert")private ConversionService conversionService;@Testpublic void test() {// 直接使用即可。Date convert = conversionService.convert(System.currentTimeMillis(), Date.class);// Mon Oct 17 21:38:07 CST 2022System.out.println(convert);}
}

举一反三,通过上面的接口能力,我们还能实现更多的使用场景。如上我们只实现了1:1的转换,其还可以1:N、N:N,更多的内容下面会讲。

1.2 格式化输出

什么是格式化输出,往往只针对的是文本类型。

  1. 对象类型转文本类型
  2. 文本类型转对象类型

所以格式化是围绕String进行的,在格式化这方面最典型的一个案例就是国际化。

同样的文本,针对不同国家地域展示为当地的语言类型。
下面我们看他的接口定义。

public interface Formatter extends Printer, Parser {
}@FunctionalInterface
public interface Printer {// 对象类型,转换String类型,支持国际化String print(T object, Locale locale);
}
@FunctionalInterface
public interface Parser {// String类型转换泛型,支持国际化T parse(String text, Locale locale) throws ParseException;
}

下面我们看详细的内容。

二、Converter 类型转换

Spring 3 引入了一个core.convert提供通用类型转换系统的包。系统定义了一个 SPI 来实现类型转换逻辑和一个 API 来在运行时执行类型转换。在 Spring 容器中,您可以使用此系统作为实现的替代PropertyEditor方案,将外部化的 bean 属性值字符串转换为所需的属性类型。您还可以在应用程序中需要类型转换的任何地方使用公共 API。

接口介绍
Converter单一的类型转换,从泛型 S -> T
ConverterFactory按照官方的描述是,具有层次的转换,从泛型 S -> 转换成 R 的子类,实现一对多个类型的转换
GenericConverter前面是一对多,一对一,这个是多对多
ConditionalGenericConverter在前者的基础上,添加上条件判断,符合条件才进行转换

下面我们来以此看下,每个接口的

2.1 Converter

2.1.1 接口定义

package org.springframework.core.convert.converter;public interface Converter {T convert(S source);
}

这个接口非常的简单,没什么好解释的。我们要创建自己的转换器,只用实现Converter接口就可以了。

2.1.2 接口功能

实现从 S,向 T 的泛型转换,Spring提供了很多内置的转换,如下示例。

Spring默认提供了很多的默认实现,下面我们看一个简单的实现。看下面的源码,感觉Spring是真的用心呀。

  • on、true、1、yes 都会转换成 true
  • off、false、0、no 都会转换成 false
final class StringToBooleanConverter implements Converter {private static final Set trueValues = new HashSet(8);private static final Set falseValues = new HashSet(8);StringToBooleanConverter() {}@Nullablepublic Boolean convert(String source) {String value = source.trim();if (value.isEmpty()) {return null;} else {value = value.toLowerCase();if (trueValues.contains(value)) {return Boolean.TRUE;} else if (falseValues.contains(value)) {return Boolean.FALSE;} else {throw new IllegalArgumentException("Invalid boolean value '" + source + "'");}}}static {trueValues.add("true");trueValues.add("on");trueValues.add("yes");trueValues.add("1");falseValues.add("false");falseValues.add("off");falseValues.add("no");falseValues.add("0");}
}

2.2 ConverterFactory

ConverterFactory 跟 Converter的区别在于, ConverterFactory 提供一个泛化的接口。根据泛型获取自己的转换类。但是前提是Converter要具备能处理返回接口的能力。以此来处理 1 对 N的转换。

2.2.1 接口定义

package org.springframework.core.convert.converter;public interface ConverterFactory { Converter getConverter(Class targetType);
}

可以看到泛型是从 S -> R, getConverter 泛型方法允许 返回 T

2.2.2 接口功能

package org.springframework.core.convert.support;final class StringToEnumConverterFactory implements ConverterFactory {public  Converter getConverter(Class targetType) {return new StringToEnumConverter(targetType);}private final class StringToEnumConverter implements Converter {private Class enumType;public StringToEnumConverter(Class enumType) {this.enumType = enumType;}// 将泛化类型public T convert(String source) {return (T) Enum.valueOf(this.enumType, source.trim());}}
}

通过 的限定, 最终实现 1 : N 的转换。

2.3 GenericConverter

  • Converter 处理 1:1的转换
  • ConverterFactory 处理 1:N的转换
  • GenericConverter 处理里 N: N的转换

下面我们看接口

2.3.1 接口定义

  • getConvertibleTypes 返回了一个集合,而集合中每个key都是一个键值对。就支持 N:N 了。
package org.springframework.core.convert.converter;public interface GenericConverter {public Set getConvertibleTypes();Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

getConvertibleTypes() 是一个Set集合。可以看到ConvertiblePair是成对的,只要转换双方是包含在这set结合中,都会调用这个进行转换。

final class ConvertiblePair {private final Class sourceType;private final Class targetType;
}  

意味着一个转换器可以处理多种类型的转换。

2.3.2 接口功能

下面举一个例子

 @Datapublic static class SourceOne {private String name;}@Datapublic static class TargetOne {private String name;}@Datapublic static class TargetTwo {private String name;}@Testpublic void test() {ApplicationConversionService applicationConversionService = new ApplicationConversionService();GenericConverter genericConverter = new GenericConverter() {@Overridepublic Set getConvertibleTypes() {Set paris = new HashSet<>();paris.add(new ConvertiblePair(SourceOne.class, TargetOne.class));paris.add(new ConvertiblePair(SourceOne.class, TargetTwo.class));return paris;}@Overridepublic Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {if (sourceType.getObjectType().equals(SourceOne.class) && targetType.getObjectType().equals(TargetOne.class)) {TargetOne targetOne = new TargetOne();targetOne.setName(((SourceOne) source).getName() + "-> TargetOne");return targetOne;}if (sourceType.getObjectType().equals(SourceOne.class) && targetType.getObjectType().equals(TargetTwo.class)) {TargetTwo TargetTwo = new TargetTwo();TargetTwo.setName(((SourceOne) source).getName() + "-> TargetTwo");return TargetTwo;}return null;}};applicationConversionService.addConverter(genericConverter);SourceOne sourceOne = new SourceOne();sourceOne.setName("Jay");System.out.println(applicationConversionService.convert(sourceOne, TargetOne.class));System.out.println(applicationConversionService.convert(sourceOne, TargetTwo.class));}

2.4 ConditionalGenericConverter

有时,你希望 Converter只有在特定条件成立时才运行,此时可以实现这个接口。这个接口是实现了 GenericConverterConditionalConverter

2.4.1 接口定义

public interface ConditionalConverter {boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

2.5 Spring 实践

ConversionService定义了一个统一的 API,用于在运行时执行类型转换逻辑。

2.5.1 ConversionService

package org.springframework.core.convert;public interface ConversionService {// 如果sourceType的对象可以转换为targetType ,则返回true boolean canConvert(Class sourceType, Class targetType);// 将给定的source转换为指定的targetType 。 T convert(Object source, Class targetType);// 如果sourceType的对象可以转换为targetType ,则返回trueboolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);// 将给定的source转换为指定的targetType 。 TypeDescriptors 提供有关将发生转换的源和目标位置的附加上下文,通常是对象字段或属性位置。Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

2.5.2 硬编码使用

如果转换失败会抛出 org.springframework.core.convert.ConversionFailedException

public class ConvertTest {@Testpublic void test(){ConversionService sharedInstance = DefaultConversionService.getSharedInstance();System.out.println(sharedInstance.convert("1", Boolean.class));System.out.println(sharedInstance.convert("123", Long.class));System.out.println(sharedInstance.convert("1234", Integer.class));System.out.println(sharedInstance.convert("1235", int.class));}}

2.5.3 整合Spring

@SpringBootApplication
public class Application {public static class LongToDateConvert implements Converter {@Overridepublic Date convert(Long source) {return new Date(source);}}@Bean("customerConvert")public ConversionServiceFactoryBean customerConvert() {ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();conversionServiceFactoryBean.setConverters(Collections.singleton(new LongToDateConvert()));return conversionServiceFactoryBean;}
}@SpringBootTest
@TestConfiguration
public class SpringConvertTest {@Autowired@Qualifier("customerConvert")private ConversionService conversionService;@Testpublic void test() {Date convert = conversionService.convert(System.currentTimeMillis(), Date.class);System.out.println(convert);}
}

三、Formatter 格式化输出

core.convert 是一个通用的类型转换系统。它提供了一个统一的ConversionServiceAPI 以及一个强类型的ConverterSPI,用于实现从一种类型到另一种类型的转换逻辑。

现在考虑典型客户端环境的类型转换要求,例如 Web 或桌面应用程序。在这样的环境中,您通常转换 fromString 以支持客户端回发过程,以及转换回String以支持视图呈现过程。

此外,您经常需要本地化String值(国际化)。core.convert Converter 不直接解决此类格式要求。为了直接解决这些问题,Spring 3 引入了一个方便的SPI,它为客户端环境的实现Formatter提供了一个简单而健壮的替代方案。PropertyEditor。

3.1 自定义Formatter

要想自定义Formatter我们只用实现 Formatter 接口即可。下面我们看他们的接口定义,就能看到。
Formatter 跟Convert的区别是什么。

  • Formatter 只支持String和对象的双向转换,适合文本格式化、国际化的处理。
  • Converter 支持任意类型的转换
public interface Formatter extends Printer, Parser {}
@FunctionalInterface
public interface Printer {// 对象转StringString print(T object, Locale locale);
}
@FunctionalInterface
public interface Parser {// String转对象T parse(String text, Locale locale) throws ParseException;
}

如下我们自定义一个时间的转换器

public class DateFormatterTest {@Testpublic void test() {DefaultFormattingConversionService defaultFormattingConversionService = new DefaultFormattingConversionService();defaultFormattingConversionService.addFormatter(new DateFormatter("yyyy-MM-dd"));Date convert = defaultFormattingConversionService.convert("2022-10-10", Date.class);System.out.println(convert);}public final class DateFormatter implements Formatter {private String pattern;public DateFormatter(String pattern) {this.pattern = pattern;}public String print(Date date, Locale locale) {if (date == null) {return "";}return getDateFormat(locale).format(date);}public Date parse(String formatted, Locale locale) throws ParseException {if (formatted.length() == 0) {return null;}return getDateFormat(locale).parse(formatted);}protected DateFormat getDateFormat(Locale locale) {DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);dateFormat.setLenient(false);return dateFormat;}}
}

3.1 注解驱动Formatter

在Spring中很多很多功能都是可以基于注解进行驱动的,开发者不用关心底层实现,直接使用注解。就能使用很强大的工具了。下面我们实现一个注解驱动的类型转换。

自定一个注解 @DatePattern , 将String类型,根据注解的配置最终给方法参数赋值。

    public String print(@DatePattern(pattern = "yyyy-MM-dd") Date date) {System.out.println(date.toString());return date.toString();}
  1. 首先我们要实现这个接口。
public interface AnnotationFormatterFactory {Set> getFieldTypes();Printer getPrinter(A annotation, Class fieldType);Parser getParser(A annotation, Class fieldType);
}

注意这里一定要用 TypeDescriptor 构造的方式来处理,因为只有这样才会处理注解。

   @Testpublic void test() throws Exception {DefaultFormattingConversionService defaultFormattingConversionService = new DefaultFormattingConversionService();defaultFormattingConversionService.addFormatterForFieldAnnotation(new DatePatternFormatAnnotationFormatterFactory());Method print = getClass().getDeclaredMethod("print", Date.class);// 注意一定要使用 TypeDescriptor 构造的方式声明才会有注解信息for (Parameter parameter : print.getParameters()) {// trueSystem.out.println(new TypeDescriptor(MethodParameter.forParameter(parameter)).hasAnnotation(DatePattern.class));}// 通过注解的方式实现解析Object convert = defaultFormattingConversionService.convert("2021-12-12", new TypeDescriptor(MethodParameter.forExecutable(print, 0)));System.out.println(convert);}public String print(@DatePattern(pattern = "yyyy-MM-dd") Date date) {System.out.println(date.toString());return date.toString();}@Documented@Retention(RetentionPolicy.RUNTIME)@Target({ElementType.FIELD, ElementType.PARAMETER})public @interface DatePattern {String pattern();}public final class DatePatternFormatAnnotationFormatterFactoryimplements AnnotationFormatterFactory {public Set> getFieldTypes() {return new HashSet>(Arrays.asList(new Class[]{Date.class}));}public Printer getPrinter(DatePattern annotation, Class fieldType) {return configureFormatterFrom(annotation, fieldType);}public Parser getParser(DatePattern annotation, Class fieldType) {return configureFormatterFrom(annotation, fieldType);}private Formatter configureFormatterFrom(DatePattern annotation, Class fieldType) {return new DateFormatter(annotation.pattern());}}public final class DateFormatter implements Formatter {private String pattern;public DateFormatter(String pattern) {this.pattern = pattern;}public String print(Date date, Locale locale) {if (date == null) {return "";}return getDateFormat(locale).format(date);}public Date parse(String formatted, Locale locale) throws ParseException {if (formatted.length() == 0) {return null;}return getDateFormat(locale).parse(formatted);}protected DateFormat getDateFormat(Locale locale) {DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);dateFormat.setLenient(false);return dateFormat;}}

最后,都看到这里了,最后如果这篇文章,对你有所帮助,请点个关注,交个朋友。

相关内容

热门资讯

我生病了小学作文【精简6篇】 我生病了小学作文 篇一我生病了前几天,我不知道怎么了,突然感觉身体不舒服。我感到头晕目眩,喉咙痛得像...
新学期新打算小学作文450字... 新学期新打算篇一:我要努力学习新的学期开始了,我制定了新的打算,那就是要努力学习。我相信只有努力学习...
我学会了西红柿炒鸡蛋小学作文... 我学会了西红柿炒鸡蛋小学作文 篇一我学会了西红柿炒鸡蛋上周,我学会了一道简单又美味的菜——西红柿炒鸡...
花朵的小学作文【最新3篇】 花朵的小学作文 篇一花朵的奇妙世界花朵是大自然的美丽礼物,它们以各种各样的颜色和形状装点着我们的环境...
小学生赏花的作文【通用4篇】 小学生赏花的作文 篇一春天是一个充满美丽花朵的季节,我非常喜欢春天。每当春天来临,我就会和家人一起去...
中秋之夜小学生作文【优选3篇... 中秋之夜小学生作文 篇一中秋之夜,月亮圆圆的,像一块白玉挂在天空中。我和爸爸妈妈一起出门,欣赏美丽的...
油面筋塞肉小学作文(推荐3篇... 油面筋塞肉小学作文 篇一我喜欢吃美食,尤其是一些特色的小吃。最近,我发现了一种非常好吃的小吃,那就是...
学游泳的小学作文(实用3篇) 学游泳的小学作文 篇一学游泳的小学作文大家好!我是小明,今天我要给大家分享一下我学游泳的经历。我是一...
小学生作文老师我想对你说【最... 小学生作文老师我想对你说 篇一尊敬的老师:您好!我是您的学生小明。我想借这篇作文向您表达我的感激之情...
一次有趣的实验小学生作文80... 一次有趣的实验篇一昨天,我参加了一次非常有趣的实验。老师让我们小组一起进行,我非常期待这个实验的结果...
春天小学一年级作文300字【... 春天小学一年级作文300字 篇一我的春天春天来了,大地上百花盛开,绿草如茵。我喜欢春天,因为春天是个...
校园的一角的作文【优选6篇】 校园的一角的作文 篇一校园的一角在校园的一角,有一个小花园,是我最喜欢的地方。虽然它不大,但却别有一...
参观科技馆的小学作文400字... 参观科技馆的小学作文400字 篇一:奇妙的科技世界我参观了我们学校附近的科技馆,这里展示了许多令人惊...
值得的学生作文【实用3篇】 值得的学生作文 篇一突破自我,迈向成功作为一名学生,我们应该时刻保持一种积极向上的心态,勇于追求进步...
走进直播间小学作文(最新4篇... 走进直播间小学作文 篇一近年来,随着互联网技术的快速发展,直播已经成为了一种非常流行的媒体形式。除了...
我的学校小学作文350字【精... 我的学校小学作文350字 篇一我所在的学校是一所小学,位于市区的中心地带。学校占地面积较小,但是设施...
春节大扫除小学作文【精选6篇... 春节大扫除小学作文 篇一:春节大扫除的乐趣春节是中国人最重要的传统节日之一,也是一年中家庭团聚最为频...
抓田螺小学作文(最新5篇) 抓田螺小学作文 篇一我和小伙伴们一起去抓田螺今天,天气晴朗,阳光明媚,我和几个好朋友决定一起去抓田螺...
送别的作文【推荐3篇】 送别的作文 篇一送别的作文 篇一人生中,不论是离别还是告别,都是一种成长的过程。无论是与亲人分离,还...
两个“可怜”作文(最新3篇) 两个“可怜”作文 篇一在生活中,我们常常会遇到一些让人心生怜悯的事情。这些事情或许是因为某些原因而导...