验证,数据绑定和类型转换
在Spring框架中,验证,数据绑定和类型转换都是比较重要的环节,下面我们来一起学习一下
一、介绍
JSR-303 / JSR-349 Bean验证
Spring Framework 4.0在安装支持方面支持Bean Validation 1.0(JSR-303)和Bean Validation 1.1(JSR-349),并将其与Spring的Validator接口相适应。
如Spring验证所述,应用程序可以选择全局启用Bean验证 ,并专门用于所有验证需求。
应用程序还可以为Validator每个 DataBinder实例注册其他Spring 实例,如配置DataBinder中所述。这对于插入验证逻辑而不使用注释可能很有用。
将验证视为业务逻辑有优点和缺点,Spring提供了验证(和数据绑定)设计,不排除其中任何一个。具体的验证不应该绑定到Web层,应该易于本地化,应该可以插入任何可用的验证器。考虑到上述情况,Spring已经提出了一个Validator界面,它在应用程序的每一层都是基本的和显着的可用的。
数据绑定对于允许用户输入动态绑定到应用程序的域模型(或用于处理用户输入的任何对象)很有用。Spring提供了所谓的DataBinder完成。在Validator和 DataBinder补validation包,它主要在使用,但不限于MVC框架。
这BeanWrapper是Spring框架中的一个基本概念,并在很多地方使用。但是,您可能不需要BeanWrapper 直接使用。因为这是参考文件,所以我们觉得有些解释可能是按顺序的。我们将BeanWrapper在本章中解释这一点,因为如果您打算使用它,那么在尝试将数据绑定到对象时很可能会这样做。
Spring的DataBinder和较低级的BeanWrapper都使用PropertyEditor来解析和格式化属性值。这个PropertyEditor概念是JavaBeans规范的一部分,本章也对此进行了解释。Spring 3引入了一个“core.convert”包,它提供了一个通用的类型转换工具,以及一个用于格式化UI字段值的高级“格式”包。这些新软件包可以作为PropertyEditor的简单替代品,本章也将对此进行讨论。
二、使用Spring的Validator接口进行验证
Spring提供了一个Validator可以用来验证对象的接口。该 Validator接口使用Errors对象工作,以便在验证时,验证器可以向Errors对象报告验证失败。
我们来考虑一个小数据对象:
public class Person {
private String name;
private int age;
// the usual getters and setters...
}
我们将Person通过实现org.springframework.validation.Validator
接口的以下两种方法来为该类提供验证行为:
supports(Class)- 这可以Validator验证提供的实例Class吗?
validate(Object, org.springframework.validation.Errors)- 验证给定对象,如果出现验证错误,则将其注册到给定Errors对象
实现a Validator相当简单,特别是当你知道ValidationUtilsSpring框架也提供的 helper类的时候。
public class PersonValidator implements Validator {
/**
* This Validator validates *just* Person instances
*/
public boolean supports(Class clazz) {
return Person.class.equals(clazz);
}
public void validate(Object obj, Errors e) {
ValidationUtils.rejectIfEmpty(e, "name", "name.empty");
Person p = (Person) obj;
if (p.getAge() < 0) {
e.rejectValue("age", "negativevalue");
} else if (p.getAge() > 110) {
e.rejectValue("age", "too.darn.old");
}
}
}
正如你所看到的,类中的static rejectIfEmpty(..)方法ValidationUtils用于拒绝’name’属性,如果它是null或空字符串。看看ValidationUtilsjavadocs,看看它提供的功能,除了前面所示的例子。
尽管可以实现一个Validator类来验证丰富对象中的每个嵌套对象,但最好在每个嵌套的对象类的自身Validator实现中封装验证逻辑。一个“富”对象的简单例子Customer就是由两个String 属性(第一个和第二个名称)和一个复杂Address对象组成。Address对象可以独立于Customer对象使用,因此AddressValidator 已经实现了独特的功能。如果您希望CustomerValidator重复使用AddressValidator类中包含的逻辑而无需复制粘贴,则可以AddressValidator在内部依赖注入或实例化CustomerValidator,并使用它:
public class CustomerValidator implements Validator {
private final Validator addressValidator;
//将复杂对象的校验addressValidator注入到CustomerValidator中
public CustomerValidator(Validator addressValidator) {
if (addressValidator == null) {
throw new IllegalArgumentException("The supplied [Validator] is " +
"required and must not be null.");
}
if (!addressValidator.supports(Address.class)) {
throw new IllegalArgumentException("The supplied [Validator] must " +
"support the validation of [Address] instances.");
}
this.addressValidator = addressValidator;
}
/**
* This Validator validates Customer instances, and any subclasses of Customer too
*/
public boolean supports(Class clazz) {
return Customer.class.isAssignableFrom(clazz);
}
public void validate(Object target, Errors errors) {
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "firstName", "field.required");
ValidationUtils.rejectIfEmptyOrWhitespace(errors, "surname", "field.required");
Customer customer = (Customer) target;
try {
errors.pushNestedPath("address");
ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
} finally {
errors.popNestedPath();
}
}
}
将验证错误报告给Errors传递给验证器的对象。在Spring Web MVC的情况下,您可以使用<spring:bind/>
标签检查错误消息,但当然您也可以自己检查错误对象。有关它提供的方法的更多信息可以在javadocs中找到。
三、将代码解析为错误消息
我们已经讨论过数据绑定和验证。输出与验证错误相对应的消息是我们需要讨论的最后一件事。在我们上面显示的例子中,我们拒绝了name该age字段。如果我们要使用a输出错误消息MessageSource,那么我们将使用我们在拒绝该字段(本例中为’name’和’age’)时给出的错误代码。从接口调用(直接或间接使用ValidationUtils类)rejectValue或其他reject方法时Errors,底层实现不仅会注册您传入的代码,还会附加一些其他错误代码。它注册的错误代码取决于MessageCodesResolver使用的错误代码。默认情况下,DefaultMessageCodesResolver例如,它不仅用您输入的代码注册消息,而且还会输入包含您传递给拒绝方法的字段名称的消息。因此,如果您拒绝一个字段 rejectValue(“age”, “too.darn.old”),除了too.darn.old代码之外,Spring还会注册too.darn.old.age并且too.darn.old.age.int(因此第一个将包含字段名称,第二个将包含字段类型); 这是为了方便开发人员定位错误消息等。
关于MessageCodesResolver和默认策略的更多信息可以分别在javadoc MessageCodesResolver 和 of中找到 DefaultMessageCodesResolver。
四、Bean操作和BeanWrapper
该org.springframework.beans软件包符合Oracle提供的JavaBeans标准。JavaBean只是一个带有默认无参构造函数的类,它遵循命名约定,其中(通过示例)名为property的属性bingoMadness将具有setter方法setBingoMadness(..)和getter方法getBingoMadness()。有关JavaBeans和规范的更多信息,请参阅Oracle网站( javabeans)。
bean包中一个非常重要的类是BeanWrapper接口及其相应的实现(BeanWrapperImpl)。正如引用javadocs, BeanWrapper提供功能设置和获取属性值(单独或批量),获取属性描述符,并查询属性,以确定它们是否可读或可写。此外,该产品BeanWrapper还支持嵌套属性,可以将子属性的属性设置为无限深度。然后, BeanWrapper支持标准JavaBean的能力PropertyChangeListeners 和VetoableChangeListeners,而不需要在辅助代码。最后但并非最不重要的是,BeanWrapper为设置索引属性提供了支持。在BeanWrapper通常不使用应用程序代码直接的,而是由DataBinder和BeanFactory。
BeanWrapper作品部分由其名称表示的方式:它包装一个bean以对该bean执行操作,例如设置和检索属性。
1.设置和获取基本和嵌套的属性
设置和获取属性是通过使用setPropertyValue(s)和 getPropertyValue(s)两个重载变量都有的方法完成的。它们都在Spring的javadocs中有更详细的描述。重要的是要知道有几个约定用于指示对象的属性。几个例子:
Examples of properties
Expression | Explanation |
---|---|
name | 指示与name方法getName()或isName() 和相对应的属性setName(..) |
account.name | 指示对应于例如方法或属性name的属性的嵌套属性accountgetAccount().setName()getAccount().getName() |
account[2] | 指示索引属性的第三个元素account。索引属性可以是类型的array,也可以是list其他自然顺序的集合 |
account[COMPANYNAME] | 指示由Map属性的键COMPANYNAME索引的地图条目的值account |
如果你不打算BeanWrapper直接使用它DataBinder,那么下一部分对你来说并不是非常重要,如果你只是使用BeanFactory 和他们的开箱即用的实现,你应该跳到关于 PropertyEditors。)
考虑以下两个类:
public class Company {
private String name;
private Employee managingDirector;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Employee getManagingDirector() {
return this.managingDirector;
}
public void setManagingDirector(Employee managingDirector) {
this.managingDirector = managingDirector;
}
}
public class Employee {
private String name;
private float salary;
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public float getSalary() {
return salary;
}
public void setSalary(float salary) {
this.salary = salary;
}
}
下面的代码片断展示了如何检索和操作的一些实例化属性的一些例子Companies和Employees:
BeanWrapper company = new BeanWrapperImpl(new Company());
// setting the company name..
company.setPropertyValue("name", "Some Company Inc.");
// ... can also be done like this:
PropertyValue value = new PropertyValue("name", "Some Company Inc.");
company.setPropertyValue(value);
// ok, let's create the director and tie it to the company:
BeanWrapper jim = new BeanWrapperImpl(new Employee());
jim.setPropertyValue("name", "Jim Stravinsky");
company.setPropertyValue("managingDirector", jim.getWrappedInstance());
// retrieving the salary of the managingDirector through the company
Float salary = (Float) company.getPropertyValue("managingDirector.salary");
2.内置的PropertyEditor实现
Spring使用概念PropertyEditors来实现an Object和a 之间的转换 String。如果你仔细想想,有时候可能会很方便地以不同于对象本身的方式表示属性。例如,Date 可以用人类可读的方式表示(如String ‘2007-14-09’),而我们仍然能够将人类可读形式转换回原始日期(或者甚至更好:将任何以人类可读形式输入的日期转换回到Date物体)。这种行为可以通过注册类型的 自定义编辑器来实现java.beans.PropertyEditor。BeanWrapper在上一章中提到的在特定的IoC容器中注册自定义编辑器,或者将自定义编辑器注册到特定的IoC容器中,使其知道如何将属性转换为所需的类型。阅读更多 PropertyEditors在java.beansOracle提供的软件包的javadoc中。
Spring中使用属性编辑的几个示例:
在bean上设置属性是使用完成的PropertyEditors。当提到 java.lang.String某个bean的属性的值时,你将在XML文件中声明,Spring将(如果相应属性的setter具有 Class-parameter)使用该ClassEditor方法尝试将参数解析为Class 对象。
在Spring的MVC框架中解析HTTP请求参数是使用各种PropertyEditors你可以手动绑定的所有子类来完成的 CommandController。
Spring有一些内置的功能,PropertyEditors让生活变得轻松。这些都列在下面,它们都位于org.springframework.beans.propertyeditors 包中。大多数(但不是全部)(如下所示)默认通过 BeanWrapperImpl。在以某种方式配置属性编辑器的情况下,您当然可以注册自己的变体以覆盖默认的变体:
表12.内置的PropertyEditors
Class | Explanation |
---|---|
ByteArrayPropertyEditor | 字节数组编辑器。字符串将被简单地转换为相应的字节表示。通过默认注册BeanWrapperImpl。 |
ClassEditor | 将表示类的字符串解析为实际的类以及其他方式。当没有找到类时,IllegalArgumentException会抛出一个类。通过默认注册 BeanWrapperImpl。 |
CustomBooleanEditor | 可定制的属性编辑器Boolean。通过默认注册 BeanWrapperImpl,但可以通过将自定义实例注册为自定义编辑器来覆盖。 |
CustomCollectionEditor | Collections的属性编辑器,将任何源Collection转换为给定的目标 Collection类型。 |
CustomDateEditor | 可定制的java.util.Date属性编辑器,支持自定义的DateFormat。没有默认注册。必须以适当的格式根据需要进行用户注册。 |
CustomNumberEditor | Number的子类定制的属性编辑器一样Integer,Long,Float, Double。默认情况下已注册BeanWrapperImpl,但可以通过将自定义实例注册为自定义编辑器进行覆盖。 |
FileEditor | 能够将字符串解析为java.io.File对象。通过默认注册 BeanWrapperImpl。 |
InputStreamEditor | 一个单向的属性编辑器,能够把文本字符串,并产生(通过中间ResourceEditor和Resource)的InputStream,所以InputStream 性能可以直接被设置成字符串。请注意,默认的用法不会InputStream为你关闭!通过默认注册BeanWrapperImpl。 |
LocaleEditor | 能够将字符串解析为Locale对象,反之亦然(字符串格式为 [country] [variant],这与Locale提供的toString()方法相同)。通过默认注册BeanWrapperImpl。 |
PatternEditor | 能够将字符串解析为java.util.regex.Pattern对象,反之亦然 |
PropertiesEditor | 能够将字符串(使用java.util.Properties类的javadoc中定义的格式进行格式化)转换为Properties对象。通过默认注册BeanWrapperImpl。 |
StringTrimmerEditor | 修剪字符串的属性编辑器。可以选择允许将空字符串转换为null值。没有默认注册; 必须根据需要进行用户注册。 |
URLEditor | 能够将URL的字符串表示形式解析为实际URL对象。通过默认注册BeanWrapperImpl。 |
Spring使用它java.beans.PropertyEditorManager来设置可能需要的属性编辑器的搜索路径。搜索路径还包括sun.bean.editors,其包括PropertyEditor实现为类型,例如Font,Color和最原始类型。还要注意的是,标准的JavaBean基础构架能够自动发现PropertyEditor类(无需做额外的注册工作),如果他们是在同一个包,因为他们处理类,并具有相同的名称作为类,它有’Editor’附加; 例如,可以有以下的类和包结构,这足以使FooEditor该类被识别并用作PropertyEditorfor- Footype属性。=
com
chank
pop
Foo
FooEditor // the PropertyEditor for the Foo class
请注意,您也可以在BeanInfo此处使用标准的JavaBeans机制(在此处 以非惊人的细节描述)。在下面查找使用该BeanInfo机制来显式注册一个或多个PropertyEditor实例以及关联类的属性的示例。
com
chank
pop
Foo
FooBeanInfo // the BeanInfo for the Foo class
以下是引用FooBeanInfo类的Java源代码。这将把a CustomNumberEditor与班级的age财产联系起来Foo。
public class FooBeanInfo extends SimpleBeanInfo {
public PropertyDescriptor[] getPropertyDescriptors() {
try {
final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Foo.class) {
public PropertyEditor createPropertyEditor(Object bean) {
return numberPE;
};
};
return new PropertyDescriptor[] { ageDescriptor };
}
catch (IntrospectionException ex) {
throw new Error(ex.toString());
}
}
}
3.注册更多的定制PropertyEditor
将bean属性设置为字符串值时,Spring IoC容器最终将使用标准JavaBeans PropertyEditors将这些字符串转换为该属性的复杂类型。Spring预注册了一些自定义PropertyEditors(例如,将表示为字符串的类名转换为真实Class对象)。另外,Java标准的JavaBeans PropertyEditor查找机制允许一个PropertyEditor 类只需简单地命名,并放入与其提供支持的类相同的包中,以便自动找到。
如果需要注册其他自定义PropertyEditors,则有几种可用的机制。大多数手动方法,通常不方便或不推荐,只是简单地使用接口的registerCustomEditor()方法 ConfigurableBeanFactory,假设您有BeanFactory参考。另一种稍微更方便的机制是使用一个叫做特殊的bean工厂后处理器CustomEditorConfigurer。虽然bean factory后处理器可以与BeanFactory实现一起使用,但CustomEditorConfigurer它具有嵌套属性设置,因此强烈建议将它与该 ApplicationContextbean配合使用,并以与其他Bean类似的方式部署,并自动检测并应用。
请注意,所有的bean工厂和应用程序上下文自动使用一些内置的属性编辑器,通过使用称为a的东西BeanWrapper来处理属性转换。标准属性编辑器,BeanWrapper 寄存器在上一节中列出。此外, ApplicationContexts还可以覆盖或添加额外数量的编辑器,以适合特定应用程序上下文类型的方式处理资源查找。
标准JavaBeans PropertyEditor实例用于将以字符串表示的属性值转换为属性的实际复杂类型。 CustomEditorConfigurer,一个bean工厂后处理器,可以被用来方便地为其他PropertyEditor实例添加支持ApplicationContext。
考虑一个用户类ExoticType,另一个DependsOnExoticType需要 ExoticType设置为属性的类:
package example;
public class ExoticType {
private String name;
public ExoticType(String name) {
this.name = name;
}
}
public class DependsOnExoticType {
private ExoticType type;
public void setType(ExoticType type) {
this.type = type;
}
}
当事情设置正确时,我们希望能够将类型属性指定为字符串,后者PropertyEditor将在后台转换为实际的 ExoticType实例:
<bean id="sample" class="example.DependsOnExoticType">
<property name="type" value="aNameForExoticType"/>
</bean>
PropertyEditor实现看上去就像这样:
// converts string representation to ExoticType object
package example;
public class ExoticTypeEditor extends PropertyEditorSupport {
public void setAsText(String text) {
setValue(new ExoticType(text.toUpperCase()));
}
}
最后,我们使用CustomEditorConfigurer注册新PropertyEditor的 ApplicationContext,然后可以根据需要使用它:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="customEditors">
<map>
<entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
</map>
</property>
</bean>
4.使用PropertyEditorRegistrars
向Spring容器注册属性编辑器的另一种机制是创建和使用a PropertyEditorRegistrar。当您需要在几种不同情况下使用同一组属性编辑器时,此接口特别有用:编写相应的注册器并在每种情况下重新使用它。PropertyEditorRegistrars与称为PropertyEditorRegistry的接口一起工作,接口由Spring BeanWrapper(和DataBinder)实现。PropertyEditorRegistrars 在与这里CustomEditorConfigurer 介绍的(在这里介绍)结合使用时会特别方便,它暴露了一个名为setPropertyEditorRegistrars(..):PropertyEditorRegistrars以CustomEditorConfigurer这种方式添加到一个 属性,可以很容易地与DataBinderSpring MVC 共享Controllers。此外,它避免了在定制编辑器上同步的需要:aPropertyEditorRegistrar预计会PropertyEditor 为每个bean创建尝试创建新的实例。
使用一个PropertyEditorRegistrar例子可能是最好的例子。首先,你需要创建你自己的PropertyEditorRegistrar实现:
package com.foo.editors.spring;
public final class CustomPropertyEditorRegistrar implements PropertyEditorRegistrar {
public void registerCustomEditors(PropertyEditorRegistry registry) {
// it is expected that new PropertyEditor instances are created
registry.registerCustomEditor(ExoticType.class, new ExoticTypeEditor());
// you could register as many custom property editors as are required here...
}
}
另请参阅org.springframework.beans.support.ResourceEditorRegistrar示例 PropertyEditorRegistrar实现。注意如何在其实现 registerCustomEditors(..)方法中创建每个属性编辑器的新实例。
接下来,我们配置一个CustomEditorConfigurer并注入一个我们的实例 CustomPropertyEditorRegistrar:
<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
<property name="propertyEditorRegistrars">
<list>
<ref bean="customPropertyEditorRegistrar"/>
</list>
</property>
</bean>
<bean id="customPropertyEditorRegistrar"
class="com.foo.editors.spring.CustomPropertyEditorRegistrar"/>
最后,从本章的重点出发,对于那些使用Spring的MVC Web框架的人来说,PropertyEditorRegistrars与数据绑定Controllers(如SimpleFormController)结合使用可能非常方便。下面查找一个PropertyEditorRegistrar在实现initBinder(..)方法中使用a的示例:
public final class RegisterUserController extends SimpleFormController {
private final PropertyEditorRegistrar customPropertyEditorRegistrar;
public RegisterUserController(PropertyEditorRegistrar propertyEditorRegistrar) {
this.customPropertyEditorRegistrar = propertyEditorRegistrar;
}
protected void initBinder(HttpServletRequest request,
ServletRequestDataBinder binder) throws Exception {
this.customPropertyEditorRegistrar.registerCustomEditors(binder);
}
// other methods to do with registering a User
}
这种PropertyEditor注册方式可以导致简洁的代码(实现initBinder(..)只是一行!),并允许将通用PropertyEditor 注册码封装在一个类中,然后在Controllers需要的时候共享 。
五、Spring Type Conversion
Spring 3引入了一个core.convert提供通用类型转换系统的包。系统定义了一个SPI来实现类型转换逻辑,以及一个在运行时执行类型转换的API。在Spring容器中,该系统可以用作PropertyEditors的替代方法,将外部化的bean属性值字符串转换为所需的属性类型。公共API也可用于需要进行类型转换的应用程序中的任何位置。
1.转换器SPI(用于类型间的转换操作)
实现类型转换逻辑的SPI非常简单且强类型化:
package org.springframework.core.convert.converter;
public interface Converter<S, T> {
T convert(S source);
}
要创建您自己的转换器,只需实现上面的界面。参数S 化为您要转换的类型,以及转换T为的类型。这样的转换器也可以透明地应用,如果S需要将一个集合或一组需要转换为一个数组或集合T,前提是委托数组/收集转换器也已被注册(DefaultConversionService默认情况下)。
对于每次调用convert(S),源参数保证不为空。如果转换失败,您的Converter可能会抛出任何未经检查的异常; 具体而言, IllegalArgumentException应抛出一个报告无效的源值。注意确保您的Converter实现是线程安全的。
core.convert.support为方便起见,包中提供了几个转换器实现。这些包括从字符串到数字和其他常见类型的转换器。考虑StringToInteger一个典型Converter实现的例子:
package org.springframework.core.convert.support;
final class StringToInteger implements Converter<String, Integer> {
public Integer convert(String source) {
return Integer.valueOf(source);
}
}
2.ConverterFactory
当您需要为整个类层次结构集中转换逻辑时(例如,从String转换为java.lang.Enum对象时),请执行 ConverterFactory:
package org.springframework.core.convert.converter;
public interface ConverterFactory<S, R> {
<T extends R> Converter<S, T> getConverter(Class<T> targetType);
}
参数化S是您要转换的类型,R是定义可以转换为的类范围的基本类型。然后实现getConverter(Class <T>)
,其中T是R的子类。
以StringToEnumConverterFactory
为例:
package org.springframework.core.convert.support;
final class StringToEnumConverterFactory implements ConverterFactory<String, Enum> {
public <T extends Enum> Converter<String, T> getConverter(Class<T> targetType) {
return new StringToEnumConverter(targetType);
}
private final class StringToEnumConverter<T extends Enum> implements Converter<String, T> {
private Class<T> enumType;
public StringToEnumConverter(Class<T> enumType) {
this.enumType = enumType;
}
public T convert(String source) {
return (T) Enum.valueOf(this.enumType, source.trim());
}
}
}
3.GenericConverter
当您需要复杂的Converter实现时,请考虑GenericConverter接口。使用更灵活但类型较弱的签名,GenericConverter支持多种源和目标类型之间的转换。另外,GenericConverter提供了可用于实现转换逻辑的源和目标字段上下文。这种上下文允许类型转换由字段注释或字段签名上声明的通用信息来驱动。
package org.springframework.core.convert.converter;
public interface GenericConverter {
public Set<ConvertiblePair> getConvertibleTypes();
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
要实现GenericConverter,请getConvertibleTypes()返回支持的源→目标类型对。然后实现convert(Object,TypeDescriptor,TypeDescriptor)来实现你的转换逻辑。源类型描述符提供对持有正在转换的值的源字段的访问。目标TypeDescriptor提供对设置转换值的目标字段的访问权限。
GenericConverter的一个很好的例子是在Java Array和Collection之间转换的转换器。这样的ArrayToCollectionConverter内省了声明目标Collection类型的字段来解析Collection的元素类型。这允许源数组中的每个元素在目标字段上设置Collection之前转换为Collection元素类型。
由于GenericConverter是一个更复杂的SPI接口,因此只有在需要时才使用它。Favor Converter或ConverterFactory用于基本类型转换需求。
4.ConditionalGenericConverter
有时你只想要一个Converter执行,如果一个特定的条件成立。例如,Converter如果目标字段上存在特定的注释,则可能只想执行一次。或者,Converter如果static valueOf在目标类上定义了特定方法(如方法),则可能只想执行一次。 ConditionalGenericConverter
是允许您定义这种自定义匹配条件的接口GenericConverter
和 ConditionalConverter
接口的结合:
public interface ConditionalConverter {
boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}
public interface ConditionalGenericConverter
extends GenericConverter, ConditionalConverter {
}
一个很好的例子ConditionalGenericConverter是一个EntityConverter,它在一个持久化实体标识符和一个实体引用之间进行转换。如果目标实体类型声明静态查找方法,例如,这样的EntityConverter可能只匹配 findAccount(Long)。你会在执行中执行这种查找方法检查 matches(TypeDescriptor, TypeDescriptor)。
5.ConversionService API
ConversionService定义了一个用于在运行时执行类型转换逻辑的统一API。转换器通常在这个外观界面后执行:
package org.springframework.core.convert;
public interface ConversionService {
boolean canConvert(Class<?> sourceType, Class<?> targetType);
<T> T convert(Object source, Class<T> targetType);
boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}
大多数ConversionService
的实现类也实现ConverterRegistry
,它提供了一个用于注册转换器的SPI。在内部,ConversionService
实现委托其注册的转换器执行类型转换逻辑。 core.convert.support
包中提供了一个健壮的ConversionService
实现。GenericConversionService
是适用于大多数环境的通用实现。ConversionServiceFactory
为创建常见的ConversionService
配置提供了便利的工厂。
6.Configuring a ConversionService
ConversionService是一个无状态对象,设计用于在应用程序启动时实例化,然后在多个线程之间共享。在Spring应用程序中,您通常为每个Spring容器(或ApplicationContext)配置一个ConversionService实例。该转换服务将被Spring接收,然后在框架需要执行类型转换时使用。您也可以将此ConversionService注入到任何bean中并直接调用它。
如果没有使用Spring注册ConversionService,则使用原始的基于PropertyEditor的系统。
要使用Spring注册默认的ConversionService,请添加以下带有id的bean定义conversionService:
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean"/>
默认的ConversionService可以在字符串,数字,枚举,Map,地图和其他常用类型之间进行转换。要用自己的自定义转换器补充或覆盖默认转换器,请设置converters属性。属性值可以实现Converter,ConverterFactory或GenericConverter接口。
<bean id="conversionService"
class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<bean class="example.MyCustomConverter"/>
</set>
</property>
</bean>
在Spring MVC应用程序中使用ConversionService也很常见。请参阅 Spring MVC章节中的转换和格式化。
在某些情况下,您可能希望在转换过程中应用格式。有关使用的详细信息, 请参阅 FormatterRegistry SPIFormattingConversionServiceFactoryBean。
未完待续~~~