spring-core-3 验证,数据绑定和类型转换

3.1 介绍

从spring 4.0开始, 支持Bean验证1.0和1.1, 即(JSR-303和JSR-349), 并将它们适配到spring的Validator接口.
spring可以全局开启Bean验证,并在需要的地方使用它.
spring也可以为每个DataBinder注册额外的Validator实例, 这对于需要插入验证逻辑而又不需要使用注解是很有用的.
spring提供了一个基础的Validator接口, 可以在每个应用层次使用它.
将用户的动态输入绑定到应用的领域对象是有用的, Spring提供了DataBinder来实现这个功能, 它与Validator接口被定义在validation包中.
BeanWrapper在spring框架中是一个基本概念, 且被大量使用. Spring的DataBinder和低层次的BeanWrapper都是使用PropertyEditor来进行属性转换及格式化的.Spring 3 添加了core.convert包, 提供了类型转换功能和一个格式化UI字段的format包.这些新的包都可以作为PropertyEditor的简单替代.

3.2 使用Spring的Validator接口

可以使用Validator接口来验证对象,在验证对象时,它将与一个Errors对象一起工作,以便当验证出现错误时将错误信息写入Errors对象.
示例:

public class Person {

    private String name;
    private int age;

    // the usual getters and setters...
}

现在我们通过实现spring的Validator接口来为Person类提供验证行为.Validator接口提供了两个方法:

  • supports(Class): 这个验证器是否支持对某个类的验证.
  • validate(Object, Errors): 验证给定的对象,如果验证错误,将错误信息注册到errors对象中.
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");
        }
    }
}

spring提供了ValidationUtils工具类,更多请参数其API文档.

虽然可以提供一个Validator实现类来验证富对象中的嵌套对象, 但是更好的做法是将验证逻辑都封装在其自己的验证器中.
假如有一个Customer类如下:

public class Customer {
  private String firstName;

  private String lastName;

  private Address address;
  
  // setter and getter
}

假设Address类已被定义了一个AddressValidator.此时你想要在Customer类中重用这个验证器而不是将其复制粘贴过来,这时你只要注入这个验证器到CustomerValidator中.示例如下:

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

    /**
     * 这个Validator验证Customer实例及它的任何子类
     */
    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, "lastname", "field.required");
        Customer customer = (Customer) target;
        try {
            errors.pushNestedPath("address");
            ValidationUtils.invokeValidator(this.addressValidator, customer.getAddress(), errors);
        } finally {
            errors.popNestedPath();
        }
    }
}

