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

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

自定义转换器/格式化器


WebMvcConfigurationSupport有这么一句:


  @Bean
  public FormattingConversionService mvcConversionService() {
    FormattingConversionService conversionService = new DefaultFormattingConversionService();
    addFormatters(conversionService);
    return conversionService;
  }


它默认使用的就是DefaultFormattingConversionService,因此我们只需要addFormatters()向里添加格式化器即可。(此处conversionService既是个ConverterRegistry,又是个FormatterRegistry,所以~~~) 此处只演示在WebMvc场景下的自定义:


@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new PersonConverter());
        //registry.addFormatter();
    }
}


就这样我们就很方便的完成了我们对自定义转换器/格式化器~的添加。


在Spring MVC开发中,我个人认为自定义转换器、格式化器还是非常重要的一个章节,应用也可以非常的广泛。比如我们可以对请求数据进行脱敏、解密等等处理,这样都是可行的~~


若你想自己用FormattingConversionServiceFactoryBean来注册转换器、格式化器们,目前来说除了自定义@Bean,还没有一个很好的用于之地


在基于xml配置中,这么做:<mvc:annotation-driven conversion-service="conversionService"/>我们就可以自定义一个名字为conversionService的FormattingConversionServiceFactoryBean,从而达到自定义注册Bean的效果。在注解驱动的情况下,这些变得更加简单方便了些~~~~(直接定义使用FormattingConversionService/DefaultFormattingConversionService即可)


选择Converter, 还是Formatter


Converter是一般工具, 可以将一种类型转换成另一种类型, 例如, 将String转换成Date, 或者Long转换成Date, Conveter既可以用在web层, 也可以用在其他层中。

Formatter只能将String转换层另一种java类型, 例如, 将String转换成Date, 但它不可能将Long转换成Date类型, 因此Formatter适用于web层, 因此, SpringMVC应用程序中, 选择Formatter比选择Converter更合适


JDK中的格式化器java.text.Format


注意是Format,不是java.util.Formatter。Formatter工具我个人认为不是特别的重点~~


Java中允许我们对指定的对象进行某种格式化,从而得到我们想要的格式化样式。

Foramt是一个抽象基类,其具体子类必须实现format方法。


public abstract class Format implements Serializable, Cloneable {
  ...
  // format方法用于将对象格式化为指定模式的字符串
    public abstract StringBuffer format(Object obj, StringBuffer toAppendTo, FieldPosition pos);
    // parseObject方法用于将字符串重新解析为对象
    public abstract Object parseObject (String source, ParsePosition pos);
  ...
}


Format的直接子类包括DateFormat、NumberFormat和MessageFormat。


image.png


DateFormat


DateFormat根据当前语言环境格式化日期和时间。DateFormat是一个抽象类,所以不能直接new创建实例对象。但该类为我们提供了工厂方法方便我们使用。


  1. getDateInstance()方法,获取格式化的日期,输出样式:2015-12-10
  2. getDateTimeInstance()方法,获取格式化的日期和时间,输出样式:2015-12-10 10:21:41
  3. getTimeInstance()方法,获取格式化的时间,输出样式:10:21:41
  4. getInstance()方法,获取格式化的日期和时间,输出样式:15-12-10 上午10:21


例如;

DateFormat format = DateFormat.getDateInstance(DateFormat.DEFAULT,Locale.CANADA);//获取加拿大的格式化日期


该抽象类的实例,都是返回默认的模版来显示的(SMALL、Full等等)。若都不合你意,你可以使用它的儿子–>我们最熟悉的SimpleDateFormat来指定partern作为我们自己的模版。


Date date = new Date();
SimpleDateFormat format = new SimpleDateFormat("今天是yyyy-MM-dd E hh:mm:ss,是yyyy年的第DD天,在该月是第dd天");
System.out.println(format.format(date)); // 将会输出:今天是2015-12-10 星期四 09:38:16,是2015年的第344天,在该月是第10天


