SpringMVC的数据格式化

xiaoxiao2021-02-28  83

在如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内建的格式化转换器

类名说明DateFormatterjava.util.Date<—->String 实现日期的格式化/解析NumberFormatterjava.lang.Number<—->String 实现通用样式的格式化/解析CurrencyFormatterjava.lang.BigDecimal<—->String 实现货币样式的格式化/解析PercentFormatterjava.lang.Number<—->String 实现百分数样式的格式化/解析NumberFormatAnnotationFormatterFactory@NumberFormat注解类型的数字字段类型<—->String ①通过@NumberFormat指定格式化/解析格式 ②可以格式化/解析的数字类型:Short、Integer、Long、Float、Double、BigDecimal、BigIntegerJodaDateTimeFormatAnnotationFormatterFactory@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>
转载请注明原文地址: https://www.6miu.com/read-78769.html

最新回复(0)