Spring中的格式化Formatter

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的实现都略过,只看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:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可。

FormatterRegistrar

要使用Formatter,除了将其配置在FormattingConversionServiceFactoryBean的formatters属性中外,还可以FormatterRegistrar注册进去。

public interface FormatterRegistrar {
    // Register Formatters and Converters with a FormattingConversionService through a FormatterRegistry SPI. 
    void registerFormatters(FormatterRegistry registry);
}

 

JodaTimeFormatterRegistrar

格式化joda的LocalDate、LocalTime、LocalDateTime、ReadableInstant、Period…等等

DateTimeFormatterRegistrar

对JSR310的那些时间类进行支持。包括:LocalDateTime、ZonedDateTime、OffsetDateTime、OffsetTime等等

@since 4.0。各种内部转换请参见:DateTimeConverters.registerConverters(registry);

DateFormatterRegistrar

单词上注意和DateTimeFormatterRegistrar的区别~~

这个和@DateTimeFormat也有关系,内部依赖的是上面说到的DateFormatter。

public class DateFormatterRegistrar implements FormatterRegistrar {
    @Override
    public void registerFormatters(FormatterRegistry registry) {
    	addDateConverters(registry); // 它是个静态方法
    	// 对`@DateTimeFormat`的支持~~~~~
    	// 所以如果你导入了joda包,这个注解可能会失效的~~~~需要特别注意~~~~~~~~~~~ 但下面的DateToLongConverter之类的依旧好使~
    	// 但是你导入的是JSR310   没有这个问题~~~~
    	registry.addFormatterForFieldAnnotation(new DateTimeFormatAnnotationFormatterFactory());

    	// In order to retain back compatibility we only register Date/Calendar
    	// types when a user defined formatter is specified (see SPR-10105)
    	// 如果你指定了dateFormatter,那么注册它  也来处理Calendar以及Date
    	if (this.dateFormatter != null) {
    		registry.addFormatter(this.dateFormatter);
    		registry.addFormatterForFieldType(Calendar.class, this.dateFormatter);
    	}
    }

    // 注意:这些converter全部为内部类实现~~~~
    public static void addDateConverters(ConverterRegistry converterRegistry) {
    	converterRegistry.addConverter(new DateToLongConverter());
    	converterRegistry.addConverter(new DateToCalendarConverter());
    	converterRegistry.addConverter(new CalendarToDateConverter());
    	converterRegistry.addConverter(new CalendarToLongConverter());
    	converterRegistry.addConverter(new LongToDateConverter());
    	converterRegistry.addConverter(new LongToCalendarConverter());
    }
}

AnnotationFormatterFactory

它是一个工厂,专门创建出处理(格式化)指定字段field上标注有指定注解的。(Spring内助了两个常用注解:@DateTimeFormat和@NumberFormat)

我们常说的,要自定义注解来处理参数的格式化,就需要实现接口来自定义一个处理类。

// @since 3.0
public interface AnnotationFormatterFactory<A extends Annotation> {

    // 此注解 可以作用的字段的类型~~~比如@DateTimeFormat只能作用域Date、Calendar、Long类型上~  标注在被的类型上无效~~~
    Set<Class<?>> getFieldTypes();
    // 对标注有指定注解的字段进行格式化输出~~
    Printer<?> getPrinter(A annotation, Class<?> fieldType);
    // 对标注有指定注解的字段进行格式化解析~~~
    Parser<?> getParser(A annotation, Class<?> fieldType);
}

AnnotationFormatterFactory的继承树如下,可以看到Spring 给我们内置了一些处理器的:

总的来说是支持了数值和日期类型(Date和JSR310、甚至joda)

NumberFormatAnnotationFormatterFactory

处理@NumberFormat对数字进行格式化。

