• springMVC学习笔记七(基于注解方式的控制器的数据验证,类型转换和格式化)


    ===================基于注解方式的控制器的数据验证,类型转换和格式化=========




    -----------------spring3之前


    springMVC数据类型转换,验证及格式化的流程是:


    a 类型转换: 表单数据通过webDataBinder绑定到命令对象(内部通过propertyEditor实现)
    b 数据验证:在处理方法中,显示的调用spring的validator,并将错误信息添加到            bindingResult对象中
    c 格式化显示:在表单页可以通过如下方式显示propertyEditor和错误信息


    <%@taglib prefix="spring" uri="http://www.springframework.org/tags" %> 
    <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 


    //格式化单个命令
    <spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>


    //通过form标签自动调用命令
    <form:form commandName="dataBinderTest"> 
     <form:input path="phoneNumber"/>
    <!-- 如果出错会显示错误之前的数据 --> 
    </form:form>


    //显示错误信息
    <form:errors></form:errors> 


    ------------------spring3开始:


    类型转换:conversionService会自动选择相应的converter spi进行转换
    数据验证: 支持jsp-303 验证框架,只需将@valid放到目标类型上即可
    格式化显示: converterSPI完成任意类型到string的转换


    springMVC数据类型转换,验证及格式化的流程是
    类型转换:表单提交数据,webDataBinder进行数据绑定到命令对象(通过converter spi)
    数据验证:使用jsp-303验证框架进行
    格式化显示:通过以下方式显示数据和错误信息:


    <%@taglib prefix="spring" uri="http://www.springframework.org/tags" %> 
    <%@taglib prefix="form" uri="http://www.springframework.org/tags/form" %> 




    //格式化单个命令
    <spring:bind path="dataBinderTest.phoneNumber">${status.value}</spring:bind>


    //<spring:eval>标签,自动调用ConversionService并选择相应的Converter SPI进行格式化展示 
    <spring:eval expression="dataBinderTest.phoneNumber"></spring:eval> 


    //通过form标签自动调用命令
    <form:form commandName="dataBinderTest"> 
     <form:input path="phoneNumber"/>
    <!-- 如果出错会显示错误之前的数据 --> 
    </form:form>


    //显示错误信息
    <form:errors></form:errors> 




     
    --------------------------spring3开始的类型转换系统


    类型转换器有如下三种接口:


    1 converter:转换s类型到t类型,实现此接口必须是线程安全且可以被共享
    接口原型:
    public interface Converter<S, T> {
    T convert(S source);
    }








    2 genericConverter/conditionalGenericConverter:
                     genericConverter实现此接口能在多种类型之间转换
    conditionalGenericConverter有条件的在多种类型之间转换
    接口原型:
    public interface GenericConverter {
    //指定可转换的目标类型
    Set<ConvertiblePair> getConvertibleTypes();
    //在sourceType和targetType之间转换
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);
    }


    public interface ConditionalGenericConverter extends GenericConverter, ConditionalConverter {


    }


    3 converterFactory:用于选择将一种s源类型转换为r类型的子类型t的转换器工厂


    接口原型:
    public interface ConverterFactory<S, R> {
    //r:目标类型 t:目标类型是r的子类型
    //得到目标类型的对应转换器
    <T extends R> Converter<S, T> getConverter(Class<T> targetType);
    }




    .......................类型转换器的注册和使用 
    有两类接口:
    1 ConverterRegistry: 注册转换器接口


    public interface ConverterRegistry {
     
    void addConverter(Converter<?, ?> converter);
     
    void addConverter(Class<?> sourceType, Class<?> targetType, Converter<?, ?> converter);
     
    void addConverter(GenericConverter converter);
     
    void addConverterFactory(ConverterFactory<?, ?> converterFactory);
     
    void removeConvertible(Class<?> sourceType, Class<?> targetType);


    }
    可以注册以上三种接口的实现::Converter 实现,GenericConverter 实现,ConverterFactory 实现


    2 ConversionService : 类型转换服务接口
    public interface ConversionService {
     
    boolean canConvert(Class<?> sourceType, Class<?> targetType);
     
    boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType);
    //将源对象转换为目标对象
    Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType);


    }


    默认实现
    DefaultConversionService:默认的类型转换服务实现 
    DefaultFormattingConversionService:带数据格式化支持的类型转换服务实现,一般使用该服务实现即可






    spring内建的类型转换器
    类名 说明 
    第一组:标量转换器 
      类名                                            说明
    StringToBooleanConverter                   String----->Boolean 
                                               true:true/on/yes/1; false:false/off/no/0 
    ObjectToStringConverter                    Object----->String 
                                               调用 toString 方法转换 
    StringToNumberConverterFactory             String----->Number(如 Integer、Long 等) 
    NumberToNumberConverterFactory             Number 子类型(Integer、Long、Double 等)<——> Number 子类型(Integer、Long、Double 等) 
    StringToCharacterConverter                 String----->java.lang.Character 取字符串第一个字符 
    NumberToCharacterConverter                 Number 子类型(Integer、Long、Double 等)——> java.lang.Character 
    CharacterToNumberFactory                   java.lang.Character ——>Number 子类型(Integer、Long、Double 等) 
    StringToEnumConverterFactory               String----->enum 类型 通过 Enum.valueOf 将字符串转换为需要的 enum 类型 
    EnumToStringConverter                      enum 类型----->String 返回 enum 对象的 name()值 
    StringToLocaleConverter                    String----->java.util.Local 
    PropertiesToStringConverter                java.util.Properties----->String 默认通过 ISO-8859-1 解码 
    StringToPropertiesConverter                String----->java.util.Properties 默认使用 ISO-8859-1 编码 


    第二组:集合、数组相关转换器 
    ArrayToCollectionConverter                 任意 S 数组---->任意 T 集合(List、Set) 
    CollectionToArrayConverter                 任意 T 集合(List、Set)---->任意 S 数组 
    ArrayToArrayConverter                      任意 S 数组<---->任意 T 数组 
    CollectionToCollectionConverter            任意 T 集合(List、Set)<---->任意 T 集合(List、Set) 即集合之间的类型转换 
    MapToMapConverter                          Map<---->Map 之间的转换 
    ArrayToStringConverter                     任意 S 数组---->String 类型 
    StringToArrayConverter                     String----->数组 默认通过“,”分割,且去除字符串的两边空格(trim) 
    ArrayToObjectConverter                     任意 S 数组---->任意 Object 的转换 (如果目标类型和源类型兼容,直接返回源对象;否则返回 S 数组的第一个元素并进行类型转换) 
    ObjectToArrayConverter                     Object----->单元素数组 
    CollectionToStringConverter                任意 T 集合(List、Set)---->String 类型 
    StringToCollectionConverter                String----->集合(List、Set) 默认通过“,”分割,且去除字符串的两边空格(trim) 
    CollectionToObjectConverter                任意 T 集合---->任意 Object 的转换 (如果目标类型和源类型兼容,直接返回源对象;否则返回 S 数组的第一个元素并进行类型转换) 
    ObjectToCollectionConverter                Object----->单元素集合




    第三组:默认(fallback)转换器:之前的转换器不能转换时调用 
    ObjectToObjectConverter                   Object(S)----->Object(T) 首先尝试 valueOf 进行转换、没有则尝试 new 构造器(S) 
    IdToEntityConverter                       Id(S)----->Entity(T) 查找并调用 public static T find[EntityName](S)获取目标对象,EntityName 是 T 类型的简单类型 
    FallbackObjectToStringConverter           Object----->StringConversionService 作为恢复使用,即其他转换器不能转换时调用(执行对象的toString()方法) 






    示例程序
    //自定义类型转换器????
    public class StringToPhoneNumberConverter implements Converter<String, PhoneNumberModel> {
    Pattern pattern = Pattern.compile("^(\d{3,4})-(\d{7,8})$");


    @Override
    public PhoneNumberModel convert(String source) {
    // 如果string为空
    if (!StringUtils.hasLength(source)) {
    return null;
    }
    Matcher matcher = pattern.matcher(source);
    if (matcher.matches()) {// 如果匹配进行转换
    PhoneNumberModel phoneNumber = new PhoneNumberModel();
    phoneNumber.setAreaCode(matcher.group(1));
    phoneNumber.setPhoneNumber(matcher.group(2));
    return phoneNumber;


    } else {
    throw new IllegalArgumentException(String.format("类型转换失败,需要类型格式:[010-12345678],但格式是[%s]", source));
    }
    }
    }








     


    修改spring配置文件:
    <!-- 注解 HandlerAdapter -->
    <bean
    class="org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter">
    <!-- 注册ConfigurableWebBindingInitializer -->
    <property name="webBindingInitializer" ref="webBindingInitializer" />
    </bean>
    <!-- 注册ConversionService和自定义类型转换器 -->
    <bean id="conversionService"
    class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
    <property name="converters">
    <list>
    <bean
    class="cn.yue.mvc.anno.controller.support.converter.StringToPhoneNumberConverter" />
    </list>
    </property>
    </bean>


    <!-- 使用ConfigurableWebBindingInitializer注册conversionService -->
    <bean id="webBindingInitializer"
    class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
    <property name="conversionService" ref="conversionService" />
    </bean>








    ----------------------数据格式化
    格式化转换器????
    1 printer: 格式化显示接口
    public interface Printer<T> { 
     String print(T object, Locale locale); 



    2 parser:  解析接口 
    public interface Parser<T> { 
     T parse(String text, Locale locale) throws ParseException; 
    }
     
    3 Formatter: 格式化spi接口
    public interface Formatter<T> extends Printer<T>, Parser<T> { 
    }


    4 annotationFormatterFactory:注册驱动的字段格式化工厂 
    //可以识别的注解类型 
    public interface AnnotationFormatterFactory<A extends Annotation> {
    //可以被 A 注解类型注解的字段类型集合
     Set<Class<?>> getFieldTypes(); 
    //根据 A 注解类型和 fieldType 类型获取 Printer 
     Printer<?> getPrinter(A annotation, Class<?> fieldType);
    //根据 A 注解类型和 ieldType 类型获取 Parser 
     Parser<?> getParser(A annotation, Class<?> fieldType);






    格式化转换器的注册和使用
    FormatterRegistry:注册格式化转换器
    FormattingConversionService:运行时类型转换和格式化服务接口




    spring内建格式化转换器
    类名                                                    说明 
    DateFormatter                                     java.util.Date<---->String 实现日期的格式化/解析 
    NumberFormatter                                   java.lang.Number<---->String 实现通用样式的格式化/解析 
    CurrencyFormatter                                 java.lang.BigDecimal<---->String 实现货币样式的格式化/解析 
    PercentFormatter                                  java.lang.Number<---->String 实现百分数样式的格式化/解析 
    NumberFormatAnnotationFormatterFactory            @NumberFormat 注解类型的数字字段类型<---->String 
                                                      ①通过@NumberFormat 指定格式化/解析格式 
                                                      ②可以格式化/解析的数字类型:Short、Integer、Long、Float、Double、BigDecimal、BigInteger 
    JodaDateTimeFormatAnnotationFormatterFactory      @DateTimeFormat 注解类型的日期字段类型<---->String 
     ①通过@DateTimeFormat 指定格式化/解析格式 
     ②可以格式化/解析的日期类型: 
     joda 中 的 日 期 类 型 ( org.joda.time 包 中 的 ): LocalDate 、
     LocalDateTime、LocalTime、ReadableInstant 
     java 内置的日期类型:Date、Calendar、Long 
     
     classpath 中必须有 Joda-Time 类库,否则无法格式化日期类型




    示例程序:
    //自定义formatter进行解析和格式化????
    public class PhoneNumberFormatter implements Formatter<PhoneNumberModel> {
    Pattern pattern = Pattern.compile("^(\d{3,4})-(\d{7,8})$");


    @Override
    public String print(PhoneNumberModel phoneNumber, Locale locale) {
    if (phoneNumber == null) {
    return "";
    }
    return new StringBuilder().append(phoneNumber.getAreaCode()).append("-").append(phoneNumber.getPhoneNumber()).toString();
    }


    @Override
    public PhoneNumberModel parse(String text, Locale locale) throws ParseException {
    // 如果 source 为空 返回 null
    if (!StringUtils.hasLength(text)) {
    return null;
    }
    Matcher matcher = pattern.matcher(text);
    // 如果匹配 进行转换
    if (matcher.matches()) {
    PhoneNumberModel phoneNumber = new PhoneNumberModel();
    phoneNumber.setAreaCode(matcher.group(1));
    phoneNumber.setPhoneNumber(matcher.group(2));
    return phoneNumber;
    } else { // 如果不匹配 转换失败
    throw new IllegalArgumentException(String.format("类型转换失败,需要格式[010-12345678],但格式是[%s]", text));
    }


    }


    }






    字段级别的格式化?????
    public class FormatterModel {
    @NumberFormat(style = Style.NUMBER, pattern = "#,###")
    private int totalCount;
    @NumberFormat(style = Style.PERCENT)
    private double discount;
    @NumberFormat(style = Style.CURRENCY)
    private double sumMoney;


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


    @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date orderDate;


    public int getTotalCount() {
    return totalCount;
    }


    public void setTotalCount(int totalCount) {
    this.totalCount = totalCount;
    }


    public double getDiscount() {
    return discount;
    }


    public void setDiscount(double discount) {
    this.discount = discount;
    }


    public double getSumMoney() {
    return sumMoney;
    }


    public void setSumMoney(double sumMoney) {
    this.sumMoney = sumMoney;
    }


    public Date getRegisterDate() {
    return registerDate;
    }


    public void setRegisterDate(Date registerDate) {
    this.registerDate = registerDate;
    }


    public Date getOrderDate() {
    return orderDate;
    }


    public void setOrderDate(Date orderDate) {
    this.orderDate = orderDate;
    }


    }


    测试:
    public void testFormatterModel() throws SecurityException, NoSuchFieldException {
    // 默认自动注册对@NumberFormat和@DateTimeFormat的支持
    DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();


    // 准备测试模型对象
    FormatterModel model = new FormatterModel();
    model.setTotalCount(10000);
    model.setDiscount(0.51);
    model.setSumMoney(10000.13);
    model.setRegisterDate(new Date(2012 - 1900, 4, 1));
    model.setOrderDate(new Date(2012 - 1900, 4, 1, 20, 18, 18));


    // 获取类型信息
    TypeDescriptor descriptor = new TypeDescriptor(FormatterModel.class.getDeclaredField("totalCount"));
    TypeDescriptor stringDescriptor = TypeDescriptor.valueOf(String.class);


    Assert.assertEquals("10,000", conversionService.convert(model.getTotalCount(), descriptor, stringDescriptor));
    Assert.assertEquals(model.getTotalCount(), conversionService.convert("10,000", stringDescriptor, descriptor));


    }
    }




    自定义注解进行字段级别的解析/格式化????


    自定义注解
    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER}) 
    @Retention(RetentionPolicy.RUNTIME) 
    public @interface PhoneNumber { 
    }
     
    实现annotationFormatterFactor注册格式化工厂
     @PhoneNumber 
    private PhoneNumberModel phoneNumber;
     
     
    测试用例 
     
     




    ---------------------------数据验证 
    控制器
    @Controller
    public class RegisterSimpleFormController {
    private UserModelValidator validator = new UserModelValidator();


    /**
    * 暴露表单引用对象为模型数据

    * @return
    */
    @ModelAttribute("user")
    public UserModel getUser() {
    return new UserModel();
    }


    /**
    * 表单展示

    * @return
    */
    @RequestMapping(value = "/annoValidator", method = RequestMethod.GET)
    public String showRegisterForm() {
    System.out.println("anno showRegisterForm");
    return "validate/registerAndValidator";
    }


    /**
    * 表单提交

    * @param user
    * @param errors
    * @return
    */
    @RequestMapping(value = "/annoValidator", method = RequestMethod.POST)
    public String submitForm(@ModelAttribute("user") UserModel user, Errors errors) {
    System.out.println("anno submitForm");
    // 调用UserModelValidator的validate方法进行验证
    validator.validate(user, errors);
    // 如果有错误再回到表单展示页面
    if (errors.hasErrors()) {
    return showRegisterForm();
    }
    return "redirect:/success";
    }


    }


     
    修改spring 配置文件
    <!-- 基于注解的方式实现数据验证 -->
    <bean class="cn.yue.mvc.anno.controller.RegisterSimpleFormController" />
     
    页面视图
    /jsp/registerAndValidator.jsp (复制之前所用)


     
    声明式数据验证??????


    添加验证框架
     
     参考:http://jinnianshilongnian.iteye.com/blog/1752171 
     
     





  • 相关阅读:
    importlib
    js给kindeditor添加值
    在kindeditor 获取textarea 中 输入的值
    获取lable选中时触发事件
    Django之ModelForm组件
    KindEditor 和 xss过滤
    from 动态显示select数据
    CBV 验证装饰器的使用
    views获取数据 -- request包含的方法
    django -- 自定义simpletag 和 filter
  • 原文地址:https://www.cnblogs.com/retacn-yue/p/6194262.html
Copyright © 2020-2023  润新知