SimpleDateFormat是DateFormat的一个具体类,它允许我们指定格式模式从而获取我们理想的格式化日期和时间。

另外DateFormat放在java.text包中,我认为是它的败笔~

NumberFormat


注意Spring有这个注解也叫这名字,注意区分。

NumberFormat根据当前语言环境格式化数字,它也是个抽象类。


  1. getCurrencyInstance()方法,根据当前语言环境获取货币数值格式。传递Locale对象可以获取指定语言环境下的货币数值格式
  2. getInstance()和getNumberInstance()方法都会获取到常规数值格式
  3. getIntegerInstance()方法获取常规整数值格式,如果需要格式化的数值为小数,则会将数值四舍五入为最接近的整数
  4. getPercentInstance()方法获取百分比的数值格式

    public static void main(String[] args) {
        NumberFormat format = NumberFormat.getCurrencyInstance(Locale.CHINA);
        System.out.println(format.format(439.6)); //¥439.60
        System.out.println(NumberFormat.getInstance().format(439.6)); //439.6
        System.out.println(NumberFormat.getIntegerInstance().format(439.6)); // 440 整数
        System.out.println(NumberFormat.getPercentInstance().format(439.6)); // 43,960%
    }


NumberFormat有两个具体实现子类DecimalFormat和ChoiceFormat。


DecimalFormat


DecimalFormat同SimpleDateFormat类似,允许我们指定格式模式获取我们想要的格式化数值


DecimalFormat类对于数值的小数部分,默认显示3位小数,在去掉超出小数点后面3位的部分时,会将数值四舍五入为最接近的数值格式化输出。但是我们可以对这个默认进行设置:

setMaximumFractionDigits(int newValue)方法,设置小数部分中允许的最大数字位数

setMinimumFractionDigits(int newValue)方法,设置小数部分中允许的最小数字位数,如果原数小数位数不够的话,会补零。


对于数值的整数部分,默认3个数字为一组进行显示,同样对此我们也可以自定义,使用setGroupingSize(int i)方法,设置分组中一组的位数。


setGroupingUsed(boolean value)方法设置是否使用分组,true表示使用,false表示取消分组


    public static void main(String[] args) {
        DecimalFormat format1 = new DecimalFormat("#\u2030");
        System.out.println(format1.format(0.3345));//输出334‰
        DecimalFormat format2 = new DecimalFormat("##.##");
        System.out.println(format2.format(12.345));//输出12.35
        DecimalFormat format3 = new DecimalFormat("0000.00");
        System.out.println(format3.format(12.345));//输出0012.35  // 前面用0补齐了~~
        DecimalFormat format4 = new DecimalFormat("#.##%");
        System.out.println(format4.format(12.345));//输出1234.5%
    }


ChoiceFormat


ChoiceFormat允许将格式化运用到某个范围的数,通常与MessageFormat一同使用。

ChoiceFormat在构造方法中接收一个limits数组和一个format数组,这两个数组的长度必须相等


limits数组实际上是个区间,可开可闭,并且必须按升序排列,如果不按升序排列,格式化结果将会不正确,还可以使用\u221E(表示无穷大)。


  1. nextDouble(double d)静态方法查找大于d的最小double值,用在limits数组中,从而使limits数组形成一个右开区间数组,例如:limits = {0,1,ChoiceFormat.nextDouble(1)}
  2. nextDouble(double d, boolean positive)静态方法,如果positive参数为true,表示查找大于d的最小double值;如果positive参数为false,表示查找小于d的最大double值,这样就可以使limits形成一个左开区间数组
  3. previousDouble(double d)静态方法,查找小于d的最大double值


    public static void main(String[] args) {
        double[] limits = {3, 4, 5, 6, 7, 8, 9};
        String[] formats = {"星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"};
        ChoiceFormat format = new ChoiceFormat(limits, formats);
        System.out.println(format.format(2.5));//将会输出"星期一"
        System.out.println(format.format(3.6)); //3.6介于3和4之间,所以会匹配3,又由于3在limits数组中的索引是0,所以会在formats数组按照索引0的值,即输出"星期一" 
    }


