美文网首页
spring-core-3 验证,数据绑定和类型转换

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

作者: xzz4632 | 来源:发表于2019-06-15 15:58 被阅读0次
    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中, 可以使用`<spring:bind/>标签来检查验证的错误信息.

    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属性设置一个字符串的值, 属性编辑器会将其转换为对应的实际类型.

    <bean id="sample" class="example.DependsOnExoticType">
        <property name="type" value="aNameForExoticType"/>
    </bean>
    

    此时我们可以像如下定义PropertyEditor.

    package example;
    
    public class ExoticTypeEditor extends PropertyEditorSupport {
    
        public void setAsText(String text) {
            setValue(new ExoticType(text.toUpperCase()));
        }
    }
    

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

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

    使用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中.

    <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框架使用者, 将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<S, T> {
    
        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<String, Integer> {
    
        public Integer convert(String source) {
            return Integer.valueOf(source);
        }
    }
    
    3.5.2 ConverterFactory

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

    package org.springframework.core.convert.converter;
    
    public interface ConverterFactory<S, R> {
    
        <T extends R> Converter<S, T> getConverter(Class<T> targetType);
    }
    

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

    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.5.3 GenericConverter

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

    package org.springframework.core.convert.converter;
    
    public interface GenericConverter {
        // 返回所支持的源->目标类型对
        public Set<ConvertiblePair> 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> T convert(Object source, Class<T> 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:

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

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

    <bean id="conversionService"
            class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <bean class="example.MyCustomConverter"/>
            </set>
        </property>
    </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<Integer> input = ....
    cs.convert(input,
        TypeDescriptor.forObject(input), // List<Integer> 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<T> extends Printer<T>, Parser<T> {
    }
    
    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接口.参数T为要格式化的对象类型.
    spring在format子包中提供了一些实现.如NumberStyleFormatter,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;
        }
    }
    
    3.6.2 注解驱动格式化

    可以通过注解格式化字段.通过实现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为你所用的格式化注解类型,如DateTimeFormat.
    getFieldTypes()返回注解可使用用的字段类型集合.
    getPrinter()返回一个Printer来输出注解字段的值.
    getParser()返回一个Parser来解析字段的值.
    示例:一个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();
                }
            }
        }
    }
    

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

    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:

    <?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>
    

    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.

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

    应将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 验证,数据绑定和类型转换

          本文链接:https://www.haomeiwen.com/subject/tqwpfctx.html