05springMVC数据格式化
- 数据格式化简介
- 内建的格式转换器
- 使用内建格式转换器示例
- 字段级别的解析/格式化
- 集成到Spring Web MVC环境
1 数据格式化简介
对属性对象的输入/输出进行格式化,其实是属于“类型转换”的范畴。格式化转换系统是Spring通用的,其定义在org.springframework.format包中,不仅仅在Spring Web MVC场景下用。
2 内建的格式转换器
- DateFormatter java.util.Date<---->String,实现日期的格式化/解析
- NumberFormatter java.lang.Number<---->String,实现通用样式的格式化/解析
- CurrencyFormatter java.lang.BigDecimal<---->String,实现货币样式的格式化/解析
- PercentFormatter java.lang.Number<---->String,实现百分数样式的格式化/解析
- NumberFormatAnnotationFormatterFactory @NumberFormat注解类型的数字字段类型<---->String
①通过@NumberFormat指定格式化/解析格式
②可以格式化/解析的数字类型:Short、Integer、Long、Float、Double、BigDecimal、BigInteger
- JodaDateTimeFormatAnnotationFormatterFactory @DateTimeFormat注解类型的日期字段类型<---->String
①通过@DateTimeFormat指定格式化/解析格式
②可以格式化/解析的日期类型:joda中的日期类型(org.joda.time包中的):LocalDate、LocalDateTime、LocalTime、ReadableInstant,java内置的日期类型:Date、Calendar、Long。
注意:classpath中必须有Joda-Time类库,否则无法格式化日期类型。
提示:
NumberFormatAnnotationFormatterFactory和JodaDateTimeFormatAnnotationFormatterFactory(如果classpath提供了Joda-Time类库)在使用格式化服务实现DefaultFormattingConversionService时会自动注册。
3 使用内建格式转换器示例
- 环境准备
在示例之前,需要到http://joda-time.sourceforge.net/下载Joda-Time类库,这里使用的是joda-time-2.5版本,将这个jar包添加到项目中。
网盘下载地址:https://pan.baidu.com/s/1dGh373r
- 类型级别的解析/格式化
一、直接使用Formatter SPI进行解析/格式化
1 //CurrencyFormatter实现货币样式的格式化/解析 2 CurrencyFormatter currencyFormatter = new CurrencyFormatter(); 3 //保留小数点后几位 4 currencyFormatter.setFractionDigits(2); 5 //舍入模式(CEILING表示四舍五入) 6 currencyFormatter.setRoundingMode(RoundingMode.CEILING); 7 //1、将带货币符号的字符串“$123.125”转换为BigDecimal("123.00") 8 BigDecimal b1 = currencyFormatter.parse("$123.125", Locale.US); 9 BigDecimal b2 = currencyFormatter.parse("¥123.125", Locale.CHINA); 10 //2、将BigDecimal("123")格式化为字符串“$123.00”展示 11 String s1 = currencyFormatter.print(new BigDecimal("123"), Locale.US); 12 String s2 = currencyFormatter.print(new BigDecimal("123"), Locale.CHINA); 13 System.out.println("b1="+b1+",b2="+b2+",s1="+s1+",s2="+s2);
print方法:将BigDecimal类型数据根据Locale信息格式化为字符串数据进行展示。parse方法:将带格式的字符串根据Locale信息解析为相应的BigDecimal类型数据;
不同于Convert SPI,Formatter SPI可以根据本地化(Locale)信息进行解析/格式化。
二:使用DefaultFormattingConversionService进行解析/格式化
1 // DefaultFormattingConversionService 带数据格式化功能的类型转换服务实现 2 //DefaultFormattingConversionService会自动根据浏览器请求的信息返回相应的格式 3 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); 4 //默认不自动注册任何Formatter 5 CurrencyFormatter currencyFormatter = new CurrencyFormatter(); 6 currencyFormatter.setFractionDigits(2);//保留小数点后几位 7 currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入) 8 //注册Formatter SPI实现 9 conversionService.addFormatter(currencyFormatter); 10 //绑定Locale信息到ThreadLocal 11 //FormattingConversionService内部自动获取作为Locale信息,如果不设值默认是Locale.getDefault() 12 //设置本地化信息到ThreadLocal,以便Formatter SPI根据本地化信息进行解析/格式化; 13 LocaleContextHolder.setLocale(Locale.US); 14 //用于将BigDecimal类型数据格式化为字符串类型 15 String s1 = conversionService.convert(new BigDecimal("1234.128"), String.class); 16 //LocaleContextHolder.setLocale(null); 17 LocaleContextHolder.setLocale(Locale.CHINA); 18 String s2 = conversionService.convert(new BigDecimal("1234.128"), String.class); 19 //用于将字符串类型数据解析为BigDecimal类型数据 20 BigDecimal b1 = conversionService.convert("¥1,234.13", BigDecimal.class); 21 //LocaleContextHolder.setLocale(null); 22 System.out.println("s1="+s1+",s2="+s2+",b1="+b1);
说明:
DefaultFormattingConversionService:带数据格式化功能的类型转换服务实现;
conversionService.addFormatter():注册Formatter SPI实现;
conversionService.convert(new BigDecimal(“1234.128”), String.class):用于将BigDecimal类型数据格式化为字符串类型,此处根据“LocaleContextHolder.setLocale(locale)”设置的本地化信息进行格式化;
conversionService.convert(“¥1,234.13”, BigDecimal.class):用于将字符串类型数据解析为BigDecimal类型数据,此处也是根据“LocaleContextHolder.setLocale(locale)”设置的本地化信息进行解;
LocaleContextHolder.setLocale(locale):设置本地化信息到ThreadLocal,以便Formatter SPI根据本地化信息进行解析/格式化;
4 字段级别的解析/格式化
前面学习了类型级别的解析/格式化,从测试用例可以看出类型级别的是对项目中的整个类型实施相的解析/格式化逻辑。
有的同学可能需要在不同的类的字段实施不同的解析/格式化逻辑,如用户模型类的注册日期字段只需要如“2013-05-02”格式进行解析/格式化即可,而订单模型类的下订单日期字段可能需要如“2013-05-02 20:13:13”格式进行展示。
这个就需要进行字段级别的解析/格式化了。
一、使用内置的注解进行字段级别的解析/格式化
1:首先准备测试用的model类
1 public class FormatterModel { 2 @NumberFormat(style = Style.NUMBER, pattern = "#,###") 3 private int totalCount; 4 @NumberFormat(style = Style.PERCENT) 5 private double discount; 6 @NumberFormat(style = Style.CURRENCY) 7 private double sumMoney; 8 @DateTimeFormat(iso = ISO.DATE) 9 private Date registerDate; 10 @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") 11 private Date orderDate; 12 //省略get/set方法 13 }
示例说明 :
此处我们使用了Spring字段级别解析/格式化的两个内置注解:
1:@Number:定义数字相关的解析/格式化元数据(通用样式、货币样式、百分数样式),参数如下:
(1)style:用于指定样式类型,包括三种:Style.NUMBER(通用样式)
Style.CURRENCY(货币样式) Style.PERCENT(百分数样式),默认Style.NUMBER;
(2)pattern:自定义样式,如patter=“#,###”;
2:@DateTimeFormat:定义日期相关的解析/格式化元数据,参数如下:
(1)pattern:指定解析/格式化字段数据的模式,如”yyyy-MM-dd HH:mm:ss”
(2)iso:指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用)
ISO.DATE(yyyy-MM-dd) ISO.TIME(hh:mm:ss.SSSZ) ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ),默认ISO.NONE;
(3)style:指定用于格式化的样式模式,默认“SS”,具体使用请参考Joda-Time
类库的org.joda.time.format.DateTimeFormat的forStyle的javadoc;
优先级: pattern 大于iso 大于style。
测试代码
1 //默认自动注册对@NumberFormat和@DateTimeFormat的支持 2 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); 3 //准备测试模型对象 4 FormatterModel model = new FormatterModel(); 5 model.setTotalCount(10000); 6 model.setDiscount(0.51); 7 model.setSumMoney(10000.13); 8 model.setRegisterDate(new Date(2013-1900, 4, 1)); 9 model.setOrderDate(new Date(2013-1900, 4, 1, 20, 18, 18)); 10 //获取类型信息 11 TypeDescriptor descriptor =new TypeDescriptor(FormatterModel.class.getDeclaredField("totalCount")); 12 TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); 13 String s1 = (String)conversionService.convert(model.getTotalCount(), descriptor,stringDescriptor); 14 int a = (Integer)conversionService.convert("10,000", stringDescriptor, descriptor); 15 System.out.println("s1=="+s1+" , a="+a);
测试说明
1:TypeDescriptor:拥有类型信息的上下文,用于Spring3类型转换系统获取类型信息的(可以包含类、字段、方法参数、属性信息);通过TypeDescriptor,我们就可以获取(类、字段、方法参数、属性)的各种信息,如注解类型信息;
2:conversionService.convert(model.getTotalCount(), descriptor,stringDescriptor):将totalCount格式化为字符串类型,此处会根据totalCount字段的注解信息(通过descriptor对象获取)来进行格式化;
3:conversionService.convert("10,000", stringDescriptor, descriptor):将字符串“10,000”解析为totalCount字段类型,此处会根据totalCount字段的注解信息(通过descriptor对象获取)来进行解析。
继续测试,可以为不同的字段指定不同的注解信息进行字段级别的细粒度数据解析/格式化,测试如下:
1 TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); 2 TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("registerDate")); 3 String s1 = (String)conversionService.convert(model.getRegisterDate(), descriptor,stringDescriptor); 4 Date d1 = (Date)conversionService.convert("2013-05-01", stringDescriptor, descriptor); 5 descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("orderDate")); 6 String s2 = (String)conversionService.convert(model.getOrderDate(), descriptor,stringDescriptor); 7 Date d2 = (Date)conversionService.convert("2013-05-01 20:18:18", stringDescriptor,descriptor); 8 System.out.println("s1=="+s1+" , d1="+d1+" , s2="+s2+" , d2="+d2);
通过如上测试可以看出,我们可以通过字段注解方式实现细粒度的数据解析/格式化控制,但是必须使用TypeDescriptor来指定类型的上下文信息,即编程实现字段的数据解析/格式化比较麻烦
5 集成到Spring Web MVC环境
如果没有自定义的Formatter的话,spring的配置跟前面讲数据转换是一样的 。
测试用的Controller:
1 @RequestMapping(value = "/tf") 2 public String testFormatter(@ModelAttribute("model") FormatterModel fm) { 3 LocaleContextHolder.setLocale(Locale.US); 4 return "formatter"; 5 }
展示结果的formatter.jsp ,使用了两种方式
1 <%@taglib prefix="spring" uri="http://www.springframework.org/tags" %> 2 <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 3 totalCount:<spring:bind path="model.registerDate">${status.value}</spring:bind><br/> 4 discount:<spring:bind path="model.orderDate">${status.value}</spring:bind><br/> 5 sumMoney:<spring:bind path="model.sumMoney">${status.value}</spring:bind><br/> 6 <br/><br/> 7 <form:form commandName="model"> 8 <form:input path="sumMoney"/> 9 </form:form> 10 <!-- &orderDate=1900-11-15 -->
测试用的url
在浏览器输入类似如下的url,
http://localhost:8088/05springMVC/tf?totalCount=12345&discount=0.25&sumMoney=123
就可以看到格式化后的结果了