ChoiceFormat类的构造方法也允许我们传入一个模式字符串,format方法会根据这个模式字符串执行格式化操作:doubleNum [占位符] formatStr

占位符可以使用#、< 、\u2264(<=)



    public static void main(String[] args) {
        ChoiceFormat cf = new ChoiceFormat("1#is 1 | 1<is more than 1");
        System.out.println(cf.format(1));//输出"is 1"
        System.out.println(cf.format(2));//输出"is more than 1"
        System.out.println(cf.format(0));//输出"is 1"
    }


由上面的例子可以看出,模式字符串中的每个模式元素之间使用"|“分割,”|"前后可以添加空格以美化代码,而且必须按照升序进行书写,否则会出现java.lang.IllegalArgumentException的运行时异常


观看ChoiceFormat类的源码我们得知,实际上在内部,模式字符串还是被转换为limits和formats两个数组


MessageFormat(常用)

MessageFormat提供了以语言环境无关的生成连接消息的方式。

常用MessageFormat的静态方法format,该方法接收一个字符串的模式和一组对象(对象数组),按照模式形式将格式化的对象插入到模式中,然后返回字符串结果。


MessageFormat占位符由三种书写格式:


  1. {index}:
  2. {index,formatType}:
  3. {index,formatType,formatStyle}


index表示数字角标。FormatType包括number、date、 time、choice等。FormatStyle包括short、medium、long、full、integer、currency、percent等


number对应了NumberFormat,其子格式对应了DecimalFormat

date和time对应了DateFormat,其子格式对应了SimpleDateFormat

choice对应了ChoiceFormat


Demo:

    public static void main(String[] args) {
        int planet = 7;
        String event = "a disturbance in the Force";
        String result = MessageFormat.format("At {1,time} on {1,date}, there was {2} on planet {0,number,integer}.", planet, new Date(), event);
        System.out.println(result); //At 15:38:07 on 2019-6-4, there was a disturbance in the Force on planet 7.
    }


备注:若你的模版被多次使用到,也可以使用MessageFormat的构造方法传入pattern string(模式字符串),然后调用普通的format方法。若只执行一次,可以用静态方法~


注意指定了formatType后,第三个参数formatStyle并不能随意写的。至于formatType和formatStyle的对应关系,此处不解释了


最后,我们用java处理国际化的时候,我们的properties配置文件里经常可议看到{0} {1}这样的占位符,其实最终都是交给MessageFormat去处理了的~~


注意,注意,注意:Format中的子类都是不同步,所以需要注意线程安全问题


String类中的format方法


String因为过于常用,所以在JDK5的时候它提供了静态方法format方法来方便我们对字符串进行格式化。 直接看例子吧~~

    public static void main(String[] args) {
        String result1 = String.format("小明今年%d岁,他住在%s,他的月工资有%.2f", 25, "北京市", 6633.435);
        System.out.println(result1);//输出:小明今年25岁,他住在北京市,他的月工资有6633.44
        /****************************************************/
        double num = 123.4567899;
        String result2 = String.format("%e", num);
        System.out.println(result2);//输出:1.234568e+02
    }




image.png

总结


Formatter就像Converter一样,也是将一种类型转换为另外一种类型,但是Formatter的源类型必须是String,而Converter的源类型可以是任意类型。


Spring中的Formatter其实包含了数据转换的内容,可以说是对标准数据转换的一个升级。

我们在Spring MVC中一般使用注解:@NumberFormat和@DateTimeFormat来格式化入参、出参。但是注意:这是Spring的能力,并非web的,只是我们一般在web层来使用。


Formatter更加适合Web层,而Converter则可以在任意层中。为了转换SpringMVC应用程序中的表单的用户输入,始终应该选择Formatter而不是Converter

相关文章
|
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抽象(上)
|
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 的颠覆性变革