三. SpringMVC 的 类型转换接口。
三.一 SpringMVC 关于类型转换,共提供了三个接口。
1 . org.springframework.core.convert.converter.Converter
package org.springframework.core.convert.converter; public abstract interface Converter<S, T> { public abstract T convert(S paramS); }
2 . org.springframework.core.convert.converter.ConverterFactory
package org.springframework.core.convert.converter; public abstract interface ConverterFactory<S, R> { public abstract <T extends R> Converter<S, T> getConverter(Class<T> paramClass); }
3 . org.springframework.core.convert.converter.GenericConverter
定义了两个方法:
public abstract Set<ConvertiblePair> getConvertibleTypes(); public abstract Object convert(Object paramObject, TypeDescriptor paramTypeDescriptor1, TypeDescriptor paramTypeDescriptor2);
这三个接口均在同一个包下。
有一个support 包。 看看,支持哪些类型转换。
这三个接口之间,有一些区别。
1 .Converter, 最简单的接口, 只是负责将一个 S 源类型 转换成 T 目标类型。
2 .ConverterFactory, 将一种类型的对象转换成另外一种类型及其子类型的对象。 比如,将String 转换成Number 以及 Number 的子类 Integer 和Double,需要定义一系列的转换器,就需要 将String 转换成 Integer的 StringToInteger 和String 转换成Double的 StringToDouble . 如果有一个Float, 那么还需要再写 一个StringToFloat.
SpringMVC 中提供了这么一个例子,可以看一下 StringToNumberConverterFactory
final class StringToNumberConverterFactory implements ConverterFactory<String, Number> { public <T extends Number> Converter<String, T> getConverter(Class<T> targetType) { return new StringToNumber(targetType); } private static final class StringToNumber<T extends Number> implements Converter<String, T> { private final Class<T> targetType; public StringToNumber(Class<T> targetType) { this.targetType = targetType; } public T convert(String source) { if (source.length() == 0) { return null; } return NumberUtils.parseNumber(source, targetType); } } }
如果想转换成 Integer, 可以 用 getConverter(Integer.class).convert(string字符串),
如果想转换成 Double, 可以用 getConverter(Double.class).convert(string字符串),
如果想转换成 Float, 可以用 getConverter(Float.class).convert(string字符串)
S 为源类型, R 为目标类型的基类, T 为目标类型基类的子类,也就是最终要转换的那个类。
这样的Factory 还有:
StringToEnumConverterFactory
CharacterToNumberFactory
NumberToNumberConverterFactory
可以方便的对有子类的 对象进行转换。
3 . GenericConverter Converter 只是将源类型转换成目标类型,并没有携带源类型和目标类型的信息,只能用于普通的转换,无法进行复杂的转换。 会根据源类型和目标类型对象的上下文信息进行类型转换。
GenericConverter接口中一共定义了两个方法,getConvertibleTypes()和convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType)。getConvertibleTypes方法用于返回这个GenericConverter能够转换的原类型和目标类型的这么一个组合;convert方法则是用于进行类型转换的,我们可以在这个方法里面实现我们自己的转换逻辑。之所以说GenericConverter是最复杂的是因为它的转换方法convert的参数类型TypeDescriptor是比较复杂的。TypeDescriptor对类型Type进行了一些封装,包括value、Field及其对应的真实类型等等。
一般用 Convert 就可以达到基本的要求了。
三.二 ConversionService 接口
为了方便的对上面的三个接口进行进行统一调用,所以定义了 ConversionService 接口。
package org.springframework.core.convert; public abstract interface ConversionService { //能否转换 public abstract boolean canConvert(Class<?> paramClass1, Class<?> paramClass2); public abstract boolean canConvert(TypeDescriptor paramTypeDescriptor1, TypeDescriptor paramTypeDescriptor2); //转换 public abstract <T> T convert(Object paramObject, Class<T> paramClass); public abstract Object convert(Object paramObject, TypeDescriptor paramTypeDescriptor1, TypeDescriptor paramTypeDescriptor2); }
该接口的实现类
故,类型转换时一般用 DefaultConversionService (默认注入到 mvc:annotation-driven 中)
类型格式化时用 DefaultFormattingConversionService.
三.三 ConverterRegistry 转换器注册接口
package org.springframework.core.convert.converter; public abstract interface ConverterRegistry { //添加 Converter 接口 public abstract void addConverter(Converter<?, ?> paramConverter); public abstract void addConverter(Class<?> paramClass1, Class<?> paramClass2, Converter<?, ?> paramConverter); // 添加 GenericConverter 接口 public abstract void addConverter(GenericConverter paramGenericConverter); // 添加 ConverterFactory 接口 public abstract void addConverterFactory(ConverterFactory<?, ?> paramConverterFactory); //移除转换器 public abstract void removeConvertible(Class<?> paramClass1, Class<?> paramClass2); }
用于 注册和移除转换器。
这儿用简单的 Convert 接口来进行讲解。
四. Convert 接口的使用
原始的错误类型转换。
四.一 前端代码
<body> <h2>两个蝴蝶飞,类型转换器使用</h2> <form:form commandName="user" action="login.action" method="post"> <form:label path="name">姓名:</form:label> <form:input path="name"/><br/> <form:label path="birthday">日期:</form:label> <form:input path="birthday"/><br/> <form:button>提交</form:button> </form:form> </body>
四.二 后端代码实现
@Controller @RequestMapping(value="/user") public class UserAction { //转到登录的页面 @RequestMapping(value="toLogin") public String toLogin(Model model){ model.addAttribute("user",new User()); return "user/login"; } //绑定到user对象。 @RequestMapping(value="login") public String login(User user){ System.out.println("设置名称:"+user.getName()); System.out.println("生日:"+user.getBirthday().toLocaleString()); return "user/list"; } }
四.三 前端输入值,进行测试
输入数值之后 ,点击提交,发生了错误。
请求不通过,无法进行转换的原因。 也就是,没有默认提交 String 到Date 的转换。
进行重新构造,添加类型转换器,来达到StringToDate 的转换。
四.四 编写类型转换器 DateConvert
为了方便,将格式 pattern 改成注入的形式。
package com.yjl.convert; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import org.springframework.core.convert.converter.Converter; public class DateConvert implements Converter<String,Date>{ // 自定义转换格式 private String pattern; @Override public Date convert(String dateStr) { SimpleDateFormat sdf=new SimpleDateFormat(this.pattern); try { return sdf.parse(dateStr); } catch (ParseException e) { // TODO 自动生成的 catch 块 e.printStackTrace(); System.out.println(dateStr+"日期转换失败"); return null; } } public String getPattern() { return pattern; } public void setPattern(String pattern) { this.pattern = pattern; } }
四.五 在springmvc.xml 配置文件中 配置类型转换
将:
<!--提供了内置的转换器--> <mvc:annotation-driven></mvc:annotation-driven>
改成 新写的转换器。
<!-- 添加转换器 --> <mvc:annotation-driven conversion-service="conversionService"> </mvc:annotation-driven> <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="com.yjl.convert.DateConvert"> <property name="pattern" value="yyyy-MM-dd"></property> </bean> </list> </property> </bean>
四.六 重启服务器,再次进行验证。
输入上面相同的值,再次提交,页面可以进行跳转。
控制台打印输出:
说明,新的日期转换器是成功的。
四.七 注解型日期转换器 @DateTimeFormat
日期转换,是非常常用的转换器。 SpringMVC 虽然没有提供,但是却提供了注解的形式。 @DateTimeFormat
JDK1.8 之前,不包括JDK1.8, 需要添加 joda-time.jar 包, JDK1.8包括,之后,不需要这个jar包,可直接使用@DateTimeFormat注解。
老蝴蝶版本是 1.8,不需要添加额外的jar包。
1 . User.java 中 在birthday 属性上面添加注解,写明格式。
//定义一个日期类 @DateTimeFormat(pattern="yyyy年MM月dd日") private Date birthday;
2 .将springmvc.xml 配置文件仍然换成以前的样式,注释掉 四.五 中的内容。
重新换成:
<mvc:annotation-driven></mvc:annotation-driven>
3 . 重启服务器,进行验证。
前端输入值之后 ,页面可以正常的跳转。
控制台打印输出
注解 @DateTimeFormat 是可以使用的。
@DateTimeFormat 代码为:
支持 DATE,TIME,DATE_TIME,NONE 等多种形式。
@Documented @Retention(RetentionPolicy.RUNTIME) @Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.ANNOTATION_TYPE}) public @interface DateTimeFormat { ISO iso() default ISO.NONE; String pattern() default ""; public static enum ISO { DATE, TIME, DATE_TIME, NONE; private ISO() {} } }
四.八 原先的类型转换器可以正常使用吗?
用的是name, 接收的是 字符串类型, 现在添加一个age 字段,要转换成int,看后台 User 能否接收呢?
注意,此时 springmvc.xml 的配置文件,又换成了 四.五 中的配置,在User.java 中去掉了 @DateTimeFormat 注解。
1 . 前端页面
<form:form commandName="user" action="login.action" method="post"> <form:label path="name">姓名:</form:label> <form:input path="name"/><br/> <form:label path="age">年龄:</form:label> <form:input path="age"/><br/> <form:label path="birthday">日期:</form:label> <form:input path="birthday"/><br/> <form:button>提交</form:button> </form:form>
2 .后端输出
@RequestMapping(value="login") public String login(User user){ System.out.println("设置名称:"+user.getName()+",年龄:"+user.getAge()); System.out.println("生日:"+user.getBirthday().toLocaleString()); return "user/list"; }
3 .测试输入
页面可以正常的进行跳转,控制台打印输出
可以发现,原先的类型转换器还是可以使用的。(不知道为什么)