// 还继承自EmbeddedValueResolutionSupport,所以有resolveEmbeddedValue()方法,能够处理占位符
public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupportimplements AnnotationFormatterFactory<NumberFormat> {

、    // 处理Byte、Short、Integer、Long、Float、Double、BigInteger、BigDecimal等类型~~~@Overridepublic Set<Class<?>> getFieldTypes() {
		return NumberUtils.STANDARD_NUMBER_TYPES;
    }

    @Override
    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
    	return configureFormatterFrom(annotation);
    }
    @Override
    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
    	return configureFormatterFrom(annotation);
    }

    // 可以看到,根据Style不同,返回的格式化器也是不同的~~~~
    // 显然pattern非常强大,支持到了占位符,el取值~~~
    private Formatter<Number> configureFormatterFrom(NumberFormat annotation) {
    	String pattern = resolveEmbeddedValue(annotation.pattern());
    	// 若指定了pattern,此处可以看出:直接当做数字处理NumberStyleFormatter
    	if (StringUtils.hasLength(pattern)) {
    		return new NumberStyleFormatter(pattern);
    	}
    	// 可能是钱币、百分比、数字   注意:都是使用的默认处理方式了~~~~  
    	// @NumberFormat并不支持自定义   比如保留小数位、四舍五入等等
    	else {
    		Style style = annotation.style();
    		if (style == Style.CURRENCY) {
    			return new CurrencyStyleFormatter();
    		}
    		else if (style == Style.PERCENT) {
    			return new PercentStyleFormatter();
    		}
    		else {
    			return new NumberStyleFormatter();
    		}
    	}
    }

}

@NumberFormat是用来验证输入的数字格式。比如一般我们这样来格式化数值:@NumberFormat(pattern="#,###.##")

@NumberFormat注解定义:

// @since 3.0 类比效果参见:java.text.NumberFormat
// 可以标注在方法上、属性field上、参数上~~~~
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface NumberFormat {
    Style style() default Style.DEFAULT;
    // 格式化数字的模版~~~  若指定了pattern 那就使用new NumberStyleFormatter(pattern)进行格式化
    String pattern() default "";

    enum Style {
    	// 默认值  同 NUMBER
    	DEFAULT,
    	NUMBER,
    	PERCENT,
    	CURRENCY
    }

}

Jsr354NumberFormatAnnotationFormatterFactory

JSR 354定义了一套新的Java货币API:目前还是javax包内~

CurrencyUnit代表的是货币。它有点类似于现在的java.util.Currency类

MontetaryAmount代表的是某种货币的具体金额。通常它都会与某个CurrencyUnit绑定。

DateTimeFormatAnnotationFormatterFactory

它和@DateTimeFormat这个注解有关,作用在Date、Calendar、Long类型上。

public class DateTimeFormatAnnotationFormatterFactory  extends EmbeddedValueResolutionSupport
    	implements AnnotationFormatterFactory<DateTimeFormat> {
    	
    // 该注解只能放在下面这集中类型上面~~~~才会生效
    private static final Set<Class<?>> FIELD_TYPES;
    static {
    	Set<Class<?>> fieldTypes = new HashSet<>(4);
    	fieldTypes.add(Date.class);
    	fieldTypes.add(Calendar.class);
    	fieldTypes.add(Long.class);
    	FIELD_TYPES = Collections.unmodifiableSet(fieldTypes);
    }

    @Override
    public Set<Class<?>> getFieldTypes() {
    	return FIELD_TYPES;
    }
    @Override
    public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
    	return getFormatter(annotation, fieldType);
    }
    @Override
    public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {
    	return getFormatter(annotation, fieldType);
    }

    protected Formatter<Date> getFormatter(DateTimeFormat annotation, Class<?> fieldType) {
    	DateFormatter formatter = new DateFormatter();
    	// style属性支持使用占位符的形式~  setStylePattern
    	// 'S' = Small  'M' = Medium  'L' = Long 'F' = Full '-' = Omitted
    	// 注意:这里需要同时设置两个。比如SS SM等等
    	// 第一个表示Date日期格式,第二个表示Time事件格式~~~~  注解默认值是SS
    	String style = resolveEmbeddedValue(annotation.style());
    	if (StringUtils.hasLength(style)) {
    		formatter.setStylePattern(style);
    	}
    	formatter.setIso(annotation.iso());
    	// patter也支持占位符~~~   
    	// DateFormatter里说过,若pattern指定了,就直接使用SimpleDateFormat格式化了
    	// 否则根据stylePattern来进行拿模版实例:return DateFormat.getTimeInstance(timeStyle, locale)
    	//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);
    	//}
    	String pattern = resolveEmbeddedValue(annotation.pattern());
    	if (StringUtils.hasLength(pattern)) {
    		formatter.setPattern(pattern);
    	}
    	return formatter;
    }
}