在spring mvc中, 可以使用`标签来检查验证的错误信息.

3.3 解析错误消息

PersonValidator示例中, 我们验证了nameage字段,如果我们使用MessageSource来输出错误信息, 这将会用到在验证错误时给定的错误码(此处是nameage), 当调用rejectValue方法时,底层实现不仅会注册我们提供的错误信息,还会添加一些附加信息, 注册哪些错误信息是由所使用的MessageCodesResolver决定的, 默认情况下会使用DefaultMessageCodesResolver, 它不仅会提供发生错误的字段,还会提供其类型,以方便开发者快速定位问题所在.更多请查看MessageCodesResolverDefaultMessageCodesResolver文档.

3.4 Bean操作和BeanWrapper

spring的beans包遵循JavaBeans的标准定义, 即拥有一个默认的无参构造器,以及setter, getter方法.
在beans包中最重要的一个类是BeanWrapper接口和其一致的实现(如BeanWrapperImpl).BeanWrapper提供了如下功能: 单个或批量的set或get属性值, 获取属性修饰符, 查询属性以确定它们是否可读写, 还提供了对嵌套属性的支持, 可以无限深度的设置子属性,还可以添加标准的JavaBeans的PropertyChangeListenerVetoableChangeListener而无需在目标类中添加相关支持代码.最重要的是他支持对属性索引的设置.BeanWrapper通常不会在应用代码中使用, 而是在DataBinderBeanFactory中使用.
BeanWrapper的工作方式部分就如同它的名字表示的那样:它包装一个bean来对那个bean执行操作,比如设置和检索属性。

3.4.1 set 和get 基本类型或嵌套类型属性

使用setPropertyValue(s)getPropertyValues(s)方法来set和get属性.
示例:

表达式 说明
name 定义了name属性并提供了set和get方法
account.name 定义了嵌套属性并定义相关的get和set方法
account[2] 指定了索引属性的第三个元素,索引属性可以是数组,list及其他有序集合
account[COMPANYNAME] Map属性, COMPANYNAME为key

下面是一些使用BeanWrapper进行set和get属性的操作方法.

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;
    }
}
BeanWrapper company = new BeanWrapperImpl(new Company());
// 设置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.4.2 内置的PropertyEditor实现

spring使用PropertyEditor的概念来进行对象与String之间的转换,如时间字符串与java.util.Date之间的相互转换. 可以注册java.beans.PropertyEditor类型的自定义属性编辑器来转换它们.
在spring中使用propertyEditor的地方:

  • spring将xml文件中定义的bean属性的字符串值转换为其对应类型.
  • 在spring mvc中,通过各种PropertyEditor转换HTTP请求参数.
    spring提供了大量的内置PropertyEditor实现. 它们都位于beans.propertyeditors包中.
class 说明
ByteArrayPropertyEditor 字符串将被简单的转换为字节数组,默认被BeanWrapperImpl注册
ClassEditor 将字符串转换为实际类型,反之亦然.如果不存在相应类型,则抛出异常.默认被BeanWrapperImpl注册
CustomBooleanEditor boolean属性的自定义属性编辑器, 默认被BeanWrapperImpl注册
CustomCollectionEditor 集合属性编辑器,将任何给定的源集合转换为给定的目标集合
CustomDateEditor java.util.Date属性编辑器,支持自定义格式
CustomNumberEditor Number子类属性编辑器,可以被自定义实例覆盖,默认被BeanWrapperImpl注册
FileEditor 可以将字符串解析为相应的文件,默认被BeanWrapperImpl注册
InputStreamEditor 单向的,能获取文本字符并转换为InputStream, 因此InputStream类型的属性也可用字符串表示.但是不会关闭它,默认被BeanWrapperImpl注册
LocaleEditor 可以将字符串转换为Locale对象.默认被BeanWrapperImpl注册
PatternEditor 可以将字符串转换为java.util.regex.Pattern对象.
PropertiesEditor 将字符串转换为java.util.Properties对象,默认被BeanWrapperImpl注册
StringTrimmerEditor trim字符串,可以选择是否将空字符串转换为null, 没有被注册,需要时必须要注册
URLEditor 将表示URL的字符串解析为实际的URL对象, 默认被BeanWrapperImpl注册

Spring使用java.beans.PropertyEditorManager为可能需要的属性编辑器设置搜索路径,这个搜索路径也包括sun.bean.editors(包含了如Font, Color以及大量的基本类型实现). 还要注意的是,如果PropertyEditor类与它们处理的类在同一个包中,并且具有与该类相同的名称,且以Editor结尾,那么标准javabean基础设施将自动发现PropertyEditor类(无需显式注册它们);例如,可以使用下面的类和包结构,这就足以识别FooEditor类并将其用作foo类型属性的PropertyEditor。

com
  chank
    pop
      Foo
      FooEditor // the PropertyEditor for the Foo class

也可以使用BeanInfo机制.为类的属性显示关联一个或多个属性编辑器.

com
  chank
    pop
      Foo
      FooBeanInfo // the BeanInfo for the Foo class

下面示例中将会把Foo的age属性与CustomNumberEditor关联起来.

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

注册自定义的PropertyEditor
当将一个bean的属性值设置为字符串时,spring容器将会使用标准的JavaBeans PropertyEditor将其转换为对应的类型. spring预定义了大量的PropertyEditor. 此外java标准的JavaBeans PropertyEditor查找机制允许对类的PropertyEditor进行适当的命名并与其支持的类放在同一个包中,以便于被自动发现.
在需要注册自定义的PropertyEditor时,有几种实现方法:

  • 通过使用ConfigurableBeanFactoryregisterCustomEditor()方法.不推荐这样使用.
  • 使用名为CustomEditorConfigurer的bean factory后置处理器,它拥有一个嵌套属性设置,因此在ApplicatonContext中强烈推荐使用它.并与任何其他bean类似的方法部署并被自动检测.
    示例:
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.

package example;

public class ExoticTypeEditor extends PropertyEditorSupport {

    public void setAsText(String text) {
        setValue(new ExoticType(text.toUpperCase()));
    }
}

最后我们将使用CustomEditorConfigurer注册这个新的属性编辑器.


    
        
            
        
    

使用PropertyEditorRegistrat
在Spring容器中注册属性编辑器的另一种机制是创建并使用PropertyEditorRegistrat.这个接口在需要在不同的场景下使用同一组属性编辑器时很有用,即编辑相应的registrat并在每种情况下重用它.PropertyEditorRegistrarPropertyEditorRegistry接口(由BeanWrapperDataBinder实现)一起工作.PropertyEditorRegistrarCustomEditorConfigurer一起使用时特别方便,因为后者有一个propertyEditorRegistrars属性及set方法,添加到后者中的PropertyEditorRegistrar可以与DataBinder和Spring mvc控制器共享.此外,它还避免了在自定义的editor上的同步的需要,即PropertyEditorRegistrar会试图为每个bean创建新的PropertyEditor实例.
示例:
创建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...
    }
}

更多可以参考beans.support.ResourceEditorRegistrar实现, 注意在registerCustomEditors()方法中它是如何为每个属性编辑器创建的实例的.

xml配置:配置CustomPropertyEditorRegistrar bean, 并将其注入到PropertyEditorConfigurer中.


    
        
            
        
    



最后,对于Spring mvc框架使用者, 将PropertyEditorRegistrar与数据绑定Controller一起使用是非常方便的.示例如下,

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
}

这个实现非常简洁,只有一行代码(initBinder方法中), 且可以在多个Controller中共享.

3.5 Spring 类型转换

spring 3 引入了core.convert包,从而提供了类型转换系统, 定义了一个SPI(Service Provider Interface)来实现类型转换逻辑以及一个API在运行时进行类型转换, 在spring容器中, 它可以代替PropertyEditor将bean属性的字符串定义转换为其所需的实际类型. 这个API可以在你应用中的任何地方使用.

3.5.1 Converter SPI
package org.springframework.core.convert.converter;

public interface Converter {

    T convert(S source);
}

要自定义转换器, 只要实现上面的接口即可, S为源类型, T为你要转换的目标类型.
每当使用转换器时,都必须确保S参数不为null(否则应抛出IllegalArgumentException), 如果转换失败,转换器会抛出未检查异常, 另外还要注意确保你的转换器是线程安全的.
core.convert.support包中定义了一些转换器实现,这些都是string类型转换为一些公共类型(如Integer等). 如StringToInteger:

package org.springframework.core.convert.support;

final class StringToInteger implements Converter {

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

当你的转换目标是具有继承关系的类,而你又想将转换逻辑集中定义,则请使用ConverterFactory.

package org.springframework.core.convert.converter;

public interface ConverterFactory {

     Converter getConverter(Class targetType);
}

参数S为转换源, R为转换目标类型的基类, T为R的子类.
示例, 将String转换为Enum:

package org.springframework.core.convert.support;

final class StringToEnumConverterFactory implements ConverterFactory {

    public  Converter getConverter(Class targetType) {
        return new StringToEnumConverter(targetType);
    }

    // 转换器
    private final class StringToEnumConverter implements Converter {

        private Class enumType;

        public StringToEnumConverter(Class enumType) {
            this.enumType = enumType;
        }

        public T convert(String source) {
            return (T) Enum.valueOf(this.enumType, source.trim());
        }
    }
}
3.5.3 GenericConverter

它支持将多种源类型转换为目标类型,在实现转换逻辑时,可以使用源或目标类型上的字段上下文(即字段上的注解或泛型).

package org.springframework.core.convert.converter;

public interface GenericConverter {
    // 返回所支持的源->目标类型对
    public Set getConvertibleTypes();

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

最好的示例参考ArrayToCollectionConverter.

ConditionalGenericConverter
有时你想在指定条件为true时才进行转换,如只在当前目标字段上有指定的注解时才执行转换等.ConditionalGenericConverterGenericConverterConditionalConverter的组合.

public interface ConditionalConverter {

    boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
}

public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
}

更多可以参考EntityConverter.

3.5.4 ConversionService API

ConversionService定义了一系列执行转换逻辑的API.

package org.springframework.core.convert;

public interface ConversionService {

    boolean canConvert(Class sourceType, Class targetType);

     T convert(Object source, Class targetType);

    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);

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

}

大部分的ConversionService实现也实现了ConverterRegistry.它提供了一个注册converter的SPI.这样,ConversionService会将转换逻辑交给提供给它的类型转换器来执行.
core.convert.support包中提供了一个很好的实现,即GenericConversionService,这是在大多数环境中都可使用的通用实现.另外ConversionServiceFactory为配置公共的ConversionService提供了方便.

3.5.5 ConversionService配置

ConversionService是一个在应用启动时就被初始化的一个无状态的对象,在多个线程之间共享. 在spring应用中,你可以为每个Spring容器配置一个 ConversionService实例,spring将会自动获取它,并在需要的地方使用它, 你也可以将它注入到你自己的bean中并直接调用它.

如没容器中没有ConversionService, 则将使用原始的PropertyEditor.

注册一个默认的ConversionService:


默认的ConversionService可以在String, numbers,enums,collections, maps及其它公共类型之间转换.
如果要使用自定义的converter补充或覆盖默认的,则注入converters属性.属性值可以是实现了Converter,ConverterFactory,GenericConverter接口的bean.


    
        
            
        
    

3.5.6 编程式使用ConversionService

只需要像其他bean一样简单注入即可:

@Service
public class MyService {

    @Autowired
    public MyService(ConversionService conversionService) {
        this.conversionService = conversionService;
    }

    public void doIt() {
        this.conversionService.convert(...)
    }
}

在大多数情况下,convert方法指定目标类型即可, 但是这对于具有泛型参数的集合却不可用,如果你想将Integer的List转换为String的List, 你必须提供源类开与目标类型的正式定义.这可以通过TypeDescriptor实现.

DefaultConversionService cs = new DefaultConversionService();

List input = ....
cs.convert(input,
    TypeDescriptor.forObject(input), // List type descriptor
    TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));

注意: 在多数情况下, DefaultConversionService可以自动注册converter.还可以使用DefaultConversionService的静态方法addDefaultConverters向任何ConverterRegistry注册相同的转换器.

3.6 Spring字段格式
3.6.1 Formatter SPI
package org.springframework.format;

public interface Formatter extends Printer, Parser {
}
public interface Printer {

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

public interface Parser {

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

要自定义格式化逻辑,只需要实现Formatter接口.参数T为要格式化的对象类型.
spring在format子包中提供了一些实现.如NumberStyleFormatter,DateFormatter等.
示例:

package org.springframework.format.datetime;

public final class DateFormatter implements Formatter {

    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;
    }
}
3.6.2 注解驱动格式化

可以通过注解格式化字段.通过实现AnnotationFormatterFactory来绑定一个格式化注解.

package org.springframework.format;

public interface AnnotationFormatterFactory {

    Set> getFieldTypes();

    Printer getPrinter(A annotation, Class fieldType);

    Parser getParser(A annotation, Class fieldType);
}

参数A为你所用的格式化注解类型,如DateTimeFormat.
getFieldTypes()返回注解可使用用的字段类型集合.
getPrinter()返回一个Printer来输出注解字段的值.
getParser()返回一个Parser来解析字段的值.
示例:一个AnnotationFormatterFactory实现绑定了NumberFormat注解:

public final class NumberFormatAnnotationFormatterFactory
        implements AnnotationFormatterFactory {

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

    public Printer getPrinter(NumberFormat annotation, Class fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

    public Parser getParser(NumberFormat annotation, Class fieldType) {
        return configureFormatterFrom(annotation, fieldType);
    }

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

使用简单的注解格式化字段:

public class MyModel {

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

Format注解API
format注解定义在org.springframework.format.annotation包中,可以在Number类型字段上使用@NumberFormat注解,可以在Date, Calendar, Long, Joda-Time类型字段上使用@DateTimeFormat.
示例, 使用@DateTimeFormat注解将Date类型字段格式化为ISO类型日期.

public class MyModel {

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

FormatterRegistry是一个注册formatter和converter的SPI, FormattingConversionService是它的一个实现且用于大多数环境,这个实现可能通过FormattingConversionServiceFactoryBean进行编程式或声明式地配置. 因为这个实现也实现了ConversionService, 所以它可以通过spring的DataBinder和spring el表达式直接配置使用.

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);
}
3.6.4 FormatterRegistrar SPI

用于注册formatter和converter.

package org.springframework.format;

public interface FormatterRegistrar {

    void registerFormatters(FormatterRegistry registry);
}
3.7 配置全局的日期和时间格式

例如,通过java配置一个全局的yyyyMMdd格式,这个例子不依赖Joda-Time库.

@Configuration
public class AppConfig {

    @Bean
    public FormattingConversionService conversionService() {

        // 使用 DefaultFormattingConversionService,但不注册缺省值
        DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(false);

        // 确保仍然支持@NumberFormat
        conversionService.addFormatterForFieldAnnotation(new NumberFormatAnnotationFormatterFactory());

        // 用一个指定的全局format注册date conversion
        DateFormatterRegistrar registrar = new DateFormatterRegistrar();
        registrar.setFormatter(new DateFormatter("yyyyMMdd"));
        registrar.registerFormatters(conversionService);

        return conversionService;
    }
}

xml配置,这里使用了Joda Time:



        
        
            
                
            
        
        
            
                
                    
                        
                            
                        
                    
                
            
        
    

Joda-Time提供了不同的类型来表示date, time, date-time的值, 通过JodaTimeFormatterRegistrardateFormatter, timeFormatter, dateTimeFormatter属性可以为每种不同的类型配置不同的formatter.

3.8 Spring Validation

从Spring3开始,引入了一起验证功能,一是完全支持JSR-303的Bean验证API, 二是当编辑式使用时,spring的DateBinder也可以验证并绑定它们, 三是spring mvc支持对Controller中输入数据的声明式验证.

3.8.1 JSR-303验证API
public class PersonForm {

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

    @Min(0)
    private int age;
}

更多请查阅JSR-303/JSR-349文档及Hibernate Validation文档.

3.8.2 配置一个Bean Validator Provider

Spring对Bean验证API提供了完全支持, 这包括对JSR-303/JSR-349的支持(声明为一个bean), 也允许你在应用中注入javax.validation.ValidatorFactoryjavax.validation.Validator.


应将JSR-303/349或Hibernate Validator引入到类路径中, spring将会进行自动检测.

注入Validator
LocalValidatorFactoryBean实现了javax.validation.ValidatorFactory, javax.validation.Validatororg.springframework.validation.Validator.你可以将这些接口的引用注入到需要执行验证逻辑的bean中.

import javax.validation.Validator;

@Service
public class MyService {

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

@Service
public class MyService {

    @Autowired
    private Validator validator;
}

配置自定义的验证约束
每个Bean的验证器都包括两部分,一是@Constraint注解.声明了约束的配置属性.二是一个java.validation.ConstraintValidator接口实现,定义了约束行为.在运行时如果要进行验证,此时ConstraintValidatorFactory将会实例化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可以通过@Autowired注入其他依赖.

Spring-driven 方法验证
Bean Validation 1.1和Hibernate Validator 4.3 支持方法验证, 这可以通过MethodValidationPostProcessor bean定义集成到spring容器中.
为了支持方法验证, 所有的目标类都必须使用spring的@Validated进行注解.详细信息参考MethodValidationPostProcessor文档.

3.8.3 配置DataBinder

从spring3开始, 可以使用Validator配置DataBinder,一旦配置,这个Validator将会通过binder.validate()方法调用.任何验证错误信息都将被添加到这个binder的BindingResult对象中.

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

一个DataBinder可以通过dataBinder.addValidators()dataBinder.replaceValidators()配置多个Validator实例.这在配置了全局验证时在某个bean上使用特定的验证情况下很有用.

你可能感兴趣的:(spring-core-3 验证,数据绑定和类型转换)