springPropertyEditor,TypeConversion,Formatting

编程

PropertyEditor

PropertyEditor本身是java bean规范中定义的组件,一开始是为实现GUI可视化开发而制定的,它是一个java接口,在核心库java.beans包中,spring使用了PropertyEditor的一部分能力,在spring中它有点像一个类型转换器的角色,spring借助于它把字符串字面值转换成其它java类型。现在通过下面这个例子来说明如何使用PropertyEditor,有两个bean:

// User bean

public class User {

private String name;

private Address address;

// getters and setters ...

}

// Address bean

public 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属性访问和检索方法:setPropertyValuegetPropertyValue,它们都有一组重载的形式,因此BeanWrapper具有操作bean的属性的能力,属性操作细节在子类AbstractPropertyAccessorAbstractNestablePropertyAccessor中实现。

TypeConverter定义了一组类型转换的规范方法,BeanWrapper继承了类型转换的能力,类型转换实现细节查看TypeConverterSupport子类。

PropertyEditorRegistry定义了一个注册中心,提供注册PropertyEditor的能力,注册PropertyEditor的含义实际上就是把特定的PropertyEditor和其可以作用的类型的对应关系维护在容器中,可以参考PropertyEditorRegistrySupport子类如何实现注册功能的。

TypeConverter的类型转换能力通常委托给PropertyEditor来完成的,因此TypeConverter通常会和PropertyEditorRegistry两个接口一起被子类实现,从上面的图中看到BeanWrapper就同时实现了它们。

BeanWrapper的有个实现类BeanWrapperImplBeanWrapperImpl的构造方法支持设置一个目标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还有一个子类型DefaultConversionServiceDefaultConversionService的额外作用是在自己内部注册了一组默认的类型转换器,这些类型转换器已经能够满足绝大多数环境下的类型转换需求,在DefaultConversionService的构造器实现中有如下代码:

public DefaultConversionService() {

// addDefaultConverters方法在当前ConverterRegistry实例中添加了很多

// 已经提供好的Converter实现

addDefaultConverters(this);

}

使用与配置ConversionService

不管是我们自己创建Converter或者是Spring提供的Converter,容器默认没有注册它们,如果需要某个Converter,则需要我们在容器中明确配置,具体在配置时我们则配置的是ConversionService,因为用户程序应该直接使用ConversionService。通过DefaultConversionService,它可以在容器中自动注册一组默认的Converter实例,它也可以注册用户自己实现的Converter实例,为此Spring提供一个ConversionServiceFactoryBean工厂类方便我们在容器中配置一个全局的ConversionService实例,ConversionServiceFactoryBeangetObject方法返回一个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;

}

DateTimeFormatNumberFormat等注解通过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

}

这个接口的实现例子可以了解DateTimeFormatAnnotationFormatterFactoryNumberFormatAnnotationFormatterFactory两个例子。

FormatterRegistry & FormatterRegistrar

FormatterRegistry是一个Formatter的注册中心,它还可以注册Converters,因为它扩展了ConverterRegistry接口。

FormatterRegistrar提供一次注册一组Formatter和Converter的能力,例如某个Formatter对某个类别的一组类型都支持,这种情况下使用FormatterRegistrar很合适。

配置Formatter

容器中默认没有注册任何可用Formatter,需要我们手动配置,spring提供一个FormattingConversionServiceFactoryBean工厂类来对xml配置实现支持,FormattingConversionServiceFactoryBean背后实际创建的是一个DefaultFormattingConversionService类型的实例,DefaultFormattingConversionServiceFormattingConversionService的子类,而FormattingConversionService实现了FormatterRegistryConverterRegistry两种接口,因此FormattingConversionService是一个Formatter注册中心,也是Converter注册中心。

下面是FormattingConversionService的类图结构:

DefaultFormattingConversionService提供两个属性来注册Formatters:formattersformatterRegistrars

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

回到顶部