@DateTimeFormat定义:

 

// @since 3.0  它比Number多一个ElementType.ANNOTATION_TYPE,表示它还能作为元注解标注在注解上
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface DateTimeFormat {
    // 默认是SS  表示否是SMALL的
    String style() default "SS";
    //  默认为null。若指定了ISO,最终也会使用SimpleDateFormat去格式化Date。
    // 因为String pattern = ISO_PATTERNS.get(this.iso)都对应着patter值的~~~ 见下面
    ISO iso() default ISO.NONE;
    // 默认不给你指定pattern 但是我们使用时一般都要指定~
    String pattern() default "";
    
    enum ISO {
    	DATE, // yyyy-MM-dd  2000-10-31
    	TIME, // HH:mm:ss.SSSXXX  01:30:00.000-05:00

    	// 注意:若你什么都没有指定,默认就会按照此种格式转换为Date~~~
    	DATE_TIME, // yyyy-MM-dd'T'HH:mm:ss.SSSXXX    2000-10-31T01:30:00.000-05:00
    	NONE
    }
}

Jsr310DateTimeFormatAnnotationFormatterFactory

它和@DateTimeFormat这个注解有关,作用在JSR310相关类型上。

注意,它也是处理标注有@DateTimeFormat注解的字段的。DateTimeFormatterRegistrar#registerFormatters方法里注册了它,从而提供了该注解对JSR310也是支持的

public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
		implements AnnotationFormatterFactory<DateTimeFormat> {
    // 可以标注在这些类型上面~~~~
    private static final Set<Class<?>> FIELD_TYPES;
    static {
    	// Create the set of field types that may be annotated with @DateTimeFormat.
    	Set<Class<?>> fieldTypes = new HashSet<>(8);
    	fieldTypes.add(LocalDate.class);
    	fieldTypes.add(LocalTime.class);
    	fieldTypes.add(LocalDateTime.class);
    	fieldTypes.add(ZonedDateTime.class);
    	fieldTypes.add(OffsetDateTime.class);
    	fieldTypes.add(OffsetTime.class);
    	FIELD_TYPES = Collections.unmodifiableSet(fieldTypes);
    }    
    // 往外输出的时候~~~~~~
    @Override
    public Printer<?> getPrinter(DateTimeFormat annotation, Class<?> fieldType) {
    	// 使用DateTimeFormatterFactory根据注解信息创建一个java.time.format.DateTimeFormatter
    	DateTimeFormatter formatter = getFormatter(annotation, fieldType);    
    	// Efficient ISO_LOCAL_* variants for printing since they are twice as fast...
    	// ISO.DATE -> DateTimeFormatter.ISO_DATE
    	// ISO.TIME -> DateTimeFormatter.ISO_TIME
    	// ISO.DATE_TIME -> DateTimeFormatter.ISO_DATE_TIME
    	// ISO.NONE 没有指定,就走最后的TemporalAccessorPrinter了~~~~
    	// isLocal(fieldType)  --> fieldType.getSimpleName().startsWith("Local");
    	// System.out.println(DateTimeFormatter.ofPattern("yyyy-MM-dd").format(LocalDate.now())); //2019-06-04 标准格式输出~~~~
    	if (formatter == DateTimeFormatter.ISO_DATE) {
    		if (isLocal(fieldType)) {
    			formatter = DateTimeFormatter.ISO_LOCAL_DATE;
    		}
    	}
    	else if (formatter == DateTimeFormatter.ISO_TIME) {
    		if (isLocal(fieldType)) {
    			formatter = DateTimeFormatter.ISO_LOCAL_TIME;
    		}
    	}
    	else if (formatter == DateTimeFormatter.ISO_DATE_TIME) {
    		if (isLocal(fieldType)) {
    			formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
    		}
    	}    
    	// 它的print方法为:return DateTimeContextHolder.getFormatter(this.formatter, locale).format(partial);
    	return new TemporalAccessorPrinter(formatter);
    }    
    // 它的parse方法,依赖于LocalDate.parse、OffsetTime.parse等等各自的parse方法~
    @Override
    @SuppressWarnings("unchecked")
    public Parser<?> getParser(DateTimeFormat annotation, Class<?> fieldType) {
    	DateTimeFormatter formatter = getFormatter(annotation, fieldType);
    	return new TemporalAccessorParser((Class<? extends TemporalAccessor>) fieldType, formatter);
    }
}

