自定义转换器/格式化器
在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。
DateFormat
DateFormat根据当前语言环境格式化日期和时间。DateFormat是一个抽象类,所以不能直接new创建实例对象。但该类为我们提供了工厂方法方便我们使用。
- getDateInstance()方法,获取格式化的日期,输出样式:2015-12-10
- getDateTimeInstance()方法,获取格式化的日期和时间,输出样式:2015-12-10 10:21:41
- getTimeInstance()方法,获取格式化的时间,输出样式:10:21:41
- 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根据当前语言环境格式化数字,它也是个抽象类。
- getCurrencyInstance()方法,根据当前语言环境获取货币数值格式。传递Locale对象可以获取指定语言环境下的货币数值格式
- getInstance()和getNumberInstance()方法都会获取到常规数值格式
- getIntegerInstance()方法获取常规整数值格式,如果需要格式化的数值为小数,则会将数值四舍五入为最接近的整数
- 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(表示无穷大)。
- nextDouble(double d)静态方法查找大于d的最小double值,用在limits数组中,从而使limits数组形成一个右开区间数组,例如:limits = {0,1,ChoiceFormat.nextDouble(1)}
- nextDouble(double d, boolean positive)静态方法,如果positive参数为true,表示查找大于d的最小double值;如果positive参数为false,表示查找小于d的最大double值,这样就可以使limits形成一个左开区间数组
- 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占位符由三种书写格式:
- {index}:
- {index,formatType}:
- {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 }
总结
Formatter就像Converter一样,也是将一种类型转换为另外一种类型,但是Formatter的源类型必须是String,而Converter的源类型可以是任意类型。
Spring中的Formatter其实包含了数据转换的内容,可以说是对标准数据转换的一个升级。
我们在Spring MVC中一般使用注解:@NumberFormat和@DateTimeFormat来格式化入参、出参。但是注意:这是Spring的能力,并非web的,只是我们一般在web层来使用。
Formatter更加适合Web层,而Converter则可以在任意层中。为了转换SpringMVC应用程序中的表单的用户输入,始终应该选择Formatter而不是Converter