Validation,Data Binding, and Type Conversion

原文地址:https://docs.spring.io/spring/docs/current/spring-framework-reference/core.html#validation

3. Validation, Data Binding, and Type Conversion

将验证视为业务逻辑有其优缺点,Spring为验证(和数据绑定)提供了一种设计,但不排除这两种设计之一。具体来说,验证不应该绑定到Web层,并且应该易于本地化,并且应该可以插入任何可用的验证器。考虑到这些问题,Spring已经提出了一个Validator接口,它在应用程序的每一层都是基本的,并且是非常有用的

数据绑定对于动态地将用户输入绑定到应用程序的域模型(或用于处理用户输入的任何对象)非常有用。Spring提供了恰当的名为DataBinder来做到这一点。Validator和DataBinder组成了验证包,它主要用于但不限于MVC框架。

BeanWrapper是Spring框架中的一个基本概念,并在许多地方使用。但是,您可能不需要直接使用BeanWrapper。然而,由于这是参考文档,我们认为可能需要一些解释。我们在本章中解释了BeanWrapper,因为如果要使用它,您很可能在尝试将数据绑定到对象时使用它(BeanWrapper)

Spring的DataBinder和底层BeanWrapper都使用PropertyEditorSupport实现来解析和格式化属性值。PropertyEditor和PropertyEditorSupport接口是JavaBeans规范的一部分,本章也将对它们进行解释。Spring 3引入了一个core.Convert包,它提供了一个通用类型转换工具,以及一个用于格式化UI字段值的更高级别的“format”包。您可以使用这些包作为PropertyEditorSupport实现的更简单的替代方案。本章也将讨论这些问题。


JSR-303/JSR-349 Bean Validation

在4.0版本中,SpringFramework支持Bean验证1.0(JSR-303)和Bean验证1.1(JSR-349),以支持安装并使它们适应Spring的Validator接口。

应用程序可以选择全局启用Bean验证一次,如Spring验证中所述,并将其专门用于所有验证需求。应用程序还可以为每个DataBinder实例注册额外的SpringValidator实例,如配置DataBinder中所述。这对于在不使注解的情况下插入验证逻辑可能很有用。


3.1使用Spring的Validator接口进行验证

Spring提供了一个Validator接口,您可以使用它来验证对象。Validator接口通过使用Error对象来工作,以便在验证时,验证器可以向Error对象报告验证失败。

考虑下面的小数据对象示例:

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):验证给定对象,如果出现验证错误,则将这些错误与给定的错误对象注册。

实现Validator相当简单,尤其是当您知道SpringFramework还提供了ValidationUtils助手类时。下面的示例为Person实例实现了Validator:

