SpringMVC的数据格式化

来源:互联网 发布:淘宝店铺降权查询 编辑:程序博客网 时间:2024/06/04 18:08

在如Web /客户端项目中,通常需要将数据转换为具有某种格式的字符串进行展示,因此上节我们学习的数据类型转换系统核心作用不是完成这个需求,因此Spring3引入了格式化转换器(Formatter SPI) 和格式化服务API(FormattingConversionService)从而支持这种需求。在Spring中它和PropertyEditor功能类似,可以替代PropertyEditor来进行对象的解析和格式化,而且支持细粒度的字段级别的格式化/解析。

Formatter SPI核心是完成解析和格式化转换逻辑,在如Web应用/客户端项目中,需要解析、打印/展示本地化的对象值时使用,如根据Locale信息将java.util.Date—->java.lang.String打印/展示、java.lang.String—->java.util.Date等。

架构

一共有如下两组四个接口:
这里写图片描述

Printer接口

Printer接口:格式化显示接口,将T类型的对象根据Locale信息以某种格式进行打印显示(即返回字符串形式);

package org.springframework.format;  public interface Printer<T> {      String print(T object, Locale locale);   }  

Parser接口

Parser接口:解析接口,根据Locale信息解析字符串到T类型的对象;

package org.springframework.format;  public interface Parser<T> {      T parse(String text, Locale locale) throws ParseException;  } 

解析失败可以抛出java.text.ParseException或IllegalArgumentException异常即可。

Formatter接口

格式化SPI接口,继承Printer和Parser接口,完成T类型对象的格式化和解析功能;

package org.springframework.format;  public interface Formatter<T> extends Printer<T>, Parser<T> {  }  

AnnotationFormatterFactory接口

注解驱动的字段格式化工厂,用于创建带注解的对象字段的Printer和Parser,即用于格式化和解析带注解的对象字段.

