springPropertyEditor,TypeConversion,Formatting
PropertyEditor
PropertyEditor
本身是java bean规范中定义的组件,一开始是为实现GUI可视化开发而制定的,它是一个java接口,在核心库java.beans
包中,spring使用了PropertyEditor的一部分能力,在spring中它有点像一个类型转换器的角色,spring借助于它把字符串字面值转换成其它java类型。现在通过下面这个例子来说明如何使用PropertyEditor,有两个bean:
// User beanpublic class User {
private String name;
private Address address;
// getters and setters ...
}
// Address beanpublic class Address {
private String province;
private String city;
private String zipCode;
// getters and setters ...
}
在spring ioc容器中配置User实例的话,我们需要先配置一个Address实例,然后在User实例的配置中使用ref
引用Address实例:
<bean id="user" class="x.y.User"> <property name="name" value="Nani"/>
<property name="address" ref="address" />
</bean>
<bean id="address" class="x.y.Address">
...
</bean>
现在假设spring容器中没有Address实例,我们想通过直接给address属性一个字符串字面值,例如value="Beijing,Haidian,100001"
,这个字符串字面值中也描述了一个Address对象的全部信息,这种场景下我们可以借助PropertyEditor把字符串常量值转换成一个Address对象,现在user bean配置是这样:
<bean id="user" class="x.y.User"> <property name="name" value="Nani"/>
<property name="address" value="Beijing,Haidian,100001" />
</bean>
这样设置address属性的值后,接下来我们还要自己实现一个PropertyEditor
属性编辑器,实现一个属性编辑器可以完整的实现PropertyEditor
接口,不过Java提供了一个基本的实现PropertyEditorSupport
,我们选择继承PropertyEditorSupport
即可,然后只需要重写setAsText
方法就可以了,看看下面这个AddressEditor的实现:
// 只需要重写setAsText方法,自己解析字符串信息并创建一个Address对象,最后// 调用setValue方法
public class AddressEditor extends PropertyEditorSupport {
@Override
public void setAsText(String text) throws IllegalArgumentException {
if (text == null || text.isEmpty()) {
throw new IllegalArgumentException();
}
String[] splitText = text.split(",");
String province = splitText[0];
String city = splitText[1];
String zipCode = splitText[2];
Address address = new Address();
address.setProvince(province);
address.setCity(city);
address.setZipCode(zipCode);
setValue(address);
}
}
接下来还需要把这个AddressEditor
注册到容器中,注册后容器就会自动使用这个AddressEditor
实现字符串到Address
对象的转换,Spring提供一个叫做CustomEditorConfigurer
的组件来注册用户自定义的PropertyEditor,通过此组件的setCustomEditors()
方法完成注入:
<!--注册定制的PropertyEditor--><bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<!--key属性指定目标类型,value属性指定为AddressEditor-->
<entry key="x.y.Address" value="x.y.editor.AddressEditor"/>
</map>
</property>
</bean>
这里我们需要注意,通过配置注册的方式只受ApplicationContext
容器支持,如果应用程序直接使用BeanFactory
这种底层容器编程,那么需要用户需要自己调用ConfigurableBeanFactory#registerCustomEditor
方法注册定制的PropertyEditor,如下所示:
beanFactory.registerCustomEditor(Address.class, AddressEditor.class);
现在当容器初始化成功后,我们通过getBean("user")
从容器中查找User实例,User实例中的address属性已经正确实例化为一个Address对象。
另一种注册属性编辑器的方式是利用spring的自动发现机制注册,这需要自己实现的PropertyEditor满足两点限制:
- 自定义的PropertyEditor的类名形式为
<目标bean类型名>+Editor
,例如对于上述目标类Address,则我们的PropertyEditor就应该取名为AddressEditor
; - 把自己实现的PropertyEditor类放在和目标类型相同的包里面;
当我们按照上面两条创建定制的PropertyEditor后,这个PropertyEditor就会被spring自动发现并注册到容器中,这点和java bean规范的自动发现机制相似(参考javaPropertyEditorManager
的实现机制),不过spring完全是自己实现了一套发现机制,可以到org.springframework.beans.BeanUtils.findEditorByConvention()
方法中跟踪实现细节。
哪里用到了PropertyEditor
上面的例子展示了PropertyEditor的基本用法,但实际开发场景中应该不会经常有这样的用法。但是PropertyEditor总是会被spring框架内部使用,是spring的基础设施,spring在执行任何属性注入时都会依赖PropertyEditor实现把字符串转换成目标类型。
spring底层完成依赖属性注入过程通过一个叫做BeanWrapper
的核心组件,BeanWrapper
的核心功能就是设置bean属性和查询bean属性,它支持任意层次的属性嵌套,就如它的名字所示的,BeanWrapper
包装一个bean实例并在此bean上执行一系列操作。用户不需要直接使用BeanWrapper
,但是理解BeanWrapper
的运行机制有助于整体层次上理解spring的实现方式。
这是BeanWrapper
的层次结构:
PropertyAccessor
定义了一组bean属性访问和检索方法:setPropertyValue
和getPropertyValue
,它们都有一组重载的形式,因此BeanWrapper具有操作bean的属性的能力,属性操作细节在子类AbstractPropertyAccessor
和AbstractNestablePropertyAccessor
中实现。
TypeConverter
定义了一组类型转换的规范方法,BeanWrapper继承了类型转换的能力,类型转换实现细节查看TypeConverterSupport
子类。
PropertyEditorRegistry
定义了一个注册中心,提供注册PropertyEditor
的能力,注册PropertyEditor的含义实际上就是把特定的PropertyEditor和其可以作用的类型的对应关系维护在容器中,可以参考PropertyEditorRegistrySupport
子类如何实现注册功能的。
TypeConverter
的类型转换能力通常委托给PropertyEditor
来完成的,因此TypeConverter
通常会和PropertyEditorRegistry
两个接口一起被子类实现,从上面的图中看到BeanWrapper就同时实现了它们。
BeanWrapper的有个实现类BeanWrapperImpl
,BeanWrapperImpl
的构造方法支持设置一个目标bean作为被wrap的对象,下面展示一个通过BeanWrapper编程的例子:
// 包装一个User对象BeanWrapper userWrapper = new BeanWrapperImpl(new User());
userWrapper.setPropertyValue("name", "Nani");
userWrapper.setPropertyValue("level", "5");
userWrapper.setPropertyValue(new PropertyValue("address", "Beijing,Haidian,100001"));
System.out.println(userWrapper.getWrappedInstance());
System.out.println(((User)userWrapper.getWrappedInstance()).getAddress());
BeanWrapper设置任何属性都要依赖一个对应的PropertyEditor,上述例子中address属性的目标类型是Address
,level属性的目标类型是Integer
,name属性的目标类型是String
:
public void setName(String name) { this.name = name;
}
public void setAddress(Address address) {
this.address = address;
}
public void setLevel(Integer level) {
this.level = level;
}
BeanWrapper设置“address”属性时需要委托AddressEditor
,我们把AddressEditor
放在和Address
类同一个包中,spring自动会加载AddressEditor
。BeanWrapper设置“level”属性时需要委托给CustomNumberEditor
,我们没有创建这个PropertyEditor,因为spring已经提供了它,并且BeanWrapperImpl默认已经注册了CustomNumberEditor
,所以“level”属性能够成功转换为Integer
类型。Spring默认提供了很多类型的PropertyEditor实现,它们都在org.springframework.beans.propertyeditors
包中可以找到,并且在BeanWrapperImpl
中默认已经注册了很多的Editor,可以满足大多数场景下的转换操作,这也是我们很少需要自定义PropertyEditor的原因。对于“name”属性来说更直接,它不需要PropertyEditor来转换,可以直接赋值,因为setName
方法的参数类型也是String
,对于setter方法参数类型和我们在BeanWrapper的setPropertyValue
方法中给出的类型如果兼容,则不需要PropertyEditor来转换,直接设置值即可。
用PropertyEditorRegistrar注册一组PropertyEditor
Spring还提供了一个叫做PropertyEditorRegistrar
的组件,使用这个组件可以很方便的注册一簇PropertyEditor,例如某个PropertyEditor对某几个类型都有用,这些类可能有相似处,这种情况可以创建一个单独的PropertyEditorRegistrar实现来注册这一组类型和PropertyEditor的对应关系。我们看看PropertyEditorRegistrar
的规范定义:
public interface PropertyEditorRegistrar { void registerCustomEditors(PropertyEditorRegistry registry);
}
registerCustomEditors
方法的参数是一个PropertyEditorRegistry
对象,因此我们可以在方法中一次注册一簇相关的PropertyEditor,可以参考spring的org.springframework.beans.support.ResourceEditorRegistrar
这个例子。
用户自己实现这个接口的例子:
public class MyZXYPropertyEditorRegistrar implements PropertyEditorRegistrar { @Override
public void registerCustomEditors(PropertyEditorRegistry registry) {
PropertyEditor editor = new StrangeZXYEditor();
registry.registerCustomEditor(ZBean.class, editor);
registry.registerCustomEditor(XBean.class, editor);
registry.registerCustomEditor(YBean.class, editor);
}
}
然后我们依然可以使用上面介绍的CustomEditorConfigurer
这个工具组件来注册PropertyEditorRegistrar
,因为CustomEditorConfigurer
还暴露有一个setPropertyEditorRegistrars()
的setter方法:
<!--实现一个PropertyEditorRegistrar--><bean id="myZXYPropertyEditorRegistrar" class="starting.springframework.container.propertyEditor.registrar.MyZXYPropertyEditorRegistrar"/>
<!-- 使用CustomEditorConfigurer组件来注册PropertyEditor -->
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="myZXYPropertyEditorRegistrar"/>
</list>
</property>
</bean>
类型转换框架
Spring 3 引入了一套类型转换框架,在core.convert
及其子包中实现,api主要包括两部分,类型转换器api和一组类型转换外观接口,外观接口屏蔽了底层不同类型转换器的不同,应用程序需要类型转换时统一使用外观接口即可。新的类型转换系统可以完全替代以前的PropertyEditor
实现把字符串转换成其他java类型。
这套类型转换api不只是提供给spring内部使用的,它是公开的,应用程序可以在任何地方使用它们。
类型转换器接口
Spring引入了两种类型转换器:
- Converter<S, T> 大多数情况这个转换器都可使用。
- GenericConverter 一个更通用的转换器,也更复杂一些。
一般情况下我们选择实现Converter
类型就行,GenericConverter使用更复杂。
ConversionService外观
ConversionService
提供了一组外观接口来应对外部应用程序的类型转换的需求,背后实际上由各种Converters在起转换作用:
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);
}
ConversionService
的规范是,当canConvert
方法返回true时,可以用对应的convert
方法执行转换。
ConversionService
的实现类通常也会实现ConverterRegistry
接口,ConverterRegistry
提供注册Converters的能力,这样ConversionService的实现者通常都是把类型转换过程委托给在其中注册的对应的Converter去处理。Spring提供了一个GenericConversionService
的基本实现类,此类就是按照上述这个原则去实现的,下面是它的类扩展图:
GenericConversionService
还有一个子类型DefaultConversionService
,DefaultConversionService
的额外作用是在自己内部注册了一组默认的类型转换器,这些类型转换器已经能够满足绝大多数环境下的类型转换需求,在DefaultConversionService
的构造器实现中有如下代码:
public DefaultConversionService() { // addDefaultConverters方法在当前ConverterRegistry实例中添加了很多
// 已经提供好的Converter实现
addDefaultConverters(this);
}
使用与配置ConversionService
不管是我们自己创建Converter或者是Spring提供的Converter,容器默认没有注册它们,如果需要某个Converter,则需要我们在容器中明确配置,具体在配置时我们则配置的是ConversionService,因为用户程序应该直接使用ConversionService。通过DefaultConversionService
,它可以在容器中自动注册一组默认的Converter实例,它也可以注册用户自己实现的Converter实例,为此Spring提供一个ConversionServiceFactoryBean
工厂类方便我们在容器中配置一个全局的ConversionService实例,ConversionServiceFactoryBean
的getObject
方法返回一个DefaultConversionService
实例。
有一些要点需要了解,一个容器中通常配置一个ConversionService实例就可以了,容器初始化时就会初始化ConversionService实例,这个实例是全局的,各线程共享的,spring框架内部需要类型转换随时使用它,另外我们也可以把ConversionService实例注入到业务bean中使用。
下面的配置示例在容器中注册一组默认的类型转换器:
<!--ConversionServiceFactoryBean在内部实际上创建的就是一个DefaultConversionService实例,
因此这个bean能够在容器中完成注册一组默认可用的转换器
-->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"/>
ConversionServiceFactoryBean
暴露了一个setConverters(Set converters)
属性设置方法,我们使用这个方法注册自定义类型转换器,因为有时候我们需要自己实现某种类型转换器,下面是一个自己实现的类型转换器例子:
// 一个可以通过把类型名转换成 BarBean 类型实例的转换器public class BarBeanConverter implements Converter<String, BarBean> {
@Override
public BarBean convert(String source) {
Objects.requireNonNull(source, "Spring suggests the "source" is not a null");
try {
Class c = Class.forName(source);
return (BarBean) c.newInstance();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
return null;
}
}
如下所示注册这个自定义转换器:
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean"> <property name="converters">
<set>
<bean class="starting.springframework.container.conversion.converter.BarBeanConverter"/>
</set>
</property>
</bean>
<bean id="fooBean" class="starting.springframework.container.conversion.bean.FooBean">
<!--对于barBean属性,直接提供类型名,让BarBeanConverter去转换成BarBean对象-->
<property name="barBean" value="starting.springframework.container.conversion.bean.BarBean"/>
</bean>
注册的自定义转换器如果和默认的类型转换器如果出现重叠,会覆盖默认的类型转换器。
ConversionService可替代PropertyEditor
上面几段说明了Converter的概念以及如何在容器中注册ConversionService,一旦在在容器中配置了ConversionService,那么在spring在处理bean的属性注入时,就会优先采用ConversionService来转换字符串值,不存在匹配的ConversionService时才使用PropertyEditor。在TypeConverterDelegate
委托类的convertIfNecessary
方法中可以找到依据,下面展示了此方法开头部分的代码:
public <T> T convertIfNecessary(String propertyName, Object oldValue, Object newValue, Class<T> requiredType, TypeDescriptor typeDescriptor) throws IllegalArgumentException {
// 从propertyEditorRegistry中查找自定义的PropertyEditor
PropertyEditor editor = this.propertyEditorRegistry.findCustomEditor(requiredType, propertyName);
ConversionFailedException conversionAttemptEx = null;
// 在propertyEditorRegistry中查找ConversionService实例;
// 参考PropertyEditorRegistrySupport类实现细节,PropertyEditorRegistrySupport
// 中持有一个ConversionService实例引用,这个ConversionService引用来源于容器中注册
// 的ConversionService实例
ConversionService conversionService = this.propertyEditorRegistry.getConversionService();
// 如果自定义的propertyEditor没有,但是有ConversionService实例,
// 则采用ConversionService来执行字符串值转换
if (editor == null && conversionService != null && newValue != null && typeDescriptor != null) {
TypeDescriptor sourceTypeDesc = TypeDescriptor.forObject(newValue);
// 如果ConversionService能够转换到目标类型,则执行转换
if (conversionService.canConvert(sourceTypeDesc, typeDescriptor)) {
try {
return (T) conversionService.convert(newValue, sourceTypeDesc, typeDescriptor);
}
catch (ConversionFailedException ex) {
// fallback to default conversion logic below
conversionAttemptEx = ex;
}
}
}
// ...
}
格式化API
Spring的类型转换系统对外提供了统一的类型转换能力,满足了spring处理属性绑定的能力,DataBinder
以及Spring表达式语言处理也依赖与这个类型转换系统。
在另一些涉及到有客户端显示需求的程序中,仅仅采用类型转换可能还无法完全满足需求,对于客户端的展示情况来说,可能还存存在本地化显示以及格式化显示的问题,例如对于日期、货币值,在不同环境下的客户端展示样式肯定有区别,Spring的类型转换系统不能提供格式化以及本地化的能力,Spring 3 还引入了一套格式化api来处理这种需求。
Formatter
Spring引入一个Formatter
接口来定义格式化规范:
// 参数类型T就是被格式化的目标类型// Printer定义了把类型T转换为特定格式的字符串
// Parser定义了从格式字符串转换为类型T
public interface Formatter<T> extends Printer<T>, Parser<T> {
}
public interface Printer<T> {
String print(T object, Locale locale);
}
public interface Parser<T> {
T parse(String text, Locale locale) throws ParseException;
}
要对某个类型进行格式化,那么实现Formatter
接口,Formmater
接口的实现类需要是线程安全的,因为Formatter是共享的,在spring的format
包下实现了一些常见的处理日期和数字的Formatter,我们自己需要实现Formatter时可以参考它们的实现例子。
AnnotationFormatterFactory
在需要格式化的字段上,spring支持我们使用java annotation来定义一些格式化的元信息,例如显示样式以及本地化信息等,在format.annotation
包中,spring目前提供了两个格式化注解:
DateTimeFormat
,定义对java.util.Date,java.util.Calendar,java.lang.Long以及joda库日期类型的格式化信息。NumberFormat
,定义数字类型的格式化信息。
例如:
public class EmployeeBean { @DateTimeFormat(iso = DateTimeFormat.ISO.DATE)
private Date hireDate;
@NumberFormat(style = NumberFormat.Style.CURRENCY)
private BigDecimal salary;
}
DateTimeFormat
和NumberFormat
等注解通过AnnotationFormatterFactory
接口定义的方法绑定到Formatter
对象上,AnnotationFormatterFactory
定义获取Formatter
实例的方法,方法参数会接受相应的Annotation
对象,这是接口的定义:
public interface AnnotationFormatterFactory<A extends Annotation> { Set<Class<?>> getFieldTypes(); // 获取字段的类型
Printer<?> getPrinter(A annotation, Class<?> fieldType); // 获取Formatter
Parser<?> getParser(A annotation, Class<?> fieldType); // 获取Formatter
}
这个接口的实现例子可以了解DateTimeFormatAnnotationFormatterFactory
和NumberFormatAnnotationFormatterFactory
两个例子。
FormatterRegistry & FormatterRegistrar
FormatterRegistry
是一个Formatter的注册中心,它还可以注册Converters,因为它扩展了ConverterRegistry
接口。
FormatterRegistrar
提供一次注册一组Formatter和Converter的能力,例如某个Formatter对某个类别的一组类型都支持,这种情况下使用FormatterRegistrar很合适。
配置Formatter
容器中默认没有注册任何可用Formatter,需要我们手动配置,spring提供一个FormattingConversionServiceFactoryBean
工厂类来对xml配置实现支持,FormattingConversionServiceFactoryBean
背后实际创建的是一个DefaultFormattingConversionService
类型的实例,DefaultFormattingConversionService
是FormattingConversionService
的子类,而FormattingConversionService实现了FormatterRegistry
和ConverterRegistry
两种接口,因此FormattingConversionService是一个Formatter注册中心,也是Converter注册中心。
下面是FormattingConversionService的类图结构:
DefaultFormattingConversionService提供两个属性来注册Formatters:formatters
,formatterRegistrars
。
formatters
属性支持直接配置一组Formatter
类型和AnnotationFomatterFactory
类型。
formatterRegistrars
属性配置一组FormatterRegistrar
类型。
DefaultFormatterConversionService还有一个属性是registerDefaultFormatters
,是个boolean类型字段,控制是否注册spring内建的Formatter,默认值是true会注册默认的Formatters。
对于DefaultFormattingConversionService来说还有一点比较重要,由于实现了ConverterRegistry
接口,因此还提供了注册Converters的能力,它还暴露有一个converters
属性,我们可以配置这个属性注册一组converter。无论用户是否配置converters
属性,DefaultFormattingConversionService默认都会注册一组默认的Converters,这种情况和前面章节中说明的DefaultConversionService
的情况相同。
按照上述说明,一个注册Formatters的配置示例:
<bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean"> <property name="formatters">
<set>
<bean class="org.springframework.format.number.NumberFormatAnnotationFormatterFactory"/>
</set>
</property>
<property name="formatterRegistrars">
<set>
<bean class="org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar">
<property name="dateFormatter">
<bean class="org.springframework.format.datetime.joda.DateTimeFormatterFactoryBean">
<property name="pattern" value="yyyyMMdd"/>
</bean>
</property>
</bean>
</set>
</property>
<property name="registerDefaultFormatters" value="false"/>
</bean>
以上是 springPropertyEditor,TypeConversion,Formatting 的全部内容, 来源链接: utcz.com/z/515327.html