public class PersonValidator implements Validator {
    /**
     * This Validator validates *only* 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");
        }
    }
}

 静态方法rejectIfEmpty(...)在ValidationUtils类中被用来注入name属性,假如它是null或者空的字符串。查看ValidationUtils javadoc,看看它除了前面显示的示例之外还提供了哪些功能。

虽然可以实现单个Validator类来验证富对象中的每个嵌套对象,但最好将每个嵌套的对象类的验证逻辑封装在自己的Validator实现中。“富”对象的一个简单示例是由两个字符串属性(第一个和第二个名称)和一个复杂的Address对象组成的客户。Address对象可以独立于Customer对象使用,因此已经实现了一个不同的AddressValidator。如果您希望CustomerValidator重用AddressValidator类中包含的逻辑,而不需要进行复制和粘贴,则可以在CustomerValidator中依赖-注入或实例化AddressValidator,如下面的示例所示:

public class CustomerValidator implements Validator {

    private final Validator addressValidator;

    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();
        }
    }
}

验证错误报告给传递给验证器的Error对象。在Spring  Web  MVC中,您可以使用<spring:bind/>标记来检查错误消息,但也可以自己检查错误对象。有关它提供的方法的更多信息可以在javadoc中找到。

3.2解析错误代码为错误消息(Resolving Codes to Error Messages)

我们讨论了数据绑定和验证。本节介绍了输出与验证错误相对应的消息。在上一节所示的示例中,我们注入了name和age字段。如果我们想通过MessageSource输出错误消息,我们可以使用我们在注入字段时提供的错误代码(在本例中为‘name’和‘age’)。当您从错误接口直接或间接调用(例如,通过使用ValidationUtils类)注入值或其他注入方法时,底层实现不仅会注册传入的代码,而且还会注册许多额外的错误代码。MessageCodesResolver确定错误接口寄存器的错误代码。默认情况下,使用DefaultMessageCodesResolver,它不仅将消息注册到您给出的代码中,而且还会注册包含传递给拒绝方法的字段名的消息。因此,如果通过使用rejectValue("age", "too.darn.old"),除了too.darn.old代码,Spring也会注册too.darn.old.age和oo.darn.old.age.int(第一个包括字段名,第二个包括字段的类型。)。这样做是为了方便开发人员在针对错误消息时提供帮助。

有关MessageCodesResolver和默认策略的更多信息,请各自常见MessageCodesResolver和DefaultMessageCodesResolver。

3.3Bean操纵和BeanWrapper

org.springframework.beans包遵守了JavaBeans的标准。JavaBean是一个具有默认非参数构造函数的类,它遵循一个命名约定,其中一个名为bingoMadness的属性将有一个setter方法setBingoMadness(.)和getter方法getBingoMadness()。有关JavaBeans和规范的更多信息,请参见javabeans。

bean包中一个非常重要的类是BeanWrapper接口及其相应的实现(BeanWrapperImpl)。正如javadoc中引用的那样,BeanWrapper提供了设置和获取属性值(单独或批量)、获取属性描述符和查询属性以确定它们是否可读或可写的功能。此外,BeanWrapper提供了对嵌套属性的支持,使子属性上的属性设置到无限深度。BeanWrapper还支持添加标准JavaBeans PropertyChangeListenerVetoableChangeListener的能力,而不需要在目标类中支持代码。最后但并非最不重要的一点是,BeanWrapper为设置索引属性提供了支持。BeanWrapper通常不被应用程序代码直接使用,而是由DataBinderBeanFactory使用。

BeanWrapper的工作方式部分是由它的名称指示的:它包装一个bean来对该bean执行操作,例如设置和检索属性。

3.3.1设置和获取基本和嵌套的属性

设置和获取属性是通过使用setPropertyValue、setPropertyValues、getPropertyValue和getPropertyValue方法完成的,这些方法附带了几个重载变体。Spring javadoc更详细地描述了它们。JavaBeans规范具有指示对象属性的约定。下表显示了这些公约的一些例子:

属性示例
Expression解释
name

指示对应于getName()或isName()和setName(.)的属性方法。

account.name

指示属性帐户的嵌套属性名称,该属性帐户对应于(例如)

getAccount().setName()或getAccount().getName()方法。

acount[2]指示第三个被索引的元素。索引属性能够是type、list或者其他自然有序的集合。
account[COMPANYNAME]

指示由Account Map属性的COMPANYNAME键索引的映射项的值。

(如果您不打算直接使用BeanWrapper,则下一节对您来说并不重要。如果您只使用DataBinder和BeanFactory及其默认实现,则应该跳过有关PropertyEditors的部分。)

以下两个示例类使用BeanWrapper获取和设置属性:

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");

3.3.2内置PropertyEditor实现

Spring使用PropertyEditor的概念来实现对象和字符串之间的转换。可以方便地以不同于对象本身的方式表示属性。例如,日期可以人类可读的方式表示(字符串:‘2007-14-09’),而我们仍然可以将人类可读的表单转换回原始日期(或者更好地将以人类可读的形式输入的任何日期转换回Date对象)。这种行为可以通过注册java.beans.PropertyEditor类型的自定义编辑器来实现。在BeanWrapper上注册自定义编辑器,或者在特定的IoC容器中注册(如上一章所述),它将了解如何将属性转换为所需的类型。有关PropertyEditor的更多信息,请参见Oracle中java.Beans包的javadoc。

在Spring中使用属性编辑的几个示例:

  • 通过使用PropertyEditor实现来设置bean的属性。当您使用String作为在XML文件中声明的某个bean的属性值时,Spring(如果相应属性的setter有Class参数)使用ClassEditor尝试将参数解析为Class对象。
  • 在Spring的MVC框架中解析HTTP请求参数是通过使用所有类型的PropertyEditor实现完成的,您可以在CommandController的所有子类中手动绑定这些实现。

Spring有许多内置的PropertyEditor实现来简化生活。它们都位于org.Spring Frawork.beans.propertyeditors 包中。在默认情况下,大多数(但不是所有,如下表所示)都是由BeanWrapperImpl注册的。在以某种方式配置属性编辑器的情况下,仍然可以注册自己的变体以覆盖默认的变体。下表描述了Spring提供的各种PropertyEditor实现:

内置的PropertyEditor实现
Class解释
ByteArrayPropertyEditor

字节数组编辑器。将字符串转换为相应的字节表示形式。由BeanWrapperImpl默认注册。

ClassEditor

解析表示类到实际类的字符串,反之亦然。如果找不到类,就会抛出IllegalArgumentException。默认情况下,由BeanWrapperImpl注册。

CustomBooleanEditor

布尔属性的可自定义属性编辑器。默认情况下,由BeanWrapperImpl注册,但可以通过将其自定义实例注册为自定义编辑器来重写。

CustomCollectionEditor

集合的属性编辑器,将任何源集合转换为给定的目标集合类型。

CustomDateEditor

支持自定义DateFormat的java.util.Date的可自定义属性编辑器。默认情况下未注册。必须根据需要以适当的格式注册用户。

CustomNumberEditor

任何数字子类(如Integer、Long、Float或Double)的可自定义属性编辑器。默认情况下,由BeanWrapperImpl注册,但可以通过将其自定义实例注册为自定义编辑器来重写。

FileEditor

将字符串解析为java.io.File对象。默认情况下,由BeanWrapperImpl注册。

InputStreamEditor

单向属性编辑器,它可以接受一个字符串并(通过中间的ResourceEditor和Resource)生成InputStream,这样InputStream属性可以直接设置为字符串。注意,默认用法不会为您关闭InputStream。默认情况下,由BeanWrapperImpl注册。

LocalEditor

可以将字符串解析为locale对象,反之亦然(字符串格式为[Country][Variable],与locale的toString()方法相同)。默认情况下,由BeanWrapperImpl注册。

还有PropertiesEditor、StringTRimmerEditor以及URLEditor。

Spring使用java.beans.PropertyEditorManager设置可能需要的属性编辑器的搜索路径。搜索路径还包括sun.bean.Editor,它包括字体、颜色和大多数原始类型的PropertyEditor实现。还请注意,如果标准JavaBeans基础设施自动发现PropertyEditor类(而不必显式注册它们),如果它们与它们处理的类在同一个包中,并且与该类具有相同的名称,并附加了Editor。例如,可以具有以下类和包结构,这足以使SomethingEditor类被识别并用作某些类型属性的PropertyEditor。

com
  chank
    pop
      Something
      SomethingEditor // the PropertyEditor for the Something class

请注意,您还可以在这里使用标准的BeanInfo JavaBeans机制(这里在某种程度上对此进行了描述)。下面的示例使用BeanInfo机制显式地使用关联类的属性注册一个或多个PropertyEditor实例:

com
  chank
    pop
      Something
      SomethingBeanInfo // the BeanInfo for the Something class

引用的SomethingBeanInfo类的以下Java源代码将CustomNumberEditor与某某类的AGE属性关联起来:

public class SomethingBeanInfo extends SimpleBeanInfo {

    public PropertyDescriptor[] getPropertyDescriptors() {
        try {
            final PropertyEditor numberPE = new CustomNumberEditor(Integer.class, true);
            PropertyDescriptor ageDescriptor = new PropertyDescriptor("age", Something.class) {
                public PropertyEditor createPropertyEditor(Object bean) {
                    return numberPE;
                };
            };
            return new PropertyDescriptor[] { ageDescriptor };
        }
        catch (IntrospectionException ex) {
            throw new Error(ex.toString());
        }
    }
}

注册其他自定义PropertyEditor实现

当将bean属性设置为字符串值时,SpringIoC容器最终使用标准JavaBeansPropertyEditor实现将这些字符串转换为该属性的复杂类型。Spring预注册了许多自定义PropertyEditor实现(例如,将表示为字符串的类名转换为Class对象)。此外,Java的标准JavaBeans PropertyEditor查找机制允许适当地命名类的PropertyEditor,并将其放在与其提供支持的类相同的包中,以便能够自动找到它。

如果需要注册其他自定义属性编辑器,可以使用几种机制。最手动的方法(通常不方便或不建议使用)是使用ConfigurableBeanFactory接口的RegisterCustomEditor()方法,假设您有BeanFactory引用。另一种(稍微方便一些)机制是使用名为CustomEditorConfigrer的特殊bean工厂后处理器。尽管您可以在BeanFactory实现中使用bean工厂后处理程序,但CustomEditorConfiguration有一个嵌套的属性设置,因此我们强烈建议您将它与ApplicationContext一起使用,在那里您可以类似于任何其他bean的方式部署它,并且可以自动检测和应用它。

请注意,所有bean工厂和应用程序上下文通过使用BeanWrapper来处理属性转换,自动使用许多内置的属性编辑器。BeanWrapper寄存器的标准属性编辑器在上一节中列出。此外,ApplicationContext还覆盖或添加其他编辑器,以便以适合特定应用程序上下文类型的方式处理资源查找。

标准JavaBeans PropertyEditor实例用于将表示为字符串的属性值转换为该属性的实际复杂类型。您可以使用一个bean工厂后处理器CustomEditorConfigrer来方便地向ApplicationContext添加对附加PropertyEditor实例的支持。

考虑以下示例,它定义了一个名为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;
    }
}

当事情正确设置时,我们希望能够将type属性赋值为字符串,PropertyEditor将该字符串转换为实际的ExoticType实例。下面的bean定义显示了如何设置这种关系:

<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()));
    }
}

最后,下面的示例演示如何使用CustomEditorConfigrerApplicationContext注册新的PropertyEditor,然后该编辑器将能够根据需要使用它:

<bean class="org.springframework.beans.factory.config.CustomEditorConfigurer">
    <property name="customEditors">
        <map>
            <entry key="example.ExoticType" value="example.ExoticTypeEditor"/>
        </map>
    </property>
</bean>

使用PropertyEditorRegistry

向Spring容器注册属性编辑器的另一种机制是创建和使用PropertyEditorRegistry。当您需要在几种不同的情况下使用同一组属性编辑器时,此接口特别有用。您可以编写相应的注册员并在每种情况下重用它。PropertyEditorRegistry实例与一个名为PropertyEditorRegistry的接口一起工作,这个接口由SpringBeanWrapper(和DataBinder)实现。当与CustomEditorConfigrer(此处描述)一起使用时,PropertyEditorRegistrars实例特别方便,它公开了一个名为setPropertyEditorRegistrars(.)的属性。以这种方式添加到CustomEditorConfigrer中的PropertyEditorRegistry实例可以很容易地与DataBinder和SpringMVC控制器共享。此外,它避免了自定义编辑器上的同步需求:为了每次尝试创建bean,预期PropertyEditorRegistry会创建新的PropertyEditor实例。

下面的示例演示如何创建您自己的PropertyEditorRegistry实现:

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中的PropertyEditorRegistryImplementation示例。请注意它是如何实现RegistrerCustomEditor(.)的。方法创建每个属性编辑器的新实例。

下一个示例展示了如何配置CustomEditorConfigrer,并将CustomPropertyEditorRegistry的一个实例注入其中:

<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与数据绑定控制器(例如SimpleFormController)是非常方便的。以下示例在实现initBinder(.)时使用PropertyEditorRegistry方法:

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注册代码封装在类中,然后根据需要在多个控制器之间共享。

3.4Spring类型转换

Spring 3引入了一个核心转换包,它提供了一个通用的类型转换系统。系统定义了用于实现类型转换逻辑的SPI和在运行时执行类型转换的API。在Spring容器中,可以使用此系统替代PropertyEditor实现,将外部化的bean属性值字符串转换为所需的属性类型。您还可以在需要进行类型转换的应用程序中的任何地方使用公共API。

3.4.1Convert SPI

实现类型转换逻辑的SPI是简单且强类型的,如下接口定义所示:

package org.springframework.core.convert.converter;
public interface Converter<S, T> {
    T convert(S source);
}

要创建您自己的转换器,请实现转换器接口,并将S作为您要转换的类型进行参数化,将T作为您要转换的类型进行参数化。如果需要将集合或S数组转换为T的数组或集合,则还可以透明地应用此类转换器,条件是委派数组或集合转换器也已注册(DefaultConversionService默认情况下是这样做的)。

对于每个要转换(S)的调用,源参数都保证不为NULL。如果转换失败,转换器可能抛出任何未经检查的异常。具体来说,它应该抛出一个IllegalArgumentException来报告无效的源值。注意确保转换器实现是线程安全的.

为了方便起见,在core.Convert.Support包中提供了几个转换器实现。这包括从字符串到数字和其他常见类型的转换器。下面的清单显示了StringToInteger类,它是一个典型的转换器实现:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter<String, Integer> {

    public Integer convert(String source) {
        return Integer.valueOf(source);
    }
}

3.4.2使用ConverterFactory

当您需要为整个类层次结构集中转换逻辑时(例如,当从String转换为Enum对象时),您可以实现转换器工厂,如下面的示例所示:

package org.springframework.core.convert.converter;

public interface ConverterFactory<S, R> {

    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
}

参数化S是您要转换的类型,R是定义可以转换到的类范围的基本类型。然后实现get转换器(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.4.3使用GenericConverter

当您需要复杂的转换器实现时,请考虑使用Generic转换器接口。使用比转换器更灵活但不太强类型的签名,Generic转换器支持在多个源类型和目标类型之间进行转换。此外,Generic转换器提供了在实现转换逻辑时可以使用的源和目标字段上下文。这样的上下文允许由字段注释或字段签名上声明的泛型信息驱动类型转换。下面的清单显示Generic转换器的接口定义:

package org.springframework.core.convert.converter;

public interface GenericConverter {

    public Set<ConvertiblePair> getConvertibleTypes();

    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
}

要实现GenericConverter,请让getConvertibleTypes()返回受支持的源→目标类型对。然后实现转换(Object,TypeDescriptor,TypeDescriptor)以包含您的转换逻辑。源TypeDescriptor提供对保存正在转换的值的源字段的访问。目标TypeDescriptor提供对要设置转换值的目标字段的访问。

GenericConverter 的一个很好的例子是在Java数组和集合之间进行转换的转换器。这样的ArrayToCollectionConverter 转换器回顾了声明目标集合类型以解析集合元素类型的字段。这允许在目标字段上设置集合之前,将源数组中的每个元素转换为集合元素类型。

使用ConditionalGenericConverter

有时,您希望只有在特定条件为真时才能运行转换器。例如,您可能只希望在目标字段存在特定注释时才运行转换器,或者仅在目标类上定义了特定方法(例如静态值方法)时才运行转换器。ConditionalGeneric转换器是Generic转换器和Conditional转换器接口的联合,它允许您定义这样的自定义匹配条件:

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

ConditionalGenericConverter 的一个很好的例子是在持久实体标识符和实体引用之间进行转换的EntityConverter。只有当目标实体类型声明静态finder方法(例如findAccount(Long)时,这样的Entity转换器才可能匹配。您可以在匹配的实现中执行这样的finder方法检查(TypeDescriptor,TypeDescriptor)。

ConversionService  API

ConversionService  定义了一个统一的API,用于在运行时执行类型转换逻辑。转换器通常在以下Facade接口后面执行:

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  实现还实现了转换器注册表,它提供了一个用于注册转换器的SPI。在内部,转换服务实现将委托给其注册转换器以执行类型转换逻辑。

在core.Convert.Support包中提供了一个健壮的转换服务实现。泛型转换服务是一个通用的实现,适合在大多数环境中使用。转换服务工厂为创建通用的转换服务配置提供了一个方便的工厂。

3.4.5配置一个ConversionService

ConversionService是设计为在应用程序启动时实例化,然后在多个线程之间共享的无状态对象。在Spring应用程序中,通常为每个Spring容器(或ApplicationContext)配置一个转换服务实例。当框架需要执行类型转换时,Spring就会选择转换服务并使用它。您还可以将此转换服务注入到任何bean中,并直接调用它。

若要向Spring注册默认ConversionService,请使用id=conversionService添加以下bean定义:

<bean id="conversionService"
    class="org.springframework.context.support.ConversionServiceFactoryBean"/>

默认ConversionService可以在字符串、数字、枚举、集合、映射和其他公共类型之间进行转换。若要用您自己的自定义转换器来补充或重写默认转换器,请设置“converters”属性。属性值可以实现任何ConverterConverterFactory, or GenericConverter接口。

<bean id="conversionService"
        class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <set>
            <bean class="example.MyCustomConverter"/>
        </set>
    </property>
</bean>

在SpringMVC应用程序中使用转换服务也很常见。请参阅SpringMVC章节中的转换和格式化。

3.4.6编程方式使用ConversionService

不做翻译。

3.5Spring Filed Formatting

正如上一节所讨论的,core.convert是一个通用的类型转换系统。它提供了统一的转换服务API以及强类型转换器SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring容器使用此系统绑定bean属性值。此外,Spring表达式语言(Spel)和DataBinder都使用此系统绑定字段值。例如,当Spel需要强制一个短到一个长来完成expression.setValue(Object bean, Object value)尝试时,core.Convert系统执行强制。

现在考虑典型客户端环境(如Web或桌面应用程序)的类型转换需求。在这种环境中,您通常从String转换为支持客户端回发进程,并将其转换回String以支持视图呈现过程。此外,您通常需要本地化字符串值。更通用的core.convert Converter SPI不直接处理此类格式要求。为了直接解决这些问题,Spring 3引入了一个方便的格式化程序SPI,它为客户端环境提供了一个简单而健壮的替代PropertyEditor实现的方法。

通常,当需要实现通用类型转换逻辑时,可以使用转换器SPI,例如,用于java.util.Date和Long之间的转换。当您在客户端环境(如Web应用程序)中工作并需要解析和打印本地化字段值时,可以使用格式化程序SPI。转换服务为两个Spis提供了统一的类型转换API。

3.5.1Formatter SPI

用于实现字段格式化逻辑的Formatter SPI是简单且强类型的。下面的清单显示了Formatter接口定义:

package org.springframework.format;

public interface Formatter<T> extends Printer<T>, Parser<T> {
}

Formatter 扩展自Printer和Parser接口.下面的清单显示了这两个接口的定义:

public interface Printer<T> {

    String print(T fieldValue, Locale locale);
}
import java.text.ParseException;

public interface Parser<T> {

    T parse(String clientValue, Locale locale) throws ParseException;
}

要创建您自己的Formatter ,请实现前面显示的Formatter 接口。参数化T是您希望格式化的对象的类型,例如java.util.Date。实现print()操作,打印T的一个实例,以便在客户端区域设置中显示。实现parse()操作,从客户机区域设置返回的格式化表示解析T的一个实例。如果尝试解析失败,Formatter 应该抛出ParseExceptionIllegalArgumentException。注意确保Formatter 实现是线程安全的。

为了方便起见,format子包提供了几个格式化程序实现。number包提供了NumberStyleFormatter、CurrencyStyleFormatter和PercentStyleFormatter ,以格式化使用java.text.NumberFormat的Number对象。datetime包提供了一个DateFormatter 来用java.text.DateFormat格式化java.util.Date对象。joda包提供了基于Joda-Time库的全面的日期时间格式化支持。

以下是一个示例DateFormatter 实现:

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter<Date> {

    private String pattern;

    public DateFormatter(String pattern) {
        this.pattern = pattern;
    }

    public String print(Date date, Locale locale) {
        if (date == null) {
            return "";
        }
        return getDateFormat(locale).format(date);
    }

    public Date parse(String formatted, Locale locale) throws ParseException {
        if (formatted.length() == 0) {
            return null;
        }
        return getDateFormat(locale).parse(formatted);
    }

    protected DateFormat getDateFormat(Locale locale) {
        DateFormat dateFormat = new SimpleDateFormat(this.pattern, locale);
        dateFormat.setLenient(false);
        return dateFormat;
    }
}

Spring团队欢迎社区驱动的格式化人员贡献参见jira.Spring.io

3.5.2注解驱动Formatting

字段格式可以通过字段类型或注释进行配置。若要将注释绑定到格式化程序,请实现AnnotationFormatterFactory。下面的清单显示了AnnotationFormatterFactory接口的定义:

package org.springframework.format;

public interface AnnotationFormatterFactory<A extends Annotation> {

    Set<Class<?>> getFieldTypes();

    Printer<?> getPrinter(A annotation, Class<?> fieldType);

    Parser<?> getParser(A annotation, Class<?> fieldType);
}

若要创建一个实现:。参数化A为要与格式化逻辑相关联的字段注释类型,例如org.springframework.format.annotation.DateTimeFormat.。让getFieldTypes()返回可以使用注释的字段类型。。让getPrter()返回一个打印机来打印带注释的字段的值。。让getParser()返回一个Parser来解析带注释的字段的clientValue。

下面的示例AnnotationFormatterFactory实现将@NumberFormat注释绑定到格式化程序,以便指定数字样式或模式:

public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory<NumberFormat> {

    public Set<Class<?>> getFieldTypes() {
        return new HashSet<Class<?>>(asList(new Class<?>[] {
            Short.class, Integer.class, Long.class, Float.class,
            Double.class, BigDecimal.class, BigInteger.class }));
    }

    public Printer<Number> getPrinter(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser<Number> getParser(NumberFormat annotation, Class<?> fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    private Formatter<Number> configureFormatterFrom(NumberFormat annotation, Class<?> fieldType) {
        if (!annotation.pattern().isEmpty()) {
            return new NumberStyleFormatter(annotation.pattern());
        } else {
            Style style = annotation.style();
            if (style == Style.PERCENT) {
                return new PercentStyleFormatter();
            } else if (style == Style.CURRENCY) {
                return new CurrencyStyleFormatter();
            } else {
                return new NumberStyleFormatter();
            }
        }
    }
}

要触发格式设置,可以使用@NumberFormat对字段进行注释,如下面的示例所示:

public class MyModel {

    @NumberFormat(style=Style.CURRENCY)
    private BigDecimal decimal;
}

Format Annotation API

一个可移植的格式注释API存在于org.SpringFrawork.format.批注包中。您可以使用@NumberFormat来格式化数字字段,如Double和Long,以及@DateTimeFormat来格式化java.util.Date、java.util.Calendar、Long(用于毫秒时间戳)以及JSR-310 java.time和Joda-time值类型。

下面的示例使用@DateTimeFormat将java.util.Date格式化为ISO日期(yyyy-mm-dd):

public class MyModel {

    @DateTimeFormat(iso=ISO.DATE)
    private Date date;
}

3.5.3FormatterRegistry  SPI

FormatterRegistry是用于注册格式化程序和转换器的SPI。FormattingConversionService是FormatterRegistry的一个实现,适用于大多数环境。您可以通过编程或声明方式将此变体配置为Springbean,例如通过使用FormattingConversionServiceFactoryBean。因为此实现还实现了转换服务,所以可以直接将其配置为与Spring的DataBinder和Spring表达式语言(Spel)一起使用。

下面的清单显示了FormatterRegistry  SPI:

package org.springframework.format;

public interface FormatterRegistry extends ConverterRegistry {

    void addFormatterForFieldType(Class<?> fieldType, Printer<?> printer, Parser<?> parser);

    void addFormatterForFieldType(Class<?> fieldType, Formatter<?> formatter);

    void addFormatterForFieldType(Formatter<?> formatter);

    void addFormatterForAnnotation(AnnotationFormatterFactory<?, ?> factory);
}

如上面的清单所示,您可以通过字段类型或注释注册格式化程序。

FormatterRegistry SPI允许您集中配置格式规则,而不是在控制器之间重复这种配置。例如,您可能希望强制所有日期字段都以某种方式格式化,或者使用特定注释的字段以某种方式进行格式化。使用共享FormatterRegistry,只需定义一次这些规则,并且在需要格式化时应用这些规则。

3.5.4FormatterRegistrar  SPI

FormatterRegistry是通过FormatterRegistry注册格式化程序和转换器的SPI。下面的清单显示了它的接口定义:

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}

当为给定格式类别(例如日期格式)注册多个相关转换器和格式化程序时,FormatterRegistry非常有用。在声明性注册不足的情况下,它也可能很有用-例如,当格式化程序需要在与其自己的<T>不同的特定字段类型下索引时,或者在注册打印机/解析器对时。下一节提供有关转换器和格式化程序注册的更多信息。

3.5.5配置Spring MVC中的Formatting

请参阅SpringMVC章节中的转换和格式化

3.6配置全局的Date和Tine格式化

默认情况下,未使用@DateTimeFormat注释的日期和时间字段将通过使用DateFormate.HORT样式从字符串中转换。如果您愿意,可以通过定义自己的全局格式来更改此格式。

为此,您需要确保Spring不注册默认格式化程序。相反,您应该手动注册所有格式化程序。使用org.springframework.format.datetime.joda.JodaTimeFormatterRegistrar或org.springframework.format.datetime.DateFormatterRegistrar类,取决于您是否使用Joda-Time库。

例如,以下Java配置注册了全局yyyyMMdd格式(本例不依赖于Joda-time库):

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // Use the DefaultFormattingConversionService but do not register defaults
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // Ensure @NumberFormat is still supported
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // Register date conversion with a specific global format
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}

如果您更喜欢基于XML的配置,则可以使用FormattingConversionServiceFactoryBean。下面的示例演示如何这样做(这次使用Joda Time):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd>

    <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="registerDefaultFormatters" value="false" />
        <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>
    </bean>
</beans>

3.7Spring Validation

Spring 3引入了对其验证支持的几个增强。首先,完全支持JSR-303 Bean验证API。其次,当以编程方式使用时,Spring的DataBinder可以验证对象并绑定到它们。第三,SpringMVC支持以声明方式验证@Controller输入。

3.7.1JSR-303 Bean验证API概述

JSR-303为Java平台标准化验证约束声明和元数据。通过使用此API,您可以用声明性验证约束对域模型属性进行注释,并且运行时强制它们。您可以使用许多内置约束。还可以定义自己的自定义约束。

考虑下面的示例,它显示了一个具有两个属性的简单PersonForm模型:

public class PersonForm {
    private String name;
    private int age;
}

JSR-303允许您针对这些属性定义声明性验证约束,如下例所示:

public class PersonForm {

    @NotNull
    @Size(max=64)
    private String name;

    @Min(0)
    private int age;
}

当JSR-303 Validator验证该类的实例时,这些约束将被强制执行。

有关JSR-303和JSR-349的一般信息,请参阅Bean验证网站。有关默认参考实现的特定功能的信息,请参阅Hibernate Validator文档。要了解如何将bean验证提供程序设置为Springbean,请继续阅读。

3.7.2配置 Validation Provider

Spring提供了对Bean验证API的完全支持。这包括对引导JSR-303或JSR-349 Bean验证提供者作为Springbean的方便支持。这允许您在应用程序需要验证的地方注入javax.validation.ValidatorFactoryjavax.validation.Validator

您可以使用LocalValidatorFactoryBean将默认Validator配置为Springbean,如下所示:

<bean id="validator"
    class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean"/>

前面示例中的基本配置通过使用其默认的引导机制触发bean验证来初始化。JSR-303或JSR-349提供程序(如Hibernate Validator)将出现在类路径中,并被自动检测。

注入一个Validator

LocalValidatorFactoryBean实现了javax.validation.ValidatorFactoryjavax.validation.Validator,以及Spring的org.Spring Frawork.valida.Validator。您可以将对这些接口的引用注入需要调用验证逻辑的bean中。

如果您希望直接使用Bean验证API,则可以插入对javax.validation.Validator的引用,如下面的示例所示:

import javax.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;

你也可以注入org.springframework.validation.Validator,假如你的Bean需要Spring Validation 的API,例如:

import org.springframework.validation.Validator;

@Service
public class MyService {

    @Autowired
    private Validator validator;
}

配置自定义约束

每个bean验证约束由两个部分组成:*A@Constraint注释,它声明约束及其可配置属性。*实现约束行为的javax.validation.ConstraintValidator接口的实现。

要将声明与实现关联起来,每个@Constraint注释都引用相应的ConstraintValidator实现类。在运行时,当域模型中遇到约束注释时,ConstraintValidatorFactory实例化引用的实现。

默认情况下,LocalValidatorFactoryBean配置Spring   ConstraintValidatorFactory,该工厂使用Spring创建ConstraintValidator实例。这使您的自定义ConstraintValidators可以像其他Springbean一样受益于依赖注入。

下面的示例显示了自定义@Constraint声明和使用Spring进行依赖注入的关联ConstraintValidator实现:

@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy=MyConstraintValidator.class)
public @interface MyConstraint {
}
import javax.validation.ConstraintValidator;

public class MyConstraintValidator implements ConstraintValidator {

    @Autowired;
    private Foo aDependency;

    ...
}

正如前面的示例所示,ConstraintValidator实现可以像任何其他Springbean一样具有它的依赖关系@AUTOWORD。

Spring 驱动方法验证

您可以通过MethodValidationPostProcessor bean定义将Bean验证1.1支持的方法验证特性(以及Hibernate Validator 4.3作为自定义扩展)集成到Spring上下文中,如下所示:

<bean class="org.springframework.validation.beanvalidation.MethodValidationPostProcessor"/>

为了符合Spring驱动的方法验证的条件,所有目标类都需要使用Spring的@Valided注释进行注释。(还可以声明要使用的验证组。)有关Hibernate Validator和Bean验证1.1提供程序的设置细节,请参阅MethodValidationPostProcessor javadoc。

额外的配置选项

大多数情况下,默认的LocalValidatorFactoryBean配置就足够了。对于各种Bean验证结构,有许多配置选项,从消息内插到遍历解析。有关这些选项的更多信息,请参见LocalValidatorFactoryBean javadoc。

3.7.3配置DataBinder

从Spring 3开始,您可以使用Validator配置DataBinder实例。一旦配置完毕,您就可以通过调用binder.validate()来调用Validator。任何验证错误都会自动添加到绑定器的BindingResult中。

下面的示例演示如何以编程方式使用DataBinder在绑定到目标对象后调用验证逻辑:

Foo target = new Foo();
DataBinder binder = new DataBinder(target);
binder.setValidator(new FooValidator());

// bind to the target object
binder.bind(propertyValues);

// validate the target object
binder.validate();

// get BindingResult that includes any validation errors
BindingResult results = binder.getBindingResult();

 以下代码是我自测代码,可作参考。

  @Autowired
    private org.springframework.validation.Validator validator;

    @GetMapping("testvalidator")
    @ResponseBody
    public String test() {
        // 进行独立的数据验证
        User user = new User();
        user.setRealName("123");
        user.setPassWord("1223");
        user.setUserName("123");
        //    Errors errors = new FieldError();
        //    localValidatorFactoryBean.validate(user,null);
        DataBinder binder = new DataBinder(user);
        binder.setValidator(validator);
        binder.validate();
        BindingResult bindingResult = binder.getBindingResult();
        if (bindingResult.hasErrors()) {
            System.out.print("err" + bindingResult);
        } else {
            System.out.println("okkkkkkkkkkkkkkkkkkkkk");

        }
        return user.toString();
    }

还可以通过 dataBinder.addValidators and dataBinder.replaceValidators.方法配置具有多个Validator实例的DataBinder

当将全局配置的bean验证与在DataBinder实例上本地配置的SpringValidator相结合时,这是非常有用的。参见[validation-mvc-configuring]。

3.7.4Spring MVC3 验证

请参阅SpringMVC章节中的验证。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值