Spring官网阅读(十五)Spring中的格式化(Formatter)
2021-04-02 09:29
标签:warnings hash 父类 system 打印 小结 前端 pos ext 在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们考虑这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支撑我们的需求了。这个时候,格式化的作用就很明显了,这篇文章我们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.5及3.6小结 Formatter 接口定义 可以看到,本身这个接口没有定义任何方法,只是聚合了另外两个接口的功能 从上面可以看出,这个两个接口维护了两个功能相反的方法,分别完成对String类型数据的解析以及格式化。 继承树 可以发现整个继承关系并不复杂,甚至可以说非常简单。只有一个抽象子类,AbstractNumberFormatter,这个类抽象了对数字进行格式化时的一些方法,它有三个子类,分别处理不同的数字类型,包括货币,百分数,正常数字。其余的子类都是直接实现了Formatter接口。其中我们比较熟悉的可能就是DateFormatter了 使用如下: 注解驱动的格式化 我们在配置格式化时,除了根据类型进行格式外(比如常见的根据Date类型进行格式化),还可以根据注解来进行格式化,最常见的注解就是org.springframework.format.annotation.DateTimeFormat。除此之外还有NumberFormat,它们都在format包下。 AnnotationFormatterFactory 以Spring内置的一个DateTimeFormatAnnotationFormatterFactory来说,这个类实现的功能就是将DateTimeFormat注解绑定到指定的格式化器,源码如下: 使用@DateTimeFormat,我们只需要在字段上添加即可 关于日期的格式化,Spring还提供了一个类似的AnnotationFormatterFactory,专门用于处理java8中的日期格式,如下 学习到现在,对Spring的脾气大家应该都有所了解,上面这些都是定义了具体的功能实现,它们必定会有一个管理者,一个Registry,用来注册这些格式化器 FormatterRegistry 接口定义 UML类图 FormattingConversionService DefaultFormattingConversionService 类比我们上篇文中介绍的GenericConversionService跟DefaultConversionService,它相比于FormattingConversionService而言,提供了大量的默认的格式化器,源码如下: FormatterRegistrar 在上面DefaultFormattingConversionService的源码中,有这么几行: 其中的JodaTimeFormatterRegistrar,DateFormatterRegistrar就是FormatterRegistrar。那么这个接口有什么用呢?我们先来看看它的接口定义: 我们思考一个问题,为什么已经有了FormatterRegistry,Spring还要开发一个FormatterRegistrar呢?直接使用FormatterRegistry完成注册不好吗? 以这句代码为例:new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry),这段代码是将joda包下所有的默认的转换器已经注册器都注册到formatterRegistry中。 我们可以发现FormatterRegistrar相当于对格式化器及转换器进行了分组,我们调用它的registerFormatters方法,相当于将这一组格式化器直接添加到指定的formatterRegistry中。这样做的好处在于,如果我们对同一个类型的数据有两组不同的格式化策略,例如就以上面的日期为例,我们既有可能采用joda的策略进行格式化,也有可能采用Date的策略进行格式化,通过分组的方式,我们可以更见方便的在确认好策略后将需要的格式化器添加到容器中。 配置SpringMVC中的格式化器 配置实现的原理 3.WebMvcConfigurationSupport Spring中的格式化到此就结束了,总结画图如下: 往期精选 Spring官网阅读专辑 程序员DMZ Spring官网阅读(十五)Spring中的格式化(Formatter) 标签:warnings hash 父类 system 打印 小结 前端 pos ext 原文地址:https://blog.51cto.com/14890701/2519725
public interface Formatter
// 将T类型的数据根据Locale信息打印成指定格式,即返回字符串的格式
public interface Printer
public interface Parser
在这里插入图片描述public class Main {
public static void main(String[] args) throws Exception {
DateFormatter dateFormatter = new DateFormatter();
dateFormatter.setIso(DateTimeFormat.ISO.DATE);
System.out.println(dateFormatter.print(new Date(), Locale.CHINA));
System.out.println(dateFormatter.parse("2020-03-26", Locale.CHINA));
// 程序打印:
// 2020-03-26
// Thu Mar 26 08:00:00 CST 2020
}
}
在这里插入图片描述
为了将一个注解绑定到指定的格式化器上,我们需要借助到一个接口AnnotationFormatterFactorypublic interface AnnotationFormatterFactory {
// 可能被添加注解的字段的类型
Set
public class DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory
public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}
public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory
// 继承了ConverterRegistry,所以它同时还是一个Converter注册器
public interface FormatterRegistry extends ConverterRegistry {
// 一系列添加格式化器的方法
void addFormatterForFieldType(Class> fieldType, Printer> printer, Parser> parser);
void addFormatterForFieldType(Class> fieldType, Formatter> formatter);
void addFormatterForFieldType(Formatter> formatter);
void addFormatterForAnnotation(AnnotationFormatterFactory, ?> factory);
}
在这里插入图片描述
我们可以发现FormatterRegistry默认只有两个实现类// 继承了GenericConversionService ,所以它能对Converter进行一系列的操作
// 实现了接口FormatterRegistry,所以它也可以注册格式化器了
// 实现了EmbeddedValueResolverAware,所以它还能有非常强大的功能:处理占位符
public class FormattingConversionService extends GenericConversionService implements FormatterRegistry, EmbeddedValueResolverAware {
// ....
// 最终也是交给addFormatterForFieldType去做的
// getFieldType:它会拿到泛型类型。并且支持DecoratingProxy
@Override
public void addFormatter(Formatter> formatter) {
addFormatterForFieldType(getFieldType(formatter), formatter);
}
// 存储都是分开存储的 读写分离
// PrinterConverter和ParserConverter都是一个GenericConverter 采用内部类实现的
// 注意:他们的ConvertiblePair必有一个类型是String.class
// Locale一般都可以这么获取:LocaleContextHolder.getLocale()
// 在进行printer之前,会先判断是否能进行类型转换,如果能进行类型转换会先进行类型转换,之后再格式化
// 在parse之后,会判断是否还需要进行类型转换,如果需要类型转换会先进行类型转换
@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
public class DefaultFormattingConversionService extends FormattingConversionService {
private static final boolean jsr354Present;
private static final boolean jodaTimePresent;
static {
ClassLoader classLoader = DefaultFormattingConversionService.class.getClassLoader();
// 判断是否导入了jsr354相关的包
jsr354Present = ClassUtils.isPresent("javax.money.MonetaryAmount", classLoader);
// 判断是否导入了joda
jodaTimePresent = ClassUtils.isPresent("org.joda.time.LocalDate", classLoader);
}
// 会注册很多默认的格式化器
public DefaultFormattingConversionService() {
this(null, true);
}
public DefaultFormattingConversionService(boolean registerDefaultFormatters) {
this(null, registerDefaultFormatters);
}
public DefaultFormattingConversionService(
@Nullable StringValueResolver embeddedValueResolver, boolean registerDefaultFormatters) {
if (embeddedValueResolver != null) {
setEmbeddedValueResolver(embeddedValueResolver);
}
DefaultConversionService.addDefaultConverters(this);
if (registerDefaultFormatters) {
addDefaultFormatters(this);
}
}
public static void addDefaultFormatters(FormatterRegistry formatterRegistry) {
// 添加针对@NumberFormat的格式化器
formatterRegistry.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());
// 针对货币的格式化器
if (jsr354Present) {
formatterRegistry.addFormatter(new CurrencyUnitFormatter());
formatterRegistry.addFormatter(new MonetaryAmountFormatter());
formatterRegistry.addFormatterForFieldAnnotation(new Jsr354NumberFormatAnnotationFormatterFactory());
}
new DateTimeFormatterRegistrar().registerFormatters(formatterRegistry);
// 如没有导入joda的包,那就默认使用Date
if (jodaTimePresent) {
// 针对Joda
new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
}
else {
// 没有joda的包,是否Date
new DateFormatterRegistrar().registerFormatters(formatterRegistry);
}
}
}
new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
new DateFormatterRegistrar().registerFormatters(formatterRegistry);
public interface FormatterRegistrar {
// 最终也是调用FormatterRegistry来完成注册
void registerFormatters(FormatterRegistry registry);
}
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
// 调用registry.addFormatter添加格式化器即可
}
}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
// 继承了WebMvcConfigurationSupport
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 这个方法会注入所有的WebMvcConfigurer,包括我们的WebConfig
@Autowired(required = false)
public void setConfigurers(List
public class WebMvcConfigurationSupport implements ApplicationContextAware, ServletContextAware {
// 这就是真相,这里会创建一个FormattingConversionService,并且是一个DefaultFormattingConversionService,然后调用addFormatters方法
@Bean
public FormattingConversionService mvcConversionService() {
FormattingConversionService conversionService = new DefaultFormattingConversionService();
addFormatters(conversionService);
return conversionService;
}
protected void addFormatters(FormatterRegistry registry) {
}
}
总结
Spring杂谈
点赞、转发、在看,多谢多谢!
钟意作者
文章标题:Spring官网阅读(十五)Spring中的格式化(Formatter)
文章链接:http://soscw.com/essay/71343.html