在上篇文章中,我们已经学习过了Spring中的类型转换机制。现在我们考虑这样一个需求:在我们web应用中,我们经常需要将前端传入的字符串类型的数据转换成指定格式或者指定数据类型来满足我们调用需求,同样的,后端开发也需要将返回数据调整成指定格式或者指定类型返回到前端页面。这种情况下,Converter已经没法直接支撑我们的需求了。这个时候,格式化的作用就很明显了,这篇文章我们就来介绍Spring中格式化的一套体系。本文主要涉及官网中的3.53.6小结

Formatter

接口定义

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

可以看到,本身这个接口没有定义任何方法,只是聚合了另外两个接口的功能

  • Printer
// 将T类型的数据根据Locale信息打印成指定格式,即返回字符串的格式
public interface Printer<T> {
String print(T fieldValue, Locale locale);
}
  • Parser
public interface Parser<T> {
// 将指定的字符串根据Locale信息转换成指定的T类型数据
T parse(String clientValue, Locale locale) throws ParseException;
}

从上面可以看出,这个两个接口维护了两个功能相反的方法,分别完成对String类型数据的解析以及格式化。

继承树

可以发现整个继承关系并不复杂,甚至可以说非常简单。只有一个抽象子类,AbstractNumberFormatter,这个类抽象了对数字进行格式化时的一些方法,它有三个子类,分别处理不同的数字类型,包括货币百分数,正常数字。其余的子类都是直接实现了Formatter接口。其中我们比较熟悉的可能就是DateFormatter

使用如下:

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

注解驱动的格式化

我们在配置格式化时,除了根据类型进行格式外(比如常见的根据Date类型进行格式化),还可以根据注解来进行格式化,最常见的注解就是org.springframework.format.annotation.DateTimeFormat。除此之外还有NumberFormat,它们都在format包下。

为了将一个注解绑定到指定的格式化器上,我们需要借助到一个接口AnnotationFormatterFactory

AnnotationFormatterFactory

public interface AnnotationFormatterFactory<A extends Annotation> {
// 可能被添加注解的字段的类型
Set<Class<?>> getFieldTypes(); // 根据注解及字段类型获取一个格式化器
Printer<?> getPrinter(A annotation, Class<?> fieldType); // 根据注解及字段类型获取一个解析器
Parser<?> getParser(A annotation, Class<?> fieldType); }

以Spring内置的一个DateTimeFormatAnnotationFormatterFactory来说,这个类实现的功能就是将DateTimeFormat注解绑定到指定的格式化器,源码如下:

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来完成格式化
DateFormatter formatter = new DateFormatter();
String style = resolveEmbeddedValue(annotation.style());
if (StringUtils.hasLength(style)) {
formatter.setStylePattern(style);
}
formatter.setIso(annotation.iso());
String pattern = resolveEmbeddedValue(annotation.pattern());
if (StringUtils.hasLength(pattern)) {
formatter.setPattern(pattern);
}
return formatter;
} }

使用@DateTimeFormat,我们只需要在字段上添加即可

public class MyModel {
@DateTimeFormat(iso=ISO.DATE)
private Date date;
}

关于日期的格式化,Spring还提供了一个类似的AnnotationFormatterFactory,专门用于处理java8中的日期格式,如下

public class Jsr310DateTimeFormatAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
implements AnnotationFormatterFactory<DateTimeFormat> { private static final Set<Class<?>> FIELD_TYPES; static {
// 这里添加了对Java8日期的支持
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);
}
........

学习到现在,对Spring的脾气大家应该都有所了解,上面这些都是定义了具体的功能实现,它们必定会有一个管理者,一个Registry,用来注册这些格式化器

FormatterRegistry

接口定义

// 继承了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);
}

UML类图

我们可以发现FormatterRegistry默认只有两个实现类

FormattingConversionService

// 继承了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<Class<?>> fieldTypes = annotationFormatterFactory.getFieldTypes();
for (Class<?> fieldType : fieldTypes) {
addConverter(new AnnotationPrinterConverter(annotationType, annotationFormatterFactory, fieldType));
addConverter(new AnnotationParserConverter(annotationType, annotationFormatterFactory, fieldType));
}
}
// ....... // 持有的一个内部类
private static class PrinterConverter implements GenericConverter { private final Class<?> fieldType; private final TypeDescriptor printerObjectType; @SuppressWarnings("rawtypes")
private final Printer printer; // 最终也是通过conversionService完成类型转换
private final ConversionService conversionService; public PrinterConverter(Class<?> fieldType, Printer<?> printer, ConversionService conversionService) {
this.fieldType = fieldType;
this.printerObjectType =
// 会通过解析Printer中的泛型获取具体类型,主要是为了判断是否需要进行类型转换
TypeDescriptor.valueOf(resolvePrinterObjectType(printer));
this.printer = printer;
this.conversionService = conversionService;
}
// ...... }

DefaultFormattingConversionService

类比我们上篇文中介绍的GenericConversionServiceDefaultConversionService,它相比于FormattingConversionService而言,提供了大量的默认的格式化器,源码如下:

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

FormatterRegistrar

在上面DefaultFormattingConversionService的源码中,有这么几行:

new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry);
new DateFormatterRegistrar().registerFormatters(formatterRegistry);

其中的JodaTimeFormatterRegistrarDateFormatterRegistrar就是FormatterRegistrar。那么这个接口有什么用呢?我们先来看看它的接口定义:

