一、概述
spring类型转换有两种方式:
- PropertyEditor:可实现String<--->Object 之间相互转换
- Converter:可实现任意类型的相互转换
类型转换的过程中,当两者同时存在时,spring首先查找PropertyEditor进行类型转换,如果没有找到,则再查找Converter进行转换
二、PropertyEditor
1、基本介绍
PropertyEditor用于 String<--->Object 之间相互转换,spring内建了一些常用的PropertyEditor,如:
ClassEditor: String<——>Class FileEditor: String<——>File PatternEditor: String<——>Pattern URLEditor: String<——>URL ResourceEditor: String<——>Resource
自定义的PropertyEditor须继承自PropertyEditorSupport,示例如下:
实体类
public class UserInfo { private String name; private Integer age; public String getName() { return name; } public void setName(String name) { this.name = name; } public Integer getAge() { return age; } public void setAge(Integer age) { this.age = age; } }
转换类
public class UserEditor extends PropertyEditorSupport { public UserEditor() { System.out.println("UserEditor constructed"); } @Override public String getAsText() { UserInfo userInfo = (UserInfo) this.getValue(); return JSON.toJSONString(userInfo); } @Override public void setAsText(String text) throws java.lang.IllegalArgumentException { if (StringUtils.isEmpty(text)) { return; } UserInfo userInfo = JSON.parseObject(text, UserInfo.class); this.setValue(userInfo); } }
控制器
@Controller public class TestController5 { @RequestMapping("/type1") @ResponseBody public String testType1(@RequestParam("user") UserInfo userInfo) { System.out.println(userInfo.getName() + " " + userInfo.getAge()); return "testType1"; } @RequestMapping("/type2") @ResponseBody public String testType2() {
System.out.println("void"); return "testType2"; }
// 注册UserEditor @InitBinder public void initBinder(WebDataBinder binder) { UserEditor userEditor = new UserEditor(); binder.registerCustomEditor(UserInfo.class, userEditor); System.out.println("initBinder invoked"); } }
web.xml与spring-mvc.xml配置略
启动后,先后访问:
http://localhost:8080/myweb/type1?user={"name":"matt","age":30}
http://localhost:8080/myweb/type2
http://localhost:8080/myweb/type1?user={"name":"matt","age":30}
输出:
UserEditor constructed initBinder invoked matt 30 void UserEditor constructed initBinder invoked matt 30
从输出结果可以看出,处理方法若包含需要类型转换的参数,每次请求都会调用注册方法;处理方法如不包含则不会调用
2、自定义PropertyEditor的注册
自定义PropertyEditor的注册有三种级别:
- 控制器级别,使用@InitBinder
- web级别,使用WebBindingInitializer
- 应用级别,自定义PropertyEditor与实体类同包,且命名为“实体名 + Editor”
i)控制器级别
控制器级别是在控制器类中使用@InitBinder注解来实现,注册范围为单个控制器,如上例所示
ii)web级别
web级别使用WebBindingInitializer来实现,注册范围为应用的所有控制器(即整个web层),示例:
WebBindingInitializer实现类
public class MyWebBindingInitializer implements WebBindingInitializer { public void initBinder(WebDataBinder binder, WebRequest request) { binder.registerCustomEditor(UserInfo.class, new UserEditor()); System.out.println("initBinder invoked!"); } }
注册
<bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer"> <bean class="cn.matt.convertor.MyWebBindingInitializer"/> </property> </bean>
注释上例控制器中的注册方法,即可测试
该注册方法的调用方式与控制器级别相同,即处理方法若包含需要类型转换的参数,每次请求都会调用注册方法;处理方法如不包含则不会调用
补充:web级别的注册方法还有:1、基于继承的BaseController,2、基于@ControllerAdvice注解,这两种方式更简洁,详细可参考:
SpringMVC中利用@InitBinder来对页面数据进行解析绑定
Spring Boot 系列(八)@ControllerAdvice 拦截异常并统一处理
iii)应用级别(推荐)
应用级别的注册范围为spring层,使用方式:自定义PropertyEditor与实体类同包,且命名为“实体名 + Editor”,示例:UserInfoEditor
该注册方法在每次类型转换时均创建新的实例
三、Converter
1、基本介绍
Converter接口定义:
public interface Converter<S, T> { T convert(S source); }
Converter作为类型转换器,用于转换S类型到T类型,其实现必须是线程安全的且可以被共享,内建的常用Converter:
StringToBooleanConverter: String----->Boolean StringToEnumConverterFactory: String----->enum类型 EnumToStringConverter: enum类型----->String
ConverterRegistry、ConversionService分别用于Converter的注册和运行支持,接口定义如下:
public interface ConverterRegistry { void addConverter(Converter<?, ?> converter); void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter); void addConverter(GenericConverter converter); void addConverterFactory(ConverterFactory<?, ?> converterFactory); void removeConvertible(Class<?> sourceType, Class<?> targetType); }
public interface ConversionService { boolean canConvert(Class<?> sourceType, Class<?> targetType); boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType); <T> T convert(Object source, Class<T> targetType); Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType); }
Spring提供了两个默认实现(其都实现了ConverterRegistry、ConversionService接口):
- DefaultConversionService:默认的类型转换服务实现
- DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该实现即可
使用示例如下:
转换器
public class StringToUserInfoConverter implements Converter<String, UserInfo> { public StringToUserInfoConverter() { System.out.println("StringToUserInfoConverter constructed"); } public UserInfo convert(String source) { if (StringUtils.isEmpty(source)) { return null; } UserInfo userInfo = JSON.parseObject(source, UserInfo.class); return userInfo; } }
测试
@Test public void test() { DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); conversionService.addConverter(new StringToUserInfoConverter()); UserInfo userInfo = conversionService.convert("{"name":"matt","age":30}", UserInfo.class); System.out.println(userInfo.getName()); }
2、spring mvc中使用Converter
配置
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="cn.matt.convertor.StringToUserInfoConverter"/> </list> </property> </bean> <bean id="webBindingInitializer" class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer"> <property name="conversionService" ref="conversionService"/> </bean> <bean class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter"> <property name="webBindingInitializer" ref="webBindingInitializer" /> </bean>
控制器
@Controller public class TestController6 { @RequestMapping("/converter") @ResponseBody public String testConverter(@RequestParam("user") UserInfo userInfo) { System.out.println(userInfo.getName() + " " + userInfo.getAge()); return "testConverter"; } }
启动后,访问http://localhost:8080/myweb/type1?user={"name":"matt","age":30} 即可
补充:当使用<mvc:annotation-driven />时,配置如下
<mvc:annotation-driven conversion-service="conversionService"/> <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="converters"> <list> <bean class="cn.matt.convertor.StringToUserInfoConverter"/> </list> </property> </bean>
参考: