【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(中)

简介: 【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(中)

FormatterRegistrar和


要使用Formatter,除了将其配置在FormattingConversionServiceFactoryBean的formatters属性中外,还可以FormatterRegistrar注册进去。下面看到FormattingConversionServiceFactoryBean的时候会很清晰:


Registers {@link Converter Converters} and {@link Formatter Formatters} with a FormattingConversionService through the {@link FormatterRegistry} SPI.

java doc里说它是一个注册Converter和Formatter的SPI


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


image.png


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 给我们内置了一些处理器的:

image.png


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


NumberFormatAnnotationFormatterFactory


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

// 还继承自EmbeddedValueResolutionSupport,所以有resolveEmbeddedValue()方法,能够处理占位符
public class NumberFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
    implements AnnotationFormatterFactory<NumberFormat> {
  // 处理Byte、Short、Integer、Long、Float、Double、BigInteger、BigDecimal等类型~~~
  @Override
  public 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


也就是Jsr354的相关类型(MonetaryAmount),也是支持@NumberFormat注解的


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

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

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

(略)


JodaDateTimeFormatAnnotationFormatterFactory


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;
  }
}


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;
  }
}


有了它,上篇文章我们讲到我们若要注册自定义的converter的话,使用的ConversionServiceFactoryBean,而本文我们就可以使用更加强大的FormattingConversionServiceFactoryBean了。


一般情况下,若是Web环境下比如Spring MVC使用转换器、格式化器。建议使用FormattingConversionServiceFactoryBean注册,其余的无所谓了。


相关文章
|
11月前
|
前端开发 Java Spring
《Spring MVC》 第六章 MVC类型转换器、格式化器
《Spring MVC》 第六章 MVC类型转换器、格式化器
149 0
|
12月前
|
前端开发 Java Spring
Spring MVC-06循序渐进之Converter和Formatter
Spring MVC-06循序渐进之Converter和Formatter
58 0
|
前端开发 Java uml
Spring官网阅读(十五)Spring中的格式化(Formatter)
在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们考虑这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支撑我们的需求了。这个时候,格式化的作用就很明显了,这篇文章我们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.5及3.6小结
241 0
Spring官网阅读(十五)Spring中的格式化(Formatter)
|
Java API Spring
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
8. 格式化器大一统 -- Spring的Formatter抽象(下)
|
存储 前端开发 安全
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)
8. 格式化器大一统 -- Spring的Formatter抽象(上)
|
XML 前端开发 Java
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
【小家Spring】聊聊Spring中的格式化:Formatter、AnnotationFormatterFactory、DateFormatter以及@DateTimeFormat...(下)
|
2天前
|
Java 应用服务中间件 Maven
SpringBoot 项目瘦身指南
SpringBoot 项目瘦身指南
57 0
|
2天前
|
缓存 安全 Java
Spring Boot 面试题及答案整理,最新面试题
Spring Boot 面试题及答案整理,最新面试题
138 0
|
2天前
|
存储 JSON Java
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
SpringBoot集成AOP实现每个接口请求参数和返回参数并记录每个接口请求时间
48 2
|
2天前
|
前端开发 搜索推荐 Java
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革
【Spring底层原理高级进阶】基于Spring Boot和Spring WebFlux的实时推荐系统的核心:响应式编程与 WebFlux 的颠覆性变革