• 20191105 《Spring5高级编程》笔记-第10章


    第10章 使用类型转换和格式化进行验证

    在应用程序开发中,数据验证通常与转换和格式化一起被提及。因为数据源的格式很可能与应用程序中所使用的格式不同。

    名词缩写:

    SPI(Service Provider Interface):服务提供接口

    10.1 依赖项

    <!--validation-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>
    

    10.4 Spring类型转换介绍

    在Spring3中,引入了一个通用类型转换系统,该系统位于org.springframework.core.convert包中。除了提供PropertyEditor所支持的替代方法之外,还可以配置类型转换系统,从而在任何Java类型和POJO之间进行转换(PropertyEditor专注于将属性文件中的String表示转换为Java类型)。

    10.4.1 实现自定义转换器

    使用PropertyEditor

    //-----JavaBean-----------------//
    @Data
    public class Singer {
        private String firstName;
        private String lastName;
        private DateTime birthDate;
        private URL personalSite;
    }
    
    //-----PropertyEdito相关-----------------//
    public class DateTimeEditorRegistrar implements PropertyEditorRegistrar {
        private DateTimeFormatter dateTimeFormatter;
    
        public DateTimeEditorRegistrar(String dateFormatPattern) {
            dateTimeFormatter = DateTimeFormat.forPattern(dateFormatPattern);
        }
    
        @Override
        public void registerCustomEditors(PropertyEditorRegistry registry) {
            registry.registerCustomEditor(DateTime.class, new DateTimeEditor(dateTimeFormatter));
        }
    
        private static class DateTimeEditor extends PropertyEditorSupport {
            private DateTimeFormatter dateTimeFormatter;
    
            public DateTimeEditor(DateTimeFormatter dateTimeFormatter) {
                this.dateTimeFormatter = dateTimeFormatter;
            }
    
            @Override
            public void setAsText(String text) throws IllegalArgumentException {
                setValue(DateTime.parse(text, dateTimeFormatter));
            }
        }
    }
    
    //-----XML配置-----------------//
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:util="http://www.springframework.org/schema/util"
           xmlns:p="http://www.springframework.org/schema/p"
    
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
    ">
    
        <context:annotation-config/>
    
        <context:property-placeholder location="classpath:chapter10/editor.properties"/>
    
        <bean id="customEditorConfigurer" class="org.springframework.beans.factory.config.CustomEditorConfigurer"
              p:propertyEditorRegistrars-ref="propertyEditorRegistrarsList"/>
    
        <util:list id="propertyEditorRegistrarsList">
            <bean class="study.hwj.chapter10.editor.DateTimeEditorRegistrar">
                <constructor-arg value="${date.format.pattern}"/>
            </bean>
        </util:list>
    
        <bean id="eric" class="study.hwj.chapter10.entities.Singer" p:firstName="Eric" p:lastName="Clapton"
              p:birthDate="1945-03-30" p:personalSite="http://www.ericclapton.com"></bean>
    
        <bean id="countrySinger" class="study.hwj.chapter10.entities.Singer"
              p:firstName="${countrySinger.firstName}" p:lastName="${countrySinger.lastName}"
              p:birthDate="${countrySinger.birthDate}" p:personalSite="${countrySinger.personalSite}"></bean>
    </beans>
    
    //-----属性配置-----------------//
    date.format.pattern=yyyy-MM-dd
    
    countrySinger.firstName=John
    countrySinger.lastName=Mayer
    countrySinger.birthDate=1997-10-16
    countrySinger.personalSite=http://johnmayer.com/
    
    //-----测试程序-----------------//
    @Slf4j
    public class PropEditorDemo {
        public static void main(String[] args) {
            GenericXmlApplicationContext ctx = new GenericXmlApplicationContext("classpath:chapter10/ac_editor.xml");
    
            Singer eric = ctx.getBean("eric", Singer.class);
            log.info("Eric==={}", eric);
    
            Singer countrySinger = ctx.getBean("countrySinger", Singer.class);
            log.info("countrySinger==={}", countrySinger);
        }
    }
    

    10.4.2 配置ConversionService

    使用ConversionService

    //-----Converter-----------------//
    public class StringToDateTimeConverter implements Converter<String, DateTime> {
        private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
        private DateTimeFormatter dateFormat;
        private String datePattern = DEFAULT_DATE_PATTERN;
    
        public String getDatePattern() {
            return datePattern;
        }
    
        public void setDatePattern(String datePattern) {
            this.datePattern = datePattern;
        }
    
        @PostConstruct
        public void init() {
            System.out.println("StringToDateTimeConverter...init...");
            dateFormat = DateTimeFormat.forPattern(datePattern);
        }
    
        @Override
        public DateTime convert(String source) {
            return dateFormat.parseDateTime(source);
        }
    }
    
    //-----Java配置类-----------------//
    @PropertySource("classpath:chapter10/editor.properties")
    @Configuration
    public class ConversionConfig {
        @Value("${date.format.pattern}")
        private String dateFormatPattern;
    
        /**
         * 将${date.format.pattern}解析成yyyy-MM-dd
         * @return
         */
        @Bean
        public static PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer() {
            return new PropertySourcesPlaceholderConfigurer();
        }
    
        @Bean
        public Singer john(@Value("${countrySinger.firstName}") String firstName, @Value("${countrySinger.lastName}") String lastName, @Value("${countrySinger.birthDate}") DateTime birthDate, @Value("${countrySinger.personalSite}") URL personalSite) {
            Singer singer = new Singer();
            singer.setFirstName(firstName);
            singer.setLastName(lastName);
            singer.setBirthDate(birthDate);
            singer.setPersonalSite(personalSite);
            return singer;
        }
    
        @Bean
        public ConversionServiceFactoryBean conversionService() {
            ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
            Set<Converter> convs = new HashSet<>();
            convs.add(converter());
            conversionServiceFactoryBean.setConverters(convs);
            return conversionServiceFactoryBean;
        }
    
        @Bean
        public StringToDateTimeConverter converter() {
            StringToDateTimeConverter conv = new StringToDateTimeConverter();
            conv.setDatePattern(dateFormatPattern);
            return conv;
        }
    }
    
    //-----测试程序-----------------//
    public class ConvServDemo {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConversionConfig.class);
            Singer john = ctx.getBean("john", Singer.class);
            System.out.println(john);
            ctx.close();
        }
    }
    

    通过使用类ConversionServiceFactoryBean声明一个conversionService bean,从而指示Spring使用类型转换系统,如果没有定义转换服务bean,Spring将使用基于PropertyEditor的系统。
    默认情况下,类型转换服务支持常用类型的转换,包括字符串、数字、枚举、集合、映射等。还支持基于PropertyEditor的系统将String转换为Java类型。

    ConversionService接口的默认实现为org.springframework.core.convert.support.DefaultConversionService

    容器添加PropertyEditor支持在refresh()prepareBeanFactory(beanFactory);
    容器添加ConversionService支持在refresh()finishBeanFactoryInitialization(beanFactory);

    10.4.3 任意类型之间的转换

    //-----定义Converter -----------------//
    public class SingerToAnotherSingerConverter implements Converter<Singer, AnotherSinger> {
        @Override
        public AnotherSinger convert(Singer singer) {
            AnotherSinger anotherSinger = new AnotherSinger();
            anotherSinger.setFirstName(singer.getLastName());
            anotherSinger.setLastName(singer.getFirstName());
            anotherSinger.setBirthDate(singer.getBirthDate());
            anotherSinger.setPersonalSite(singer.getPersonalSite());
            return anotherSinger;
        }
    }
    
    //-----Java配置类 -----------------//
    @Configuration
    public class ConvertObjectConfig {
    
        @Bean
        public Singer john() throws MalformedURLException {
            Singer singer = new Singer();
            singer.setFirstName("John");
            singer.setLastName("Mayer");
            singer.setBirthDate(converter().convert("1977-10-16"));
            singer.setPersonalSite(new URL("http://johnmayer.com/"));
            return singer;
        }
    
        @Bean
        public ConversionServiceFactoryBean conversionService() {
            ConversionServiceFactoryBean conversionServiceFactoryBean = new ConversionServiceFactoryBean();
            Set<Converter> convs = new HashSet<>();
            convs.add(converter());
            convs.add(singerConverter());
            conversionServiceFactoryBean.setConverters(convs);
            return conversionServiceFactoryBean;
        }
    
        @Bean
        public StringToDateTimeConverter converter() {
            return new StringToDateTimeConverter();
        }
    
        @Bean
        public SingerToAnotherSingerConverter singerConverter() {
            return new SingerToAnotherSingerConverter();
        }
    }
    
    //-----测试程序-----------------//
    public class ConvertObjectDemo {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ConvertObjectConfig.class);
    
            Singer john = ctx.getBean("john", Singer.class);
    
            System.out.println(john);
    
            ConversionService conversionService = ctx.getBean(ConversionService.class);
    
            // 对象转换
            AnotherSinger anotherSinger = conversionService.convert(john, AnotherSinger.class);
    
            System.out.println(anotherSinger);
    
            // 字符串转数组
            String[] stringArray = conversionService.convert("a,b,c", String[].class);
            System.out.println(Arrays.toString(stringArray));
    
            // List转Set
            ImmutableList<String> list = ImmutableList.of("d", "e", "f");
            HashSet setString = conversionService.convert(list, HashSet.class);
    
            System.out.println(setString);
        }
    }
    

    10.5 Spring中的字段格式化

    除类型转换系统外,Spring带来另一个重要功能 Formatter SPI 帮助配置字段格式化。
    在 Formatter SPI 中,实现格式化器的主要接口是 org.springframework.format.Formatter 。Spring提供了一些常用类型的实现。

    10.5.1 实现自定义格式化器

    扩展 org.springframework.format.support.FormattingConversionServiceFactoryBean 类并提供自定义格式化器。 FormattingConversionServiceFactoryBean 是一个工厂类,可以方便的访问底层 FormattingConversionService 类(该类支持类型转换系统),以及根据每个字段类型所定义的格式化规则完成字段格式化。

    FormattingConversionServiceConversionService 的实现类。

    //------定义FormattingConversionServiceFactoryBean子类-------------------------//
    @Component("conversionService")
    public class ApplicationConversionServiceFactoryBean extends FormattingConversionServiceFactoryBean {
        private static final String DEFAULT_DATE_PATTERN = "yyyy-MM-dd";
        private DateTimeFormatter dateTimeFormatter;
        private String datePattern = DEFAULT_DATE_PATTERN;
    
        private Set<Formatter<?>> formatters = new HashSet<>();
    
        public String getDatePattern() {
            return datePattern;
        }
    
        @Autowired(required = false)
        public void setDatePattern(String datePattern) {
            this.datePattern = datePattern;
        }
    
        @PostConstruct
        public void init() {
            dateTimeFormatter = DateTimeFormat.forPattern(datePattern);
            formatters.add(getDateTimeFormatter());
            setFormatters(formatters);
        }
    
        public Formatter<DateTime> getDateTimeFormatter() {
            return new Formatter<DateTime>() {
                @Override
                public DateTime parse(String text, Locale locale) throws ParseException {
                    System.out.println(text);
                    return dateTimeFormatter.parseDateTime(text);
                }
    
                @Override
                public String print(DateTime dateTime, Locale locale) {
                    return dateTimeFormatter.print(dateTime);
                }
            };
        }
    }
    
    //------定义Java配置类-------------------------//
    @Configuration
    @Import({ApplicationConversionServiceFactoryBean.class})
    public class FormatterConfig {
        @Autowired
        private ApplicationConversionServiceFactoryBean conversionService;
    
        @Bean
        public Singer john() throws MalformedURLException, ParseException {
            Singer singer = new Singer();
            singer.setFirstName("John");
            singer.setLastName("Mayer");
            singer.setPersonalSite(new URL("http://johnmayer.com"));
            singer.setBirthDate(conversionService.getDateTimeFormatter().parse("1977-10-16", Locale.ENGLISH));
            return singer;
        }
    }
    
    //------测试程序-------------------------//
    public class ConvFormatServDemo {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(FormatterConfig.class);
    
            Singer john = ctx.getBean("john", Singer.class);
            System.out.println(john);
    
            ConversionService conversionService = ctx.getBean("conversionService", ConversionService.class);
            System.out.println(conversionService.convert(john.getBirthDate(), String.class));
        }
    }
    

    10.6 Spring中的验证

    Spring支持两种主要类型的验证。第一种验证类型是由Spring提供的,可以通过实现 org.springframework.validation.Validator 接口来创建自定义验证器。另一种类型是通过Spring对JSR-349(Bean Validation)的支持实现的。

    10.6.1 使用Spring Validator接口

    //-----------定义Validator--------//
    @Configuration
    @Component("singerValidator")
    public class SingerValidator implements Validator {
    
        @Override
        public boolean supports(Class<?> clazz) {
            return Singer.class.equals(clazz);
        }
    
        @Override
        public void validate(Object target, Errors errors) {
            ValidationUtils.rejectIfEmpty(errors, "firstName", "firstName.empty");
        }
    }
    
    //-----------测试程序--------//
    public class SpringValidatorDemo {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(SingerValidator.class);
    
            Singer singer = new Singer();
            singer.setFirstName(null);
            singer.setLastName("Mayer");
    
            SingerValidator singerValidator = ctx.getBean("singerValidator", SingerValidator.class);
            BeanPropertyBindingResult result = new BeanPropertyBindingResult(singer, "John");
    
            ValidationUtils.invokeValidator(singerValidator, singer, result);
    
            List<ObjectError> errors = result.getAllErrors();
            errors.forEach(System.out::println);
    
        }
    }
    

    10.6.2 使用JSR-349 Bean Validation

    Spring4开始对JSR-349(Bean Validation)提供全面支持。Bean Validation API在包 javax.validation.constraints 中以Java注解的形式定义了一组可应用于域对象的约束。另外,可以使用注解开发和应用自定义验证器(例如,类级验证器)。

    通过使用Bean Validation API,可以避免耦合到特定的验证服务提供程序。

    10.6.3 在Spring中配置Bean Validation支持

    为了在Spring的ApplicationContext中配置对Bean Validation API的支持,可以在Spring的配置中定义一个类型为 org.springframework.validation.beanvalidation.LocalValidatorFactoryBean 的bean。

    注意区分:javax.validation.Validatororg.springframework.validation.Validator

    // -------定义要验证的JavaBean,加上验证注解---------------------------- //
    @Data
    public class VSinger {
    
        @NotNull
        @Size(min = 2, max = 60)
        private String firstName;
    
        private String lastName;
    
        @NotNull
        private Genre genre;
    
        private Gender gender;
    }
    
    // -------定义Service类---------------------------- //
    @Service
    public class SingerValidationService {
        @Autowired
        private Validator validator;
    
        public Set<ConstraintViolation<VSinger>> validateSinger(VSinger singer) {
            return validator.validate(singer);
        }
    }
    
    // -------定义Java配置类---------------------------- //
    @Configuration
    @Import({SingerValidationService.class})
    public class ValidatorConfig {
        @Bean
        public LocalValidatorFactoryBean validator() {
            return new LocalValidatorFactoryBean();
        }
    }
    
    // -------测试程序---------------------------- //
    public class Jsr349Demo {
        public static void main(String[] args) {
            AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(ValidatorConfig.class);
    
            SingerValidationService service = ctx.getBean(SingerValidationService.class);
    
            VSinger singer = new VSinger();
            singer.setFirstName("J");
            singer.setLastName("Mayer");
            singer.setGenre(null);
    
            validateSinger(singer, service);
    
            ctx.close();
        }
    
        private static void validateSinger(VSinger singer, SingerValidationService service) {
            Set<ConstraintViolation<VSinger>> violations = service.validateSinger(singer);
            listViolations(violations);
        }
    
        private static void listViolations(Set<ConstraintViolation<VSinger>> violations) {
            violations.forEach(violation -> {
                System.out.println("Validation error for property: 【" + violation.getPropertyPath() + "】 with value: 【" + violation.getInvalidValue() + "】 with error message: 【" + violation.getMessage() + "】");
            });
        }
    }
    

    10.6.4 创建自定义验证器

    除了进行属性级验证之外,还可以应用类级验证。在Bean Validation API中,开发一个自定义验证器分两步。第一步是为验证器创建要给注解类型;第二步是开发实现验证逻辑的类。

    第一步:
    注解类型包含三个属性:

    • message属性定义违反约束条件时返回的消息(或错误代码。也可以在注解中提供默认消息。
    • group属性指定适用的验证组。可以将验证器分配给不同的组,并对特定组执行验证。
    • payload属性指定其他有效载荷对象(即实现了javax.validation.Payload 接口的类)。它允许将附加消息附加到约束上(例如,有效载荷对象可以指明违反约束的严重性)。
    @Retention(RetentionPolicy.RUNTIME)
    @Target(ElementType.TYPE)
    @Constraint(validatedBy = CountrySingerValidator.class)
    public @interface CheckCountrySinger {
        String message() default "Country Singer should have gender and xxx";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    

    第二步:

    public class CountrySingerValidator implements ConstraintValidator<CheckCountrySinger, VSinger> {
        @Override
        public void initialize(CheckCountrySinger constraintAnnotation) {
    
        }
    
        @Override
        public boolean isValid(VSinger singer, ConstraintValidatorContext context) {
            if (singer.getGenre() != null && (singer.isCountrySinger() && (singer.getLastName() == null || singer.getGender() == null))) {
                return false;
            }
            return true;
        }
    }
    

    10.7 使用AssertTrue进行自定义验证

    除了实现自定义验证器外,在 Bean Validation API 中应用自定义验证的另一种方法是使用 @AssertTrue@AssertFalse 注解。

    @Data
    public class VSinger {
    
        @NotNull
        @Size(min = 2, max = 60)
        private String firstName;
    
        private String lastName;
    
        @NotNull
        private Genre genre;
    
        private Gender gender;
    
        @AssertTrue(message = "AssertTrue xxx")
        public boolean isCountrySinger(){
            if (genre != null && (genre == Genre.COUNTRY && (getLastName() == null || getGender() == null))) {
                return false;
            }
            return false;
        }
    }
    

    10.8 自定义验证的注意事项

    对于JSR-349中的自定义验证,应该使用哪种方法:自定义验证器还是@AssertTure注解?
    通常,@AssertTure方法实现起来更简单,可以在域对象的代码中看到验证规则。但是,对于具有更复杂逻辑的验证器(例如,需要注入一个服务类,访问数据库并检查有效值),实现自定义验证器是不错的方法,因为你可能并不像将服务层对象添加到域对象中。而且,自定义验证器可以在相似的域对象中重用。

    10.9 决定使用哪种验证API

    Spring的Validator接口以及JSR-349(Bean Validation API),更应该使用JSR-349(Bean Validation API)。
    主要原因:

    • JSR-349是JEE标准,得到很多前后端框架的广泛支持。
    • JSR-349提供了标准验证API隐藏了底层提供程序,不受限于特定的提供程序。
    • Spring从版本4开始与JSR-349紧密集成。例如,在Spring MVC Web 控制器中,可以使用@Valid注解(javax.validation.Valid)来注解入参,Spring 将在数据绑定过程中自动调用JSR-349验证。
    • 如果使用的是JPA2,那么提供程序会在吃就会之前自动对实体执行JSR-349验证。
  • 相关阅读:
    Json
    JQuery的validate不起作用的情况
    ajax的同步异步
    Bootstrap--switch
    Bootstrap--multiselect
    ArcGIS地图打印那些事
    openlayers调用瓦片地图分析
    多种在线地图综合对比,Google,必应,arcgis Online...
    map的infowindow的show事件(ArcGIS API for JS)
    在ArcGIS中导出现有mxd的style文件
  • 原文地址:https://www.cnblogs.com/huangwenjie/p/11798213.html
Copyright © 2020-2023  润新知