前言
Converter只完成了数据类型的转换,却不负责输入输出数据的格式化工作,日期时间、货币等虽都以字符串形式存在,却有不同的格式。
Spring格式化框架要解决的问题是:从格式化的数据中获取真正的数据,绑定数据,将处理完成的数据输出为格式化的数据。Formatter接口就承担着这样的责任.
Converter主要是做Object与Object之间的类型转换,Formatter则是要完成任意Object与String之间的类型转换。前者适合于任何一层,而后者则主要用于web层
Formatter
org.springframework.format.Formatter顾名思义,它表示格式化。
// @since 3.0 public interface Formatter<T> extends Printer<T>, Parser<T> { }
它自己一个方法都木有定义,因此需要看看它的两个父接口。
Printer
格式化显示接口,将T类型的对象根据Locale信息以某种格式进行打印显示(即返回字符串形式)
@FunctionalInterface public interface Printer<T> { String print(T object, Locale locale); }
Parser
解析接口,根据Locale信息解析字符串到T类型的对象
@FunctionalInterface public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; }
从这两个接口定义中我们可以很清晰的看出定义了两个相反的接口。代表着格式化和解析(功能上和转换器Converter
还是蛮像的)
Formatter它的继承树如下:
从包结构中看:
发现Spring竟然内置了对joda的支持,可见当初joda这个包的流行的程度。但是随着Java8中的JSR310日期的普及,我预言joda必将走向死亡(毕竟亲儿子才是最好的)。因此本文涉及到joda的实现都略过,只看JSR310标准实现。
InstantFormatter
对java.time.Instant时间戳的转换和解析:(相信一般很少这么使用吧~~~)
public class InstantFormatter implements Formatter<Instant> { // 如果你的请求入参串为:2007-12-03T10:15:30.00Z这种格式,是可以使用Instant接收的~~~ @Override public Instant parse(String text, Locale locale) throws ParseException { if (text.length() > 0 && Character.isDigit(text.charAt(0))) { // assuming UTC instant a la "2007-12-03T10:15:30.00Z" return Instant.parse(text); } else { // assuming RFC-1123 value a la "Tue, 3 Jun 2008 11:05:30 GMT" return Instant.from(DateTimeFormatter.RFC_1123_DATE_TIME.parse(text)); } } // System.out.println(Instant.now())输出:2019-06-03T13:11:22.638Z @Override public String print(Instant object, Locale locale) { return object.toString(); } }
CurrencyUnitFormatter
它需要javax.money.包的支持属于`JSR-354`的内容,暂时略过
PeriodFormatter/DurationFormatter/MonthDayFormatter/YearMonthFormatter/YearFormatter/MonthFormatter
他们的实现都很简单,都是调各自的parse()和toString()方法~ 就不详细说明了
DateFormatter
注意处理Java8中JSR310日期的叫做DateTimeFormatter,但它并没有实现Formatter接口,注意区分
另外注意和java.text.DateFormat的区分,它是JDK的。而这个是Spring的~ 但是Spring的这个底层实现其实还是依赖的java.text.DateFormat
这个是最为重要的一个转换,因为Spring MVC中我们经常会使用Date来接收参数和返回,因此这个转换器个人建议有必要了解一下,非常有助于了解序列化的原理啥的~~~依赖于java.text.DateFormat来处理的。
// @since 3.0 // 处理java.util.Date 和JSR310无关 public class DateFormatter implements Formatter<Date> { // 使用的标准时区~ private static final TimeZone UTC = TimeZone.getTimeZone("UTC"); // 因为Date包含日期、时间 所以这里表述的是各自的默认支持的模版格式~~~ // System.out.println(new Date()); //Mon Jun 03 21:18:45 CST 2019 // System.out.println(new Timestamp(Instant.now().toEpochMilli())); //2019-06-03 21:18:45.346 private static final Map<ISO, String> ISO_PATTERNS; static { Map<ISO, String> formats = new EnumMap<>(ISO.class); formats.put(ISO.DATE, "yyyy-MM-dd"); formats.put(ISO.TIME, "HH:mm:ss.SSSXXX"); formats.put(ISO.DATE_TIME, "yyyy-MM-dd'T'HH:mm:ss.SSSXXX"); ISO_PATTERNS = Collections.unmodifiableMap(formats); } @Nullable private String pattern; private int style = DateFormat.DEFAULT; //FULL LONG MEDIUM SHORT 默认是MEDIUM @Nullable private String stylePattern; @Nullable private ISO iso; @Nullable private TimeZone timeZone; // 指定分析是否要宽松 默认是false private boolean lenient = false; // ==========备注:上面所有参数和getDateFormat()格式化模版有关=========== // 省略get/set方法 public String print(Date date, Locale locale) { return getDateFormat(locale).format(date); } @Override public Date parse(String text, Locale locale) throws ParseException { return getDateFormat(locale).parse(text); } // ====getDateFormat()方法,就是根据上面定义的参数生成~~~ // 1、若指定了pattern参数,那就直接使用new SimpleDateFormat(this.pattern, locale) // 2、若没指定,那就根据配置项,DateFormat.getXXXInstance()... }
Demo如下:
public static void main(String[] args) { Date date = new Date(); Timestamp timestamp = new Timestamp(System.currentTimeMillis()); DateFormatter dateFormatter = new DateFormatter(); System.out.println(dateFormatter.print(date, Locale.CHINA)); //2019-6-3 System.out.println(dateFormatter.print(timestamp, Locale.CHINA)); //2019-6-3 dateFormatter.setIso(DateTimeFormat.ISO.DATE_TIME); //dateFormatter.setStyle(DateFormat.FULL); System.out.println(dateFormatter.print(date, Locale.CHINA)); //2019-06-03T13:28:44.252Z System.out.println(dateFormatter.print(timestamp, Locale.CHINA)); //2019-06-03T13:28:44.252Z }
AbstractNumberFormatter
对java.lang.Number进行格式化。依赖于java.text.NumberFormat来处理的,java.text.DecimalFormat是它的子类。
CurrencyStyleFormatter
以BigDecimal的格式来处理数字,当作钱币处理。
// @since 4.2 public class CurrencyStyleFormatter extends AbstractNumberFormatter { private int fractionDigits = 2; // 默认保留两位小数点 @Nullable private RoundingMode roundingMode; // 四舍五入 @Nullable private Currency currency; // 货币 java.util.Currency // 例如:#,#00.0# --> 1,234.56 @Nullable private String pattern; @Override public BigDecimal parse(String text, Locale locale) throws ParseException { BigDecimal decimal = (BigDecimal) super.parse(text, locale); // 对结果做四舍五入处理~~~~~~~~~~~ if (this.roundingMode != null) { decimal = decimal.setScale(this.fractionDigits, this.roundingMode); } else { decimal = decimal.setScale(this.fractionDigits); } return decimal; } }
Demo:
public static void main(String[] args) throws ParseException { String curr = "1,234.56"; CurrencyStyleFormatter formatter = new CurrencyStyleFormatter(); //formatter.setRoundingMode(RoundingMode.DOWN); formatter.setPattern("#,#00.0#"); // 若不设置格式 抛错ParseException System.out.println(formatter.parse(curr, Locale.CHINA)); //1234.56 }
PercentStyleFormatter
对百分数进行格式化,@since 4.2
。
public static void main(String[] args) throws ParseException { String curr = "12%"; PercentStyleFormatter formatter = new PercentStyleFormatter(); System.out.println(formatter.parse(curr, Locale.CHINA)); //0.12 System.out.println(formatter.print(0.12, Locale.CHINA)); //12% }
NumberStyleFormatter
数字的格式进行转换,也可以指定pattern
Demo:
public static void main(String[] args) throws ParseException { String curr = "12,000.1567"; NumberStyleFormatter formatter = new NumberStyleFormatter(); formatter.setPattern("#,#00.0#"); System.out.println(formatter.parse(curr, Locale.CHINA)); //12000.1567 System.out.println(formatter.print(0.12, Locale.CHINA)); // 00.12 看这格式化的威力 }
以上。
其中最为主要的是Date的转换,以及对Number的转换(它可以转为货币、百分比、数字)
FormatterRegistry
从接口继承关系中可以看出,它既可以注册格式化器,又可议注册转换器
// @since 3.0 public interface FormatterRegistry extends ConverterRegistry { void addFormatter(Formatter<?> formatter); void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter); // 单独指定Printer和parser也是被允许的 void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser); // 注册处理注解的格式化器~~~~~ AnnotationFormatterFactory的实现类~~ void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory); }
FormattingConversionService
// @since 3.0 它继承自GenericConversionService ,所以它能对Converter进行一系列的操作~~~ // 实现了接口FormatterRegistry,所以它也可以注册格式化器了 // 实现了EmbeddedValueResolverAware,所以它还能有非常强大的功能:处理占位 public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware { @Nullable private StringValueResolver embeddedValueResolver; private final Map<AnnotationConverterKey, GenericConverter> cachedPrinters = new ConcurrentHashMap<>(64); private final Map<AnnotationConverterKey, GenericConverter> cachedParsers = new ConcurrentHashMap<>(64); // 最终也是交给addFormatterForFieldType去做的 // getFieldType:它会拿到泛型类型。并且支持DecoratingProxy~~~ @Override public void addFormatter(Formatter<?> formatter) { addFormatterForFieldType(getFieldType(formatter), formatter); } // 存储都是分开存储的 读写分离 // PrinterConverter和ParserConverter都是一个GenericConverter 采用内部类实现的~~~ this代表一个ConversionService // 注意:他们的ConvertiblePair必有一个类型是String.class // Locale一般都可以这么获取:LocaleContextHolder.getLocale() // 最终parse出来的result有可能也会交给conversionService.convert() 若类型能够匹配上的话 @Override public void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter) { addConverter(new PrinterConverter(fieldType, formatter, this)); addConverter(new ParserConverter(fieldType, formatter, this)); } // 哪怕你是一个AnnotationFormatterFactory,最终也是被适配成了GenericConverter(ConditionalGenericConverter) @Override public void addFormatterForFieldAnnotation(AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory) { Class<? extends Annotation> annotationType = getAnnotationType(annotationFormatterFactory); // 若你自定义的实现了EmbeddedValueResolverAware接口,还可以使用占位符哟~~~~ // AnnotationFormatterFactory是下面的重点内容~~~~ if (this.embeddedValueResolver != null && annotationFormatterFactory instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) annotationFormatterFactory).setEmbeddedValueResolver(this.embeddedValueResolver); } // 对每一种字段的type 都注册一个AnnotationPrinterConverter去处理~~~~~ // AnnotationPrinterConverter是一个ConditionalGenericConverter // matches方法为:sourceType.hasAnnotation(this.annotationType); // 这个判断是呼应的:因为annotationFormatterFactory只会作用在指定的字段类型上的~~~不符合类型条件的不用添加 Set<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes(); for (Class<?> fieldType : fieldTypes) { addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType)); addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType)); } } ... }
DefaultFormattingConversionService
实际使用时,基本就是使用它。它的模式属于默认模式:就是注册了一些常用的,默认支持的转换器们。
public class DefaultFormattingConversionService extends FormattingConversionService { // 再一次看出来joda这个库的成功啊~~~ private static final boolean jsr354Present; private static final boolean jodaTimePresent; static { ClassLoader classLoader = DefaultFormattingConversionService.class.getClassLoader(); jsr354Present = ClassUtils.isPresent("javax.money.MonetaryAmount", classLoader); jodaTimePresent = ClassUtils.isPresent("org.joda.time.LocalDate", classLoader); } public DefaultFormattingConversionService( @Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) { if (embeddedValueResolver != null) { setEmbeddedValueResolver(embeddedValueResolver); } // 由此可见,它是DefaultConversionService的超集,比它强大得多的~~~ DefaultConversionService.addDefaultConverters(this); if (registerDefaultFormatters) { addDefaultFormatters(this); } } // 默认添加进去的格式化器们~~~~ public static void addDefaultFormatters(FormatterRegistry formatterRegistry) { // Default handling of number values // 支持@NumberFormat注解~~~~~对数字进行格式化~ formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory()); // Default handling of monetary values // JSR354使用较少~略过 银行、金融项目使用多~ if (jsr354Present) { formatterRegistry.addFormatter(new CurrencyUnitFormatter()); formatterRegistry.addFormatter(new MonetaryAmountFormatter()); formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory()); } // Default handling of date-time values // just handling JSR-310 specific date and time types // 对JSR310的转换的支持 DateTimeFormatterRegistrar是一个FormatterRegistrar new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry); // 如没有导入joda的包 那就默认使用Date吧~~~~~ if (jodaTimePresent) { // handles Joda-specific types as well as Date, Calendar, Long new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry); } else { // regular DateFormat-based Date, Calendar, Long converters new DateFormatterRegistrar().registerFormatters(formatterRegistry); } } }
Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):
- DefaultConversionService:默认的类型转换服务实现;
- DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。
备注:自定义Converter的场景其实蛮多的,比如最常见的StringToPhoneNumberConverter它俩的互转,就可以定义个转换器,支持中间空格的电话号码格式~