package org.springframework.format;  public interface AnnotationFormatterFactory<A extends Annotation> {//①可以识别的注解类型      Set<Class<?>> getFieldTypes();//②可以被A注解类型注解的字段类型集合      Printer<?> getPrinter(A annotation, Class<?> fieldType);//③根据A注解类型和fieldType类型获取Printer      Parser<?> getParser(A annotation, Class<?> fieldType);//④根据A注解类型和fieldType类型获取Parser  }  

返回用于格式化和解析被A注解类型注解的字段值的Printer和Parser。如JodaDateTimeFormatAnnotationFormatterFactory可以为带有@DateTimeFormat注解的java.util.Date字段类型创建相应的Printer和Parser进行格式化和解析

格式化转换器注册器、格式化服务

提供类型转换器注册支持,运行时类型转换API支持。
一个有如下两种接口:

FormatterRegistry

FormatterRegistry:格式化转换器注册器,用于注册格式化转换器(Formatter、Printer和Parser、AnnotationFormatterFactory);

package org.springframework.format;  public interface FormatterRegistry extends ConverterRegistry {      //①添加格式化转换器(Spring3.1 新增API)      void addFormatter(Formatter<?> formatter);      //②为指定的字段类型添加格式化转换器      void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);      //③为指定的字段类型添加Printer和Parser      void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);      //④添加注解驱动的字段格式化工厂AnnotationFormatterFactory      void addFormatterForFieldAnnotation(                  AnnotationFormatterFactory<? extends Annotation> annotationFormatterFactory);  }   

FormattingConversionService

FormattingConversionService:继承自ConversionService,运行时类型转换和格式化服务接口,提供运行期类型转换和格式化的支持。
FormattingConversionService内部实现如下图所示:

我们可以看到FormattingConversionService内部实现如上所示,当你调用convert方法时:
- 若是S类型—–>String:调用私有的静态内部类PrinterConverter,其又调用相应的Printer的实现进行格式化;
- 若是String—–>T类型:调用私有的静态内部类ParserConverter,其又调用相应的Parser的实现进行解析;
- 若是A注解类型注解的S类型—–>String:调用私有的静态内部类AnnotationPrinterConverter,其又调用相应的AnnotationFormatterFactory的getPrinter获取Printer的实现进行格式化;
- 若是String—–>A注解类型注解的T类型:调用私有的静态内部类AnnotationParserConverter,其又调用相应的AnnotationFormatterFactory的getParser获取Parser的实现进行解析。

注:S类型表示源类型,T类型表示目标类型,A表示注解类型。

此处可以可以看出之前的Converter SPI完成任意Object与Object之间的类型转换,而Formatter SPI完成任意Object与String之间的类型转换(即格式化和解析,与PropertyEditor类似)。

Spring内建的格式化转换器

类名 说明 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时会自动注册。

类型级别的解析/格式化

直接使用Formatter SPI进行解析/格式化

//二、CurrencyFormatter:实现货币样式的格式化/解析  CurrencyFormatter currencyFormatter = new CurrencyFormatter();  currencyFormatter.setFractionDigits(2);//保留小数点后几位  currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入)  //1、将带货币符号的字符串“$123.125”转换为BigDecimal("123.00")  Assert.assertEquals(new BigDecimal("123.13"), currencyFormatter.parse("$123.125", Locale.US));  //2、将BigDecimal("123")格式化为字符串“$123.00”展示  Assert.assertEquals("$123.00", currencyFormatter.print(new BigDecimal("123"), Locale.US));  Assert.assertEquals("¥123.00", currencyFormatter.print(new BigDecimal("123"), Locale.CHINA));  Assert.assertEquals("¥123.00", currencyFormatter.print(new BigDecimal("123"), Locale.JAPAN)); 

parse方法:将带格式的字符串根据Locale信息解析为相应的BigDecimal类型数据;
print方法:将BigDecimal类型数据根据Locale信息格式化为字符串数据进行展示。

不同于Convert SPI,Formatter SPI可以根据本地化(Locale)信息进行解析/格式化。

自定义Formatter进行解析/格式化

定义Formatter SPI实现

//省略import  public class PhoneNumberFormatter implements Formatter<PhoneNumberModel> {      Pattern pattern = Pattern.compile("^(\\d{3,4})-(\\d{7,8})$");      @Override      public String print(PhoneNumberModel phoneNumber, Locale locale) {//①格式化          if(phoneNumber == null) {              return "";          }          return new StringBuilder().append(phoneNumber.getAreaCode()).append("-")                                    .append(phoneNumber.getPhoneNumber()).toString();      }      @Override      public PhoneNumberModel parse(String text, Locale locale) throws ParseException {//②解析          if(!StringUtils.hasLength(text)) {              //①如果source为空 返回null              return null;          }          Matcher matcher = pattern.matcher(text);          if(matcher.matches()) {              //②如果匹配 进行转换              PhoneNumberModel phoneNumber = new PhoneNumberModel();              phoneNumber.setAreaCode(matcher.group(1));              phoneNumber.setPhoneNumber(matcher.group(2));              return phoneNumber;          } else {              //③如果不匹配 转换失败              throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", text));          }      }  }  

测试用例:

  @Test      public void test() {          DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();          conversionService.addFormatter(new PhoneNumberFormatter());          Assert.assertEquals("010-12345678", conversionService.convert(phoneNumber, String.class));          Assert.assertEquals("010", conversionService.convert("010-12345678", PhoneNumberModel.class).getAreaCode());      }  

字段级别的解析/格式化

使用内置的注解进行字段级别的解析/格式化

public class FormatterModel {      @NumberFormat(style=Style.NUMBER, pattern="#,###")      private int totalCount;      @NumberFormat(style=Style.PERCENT)      private double discount;      @NumberFormat(style=Style.CURRENCY)      private double sumMoney;      @DateTimeFormat(iso=ISO.DATE)       private Date registerDate;      @DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")       private Date orderDate;      //省略getter/setter  }   

@Number:定义数字相关的解析/格式化元数据(通用样式、货币样式、百分数样式),参数如下:
style:用于指定样式类型,包括三种:Style.NUMBER(通用样式) Style.CURRENCY(货币样式) Style.PERCENT(百分数样式),默认Style.NUMBER;
pattern:自定义样式,如patter=”#,###”;

@DateTimeFormat:定义日期相关的解析/格式化元数据,参数如下:
pattern:指定解析/格式化字段数据的模式,如”yyyy-MM-dd HH:mm:ss”
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;
style:指定用于格式化的样式模式,默认“SS”,具体使用请参考Joda-Time类库的org.joda.time.format.DateTimeFormat的forStyle的javadoc;
优先级: pattern 大于 iso 大于 style。

自定义注解进行字段级别的解析/格式化:

定义解析/格式化字段的注解类型:

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER})  @Retention(RetentionPolicy.RUNTIME)  public @interface PhoneNumber {  }  

实现AnnotationFormatterFactory注解格式化工厂:

//省略import  public class PhoneNumberFormatAnnotationFormatterFactory      implements AnnotationFormatterFactory<PhoneNumber> {//①指定可以解析/格式化的字段注解类型      private final Set<Class<?>> fieldTypes;      private final PhoneNumberFormatter formatter;      public PhoneNumberFormatAnnotationFormatterFactory() {          Set<Class<?>> set = new HashSet<Class<?>>();          set.add(PhoneNumberModel.class);          this.fieldTypes = set;          this.formatter = new PhoneNumberFormatter();//此处使用之前定义的Formatter实现      }      //②指定可以被解析/格式化的字段类型集合      @Override      public Set<Class<?>> getFieldTypes() {          return fieldTypes;      }      //③根据注解信息和字段类型获取解析器      @Override      public Parser<?> getParser(PhoneNumber annotation, Class<?> fieldType) {          return formatter;      }      //④根据注解信息和字段类型获取格式化器      @Override         public Printer<?> getPrinter(PhoneNumber annotation, Class<?> fieldType) {          return formatter;      }  }   

集成到Spring Web MVC环境

注册FormattingConversionService实现和自定义格式化转换器:

package com.lf.web;import com.lf.web.config.MyFormatterRegistry;import com.lf.web.config.MyHttpMessage;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.ComponentScan;import org.springframework.context.annotation.Configuration;import org.springframework.format.FormatterRegistry;import org.springframework.http.converter.HttpMessageConverter;import org.springframework.web.servlet.ViewResolver;import org.springframework.web.servlet.config.annotation.DefaultServletHandlerConfigurer;import org.springframework.web.servlet.config.annotation.EnableWebMvc;import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;import org.springframework.web.servlet.view.InternalResourceViewResolver;import java.util.List;/** * Created by LF on 2017/5/4. */@Configuration@EnableWebMvc@ComponentScan//组件扫描public class WebConfig extends WebMvcConfigurerAdapter {    @Override    public void addFormatters(FormatterRegistry registry) {        super.addFormatters(registry);        registry.addFormatter(new PhoneNumberFormatter());    }}

示例:

(1、模型对象字段的数据解析/格式化:

@RequestMapping(value = "/format1")  public String test1(@ModelAttribute("model") FormatterModel formatModel) {      return "format/success";  }  
totalCount:<spring:bind path="model.totalCount">${status.value}</spring:bind><br/>  discount:<spring:bind path="model.discount">${status.value}</spring:bind><br/>  sumMoney:<spring:bind path="model.sumMoney">${status.value}</spring:bind><br/>  phoneNumber:<spring:bind path="model.phoneNumber">${status.value}</spring:bind><br/>  <!-- 如果没有配置org.springframework.web.servlet.handler.ConversionServiceExposingInterceptor将会报错 -->  phoneNumber:<spring:eval expression="model.phoneNumber"></spring:eval><br/>  <br/><br/>  <form:form commandName="model">      <form:input path="phoneNumber"/><br/>      <form:input path="sumMoney"/>  </form:form> 
0 0