有了它,我们处理Date、JSR310之类的日期能达到统一的效果了。 

DateTimeFormatterFactory

根据一些参数比如:pattern、org.springframework.format.annotation.DateTimeFormat.ISO、java.time.format.FormatStyle、java.util.TimeZone等等来创建一个java.time.format.DateTimeFormatter。

// @since 4.0
public class DateTimeFormatterFactory {

    @Nullable
    private String pattern;
    @Nullable
    private ISO iso;
    @Nullable
    private FormatStyle dateStyle;
    @Nullable
    private FormatStyle timeStyle;
    @Nullable
    private TimeZone timeZone;
    
    // ...
    public void setStylePattern(String style) {
    	Assert.isTrue(style.length() == 2, "Style pattern must consist of two characters");
    	this.dateStyle = convertStyleCharacter(style.charAt(0));
    	this.timeStyle = convertStyleCharacter(style.charAt(1));
    }
    @Nullable
    private FormatStyle convertStyleCharacter(char c) {
    	switch (c) {
    		case 'S': return FormatStyle.SHORT;
    		case 'M': return FormatStyle.MEDIUM;
    		case 'L': return FormatStyle.LONG;
    		case 'F': return FormatStyle.FULL;
    		case '-': return null;
    		default: throw new IllegalArgumentException("Invalid style character '" + c + "'");
    	}
    }

    public DateTimeFormatter createDateTimeFormatter() {
    	return createDateTimeFormatter(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM));
    }
    // fallbackFormatter表示最后的格式化器的默认值~~~~
    public DateTimeFormatter createDateTimeFormatter(DateTimeFormatter fallbackFormatter) {
    	// 若指定了pattern  那就简单了~~~
    	if (StringUtils.hasLength(this.pattern)) {
    		// 这一句是为了兼容Joda-Time到JSR里~~
    		String patternToUse = StringUtils.replace(this.pattern, "yy", "uu");
    		//  采用STRICT方式格式化~~~
    		dateTimeFormatter = DateTimeFormatter.ofPattern(patternToUse).withResolverStyle(ResolverStyle.STRICT);
    	} else if (this.iso != null && this.iso != ISO.NONE) { //ISO不能为null和NONE
    		switch (this.iso) {
    			case DATE:
    				dateTimeFormatter = DateTimeFormatter.ISO_DATE;
    				break;
    			case TIME:
    				dateTimeFormatter = DateTimeFormatter.ISO_TIME;
    				break;
    			case DATE_TIME:
    				dateTimeFormatter = DateTimeFormatter.ISO_DATE_TIME;
    				break;
    			default:
    				throw new IllegalStateException("Unsupported ISO format: " + this.iso);
    		}
    	}
    	... // 根据dateStyle和timeStyle来生成实例~~~~略
    }
}

通过它,我们只需要关注一些元信息,就能很快的生成出一个DateTimeFormatter来。比如我们根据注解信息,就生成出来就是这么个原理。

DateTimeFormatterFactoryBean

这里指的是org.springframework.format.datetime.standard.DateTimeFormatterFactoryBean,而不是joda包的,需要稍微注意。它借助了DateTimeFormatterFactory然后实现了一波FactoryBean。