public interface FormatterRegistrar {
// 最终也是调用FormatterRegistry来完成注册
void registerFormatters(FormatterRegistry registry);
}

我们思考一个问题,为什么已经有了FormatterRegistry,Spring还要开发一个FormatterRegistrar呢?直接使用FormatterRegistry完成注册不好吗?

以这句代码为例:new JodaTimeFormatterRegistrar().registerFormatters(formatterRegistry),这段代码是将joda包下所有的默认的转换器已经注册器都注册到formatterRegistry中。

我们可以发现FormatterRegistrar相当于对格式化器及转换器进行了分组,我们调用它的registerFormatters方法,相当于将这一组格式化器直接添加到指定的formatterRegistry中。这样做的好处在于,如果我们对同一个类型的数据有两组不同的格式化策略,例如就以上面的日期为例,我们既有可能采用joda的策略进行格式化,也有可能采用Date的策略进行格式化,通过分组的方式,我们可以更见方便的在确认好策略后将需要的格式化器添加到容器中。

配置SpringMVC中的格式化器

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer { @Override
public void addFormatters(FormatterRegistry registry) {
// 调用registry.addFormatter添加格式化器即可
}
}

配置实现的原理

  1. @EnableWebMvc注解上导入了一个DelegatingWebMvcConfiguration
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
  1. DelegatingWebMvcConfiguration
// 继承了WebMvcConfigurationSupport
@Configuration
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport { private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite(); // 这个方法会注入所有的WebMvcConfigurer,包括我们的WebConfig
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
} //.....,省略无关代码 // 复写了父类WebMvcConfigurationSupport的方法
// 调用我们配置的configurer的addFormatters方法
@Override
protected void addFormatters(FormatterRegistry registry) {
this.configurers.addFormatters(registry);
}
//.....,省略无关代码
}

3.WebMvcConfigurationSupport

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)的更多相关文章

  1. Spring官网阅读(五)BeanDefinition(下)

    上篇文章已经对BeanDefinition做了一系列的介绍,这篇文章我们开始学习BeanDefinition合并的一些知识,完善我们整个BeanDefinition的体系,Spring在创建一个bea ...

  2. Spring官网阅读(十六)Spring中的数据绑定

    文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...

  3. Spring官网阅读 | 总结篇

    接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...

  4. Spring官网阅读(十八)Spring中的AOP

    文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...

  5. Spring官网阅读(十七)Spring中的数据校验

    文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...

  6. Spring官网阅读(十一)ApplicationContext详细介绍(上)

    文章目录 ApplicationContext 1.ApplicationContext的继承关系 2.ApplicationContext的功能 Spring中的国际化(MessageSource) ...

  7. Spring官网阅读(三)自动注入

    上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...

  8. Spring官网阅读(一)容器及实例化

    从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...

  9. Spring官网阅读(二)(依赖注入及方法注入)

    上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...

随机推荐

  1. IOCP完成端口

    转:https://blog.csdn.net/piggyxp/article/details/6922277 本系列里完成端口的代码在两年前就已经写好了,但是由于许久没有写东西了,不知该如何提笔,所 ...

  2. Git把本地代码推送到远程github仓库

    运用Git版本控制系统进行代码的管理,以便于团队成员的协作,由于之前是使用svn来进行版本控制,所以对于Git使用还有待熟练掌握.Git与svn类似,个人认为两者之间比较直观的区别就是 Git 不需要 ...

  3. jmeter事务控制器

    jmeter事务控制器常用于压力测试时如果一个功能包括多个请求时,需要测试这个功能的压力情况,则需要把多个请求放到一个事务控制器里面

  4. 基于Neo4j的个性化Pagerank算法文章推荐系统实践

    新版的Neo4j图形算法库(algo)中增加了个性化Pagerank的支持,我一直想找个有意思的应用来验证一下此算法效果.最近我看Peter Lofgren的一篇论文<高效个性化Pagerank ...

  5. Python 开发工具链全解

    可能刚开始学习Python时,有人跟你说可以将源文件所在的文件夹添加到 PYTHONPATH环境变量中,然后可以从其他位置导入此代码.在大多数情况下,这个人常常忘记补充这是一个非常糟糕的主意.有些人在 ...

  6. Java中的二分查找

    二分查找:(折半查找) 前提:数组必须是有序的. 思想:每次都猜中间的那个元素,比较大或者小,就能减少一半的元素.思路:A:定义最小索引,最大索引. B:比较出中间索引 C:拿中间索引的值和要查找的元 ...

  7. Spring基于注解@Required配置

    基于注解的配置 从 Spring 2.5 开始就可以使用注解来配置依赖注入.而不是采用 XML 来描述一个 bean 连线,你可以使用相关类,方法或字段声明的注解,将 bean 配置移动到组件类本身. ...

  8. 不使用tomcat,仅适用javaSE手写服务器--模拟登陆

    1.搭建框架 我们只是简单模拟,框架简单分三个模块 a,服务器端server包 b,servlet,根据不同的请求url,利用反射生产对应的servlet c,IO工具包,用来关闭IO流 d,编写we ...

  9. Git基本操作和使用

    基本命令: git config git init git clone git remote git fetch git commit git rebase git push 本地基本操作: git ...

  10. python学习13类2之封装

    '''''''''面向对象三大特性:封装,继承,多态1.封装: 类中以_或者__的属性,都是私有属性,禁止外部调用.'''class Student(object): def __init__(sel ...