0


第07篇:巧用Spring类型转换, Converter&Formatter知识点学习。

公众号: 西魏陶渊明

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

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

文章目录

一、前言

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

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

1.1 类型转换

类型转换,比如说Long类型转换Date、String类型转换Long类型。
在实际的开发中我们可能直接使用

BeanUtils.copy()

或者其他三方工具来实现,但其实Spring已经提供了这种的接口能力了。我们只需要下面这样就可以了。

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

@SpringBootApplicationpublicclassApplication{// 注册一个转换器,目标由Long转DatepublicstaticclassLongToDateConvertimplementsConverter<Long,Date>{@OverridepublicDateconvert(Long source){returnnewDate(source);}}@Bean("customerConvert")publicConversionServiceFactoryBeancustomerConvert(){ConversionServiceFactoryBean conversionServiceFactoryBean =newConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(Collections.singleton(newLongToDateConvert()));return conversionServiceFactoryBean;}}@SpringBootTest@TestConfigurationpublicclassSpringConvertTest{@Autowired@Qualifier("customerConvert")privateConversionService conversionService;@Testpublicvoidtest(){// 直接使用即可。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进行的,在格式化这方面最典型的一个案例就是国际化。

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

publicinterfaceFormatter<T>extendsPrinter<T>,Parser<T>{}@FunctionalInterfacepublicinterfacePrinter<T>{// 对象类型,转换String类型,支持国际化Stringprint(T object,Locale locale);}@FunctionalInterfacepublicinterfaceParser<T>{// String类型转换泛型,支持国际化Tparse(String text,Locale locale)throwsParseException;}

下面我们看详细的内容。

二、Converter 类型转换

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


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

2.1 Converter

2.1.1 接口定义

package org.springframework.core.convert.converter;

public interface Converter<S, T> {

    T convert(S source);
}

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

2.1.2 接口功能

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

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

  • on、true、1、yes 都会转换成 true
  • off、false、0、no 都会转换成 false
finalclassStringToBooleanConverterimplementsConverter<String,Boolean>{privatestaticfinalSet<String> trueValues =newHashSet(8);privatestaticfinalSet<String> falseValues =newHashSet(8);StringToBooleanConverter(){}@NullablepublicBooleanconvert(String source){String value = source.trim();if(value.isEmpty()){returnnull;}else{
            value = value.toLowerCase();if(trueValues.contains(value)){returnBoolean.TRUE;}elseif(falseValues.contains(value)){returnBoolean.FALSE;}else{thrownewIllegalArgumentException("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 接口定义

packageorg.springframework.core.convert.converter;publicinterfaceConverterFactory<S,R>{<TextendsR>Converter<S,T>getConverter(Class<T> targetType);}

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

<T extends R>

返回 T

2.2.2 接口功能

packageorg.springframework.core.convert.support;finalclassStringToEnumConverterFactoryimplementsConverterFactory<String,Enum>{public<TextendsEnum>Converter<String,T>getConverter(Class<T> targetType){returnnewStringToEnumConverter(targetType);}privatefinalclassStringToEnumConverter<TextendsEnum>implementsConverter<String,T>{privateClass<T> enumType;publicStringToEnumConverter(Class<T> enumType){this.enumType = enumType;}// 将泛化类型publicTconvert(String source){return(T)Enum.valueOf(this.enumType, source.trim());}}}

通过

<T exends R>

的限定, 最终实现 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<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

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

finalclassConvertiblePair{privatefinalClass<?> sourceType;privatefinalClass<?> targetType;}

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

2.3.2 接口功能

下面举一个例子

@DatapublicstaticclassSourceOne{privateString name;}@DatapublicstaticclassTargetOne{privateString name;}@DatapublicstaticclassTargetTwo{privateString name;}@Testpublicvoidtest(){ApplicationConversionService applicationConversionService =newApplicationConversionService();GenericConverter genericConverter =newGenericConverter(){@OverridepublicSet<ConvertiblePair>getConvertibleTypes(){Set<ConvertiblePair> paris =newHashSet<>();
                paris.add(newConvertiblePair(SourceOne.class,TargetOne.class));
                paris.add(newConvertiblePair(SourceOne.class,TargetTwo.class));return paris;}@OverridepublicObjectconvert(Object source,TypeDescriptor sourceType,TypeDescriptor targetType){if(sourceType.getObjectType().equals(SourceOne.class)&& targetType.getObjectType().equals(TargetOne.class)){TargetOne targetOne =newTargetOne();
                    targetOne.setName(((SourceOne) source).getName()+"-> TargetOne");return targetOne;}if(sourceType.getObjectType().equals(SourceOne.class)&& targetType.getObjectType().equals(TargetTwo.class)){TargetTwoTargetTwo=newTargetTwo();TargetTwo.setName(((SourceOne) source).getName()+"-> TargetTwo");returnTargetTwo;}returnnull;}};
        applicationConversionService.addConverter(genericConverter);SourceOne sourceOne =newSourceOne();
        sourceOne.setName("Jay");System.out.println(applicationConversionService.convert(sourceOne,TargetOne.class));System.out.println(applicationConversionService.convert(sourceOne,TargetTwo.class));}

2.4 ConditionalGenericConverter

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

GenericConverter

ConditionalConverter

2.4.1 接口定义

publicinterfaceConditionalConverter{booleanmatches(TypeDescriptor sourceType,TypeDescriptor targetType);}publicinterfaceConditionalGenericConverterextendsGenericConverter,ConditionalConverter{}

2.5 Spring 实践

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

2.5.1 ConversionService

packageorg.springframework.core.convert;publicinterfaceConversionService{// 如果sourceType的对象可以转换为targetType ,则返回true booleancanConvert(Class<?> sourceType,Class<?> targetType);// 将给定的source转换为指定的targetType 。<T>Tconvert(Object source,Class<T> targetType);// 如果sourceType的对象可以转换为targetType ,则返回truebooleancanConvert(TypeDescriptor sourceType,TypeDescriptor targetType);// 将给定的source转换为指定的targetType 。 TypeDescriptors 提供有关将发生转换的源和目标位置的附加上下文,通常是对象字段或属性位置。Objectconvert(Object source,TypeDescriptor sourceType,TypeDescriptor targetType);}

2.5.2 硬编码使用

如果转换失败会抛出

org.springframework.core.convert.ConversionFailedException
publicclassConvertTest{@Testpublicvoidtest(){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

@SpringBootApplicationpublicclassApplication{publicstaticclassLongToDateConvertimplementsConverter<Long,Date>{@OverridepublicDateconvert(Long source){returnnewDate(source);}}@Bean("customerConvert")publicConversionServiceFactoryBeancustomerConvert(){ConversionServiceFactoryBean conversionServiceFactoryBean =newConversionServiceFactoryBean();
        conversionServiceFactoryBean.setConverters(Collections.singleton(newLongToDateConvert()));return conversionServiceFactoryBean;}}@SpringBootTest@TestConfigurationpublicclassSpringConvertTest{@Autowired@Qualifier("customerConvert")privateConversionService conversionService;@Testpublicvoidtest(){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 支持任意类型的转换
publicinterfaceFormatter<T>extendsPrinter<T>,Parser<T>{}@FunctionalInterfacepublicinterfacePrinter<T>{// 对象转StringStringprint(T object,Locale locale);}@FunctionalInterfacepublicinterfaceParser<T>{// String转对象Tparse(String text,Locale locale)throwsParseException;}

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

publicclassDateFormatterTest{@Testpublicvoidtest(){DefaultFormattingConversionService defaultFormattingConversionService =newDefaultFormattingConversionService();
        defaultFormattingConversionService.addFormatter(newDateFormatter("yyyy-MM-dd"));Date convert = defaultFormattingConversionService.convert("2022-10-10",Date.class);System.out.println(convert);}publicfinalclassDateFormatterimplementsFormatter<Date>{privateString pattern;publicDateFormatter(String pattern){this.pattern = pattern;}publicStringprint(Date date,Locale locale){if(date ==null){return"";}returngetDateFormat(locale).format(date);}publicDateparse(String formatted,Locale locale)throwsParseException{if(formatted.length()==0){returnnull;}returngetDateFormat(locale).parse(formatted);}protectedDateFormatgetDateFormat(Locale locale){DateFormat dateFormat =newSimpleDateFormat(this.pattern, locale);
            dateFormat.setLenient(false);return dateFormat;}}}

3.1 注解驱动Formatter

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

自定一个注解

@DatePattern

, 将String类型,根据注解的配置最终给方法参数赋值。

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

注意这里一定要用

TypeDescriptor

构造的方式来处理,因为只有这样才会处理注解。

@Testpublicvoidtest()throwsException{DefaultFormattingConversionService defaultFormattingConversionService =newDefaultFormattingConversionService();
        defaultFormattingConversionService.addFormatterForFieldAnnotation(newDatePatternFormatAnnotationFormatterFactory());Method print =getClass().getDeclaredMethod("print",Date.class);// 注意一定要使用 TypeDescriptor 构造的方式声明才会有注解信息for(Parameter parameter : print.getParameters()){// trueSystem.out.println(newTypeDescriptor(MethodParameter.forParameter(parameter)).hasAnnotation(DatePattern.class));}// 通过注解的方式实现解析Object convert = defaultFormattingConversionService.convert("2021-12-12",newTypeDescriptor(MethodParameter.forExecutable(print,0)));System.out.println(convert);}publicStringprint(@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@interfaceDatePattern{Stringpattern();}publicfinalclassDatePatternFormatAnnotationFormatterFactoryimplementsAnnotationFormatterFactory<DatePattern>{publicSet<Class<?>>getFieldTypes(){returnnewHashSet<Class<?>>(Arrays.asList(newClass<?>[]{Date.class}));}publicPrinter<Date>getPrinter(DatePattern annotation,Class<?> fieldType){returnconfigureFormatterFrom(annotation, fieldType);}publicParser<Date>getParser(DatePattern annotation,Class<?> fieldType){returnconfigureFormatterFrom(annotation, fieldType);}privateFormatter<Date>configureFormatterFrom(DatePattern annotation,Class<?> fieldType){returnnewDateFormatter(annotation.pattern());}}publicfinalclassDateFormatterimplementsFormatter<Date>{privateString pattern;publicDateFormatter(String pattern){this.pattern = pattern;}publicStringprint(Date date,Locale locale){if(date ==null){return"";}returngetDateFormat(locale).format(date);}publicDateparse(String formatted,Locale locale)throwsParseException{if(formatted.length()==0){returnnull;}returngetDateFormat(locale).parse(formatted);}protectedDateFormatgetDateFormat(Locale locale){DateFormat dateFormat =newSimpleDateFormat(this.pattern, locale);
            dateFormat.setLenient(false);return dateFormat;}}

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

标签: spring 学习 java

本文转载自: https://blog.csdn.net/Message_lx/article/details/127451777
版权归原作者 西魏陶渊明 所有, 如有侵权,请联系我们删除。

“第07篇:巧用Spring类型转换, Converter&Formatter知识点学习。”的评论:

还没有评论