public class DateTimeFormatterFactoryBean extends DateTimeFormatterFactory
		implements FactoryBean<DateTimeFormatter>, InitializingBean {
    @Nullable
    private DateTimeFormatter dateTimeFormatter;
    @Override
    public void afterPropertiesSet() {
    	// 父类创建~~
    	this.dateTimeFormatter = createDateTimeFormatter();
    }

    @Override
    @Nullable
    public DateTimeFormatter getObject() {
    	return this.dateTimeFormatter;
    }
    @Override
    public Class<?> getObjectType() {
    	return DateTimeFormatter.class;
    }
    @Override
    public boolean isSingleton() {
    	return true;
    }

}

FormattingConversionServiceFactoryBean

它和上面的不同,它是用于管理转换器、格式化器们的。比如我们自己自定义了一个转换器、格式化器需要注册,都以交给它。从名字可以看出,它主要是创建一个FormattingConversionService

public class FormattingConversionServiceFactoryBean implements FactoryBean<FormattingConversionService>, EmbeddedValueResolverAware, InitializingBean {

    @Nullable
    private Set<?> converters;
    @Nullable
    private Set<?> formatters;

    // 由此可见,我们要注册formatter不仅仅可以直接注册,也可通过formatterRegistrars注册进来~
    @Nullable
    private Set<FormatterRegistrar> formatterRegistrars;
    private boolean registerDefaultFormatters = true;

    @Nullable
    private StringValueResolver embeddedValueResolver;
    @Nullable
    private FormattingConversionService conversionService; // 最终是它用于管理所有  备注:所有的formatter最终都是一个converter

	// 这里把上面字段set进来的值,进行解析~~~~拆分~~~
    @Override
    public void afterPropertiesSet() {
    	// 由此可见,最终返回的是一个DefaultFormattingConversionService
    	this.conversionService = new DefaultFormattingConversionService(this.embeddedValueResolver, this.registerDefaultFormatters);
    	//  把set进来的这些converters都注册进去保存着~~~
    	ConversionServiceFactory.registerConverters(this.converters, this.conversionService);
    	// 这里处理注册formatters和formatterRegistrars们~~~~
    	registerFormatters(this.conversionService);
    }

    private void registerFormatters(FormattingConversionService conversionService) {
    	if (this.formatters != null) {
    		for (Object formatter : this.formatters) {
    			if (formatter instanceof Formatter<?>) {
    				conversionService.addFormatter((Formatter<?>) formatter);
    			} else if (formatter instanceof AnnotationFormatterFactory<?>) {
    				conversionService.addFormatterForFieldAnnotation((AnnotationFormatterFactory<?>) formatter);
    			} else {
    				throw new IllegalArgumentException( "Custom formatters must be implementations of Formatter or AnnotationFormatterFactory");
    			}
    		}
    	}
    	if (this.formatterRegistrars != null) {
    		for (FormatterRegistrar registrar : this.formatterRegistrars) {
    			registrar.registerFormatters(conversionService);
    		}
    	}
    }


    @Override
    @Nullable
    public FormattingConversionService getObject() {
    	return this.conversionService;
    }
    // 类型实际上为DefaultFormattingConversionService
    @Override
    public Class<? extends FormattingConversionService> getObjectType() {
    	return FormattingConversionService.class;
    }
    @Override
    public boolean isSingleton() {
    	return true;
    }

}

 

posted @   残城碎梦  阅读(379)  评论(0编辑  收藏  举报
编辑推荐:
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!
历史上的今天:
2021-11-24 类加载子系统
2021-11-24 JAVA对象的创建及内存分配详解
2021-11-24 【杂谈Spring】Spring事务_实现原理
2021-11-24 【杂谈Spring】Spring事务_七大传播机制与五个隔离级别
2021-11-24 JVM的垃圾回收机制
2021-11-24 JVM的内存分区/内存结构/内存区域/JVM内存模型
2021-11-24 JVM整体架构
点击右上角即可分享
微信分享提示