目录
1.DefaultConversionService(默认转换器服务)
2. DefaultFormattingConversionService(格式化转换器服务)
JDK在界面Bean开发技术中提供了PropertyEditor接口,实现该接口的类可以达成字符串类型和特定类型的转换。Spring核心框架提供了基本类型的属性编辑器实现。另外,Spring还提供了转换器(Converter)和转换服务(ConversionService)用来实现任意对象类型之间的转换。Converter和PropertyEditor的功能相似,ConversionService则是不同类型转换器的容器,包含不同类型的转换器。
一、属性编辑器:PropertyEditor
属性编辑器是JavaBean范畴的概念。之所以称为编辑器,是因为其可以用来进行AWT的Java等图形界面开发,不过因为Java界面开发已经较少使用了,属性编辑器现在主要用于进行字符串类型和对象类型之间的转换。Java在java.beans包中提供了属性编辑器的接口PropertyEditor,如下:
public interface PropertyEditor {
//设置属性的值,基本类型以包装类传入(自动装箱)
void setValue(Object value);
//返回属性的当前值。基本类型被封装成对应的包装类实例
Object getValue();
//为属性提供一个表示初始值的字符串,属性编辑器以此值作为属性的默认值
String getJavaInitializationString();
//将属性对象以字符串表示然后返回,若返回null,表示该对象不能用字符串表示
String getAsText();
//使用字符串准换成需要的属性对象
void setAsText(String text) throws java.lang.IllegalArgumentException;
//返回表示有效属性值的字符串数组(如boolean属性对应的有效Tag为true和false),以便属性编辑器能以下拉框的方式显示出来。缺省返回null,表示属性没有匹配的字符值有限集合;
String[] getTags();
//……省略一些方法
}
在PropertyEditor接口中,Java提供了实现类PropertyEditorSupport,该类实现了接口的所有方法,也可以扩展自定义属性编辑器。如下实现一个日期类型的属性编辑器MyDatePropertyEditor,用于将yyyy-MM-dd格式的字符串转换成Date类型的日期对象。编辑器的代码如下:
public class MyDatePropertyEditor extends PropertyEditorSupport {
// 字符串准换成日期对象
@Override
public void setAsText(String text) throws IllegalArgumentException {
SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
Date date = null;
try {
date = format.parse(text);
} catch (ParseException e) {
e.printStackTrace();
}
setValue(date);
}
}
//调用此方法转换后,使用该类的getValue方法即可获取Date类型的对象
Spring提供了许多实现PropertyEditorSupport接口的属性编辑器,位于org.springframework.beans.propertyeditors包中,这些编辑器可以实现字符串类型与日期类型、布尔类型和数字类型之间的转换,具体如下:
- 布尔类型编辑器:CustomBooleanEditor
- 日期类型编辑器:CustomDateEditor
- 集合类型编辑器:CustomCollectionEditor
- 映射类型编辑器:CustomMapEditor
- 数字类型编辑器:CustomNumberEditor
- URL地址编辑器:URLEditor
二、转换器服务:ConversionService
Spring3.0之后使用转换器(Converter)替代PropertyEditor的机制。除了接口更干净之外,相比PropertyEditor提供String和Object类型的转换,Convert可以实现任意对象之间的类型转换。转换器服务(ConversionService)是各种类型转换器的容器,Spring核心框架提供了转换器服务接口类ConversionService,针对该接口有以下两种类型的实现:
- DefaultConversionService:默认转换器服务。维护不同类型的转换器(Converter)进行数据类型的转换。DefaultConversionService间接实现了ConversionService和ConverterRegistry接口;ConversionService的convert()方法用于数据类型的转换;ConverterRegistry中的addConverter()方法用于添加转换器。
- DefaultFormattingConversionService:带格式化支持的转换器服务。在DefaultConversionService基础上进行了功能的延伸,支持国际化(Locale)的格式化和解析。DefaultFormattingConversionService除了会调用DefaultConversionService的addDefaultConverters()方法添加默认的转换器之外,还实现了FormatterRegistry(),可以进行格式化转换器的添加。
1.DefaultConversionService(默认转换器服务)
Spring核心框架提供了基本类型之间的转换器类,包括数组、字符、集合、枚举、整型、数字型、对象型和字符串类型之间的转换器。这些转换器类继承自Converter接口。在DefaultConversionService中将这些转换器分为三组进行注册和添加;
- 标尺转换器组(Scalar):主要是基本类型之间的转换,包括String、Number、Boolean、Enum、Charset、Properties和UUID。
- 集合转换器组(Collection):主要包括String、Object和Array、Collection、Map之间的转换。
- 其他:包括Byte与Buffer、String与TimeZone、Object与Object等类型之间的转换。
转换器服务可以脱离Spring容器单独使用,以字符串和日期类型为例,DefaultConversionService默认支持将yyyy/mm/dd格式的字符串转换为Date类型的对象,如下:
public static void main(String[] args) {
ConversionService conversionService = new DefaultConversionService();
Date date = conversionService.convert("2021/4/28", Date.class);
}
如果Spring默认提供的转换器不够使用,可以实现Converter接口自定义转换器,并将此转换器添加到DefaultConversionService的对象中。如下为String类型与User类型转换器:
public class User {
private int id;
private String name;
private Date birthday;
//……省略getter和setter方法
}
public class MyUserConvert implements Converter<String, User> {
@Override
public User convert(String source) {
// 规定字符串表示User对象方式示例:123#郭靖#1341-10-15
String[] strArr = source.split("#");
User user = null;
if (strArr.length != 3) {
return user;
}
try {
Integer id = Integer.parseInt(strArr[0]);
String name = strArr[1];
Date birthday = new SimpleDateFormat("yyyy-mm-dd").parse(strArr[2]);
user = new User(id, name, birthday);
} catch (Exception e) {
e.printStackTrace();
}
return user;
}
}
然后通过转换器服务的addConverter()方法将新定义的转换器类型的实例添加到转换器服务的实例中之后,就可以调用convert()方法进行转换,如下:
public static void main(String[] args) {
DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(new MyUserConvert());
String userStr = "123#郭靖#1341-10-15";
User user = conversionService.convert(userStr, User.class);
System.out.println(user);
}
2. DefaultFormattingConversionService(格式化转换器服务)
格式化转换器服务(DefaultFormattingConversionService)调用了DefaultConversionService的addDefaultConverters()方法默认注册的转换器。此外,其还提供了两个扩展功能:
- 支持国际化的格式化和解析;
- 可以使用注解(比如@DateTimeFormat)对Bean的属性进行细粒度的配置。
Spring默认提供日期类型格式转换器(DateFormatter、MonthDayFormatter和YearFormatter)和数字类型的日期转换器(NumberStyleFormatter、CurrencyStyleFormatter和PercentStyleFormatter)。Format提供消息、日期等国际化的支持。
格式化转换器也可以通过实现Formatter<T>接口自定义,实现接口的print()和parse()接口方法。
public class MyDateFormatter implements Formatter<Date> {
// 日期对象转换为字符串
@Override
public String print(Date object, Locale locale) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd");
return dateFormat.format(object);
}
// 字符串转换为日期对象
@Override
public Date parse(String text, Locale locale) throws ParseException {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-mm-dd", locale);
return dateFormat.parse(text);
}
}
public static void main(String[] args) {
DefaultFormattingConversionService dfcs = new DefaultFormattingConversionService();
dfcs.addFormatter(new MyDateFormatter());
String dateStr = dfcs.convert("2021-04-28", String.class);
}
三、类型转换在容器中的使用
在实际开发中,属性编辑器和转换器服务很少单独使用,而是由容器进行调用进行Bean的组装。在SpringMVC项目中,常见的使用方式是对前端请求参数进行自动类型的转换,组装成后端需要的对象类型。
核心容器默认使用属性编辑器进行类型转换,如果配置了转换器服务的Bean,则会使用转换器服务。SpringMVC开启MVC注解驱动之后,会默认注册一个转换器服务Bean。
1.Spring核心容器的类型转换
以User类型为例,他在XML中配置成Bean,如下:
<bean id="user" class="com.mec.springmvc.model.User">
<property name="id" value="123"></property>
<property name="name" value="Zhang San"></property>
<!-- 以字符串注入日期类型的属性值 -->
<property name="birthday" value="2021/4/28"></property>
</bean>
上述配置中使用yyyy/MM/dd字符串注入日期类型的属性值(配置成yyyy/MM/dd格式之后不用其他配置,Spring默认可以解析这种格式的字符串为Date对象,但是如yyyy-MM-dd这种格式便解析不了)。容器在初始化Bean的时候会将字符串的值转换成Date日期类型。Spring容器默认使用本身定义的属性编辑器进行转换。(Spring中没有默认注册CustomDateEditor编辑器,通过查看PropertyEditorRegistrySupport类的createDefaultEditors()方法可以看到Spring默认注册的编辑器,其中并没有CustomDateEditor)。Spring提供了如下几个Map存储属性编辑器:
public class PropertyEditorRegistrySupport implements PropertyEditorRegistry {
@Nullable
private ConversionService conversionService;
private boolean defaultEditorsActive = false;
private boolean configValueEditorsActive = false;
//默认的属性编辑器,createDefaultEditors()方法中就将默认的属性编辑器们设置在该map中
@Nullable
private Map<Class<?>, PropertyEditor> defaultEditors;
//重写默认的属性编辑器,在getDefaultEditor方法中(获取默认的属性编辑器)首先是从该map中获取,接下来才是从defaultEditors中获取
@Nullable
private Map<Class<?>, PropertyEditor> overriddenDefaultEditors;
//存储自定义属性编辑器的map
@Nullable
private Map<Class<?>, PropertyEditor> customEditors;
@Nullable
private Map<String, CustomEditorHolder> customEditorsForPath;
@Nullable
private Map<Class<?>, PropertyEditor> customEditorCache;
}
除了使用Spring默认提供的属性编辑器之外,也可以向容器中加入自定义的编辑器。配置方式如下(注意下面使用value属性直接指定类的全限定名。不同版本配置方式可能有所不同,此处版本为Spring5):
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="java.util.Date" value="com.mec.springmvc.model.MyDatePropertyEditor" />
</map>
</property>
</bean>
上面是对CustomEditorConfigurer这个类的customEditors成员(map)进行注入;该类还有一个成员propertyEditorRegistrars(PropertyEditorRegistrar[]类型),通过注入该成员也可以加入自定义的编辑器,如下:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<array>
<bean class="com.mec.springmvc.model.MyDatePropertyEditor" />
</array>
</property>
</bean>
此处需要注意的是MydatePropertyEditor除了继承PropertyEditorSupport之外,还需要实现PropertyEditorRegistrar接口及其registerCustomEditors方法,此处该方法实现如下:
public void registerCustomEditors(PropertyEditorRegistry registry) {
registry.registerCustomEditor(Date.class, this);
}
如果在容器中定义了id值是conversionService的Bean,则会使用转换服务器进行类型转换,和属性编辑器一样,也可以添加自定义的转换器实现特殊类型的转换。如下通过配置ConversionServiceFactoryBean类型的Bean之后,容器就会使用该Bean进行类型转换,转换器服务默认维护了基本类型的类型转换器,设置converters属性可以增加自定义的转换器。
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 增加自定义的转换器 -->
<property name="converters">
<set>
<bean class="com.mec.springmvc.model.MyDateConvert"></bean>
</set>
</property>
</bean>
上述配置有如下几个注意点:
- Bean的ID需要指定,且必须是conversionService。
- Bean的类使用的是FormattingConversionServiceFactoryBean,也可以使用ConversionServiceFactoryBean,但不能使用FormattingConversionService等,也就是说必须使用转换器服务的工厂Bean类。FormattingConversionServiceFactoryBean是FactoryBean<FormattingConversionService>的实现。FactoryBean(工厂Bean)是Spring提供的普通Bean之外的另一种Bean,该Bean提供了getObject()返回实际的对象,也就是可以返回不同类型的实际对象。此类可以实现单例工厂,而且在AOP开发中很有用。
- BeanFactory:Bean工厂,通过其可以得到不同类型的Bean,也就是Bean容器;
- FactoryBean:工厂Bean,是一种类型的Bean,是单一Bean的工厂模式。
2.SpringMVC容器的类型转换
在SpringMVC项目中,如果开启了MVC注解功能,则WebMvcConfigurationSupport会自动注册名字为mvcConversionService的FormattingConversionService的Bean和FormattingConversionServiceFactoryBean类型的Bean。MVC注解可以使用XML和类配置的方式开启。
若采用XML方式(<mvc:annotation-driven>)开启MVC注解功能,默认会装配FormattingConversionServiceFactoryBean,如果要增加自定义的转换器,则需要给该配置标签的conversion-service属性指定一个转换器服务的Bean,如下:
<mvc:annotation-driven conversion-service="conversionService" />
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
<!-- 增加自定义的转换器 -->
<property name="converters">
<set>
<bean class="com.mec.springmvc.model.MyDateConvert"></bean>
</set>
</property>
</bean>
采用类配置开启方式时,使用@EnableWebMvc注解:
@Configuration
@ComponentScan(basePackages = { "com.mec.springmvc.controller" })
@Import(value = { MvcConvertConfigure.class })
@EnableWebMvc //开启spring mvc注解
public class WebAppConfig {
//……
}
如果要增加转换器,则配置类需要继承WebMvcConfigurationSupport类,使用@PostConstruct在该Bean实例初始化后添加自定义的转换器,如下:
@Configuration
public class MvcConvertConfigure extends WebMvcConfigurationSupport {
@Autowired
private FormattingConversionService mvcConversionService;
@PostConstruct
public void addCustomConvert() {
if (mvcConversionService != null) {
mvcConversionService.addConverter(new MyDateConvert());
}
}
}
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
AnnotationConfigWebApplicationContext annoWebContext = new AnnotationConfigWebApplicationContext();
annoWebContext.register(WebAppConfig.class);
annoWebContext.registerShutdownHook();
// annoWebContext.refresh(); //如果配置了@EnableWebMvc,这里一定要注释起来,否则无法启动tomcat。Spring版本5.2.3
//省略中央控制器配置……
}
}
Spring容器对Bean的属性进行转换是通过数据绑定的方式进行的,包括Bean包装器(BeanWrapper)和数据绑定(DataBinder、WebDataBinder)两种方式,他们通过调用容器中管理的转换器服务Bean进行数据的类型转换。