zoukankan      html  css  js  c++  java
  • 验证、数据绑定和类型转换

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

    数据绑定有助于让用户输入动态绑定到应用程序的实体模型(或你用来处理用户输入的任何对象)。Spring提供了DataBinder来实现这一点。Validator和DataBinder组成了验证包,主要用于但不限于MVC框架。

    BeanWrapper是Spring框架中的一个基本概念,在很多地方都有使用。但是,你可能不需要直接使用BeanWrapper。

    Spring的DataBinder和较低级别的BeanWrapper都使用PropertyEditorSupport实现来解析和格式化属性值。PropertyEditor和PropertyEditorSupport类型是JavaBeans规范的一部分。Spring 3引入了核心转换工具包,以及用于格式化UI字段值的高级“format”包。你可以使用这些包作为PropertyEditor支持实现的更简单的替代方案。

    JSR-303/JSR-349 Bean Validation

    从4.0版开始,Spring框架支持BeanValidation1.0(JSR-303)和BeanValidation1.1(JSR-349)来支持设置并使它们适应Spring的验证器接口。

    应用程序可以选择全局启用一次Bean验证,可以为每个DataBinder实例注册额外的Spring验证器实例。

    使用Spring的Validator接口进行验证

    Spring提供了一个验证器接口,可以用来验证对象。Validator接口的工作原理是使用Errors对象,以便在验证时,验证器可以向Errors对象报告验证失败。

    public interface Validator {
        boolean supports(Class<?> var1);
    
        void validate(Object var1, Errors var2);
    }
    • supports(Class):验证提供的类是否支持
    • validate(Object, org.springframework.validation.Errors)::验证给定的对象,如果出现验证错误,则使用给定的Errors对象注册这些对象。

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

    public class Person {
    
        private String name;
        private int age;
    
        // the usual getters and setters...
    }

    实现验证器相当简单,尤其是当你知道Spring框架还提供ValidationUtils帮助器类时。以下示例为Person实例实现验证器:

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

    单元测试

    @RunWith(SpringRunner.class)
    @WebAppConfiguration
    @ContextHierarchy({
            @ContextConfiguration(classes = SpringConfig.class),
            @ContextConfiguration(classes = SpringMVCConfig.class)
    })
    public class ValidatorTest {
    
        @Test
        public void test() {
            PersonValidator validator = new PersonValidator();
            Person person = new Person();
            person.setAge(200);
    
            BindException errors = new BindException(person, "person");
            ValidationUtils.invokeValidator(validator, person, errors);
            if (errors.hasErrors()) {
                // 获取的错误信息需要与ValidationUtils.rejectXxxx()方法设置的对应
                // 输出 name.empty
                System.out.println(errors.getFieldError("name").getCode());
                // 输出 too.darn.old
                System.out.println(errors.getFieldError("age").getCode());
            }
        }
    }

    当然可以实现一个验证程序类来验证富对象中的每个嵌套对象,但最好将每个对象嵌套类的验证逻辑封装在自己的验证器实现中。如果Customer包含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();
            }
        }
    }
    Bean操纵和BeanWrapper

    org.springframework.beans包遵循JavaBeans标准。JavaBean是一个具有默认无参数构造函数的类,它遵循命名约定,其中(例如)名为bingoMadness 的属性将具有setter方法setBingoMadness(..) 和getter方法getBingoMadness()。

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

    BeanWrapper的工作方式如它的名称描述那样:它包装一个bean来对这个bean执行操作,比如设置和检索属性。

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

    设置和获取属性是通过使用setPropertyValue、setPropertyValues、getPropertyValue和getPropertyValues方法完成的,这些方法带有几个重载变量。

    以下两个示例类使用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;
        }
    }

    以下代码片段显示了如何检索和操作实例化Company和Employee的某些属性的示例:

    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");
    PropertyEditor实现

    Spring使用PropertyEditor的概念来实现对象和字符串之间的转换。用不同于对象本身的方式来表示属性是很方便的。此行为可以通过注册类型的自定义编辑器来实现java.beans.PropertyEditor. 在BeanWrapper上注册自定义编辑器,或者在特定的IoC容器中注册自定义编辑器,可以让它了解如何将属性转换为所需的类型。

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

    • 在Spring的MVC框架中解析HTTP请求参数是通过使用各种PropertyEditor实现完成的。
    • 当使用String作为在XML文件中声明的某个bean的属性值时,Spring使用ClassEditor尝试将参数解析为类对象。

    Spring有许多内置的PropertyEditor实现来简化工作。它们都位于org.springframework.beans.PropertyEditor包。默认情况下,大多数(但不是全部,如下表所示)由BeanWrapperImpl注册。当属性编辑器以某种方式可配置时,你仍然可以注册自己的变量来覆盖默认变量。

    Spring内置的一些实现:

    • ByteArrayPropertyEditor:字节数组编辑器。将字符串转换为相应的字节表示形式。默认由BeanWrapperImpl注册。
    • ClassEditor:将表示类的字符串(类的全限定名)解析为实际类,反之亦然。当找不到类时,将抛出IllegalArgumentException。默认情况下,由BeanWrapperImpl注册。
    • CustomBooleanEditor:布尔属性的可自定义属性编辑器。默认情况下,由BeanWrapperImpl注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。
    • CustomCollectionEditor:集合的属性编辑器,将任何源集合转换为给定的目标集合类型。
    • CustomDateEditor:java.util.Date的属性编辑器,支持自定义日期格式。默认情况下未注册。必须由用户根据需要以适当的格式注册。
    • CustomNumberEditor:任何数字子类的可自定义属性编辑器,如Integer、Long、Float或Double。默认情况下,由BeanWrapperImpl注册,但可以通过将其自定义实例注册为自定义编辑器来覆盖。
    • FileEditor:将字符串解析为java.io.File文件对象。默认情况下,由BeanWrapperImpl注册。
    • InputStreamEditor:单向属性编辑器,它可以获取字符串并生成(通过中间资源编辑器和资源)InputStream,以便可以直接将InputStream属性设置为字符串。请注意,默认用法不会关闭InputStream。默认情况下,由BeanWrapperImpl注册。
    • LocaleEditor:可以将字符串解析为Locale对象,反之亦然(字符串格式为[country][variant],与Locale的toString()方法相同)。默认情况下,由BeanWrapperImpl注册。
    • PatternEditor:可以将字符串解析为java.util.regex.Pattern对象,反之亦然。
    • StringTrimmerEditor:除去字符串的属性编辑器。可选地允许将空字符串转换为空值。默认情况下未注册 - 必须是用户注册的。
    • URLEditor:可以解析URL到实际URL对象的字符串表示形式。默认情况下,由BeanWrapperImpl注册。

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

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

    注册其他自定义PropertyEditor实现

    当将bean属性设置为字符串值时,spring ioc容器最终使用标准JavaBeans PropertyEditor实现将这些字符串转换为复杂的属性类型。Spring预先注册了许多自定义PropertyEditor实现(例如,将以字符串表示的类名转换为类对象)。此外,Java的标准JavaBeans PropertyEditor查找机制允许对类的PropertyEditor进行适当的命名,并将其与它所支持的类放在同一个包中,这样就可以自动找到它。

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

    请注意,所有bean工厂和应用程序上下文都自动使用许多内置的属性编辑器,通过它们使用BeanWrapper来处理属性转换。此外,ApplicationContexts还重写或添加其他编辑器,以适合特定应用程序上下文类型的方式处理资源查找。

    标准JavaBeans PropertyEditor实例用于将以字符串表示的属性值转换为实际的复杂属性类型。你可以使用CustomEditorConfigurer,一个bean工厂后处理程序,方便地向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()));
        }
    }

    最后,下面的示例演示如何使用CustomEditorConfigure向ApplicationContext注册新的PropertyEditor,然后ApplicationContext将能够根据需要使用它:

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

    使用PropertyEditorRegistrar

    向Spring容器注册属性编辑器的另一种机制是创建和使用PropertyEditorRegistrar。当你需要在几种不同的情况下使用同一组属性编辑器时,此接口特别有用。你可以编写相应的注册器,并在每种情况下重用它。

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

    注入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类型转换

    Spring 3 引入了提供通用类型转换的org.springframework.core.convert包。可以使用这个转换服务系统替代PropertyEditor的属性转换功能。

    Converter SPI

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

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

    要创建自己的转换器,请实现converter接口并将S参数化为需要转换的类型,将T参数化为转换后的类型。如果需要将S的集合或数组转换为T的数组或集合,也可以透明地应用这样的转换器,前提是也注册了委派数组或集合转换器(DefaultConversionService在默认情况下是这样做的)。

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

    package org.springframework.core.convert.support;
    
    final class StringToInteger implements Converter<String, Integer> {
    
        public Integer convert(String source) {
            return Integer.valueOf(source);
        }
    }
    使用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参数化为定义可转换为的类范围的基类型。然后实现getConverter(Class<T>),其中T是R的一个子类。

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

    当你需要复杂的转换器实现时,请考虑使用GenericConverter接口。GenericConverter具有比Converter更灵活但不太强类型的签名,它支持在多个源类型和目标类型之间进行转换。此外,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提供对保存正在转换的值的源字段的访问。目标TypeDescriptor提供对要在其中设置转换值的目标字段的访问。

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

    因为GenericConverter是一个更复杂的SPI接口,所以应该只在需要时使用它。优先选择转换器或转换器工厂,以满足基本类型转换的需要。

    使用ConditionalGenericConverter

    有时,你希望转换器仅在特定条件成立时才运行。例如,您可能希望仅在目标字段上存在特定注释时才运行转换器,或者您可能希望仅在目标类上定义了特定方法(例如static valueOf)时才运行转换器。ConditionalGenericConverter是GenericConverter和ConditionalConverter接口的联合,可用于定义此类自定义匹配条件:

    public interface ConditionalConverter {
    
        boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType);
    }
    
    public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {
    }

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

    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。在内部,转换服务实现委托给它注册的转换器来执行类型转换逻辑。

    GenericConversionService是适合在大多数环境中使用的通用实现。ConversionServiceFactory为创建通用转换服务配置提供了一个方便的工厂。

    配置ConversionService

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

    如果没有向Spring注册转换服务,则使用原始的基于PropertyEditor的系统。

    要向Spring注册默认的ConversionService,请添加以下bean定义:

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

    默认转换服务可以在字符串、数字、枚举、集合、映射和其他常见类型之间进行转换。若要使用自己的自定义转换器补充或重写默认转换器,请设置转换器属性。属性值可以实现任何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实例,可以像对任何其他bean一样注入对它的引用。下面的示例演示如何执行此操作:

    @Service
    public class MyService {
    
        @Autowired
        public MyService(ConversionService conversionService) {
            this.conversionService = conversionService;
        }
    
        public void doIt() {
            this.conversionService.convert(...)
        }
    }

    对于大多数用例,可以使用指定targetType的convert方法,但它不适用于更复杂的类型,例如参数化元素的集合。如果要以编程方式将整数列表转换为字符串列表,则需要提供源类型和目标类型的正式定义。

    幸运的是,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会自动注册适用于大多数环境的转换器。这包括集合转换器、标量转换器和基本的对象到字符串转换器。通过对DefaultConversionService类使用静态addDefaultConverters方法,可以向任何ConverterRegistry注册相同的转换器。

    值类型的转换器可重用于数组和集合,因此不需要创建特定的转换器来从S集合转换为T集合,前提是标准集合处理是适当的。

    Spring 字段格式化

    org.springframework.core.convert是一个通用类型转换系统。它提供了一个统一的转换服务API以及一个强类型转换器SPI,用于实现从一种类型到另一种类型的转换逻辑。Spring容器使用这个系统来绑定bean属性值。此外,Spring表达式语言(SpEL)和DataBinder都使用这个系统来绑定字段值。

    更多内容查看:https://docs.spring.io/spring/docs/5.1.16.RELEASE/spring-framework-reference/core.html#format

    配置全局日期和时间格式

    默认情况下,使用DateFormat.SHORT风格,如果愿意,可以通过定义自己的全局格式来更改此设置。

    为此,你需要确保Spring没有注册默认的格式化器。相反,你应该手动注册所有格式化程序。使用org.springframework.format.datetime.joda.jodaTimeFormatRegistrar或者org.springframework.format.datetime.DateFormatterRegistrar取决于你是否使用Joda时间库。

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

    如果你使用spring mvc,记得显式地配置所使用的转换服务。对于基于注解的Java类,必须实现WebMvcConfigurer接口的addFormatters方法。

    Spring校验

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

    JSR-303 Bean验证API

    JSR-303标准化了Java平台的验证约束声明和元数据。通过使用此API,你可以使用声明性验证约束来注解实体类型的属性,然后运行时将强制执行这些约束。可以使用许多内置约束。也可以定义自己的自定义约束。

    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验证器验证此类的实例时,这些约束将被强制执行。

    有关JSR-303和JSR-349的一般信息,请访问Bean Validation website。有关默认引用实现的特定功能的信息,请参阅Hibernate Validator 文档。

    配置Bean验证提供程序

    Spring提供了对Bean验证API的完全支持。这样你就可以注射javax.validation.ValidatorFactory或者javax.validation.Validator验证程序应用程序中需要验证的地方。

    可以使用LocalValidatorFactoryBean将默认验证器配置为Spring bean

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

    注入验证器

    如果您喜欢直接使用Bean验证API可以将直接注入javax.validation.Validator

    import javax.validation.Validator;
    
    @Service
    public class MyService {
    
        @Autowired
        private Validator validator;
    }

    javax.validation 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现。Hibernate-Validator是一个hibernate独立的包,可以直接引用,他实现了javax.validation同时有做了扩展,比较强大。

    <dependency>
      <groupId>javax.validation</groupId>
      <artifactId>validation-api</artifactId>
    </dependency>
    <dependency>
      <groupId>org.hibernate</groupId>
      <artifactId>hibernate-validator</artifactId>
    </dependency>

    注入org.springframework.validation.Validator

    import org.springframework.validation.Validator;
    
    @Service
    public class MyService {
    
        @Autowired
        private Validator validator;
    }

    配置自定义约束

    每个bean验证约束由两部分组成:

    • 实现javax.validation.ConstraintValidator接口
    • @Constraint注解声明约束

    为了将声明与实现相关联,每个@Constraint注解都引用相应的ConstraintValidator实现类。在运行时,当在实体对象中遇到约束注释时,ConstraintValidatorFactory实例化引用的实现。

    以下示例显示了一个自定义@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;
    
        ...
    }
    配置DataBinder

    从spring3开始,你可以使用验证器配置DataBinder实例。配置完成后,可以通过调用binder.validate()进行验证。 任何验证错误都会自动添加到绑定器的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();

    你还可以通过DataBinderdataBinder.addValidators以及dataBinder.replaceValidators配置多个验证器实例。

  • 相关阅读:
    python 线程Queue 用法代码展示
    Python中的join()函数的用法
    python 中爬虫 content和text的区别
    免费代理ip爬虫分享
    django数据库的表已迁移的不能重新迁移的解决办法
    RuntimeError: Model class app_anme.models.User doesn't declare an explicit app_label and isn't in an application in INSTALLED_APPS.---python学习错误记录
    MYSQL查询操作 详细
    mysql数据库的基本操作命令总结
    http短连接与长连接简介
    浅谈http协议
  • 原文地址:https://www.cnblogs.com/myitnews/p/13301977.html
Copyright © 2011-2022 走看看