- 浏览: 21503989 次
- 性别:
博客专栏
-
跟我学spring3
浏览量:2418666
-
Spring杂谈
浏览量:3008769
-
跟开涛学SpringMVC...
浏览量:5639480
-
Servlet3.1规范翻...
浏览量:259916
-
springmvc杂谈
浏览量:1597310
-
hibernate杂谈
浏览量:250216
-
跟我学Shiro
浏览量:5858947
-
跟我学Nginx+Lua开...
浏览量:701999
-
亿级流量网站架构核心技术
浏览量:785222
文章分类
- 全部博客 (329)
- 跟我学Nginx+Lua开发 (13)
- 跟我学spring (54)
- 跟开涛学SpringMVC (34)
- spring4 (16)
- spring杂谈 (50)
- springmvc杂谈 (22)
- 跟我学Shiro (26)
- shiro杂谈 (3)
- hibernate杂谈 (10)
- java开发常见问题分析 (36)
- 加速Java应用开发 (5)
- Servlet 3.1规范[翻译] (21)
- servlet3.x (2)
- websocket协议[翻译] (14)
- websocket规范[翻译] (1)
- java web (6)
- db (1)
- js & jquery & bootstrap (4)
- 非技术 (4)
- reminder[转载] (23)
- 跟叶子学把妹 (8)
- nginx (2)
- 架构 (19)
- flume架构与源码分析 (4)
最新评论
-
xxx不是你可以惹得:
认真看错误代码,有时候重启电脑就行了 醉了 我把数据库配置写死 ...
第十六章 综合实例——《跟我学Shiro》 -
dagger9527:
holyselina 写道您前面说到能获取调用是的参数数组,我 ...
【第六章】 AOP 之 6.6 通知参数 ——跟我学spring3 -
xxx不是你可以惹得:
Access denied for user 'root'@' ...
第十六章 综合实例——《跟我学Shiro》 -
dagger9527:
只有@AspectJ支持命名切入点,而Schema风格不支持命 ...
【第六章】 AOP 之 6.5 AspectJ切入点语法详解 ——跟我学spring3 -
dagger9527:
支持虽然会迟到,但永远不会缺席!
【第四章】 资源 之 4.3 访问Resource ——跟我学spring3
7.3、数据格式化
在如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等。
该格式化转换系统是Spring通用的,其定义在org.springframework.format包中,不仅仅在Spring Web MVC场景下。
7.3.1、架构
1、格式化转换器:提供格式化转换的实现支持。
一共有如下两组四个接口:
(1、Printer接口:格式化显示接口,将T类型的对象根据Locale信息以某种格式进行打印显示(即返回字符串形式);
package org.springframework.format; public interface Printer<T> { String print(T object, Locale locale); }
(2、Parser接口:解析接口,根据Locale信息解析字符串到T类型的对象;
package org.springframework.format; public interface Parser<T> { T parse(String text, Locale locale) throws ParseException; }
解析失败可以抛出java.text.ParseException或IllegalArgumentException异常即可。
(3、Formatter接口:格式化SPI接口,继承Printer和Parser接口,完成T类型对象的格式化和解析功能;
package org.springframework.format; public interface Formatter<T> extends Printer<T>, Parser<T> { }
(4、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进行格式化和解析。
2、格式化转换器注册器、格式化服务:提供类型转换器注册支持,运行时类型转换API支持。
一个有如下两种接口:
(1、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); }
(2、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类似)。
7.3.2、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时会自动注册。
7.3.3、示例
在示例之前,我们需要到http://joda-time.sourceforge.net/下载Joda-Time类库,本书使用的是joda-time-2.1版本,将如下jar包添加到classpath:
joda-time-2.1.jar
7.3.3.1、类型级别的解析/格式化
一、直接使用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)信息进行解析/格式化。
其他测试用例请参考cn.javass.chapter7.web.controller.support.formatter.InnerFormatterTest的testNumber测试方法和testDate测试方法。
@Test public void testWithDefaultFormattingConversionService() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); //默认不自动注册任何Formatter CurrencyFormatter currencyFormatter = new CurrencyFormatter(); currencyFormatter.setFractionDigits(2);//保留小数点后几位 currencyFormatter.setRoundingMode(RoundingMode.CEILING);//舍入模式(ceilling表示四舍五入) //注册Formatter SPI实现 conversionService.addFormatter(currencyFormatter); //绑定Locale信息到ThreadLocal //FormattingConversionService内部自动获取作为Locale信息,如果不设值默认是 Locale.getDefault() LocaleContextHolder.setLocale(Locale.US); Assert.assertEquals("$1,234.13", conversionService.convert(new BigDecimal("1234.128"), String.class)); LocaleContextHolder.setLocale(null); LocaleContextHolder.setLocale(Locale.CHINA); Assert.assertEquals("¥1,234.13", conversionService.convert(new BigDecimal("1234.128"), String.class)); Assert.assertEquals(new BigDecimal("1234.13"), conversionService.convert("¥1,234.13", BigDecimal.class)); LocaleContextHolder.setLocale(null);}
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根据本地化信息进行解析/格式化;
具体测试代码请参考cn.javass.chapter7.web.controller.support.formatter.InnerFormatterTest的testWithDefaultFormattingConversionService测试方法。
三、自定义Formatter进行解析/格式化
此处以解析/格式化PhoneNumberModel为例。
(1、定义Formatter SPI实现
package cn.javass.chapter7.web.controller.support.formatter; //省略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)); } } }
类似于Convert SPI实现,只是此处的相应方法会传入Locale本地化信息,这样可以为不同地区进行解析/格式化数据。
(2、测试用例:
package cn.javass.chapter7.web.controller.support.formatter; //省略import public class CustomerFormatterTest { @Test public void test() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); conversionService.addFormatter(new PhoneNumberFormatter()); PhoneNumberModel phoneNumber = new PhoneNumberModel("010", "12345678"); Assert.assertEquals("010-12345678", conversionService.convert(phoneNumber, String.class)); Assert.assertEquals("010", conversionService.convert("010-12345678", PhoneNumberModel.class).getAreaCode()); } }
通过PhoneNumberFormatter可以解析String--->PhoneNumberModel和格式化PhoneNumberModel--->String。
到此,类型级别的解析/格式化我们就介绍完了,从测试用例可以看出类型级别的是对项目中的整个类型实施相同的解析/格式化逻辑。
有的同学可能需要在不同的类的字段实施不同的解析/格式化逻辑,如用户模型类的注册日期字段只需要如“2012-05-02”格式进行解析/格式化即可,而订单模型类的下订单日期字段可能需要如“2012-05-02 20:13:13”格式进行展示。
接下来我们学习一下如何进行字段级别的解析/格式化吧。
7.3.3.2、字段级别的解析/格式化
一、使用内置的注解进行字段级别的解析/格式化:
(1、测试模型类准备:
package cn.javass.chapter7.model; 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 }
此处我们使用了Spring字段级别解析/格式化的两个内置注解:
@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。
(2、测试用例:
@Test public void test() throws SecurityException, NoSuchFieldException { //默认自动注册对@NumberFormat和@DateTimeFormat的支持 DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); //准备测试模型对象 FormatterModel model = new FormatterModel(); model.setTotalCount(10000); model.setDiscount(0.51); model.setSumMoney(10000.13); model.setRegisterDate(new Date(2012-1900, 4, 1)); model.setOrderDate(new Date(2012-1900, 4, 1, 20, 18, 18)); //获取类型信息 TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("totalCount")); TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); Assert.assertEquals("10,000", conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor)); Assert.assertEquals(model.getTotalCount(), conversionService.convert("10,000", stringDescriptor, descriptor)); }
TypeDescriptor:拥有类型信息的上下文,用于Spring3类型转换系统获取类型信息的(可以包含类、字段、方法参数、属性信息);通过TypeDescriptor,我们就可以获取(类、字段、方法参数、属性)的各种信息,如注解类型信息;
conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor):将totalCount格式化为字符串类型,此处会根据totalCount字段的注解信息(通过descriptor对象获取)来进行格式化;
conversionService.convert("10,000", stringDescriptor, descriptor):将字符串“10,000”解析为totalCount字段类型,此处会根据totalCount字段的注解信息(通过descriptor对象获取)来进行解析。
(3、通过为不同的字段指定不同的注解信息进行字段级别的细粒度数据解析/格式化
descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("registerDate")); Assert.assertEquals("2012-05-01", conversionService.convert(model.getRegisterDate(), descriptor, stringDescriptor)); Assert.assertEquals(model.getRegisterDate(), conversionService.convert("2012-05-01", stringDescriptor, descriptor)); descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("orderDate")); Assert.assertEquals("2012-05-01 20:18:18", conversionService.convert(model.getOrderDate(), descriptor, stringDescriptor)); Assert.assertEquals(model.getOrderDate(), conversionService.convert("2012-05-01 20:18:18", stringDescriptor, descriptor));
通过如上测试可以看出,我们可以通过字段注解方式实现细粒度的数据解析/格式化控制,但是必须使用TypeDescriptor来指定类型的上下文信息,即编程实现字段的数据解析/格式化比较麻烦。
其他测试用例请参考cn.javass.chapter7.web.controller.support.formatter.InnerFieldFormatterTest的test测试方法。
二、自定义注解进行字段级别的解析/格式化:
此处以解析/格式化PhoneNumberModel字段为例。
(1、定义解析/格式化字段的注解类型:
package cn.javass.chapter7.web.controller.support.formatter; //省略import @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface PhoneNumber { }
(2、实现AnnotationFormatterFactory注解格式化工厂:
package cn.javass.chapter7.web.controller.support.formatter; //省略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; } }
AnnotationFormatterFactory实现会根据注解信息和字段类型获取相应的解析器/格式化器。
(3、修改FormatterModel添加如下代码:
@PhoneNumber private PhoneNumberModel phoneNumber;
(4、测试用例
@Test public void test() throws SecurityException, NoSuchFieldException { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();//创建格式化服务 conversionService.addFormatterForFieldAnnotation( new PhoneNumberFormatAnnotationFormatterFactory());//添加自定义的注解格式化工厂 FormatterModel model = new FormatterModel(); TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("phoneNumber")); TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class); PhoneNumberModel value = (PhoneNumberModel) conversionService.convert("010-12345678", stringDescriptor, descriptor); //解析字符串"010-12345678"--> PhoneNumberModel model.setPhoneNumber(value); Assert.assertEquals("010-12345678", conversionService.convert(model.getPhoneNumber(), descriptor, stringDescriptor));//格式化PhoneNumberModel-->"010-12345678" }
此处使用DefaultFormattingConversionService的addFormatterForFieldAnnotation注册自定义的注解格式化工厂PhoneNumberFormatAnnotationFormatterFactory。
到此,编程进行数据的格式化/解析我们就完成了,使用起来还是比较麻烦,接下来我们将其集成到Spring Web MVC环境中。
7.3.4、集成到Spring Web MVC环境
一、注册FormattingConversionService实现和自定义格式化转换器:
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <!—此处省略之前注册的自定义类型转换器--> <property name="formatters"> <list> <bean class="cn.javass.chapter7.web.controller.support.formatter. PhoneNumberFormatAnnotationFormatterFactory"/> </list> </property> </bean>
其他配置和之前学习7.2.2.4一节一样。
二、示例:
(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>
在浏览器输入测试URL:
http://localhost:9080/springmvc-chapter7/format1?totalCount=100000&discount=0.51&sumMoney=100000.128&phoneNumber=010-12345678
数据会正确绑定到我们的formatModel,即请求参数能被正确的解析并绑定到我们的命令对象上,而且在JSP页面也能正确的显示格式化后的数据(即正确的被格式化显示)。
(2、功能处理方法参数级别的数据解析:
@RequestMapping(value = "/format2") public String test2( @PhoneNumber @RequestParam("phoneNumber") PhoneNumberModel phoneNumber, @DateTimeFormat(pattern="yyyy-MM-dd") @RequestParam("date") Date date) { System.out.println(phoneNumber); System.out.println(date); return "format/success2"; }
此处我们可以直接在功能处理方法的参数上使用格式化注解类型进行注解,Spring Web MVC能根据此注解信息对请求参数进行解析并正确的绑定。
在浏览器输入测试URL:
http://localhost:9080/springmvc-chapter7/format2?phoneNumber=010-12345678&date=2012-05-01
数据会正确的绑定到我们的phoneNumber和date上,即请求的参数能被正确的解析并绑定到我们的参数上。
控制器代码位于cn.javass.chapter7.web.controller.DataFormatTestController中。
如果我们请求参数数据不能被正确解析并绑定或输入的数据不合法等该怎么处理呢?接下来的一节我们来学习下绑定失败处理和数据验证相关知识。
评论
<form:input path="person.birthday"/>
后台person中的字段birthday使用注解DateFormatter(pattern="yyyy-MM-dd")
那么前台传到后台是能够正确的转换成Date的,后台传到前台怎么样格式化呢?
比如一个修改的功能,回显和form表达都用<form:input path="person.birthday"/>这个标签,有办法做吗?
user中有int属性为空,就报错怎么处理,O(∩_∩)O谢谢
Failed to convert property value of type 'java.lang.String' to required type 'int' for property 'downAmount'; nested exception is java.lang.NumberFormatException: For input string: ""
bean的日期是那么配的:
@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss")
页面debug js脚本发现值为:2014-7-26 00:25:31
而到了java controller,发现bean中的日期却是:2014-7-26 08:25:31
+8个小时了,这个是时区还是什么问题,该怎么解决?!谢谢
我们可以看到FormattingConversionService内部实现如上所示,当你调用convert方法时:
⑴若是S类型----->String:调用私有的静态内部类PrinterConverter,其又调用相应的Printer的实现进行格式化;
⑵若是String----->T类型:调用私有的静态内部类ParserConverter,其又调用相应的Parser的实现进行解析;
⑶若是A注解类型注解的S类型----->String:调用私有的静态内部类AnnotationPrinterConverter,其又调用相应的AnnotationFormatterFactory的getPrinter获取Printer的实现进行格式化;
⑷若是String----->A注解类型注解的T类型:调用私有的静态内部类AnnotationParserConverter,其又调用相应的AnnotationFormatterFactory的getParser获取Parser的实现进行解析。
这个解释还是有些问题的,本质上还是用converter包装了formatter,用sourceType和targetType最为key,进行缓存,需要转换时根据sourceType和targetType得到converter调用convert进行转换的。因此第1点和第2点解释的不明确。
public ParserConverter(java.lang.Class<?> fieldType, org.springframework.format.Parser<?> parser, org.springframework.core.convert.ConversionService conversionService) {}
public PrinterConverter(java.lang.Class<?> fieldType, org.springframework.format.Printer<?> printer, org.springframework.core.convert.ConversionService conversionService) { }
可以看到 其实这两个converter 只关心Parser/Printer
我们可以看到FormattingConversionService内部实现如上所示,当你调用convert方法时:
⑴若是S类型----->String:调用私有的静态内部类PrinterConverter,其又调用相应的Printer的实现进行格式化;
⑵若是String----->T类型:调用私有的静态内部类ParserConverter,其又调用相应的Parser的实现进行解析;
⑶若是A注解类型注解的S类型----->String:调用私有的静态内部类AnnotationPrinterConverter,其又调用相应的AnnotationFormatterFactory的getPrinter获取Printer的实现进行格式化;
⑷若是String----->A注解类型注解的T类型:调用私有的静态内部类AnnotationParserConverter,其又调用相应的AnnotationFormatterFactory的getParser获取Parser的实现进行解析。
这个解释还是有些问题的,本质上还是用converter包装了formatter,用sourceType和targetType最为key,进行缓存,需要转换时根据sourceType和targetType得到converter调用convert进行转换的。因此第1点和第2点解释的不明确。
本质上还是用converter包装了formatter: 这里要注意的是Formatter继承自Printer和Parser;而比如PrinterConverter直接找Printer(不一定是Formatter,但是一般我们实现时可能是Formatter);
我们可以看到FormattingConversionService内部实现如上所示,当你调用convert方法时:
⑴若是S类型----->String:调用私有的静态内部类PrinterConverter,其又调用相应的Printer的实现进行格式化;
⑵若是String----->T类型:调用私有的静态内部类ParserConverter,其又调用相应的Parser的实现进行解析;
⑶若是A注解类型注解的S类型----->String:调用私有的静态内部类AnnotationPrinterConverter,其又调用相应的AnnotationFormatterFactory的getPrinter获取Printer的实现进行格式化;
⑷若是String----->A注解类型注解的T类型:调用私有的静态内部类AnnotationParserConverter,其又调用相应的AnnotationFormatterFactory的getParser获取Parser的实现进行解析。
这个解释还是有些问题的,本质上还是用converter包装了formatter,用sourceType和targetType最为key,进行缓存,需要转换时根据sourceType和targetType得到converter调用convert进行转换的。因此第1点和第2点解释的不明确。
我明白你的意思了
你如果想接错误,需要在model后加一个BindingResult,所有错误信息封装到这
public String test01(@ModelAttribute("formatterModel") FormatterModel model, BindingResult result)
页面就可以使用 form:errors显示错误信息了
如果同时出现CustomDateInitializer(实现WebBindingInitializer接口)日期转换,然后还有@DatetimeFormat,怎么调整它俩的order,让@DatetimeFormat先执行,没有@DatetimeFormat时候,再执行CustomDateInitializer。
这个还真没有研究过,不过我记得是先PropertyEditor 再Converter, Converter时先默认的再自定义的,按照注册顺序执行,无法指定order。
如果你需要自定义还是使用如
http://jinnianshilongnian.iteye.com/blog/1888474
registerCustomEditor 注册
好吧,先谢谢了。
如果同时出现CustomDateInitializer(实现WebBindingInitializer接口)日期转换,然后还有@DatetimeFormat,怎么调整它俩的order,让@DatetimeFormat先执行,没有@DatetimeFormat时候,再执行CustomDateInitializer。
这个还真没有研究过,不过我记得是先PropertyEditor 再Converter, Converter时先默认的再自定义的,按照注册顺序执行,无法指定order。
如果你需要自定义还是使用如
http://jinnianshilongnian.iteye.com/blog/1888474
registerCustomEditor 注册
如果同时出现CustomDateInitializer(实现WebBindingInitializer接口)日期转换,然后还有@DatetimeFormat,怎么调整它俩的order,让@DatetimeFormat先执行,没有@DatetimeFormat时候,再执行CustomDateInitializer。
我测试没有问题的
http://localhost:10080/springmvc-chapter7/format1?totalCount=5555555&discount=0.51&sumMoney=100000.128&phoneNumber=010-12345678
如果方便把你的测试发我 我帮你看看么问题
我的测试除了之前发的model 和 页面外 还有就是*-servlet.xml的配置中 有相关注解的配置 InternalResourceViewResolver ,RequestMappingHandlerMapping,RequestMappingHandlerAdapter 还有一个自定义的FormatterTestcontroller控制器,
控制器方法 :
@RequestMapping(value = "/test1") public String test1(@ModelAttribute("formatModel") FormatterModel formatModel,Errors errors){ System.out.println(formatModel); return "format/register"; }
然后其他的就没了,是不是缺少验证类的配置 ?
我没有参考你源码中的示例,基本都是通过博客的描述,自己写的例子
如果控制台有错误 把错误发给我 我看看 或者你把项目发我吧 我看看
我测试没有问题的
http://localhost:10080/springmvc-chapter7/format1?totalCount=5555555&discount=0.51&sumMoney=100000.128&phoneNumber=010-12345678
如果方便把你的测试发我 我帮你看看么问题
我的测试除了之前发的model 和 页面外 还有就是*-servlet.xml的配置中 有相关注解的配置 InternalResourceViewResolver ,RequestMappingHandlerMapping,RequestMappingHandlerAdapter 还有一个自定义的FormatterTestcontroller控制器,
控制器方法 :
@RequestMapping(value = "/test1") public String test1(@ModelAttribute("formatModel") FormatterModel formatModel,Errors errors){ System.out.println(formatModel); return "format/register"; }
然后其他的就没了,是不是缺少验证类的配置 ?
我没有参考你源码中的示例,基本都是通过博客的描述,自己写的例子
我测试没有问题的
http://localhost:10080/springmvc-chapter7/format1?totalCount=5555555&discount=0.51&sumMoney=100000.128&phoneNumber=010-12345678
如果方便把你的测试发我 我帮你看看么问题
后来的这几种方式 虽然后台的异常信息已经抛出,但是form:error没任何内容
你之前有说过把错误信息添加到Error 这个怎么添加? 貌似文章中没有说明 ?
我的测试很简单 直接就是写了一个FormatterModel类
@NumberFormat(style = Style.NUMBER,pattern="#,###") private int totalCount; @NumberFormat(style = Style.PERCENT) private double discount; @NumberFormat(style = Style.CURRENCY) private double totalMoney; @DateTimeFormat(iso = ISO.DATE) private Date registerDate; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date orderDate; //页面 <form:form commandName="formatModel"> <form:errors path="*"></form:errors><br/> totalCount<form:input path="totalCount"/><br/> discount<form:input path="discount"/><br/> totalMoney<form:input path="totalMoney"/><br/> registerDate<form:input path="registerDate"/><br/> orderDate<form:input path="orderDate"/><br/> <input type="submit" value="提交"/> </form:form>
还有就是,我在页面上 比如totalCount中输入 5555555 为什么是页面直接报500,为不是在异常信息中显示呢。。。这几个数据验证格式化的章节看着很迷糊。。。弄不明白。。。
因为你的格式不对(你需要的是 #,###),如果你需要如错误信息,你需要加如JSR-303注解来完成。
谢谢你的耐心回复,我记得之前第一次讲,使用继承PropertyEditorSupport实现格式化(setAsText)的时候,即使页面上我们输入错误的格式,异常会显示在页面的错误信息中,而上面的这种方式是直接页面抛出500异常,这两者之间都是对数据的格式化操作,区别是什么呢。。。
后来的这几种方式 虽然后台的异常信息已经抛出,但是form:error没任何内容
你之前有说过把错误信息添加到Error 这个怎么添加? 貌似文章中没有说明 ?
我的测试很简单 直接就是写了一个FormatterModel类
@NumberFormat(style = Style.NUMBER,pattern="#,###") private int totalCount; @NumberFormat(style = Style.PERCENT) private double discount; @NumberFormat(style = Style.CURRENCY) private double totalMoney; @DateTimeFormat(iso = ISO.DATE) private Date registerDate; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date orderDate; //页面 <form:form commandName="formatModel"> <form:errors path="*"></form:errors><br/> totalCount<form:input path="totalCount"/><br/> discount<form:input path="discount"/><br/> totalMoney<form:input path="totalMoney"/><br/> registerDate<form:input path="registerDate"/><br/> orderDate<form:input path="orderDate"/><br/> <input type="submit" value="提交"/> </form:form>
还有就是,我在页面上 比如totalCount中输入 5555555 为什么是页面直接报500,为不是在异常信息中显示呢。。。这几个数据验证格式化的章节看着很迷糊。。。弄不明白。。。
因为你的格式不对(你需要的是 #,###),如果你需要如错误信息,你需要加如JSR-303注解来完成。
后来的这几种方式 虽然后台的异常信息已经抛出,但是form:error没任何内容
你之前有说过把错误信息添加到Error 这个怎么添加? 貌似文章中没有说明 ?
我的测试很简单 直接就是写了一个FormatterModel类
@NumberFormat(style = Style.NUMBER,pattern="#,###") private int totalCount; @NumberFormat(style = Style.PERCENT) private double discount; @NumberFormat(style = Style.CURRENCY) private double totalMoney; @DateTimeFormat(iso = ISO.DATE) private Date registerDate; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date orderDate; //页面 <form:form commandName="formatModel"> <form:errors path="*"></form:errors><br/> totalCount<form:input path="totalCount"/><br/> discount<form:input path="discount"/><br/> totalMoney<form:input path="totalMoney"/><br/> registerDate<form:input path="registerDate"/><br/> orderDate<form:input path="orderDate"/><br/> <input type="submit" value="提交"/> </form:form>
还有就是,我在页面上 比如totalCount中输入 5555555 为什么是页面直接报500,为不是在异常信息中显示呢。。。这几个数据验证格式化的章节看着很迷糊。。。弄不明白。。。
后来的这几种方式 虽然后台的异常信息已经抛出,但是form:error没任何内容
你之前有说过把错误信息添加到Error 这个怎么添加? 貌似文章中没有说明 ?
我的测试很简单 直接就是写了一个FormatterModel类
@NumberFormat(style = Style.NUMBER,pattern="#,###") private int totalCount; @NumberFormat(style = Style.PERCENT) private double discount; @NumberFormat(style = Style.CURRENCY) private double totalMoney; @DateTimeFormat(iso = ISO.DATE) private Date registerDate; @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss") private Date orderDate; //页面 <form:form commandName="formatModel"> <form:errors path="*"></form:errors><br/> totalCount<form:input path="totalCount"/><br/> discount<form:input path="discount"/><br/> totalMoney<form:input path="totalMoney"/><br/> registerDate<form:input path="registerDate"/><br/> orderDate<form:input path="orderDate"/><br/> <input type="submit" value="提交"/> </form:form>
请问!
1.而且注解只能在parse时候起作用,print的时候不起作用。
是吗?
2.哪如果要写个输出时候的格式化注解怎么实现?
使用spring:eval输出即可
如
<td><spring:eval expression="m.createDate"/></td>
发表评论
-
跟我学SpringMVC目录汇总贴、PDF下载、源码下载
2012-12-22 08:05 620585扫一扫,关注我的公众号 购买地址 ... -
源代码下载——第七章 注解式控制器的数据验证、类型转换及格式化
2012-12-01 07:12 33465源代码请到附件中下载。 其他下载: ... -
SpringMVC数据验证——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC
2012-11-23 07:47 2388957.4、数据验证 7.4.1 ... -
SpringMVC数据格式化——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC
2012-11-19 16:41 46支持一下博主:------------------ ... -
SpringMVC数据类型转换——第七章 注解式控制器的数据验证、类型转换及格式化——跟着开涛学SpringMVC
2012-11-12 20:08 1233117.1、简介 在编写可 ... -
扩展SpringMVC以支持绑定JSON格式的请求参数
2012-11-08 07:43 126062上一篇:《扩展SpringMVC以支持更精准的数据绑 ... -
扩展SpringMVC以支持更精准的数据绑定1
2012-11-06 07:38 76769最新版请点击查看FormM ... -
SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-10-26 07:57 1528436.6.2、@RequestParam绑定单个请求参 ... -
SpringMVC强大的数据绑定(2)——第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-10-23 14:45 15---------------------------- ... -
SpringMVC强大的数据绑定(1)——第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-10-16 07:48 121052到目前为止,请求已经能交给我们的处理器进行处理了,接下来 ... -
Spring MVC 3.1新特性 生产者、消费者请求限定 —— 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-10-10 14:16 484776.6.5、生产者、消费者限定 6.6.5.1、基 ... -
SpringMVC3强大的请求映射规则详解 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-24 15:29 104866声明:本系列都是原创内容,觉得好就顶一个,让更多人知道! ... -
请求映射之URL路径映射 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-24 11:50 946.5.1.1、普通URL路径映射 @Request ... -
请求映射之请求方法映射限定 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-24 11:48 536.5.2、请求方法映射 ... -
请求映射之请求方法映射限定 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-24 10:47 216.5.2、请求方法映射限定 一般我们熟悉的表单 ... -
请求映射之URL路径映射 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-24 07:05 1636.5.1.1、普通URL路径映射 @Reques ... -
源代码下载 第六章 注解式控制器详解
2012-09-22 07:11 42034源代码请到附件中下载。 其他下载: 跟着 ... -
注解式控制器运行流程及处理器定义 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-20 17:47 67423声明:本系列都是原创内容,觉得好就顶一个,让更多人知道! ... -
注解式控制器运行流程及处理器定义 第六章 注解式控制器详解——跟着开涛学SpringMVC
2012-09-20 15:54 96.1、注解式控制器简介 一、Spring2. ... -
源代码下载 第五章 处理器拦截器详解——跟着开涛学SpringMVC
2012-09-17 07:34 33668源代码请到附件中下 ...
相关推荐
总结来说,Spring MVC的注解式控制器提供了强大的数据验证、类型转换和格式化功能,简化了Web开发过程,提升了应用的安全性和用户体验。通过合理利用这些特性,开发者可以构建更加健壮、易于维护的Web应用。
PDF,源代码 开涛学SpringMVC 第一章源代码下载 第二章 Spring MVC入门 源代码下载 第四章 Controller接口控制器详解 源代码下载 第五章 处理器拦截器详解——跟着...第七章 注解式控制器的数据验证、类型转换及格式化
SpringMVC在数据类型转换、验证及格式化方面做了大量的改进和完善,为开发者提供了极为方便的工具和支持。通过对Spring 3之后的新特性进行深入理解,可以帮助开发者更好地利用这些功能,提升项目的整体质量。
总之,"跟开涛学SpringMVC(7.2)SpringMVC数据"课程旨在通过详细讲解Spring MVC的数据处理、格式化和Java开发实践,帮助开发者掌握Web应用程序开发的关键技术,提高他们的开发效率和代码质量。通过学习和实践,学员...
在Spring MVC框架中,数据类型转换、数据格式化和数据校验是开发Web应用程序时不可或缺的部分。这些功能有助于确保从客户端接收到的数据准确无误,同时提供了一种优雅的方式来处理和展示这些数据。本篇文章将深入...
第七章注解式控制器的数据验证、类型转换及格式化则专注于如何在SpringMVC中处理数据验证、类型转换和格式化。这包括了如何使用SpringMVC内建的数据绑定器,将HTTP请求参数绑定到控制器方法的参数上;如何对输入数据...
在进行Web应用开发时,尤其是在使用Spring MVC框架的过程中,经常需要对用户提交的数据进行处理,包括但不限于数据类型转换、数据验证以及数据格式化等操作。这些操作对于确保数据的准确性和应用程序的健壮性至关...
当你在控制器方法的参数中使用 `@RequestParam` 或 `@PathVariable` 注解时,Spring MVC 将自动使用已注册的 `Formatter` 进行解析和格式化。 除了手动实现 `Formatter`,Spring MVC还提供了 `...
数据类型转换、数据格式化也是SpringMVC提供的强大功能,这部分内容将教会我们如何进行数据类型的转换和格式化操作。 最后,教程会提供每章的源代码下载链接,帮助学习者通过实践来加深对知识的理解。通过下载和...
SpringMVC 数据类型转换是Spring Web MVC框架中的一个重要部分,主要负责将HTTP请求中的字符串数据转换为控制器方法中期望的数据类型。在2021-2022年的专题资料中,这一主题聚焦于Spring 3之后引入的新特性和改进,...
在SpringMVC中,数据格式化是将用户输入的数据转换为业务对象的过程。这通常涉及到日期、货币和其他复杂类型的输入。Spring提供了`@DateTimeFormat`和`@NumberFormat`注解来帮助自动解析和格式化日期和数字。另外,...
在控制器方法中,可以使用`@Valid`注解来激活验证,如: ```java @PostMapping("/register") public String register(@Valid User user, BindingResult result) { if (result.hasErrors()) { // 处理错误,如...
在Spring MVC中,注解被广泛用于配置控制器、方法映射、数据绑定等。例如,`@Controller`用于标识一个类作为处理HTTP请求的控制器,而`@RequestMapping`注解则用于将URL路径映射到特定的方法。 2. **@Controller与...
**第七章 注解式控制器的数据验证、类型转换及格式化** 这部分内容详细讲解了Spring MVC如何处理数据验证,利用JSR-303/JSR-349标准进行Bean验证。同时,探讨了数据类型的自动转换和格式化,包括日期、数字等特殊...
开涛在其开源电子书中《跟开涛学SpringMVC》对SpringMVC进行了详细的介绍,并提供了入门Web开发的学习手册,以其简单易懂的方式阐述了相关知识点。 首先,书中提到了Web开发中的请求-响应模型。在Web世界中,Web...
为了解决这个问题,我们需要确保SpringMVC能够正确地将返回的数据转换为JSON格式,并设置合适的响应头。 首先,我们需要引入Jackson库来处理JSON序列化和反序列化。Jackson是Java中广泛使用的JSON处理库,包括三个...
1. **@Controller**:这个注解用于标记一个类作为SpringMVC的控制器。控制器类将处理HTTP请求,并调用业务逻辑方法。 2. **@RequestMapping**:此注解用于映射HTTP请求到特定的方法。可以放在类级别或方法级别,...