数据转换

数据绑定流程

  1. Spring MVC主框架ServletRequest对象及目标方法的入参实例传递给WebDataBinderFactory实例,以创建DataBinder实例对象
  2. DataBinder调用装配在Spring MVC上下文中的ConversionService组件进行数据类型转换、数据格式化工作。将Servlet中的请求信息填充到入参对象中。
  3. 调用Validator组件对已经绑定了请求消息的入参对象进行数据合法性校验,并最终生成数据绑定结果BindingData对象。
  4. Spring MVC抽取BindingResult中的入参对象和校验错误对象,将他们赋给处理方法的响应入参。
  5. Spring MVC通过反射机制对目标处理方法进行解析,将请求消息绑定到处理方法的入参中。数据绑定的核心部件是DataBinder,运行机制如下:
  6. image-20200920122803187.png

    1. WebDataBinder binder = binderFactory.createBinder(request, attribute, name);
    2. if (binder.getTarget() != null) {
    3. bindRequestParameters(binder, request);
    4. validataIfApplicable(binder, parameter);
    5. if (binder.getBindingResult().hasErrors()) {
    6. if (isBindExceptionRequired(binder, parameter)) {
    7. throw new BindException(binder.getBindingResult());
    8. }
    9. }
    10. }
  7. Spring MVC 上下文中内创建了很多转换器,可完成大多数java类型的转换工作。

  8. image-20200920130633885.png

自定义类型转换器

  1. ConversionService是Spring类型转换体系的核心接口。
  2. 可以利用ConversionServiceFactoryBean在Spring的IOC容器中定义一个ConversionService,并在Bean属性配置及Spring MVC处理方法入参绑定等场合使用它进行数据转换。
  3. 可通过ConversionServiceFactoryBean的converters属性注册自定义的类型转换器。

    1. <bean id="conversionService" class="org.springframework.context.suppport.ConversionServiceFactoryBean">
    2. <property name="converters">
    3. <list>
    4. <bean class="com.zh.springmvc.UserConverter"></bean>
    5. </list>
    6. </property>
    7. </bean>
  4. 转换示例:

    @Component
    public class UserConverter implements Converter<String, Employee> {
    
     @Override
     public Employee convert(String string) {
         // GG-gg@zh.com-0-105
         if (string != null) {
             String[] splits = string.split("-");
             if (splits != null && splits.length == 4) {
                 String lastName = splits[0];
                 String email = splits[1];
                 Integer gender = Integer.parseInt(splits[2]);
                 Department department = new Department();
                 department.setId(Integer.parseInt(splits[3]));
    
                 Employee employee = new Employee(null, lastName, email, gender, department);
                 System.out.println(string + "---UserConverter.convert---" + employee);
                 return employee;
             }
         }
         return null;
     }
    }
    

Spring 支持的转换器

Spring定义了3种类型的转换器接口,实现任意一个转换器接口斗可以定义为自定义转换器注册到ConversionServiceFactoryBean中:

  1. Converter :将S类型对象转为T类型对象
  2. ConverterFactory:将相同系列多个“同质”Converter封装在一起。如果希望将一种类型的对象转换为另一种类型及其子类的对象(例如将String转换为Number及Number子类(Integer、Long、Double等)对象)可使用该转换器工厂类。
  3. GenericConverter:会根据源类对象及目标类对象所在的宿主类中的上下文信息进行转换。

自定义转换器示例

会将自定义的ConversionService注册到Spring MVC的上下文中。

<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>

<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
    <property name="converters">
        <list>
            <bean class="com.zh.springmvc.UserConverter"></bean>
        </list>
    </property>
</bean>
@RequestMapping("/handle24")
public String handle24(@RequestParam("user") User user) {
    System.out.println(user);
    return "success";
}
@Component
public class UserConverter implements Converter<String, Employee> {

    @Override
    public Employee convert(String string) {
        // GG-gg@zh.com-0-105
        if (string != null) {
            String[] splits = string.split("-");
            if (splits != null && splits.length == 4) {
                String lastName = splits[0];
                String email = splits[1];
                Integer gender = Integer.parseInt(splits[2]);
                Department department = new Department();
                department.setId(Integer.parseInt(splits[3]));

                Employee employee = new Employee(null, lastName, email, gender, department);
                System.out.println(string + "---UserConverter.convert---" + employee);
                return employee;
            }
        }
        return null;
    }
}

关于MVC:annotation-driven

  1. 会自动注册RequestMappingHandlerMapping、RequestMappingHandlerAdapter与ExceptionHandlerExceptionResolver三个bean。
  2. 还将提供一下支持:
    1. 支持使用ConversionService实例对表单参数进行类型转换。
    2. 支持使用@NumberFormat annotation、@DateTimeFormat注解完成数据类型的格式化。
    3. 支持使用@Valid注解对JavaBean实例进行JSR 303验证。
    4. 支持使用@RequestBody和ResponseBody注解。

image-20200920135116361.png

@InitBinder

  1. 由@InitBinder标识的方法,可以对WebDataBinder对象进行初始化。WebDataBinder是DataBinder的子类,用于完成由表单字段到JavaBean属性的绑定。
  2. @InitBinder方法不能有返回值,他必须声明为void。
  3. @InitBinder方法的参数通常是WebDataBinder
    @InitBinder
    public void initBinder(WebDataBinder databinder) {
     dataBinder.setDisallwedFields("roleSet");
    }
    

数据格式化

  1. 对属性的输入/输出进行格式化,从其本质上讲依然属于“类型转换”的范畴。
  2. Spring 在格式化模块中定义了一个实现ConversionService接口的FormattingConversionService实现类,该实现类扩展了GenericConversionService,因此他既有类型转换的功能,又具有格式化的功能。
  3. FormattingConversionService拥有一个FormattingConversionServiceFactoryBean工厂类,后者用于在Spring上下文忠构造前者。
  4. FormattingConversionServiceFactoryBean内部已经注册了:
    1. NumberFormatAnnotationFormatterFactory:支持对数字类型的属性使用@NumberFormat注解。
    2. JodaDateTimeFormatAnnotationFormatterFactory:支持对日期类型的属性使用@DateTimeFormat注解。
  5. 装配了FormattingConversionServiceFactoryBean后,就可以在Spring MVC入参绑定及模型数据输出时使用注解驱动了。默认创建的ConversionService实例即为FormattingConversionServiceFactoryBean

日期格式化

@DateTimeFormat注解可对java.util.Date、java.util.Calendar、java.long.Long事件类型进行标注:

  1. pattern属性:类型为字符串。所制定解析/格式化字段数据的模式,如:“yyyy-MM-dd hh:mm:ss”
  2. iso属性:类型为DateTimeFormat.ISO。指定解析/格式化字段数据的ISO模式,包括四种:ISO.NONE(不使用)— 默认、ISO.DATE(yyyy-MM-dd)、ISO.TIME(hh:mm:ss.SSSZ)、ISO.DATE_TIME(yyyy-MM-dd hh:mm:ss.SSSZ)
  3. style属性:字符串类型。通过样式指定日期时间的格式,有两位字符组成,第一位表示日期的格式,第二位表示时间的格式:S:短日期,M:中日期/时间格式,L:长日期/时间格式,F:完整日期/时间格式,-:忽略日期或时间格式。

数值格式化

@NumberFormat可对类似数字类型的属性进行标注,它拥有两个互斥的属性:

  1. style: 类型为 NumberFormat.Style。用于指定样式类 型,包括三种:Style.NUMBER(正常数字类型)、 Style.CURRENCY(货币类型)、 Style.PERCENT( 百分数类型)
  2. pattern:类型为String,自定义样式,如patter=”#,###”;

格式化示例

<mvc:annotation-driven></mvc:annotation-driven>
public class User{

    @DateTimeFormat(patern="yyyy/MM/dd")
    private Date birthday;
}

@RequestMapping("/handle19")
public String handle19(@ModelAttribute("user") User user) {
    user.setId(1000);
    System.out.println(User);
    return "success";
}

数据校验

JSR 303

  1. JSR 303是java为Bean数据合法性校验提供的标准框架,他已经包含在JavaEE6.0中。
  2. JSR 303通过在Bean属性上标注类似于@NotNull、@Max等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证 | 注解 | 功能说明 | | —- | —- | | @Null | 被注释的元素必须是null | | @NotNull | 被注释的元素必须不为null | | @AssertTrue | 被注释的元素必须为true | | @AssertFalse | 被注释的元素必须为false | | @Min(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | | @Max(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @DecimalMin(value) | 被注释的元素必须是一个数字,其值必须大于等于指定的最小值 | | @DecimalMax(value) | 被注释的元素必须是一个数字,其值必须小于等于指定的最大值 | | @Size(max, min) | 被注释的元素的大小必须在指定的范围内。 | | @Digits(Integer, fraction) | 被注释的元素必须是一个数字,其值必须在可接受的范围内 | | @Past | 被注释的元素必须是一个过去的日期 | | @Future | 被注释的元素必须是一个将来的日期 | | @Pattern(value) | 被注释的元素必须符合指定的正则表达式。 |

Hibernate Validator扩展注解

Hibernate Validator是JSR 303的一个参考实现,除支持所有标准的校验注解外,它还支持一下的扩展注解

注解 功能说明
@Email 被注释的元素必须是电子邮箱地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty 被注释的字符串的必须非空
@Range 被注释的元素必须在合适的范围内

Spring MVC数据校验

  1. Spring 4.0拥有自己独立的数据校验框架,同时支持JSR 303标准的校验框架。
  2. Spring在进行数据绑定时,可通过调用校验框架完成数据校验工作。在Spring MVC中,可直接通过注解驱动的方式进行数据校验。
  3. Spring 的 LocalValidatorFactoryBean既实现了Spring的Validator接口,也实现了 JSR 303的Validator接口。只要在Spring容器中定义了一个LocalVaildatorFactoryBean,即可将其注入到需要数据校验的Bean中。
  4. Spring 本身并没有提供JSR303的实现,所以必须将JSR303的实现者的jar包放到类路径下。
  5. 会默认装配好一个LocalValidatorFactoryBean,通过在处理方法的入参上标注@valid注解即可以让Spring MVC在完成数据绑定后执行数据校验的工作。
  6. Spring MVC是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是BindingResult或Errors类型,这两个类都位于org.springframework.validation包中。
  7. 需校验的Bean对象和绑定结果对象或错误对象时成对出现的,他们之间不需要声明其他的入参。
  8. Errors接口体用了获取错误信息的方法,如getErrorCount()或getFieldErrors(String field)
  9. BindingResult扩展了Errors接口
    public String handle(@Vaild User user, BindingResult userBindingResult, String sessionId, ModelMap mm, @Vaild Dept dept, Errors deptErrors) {}
    

在目标方法中获取校验结果

  1. 在表单/命令对象类的属性中标注校验注解,在处理方法对 应的入参前添加 @Valid,Spring MVC 就会实施校验并将校 验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。
  2. FieldError getFieldError(String field)
  3. List getFieldErrors()
  4. Object getFieldValue(String field)
  5. Int getErrorCount()

在页面上显示错误

  1. Spring MVC除了会将表单/命令对象的校验结果保存到对应的BindingResult或Errors对象中外,还会将所有校验结果保存到“隐含模型”
  2. 即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在“隐含对象”中。
  3. 隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给JSP视图对象,因此在JSP中可以获取错误信息。
  4. 在JSP页面上可通过 显示错误消息
<!-- index.jsp -->
<form:form action="hello/handle19.action" modelAttribute="user">
    <form:errors path="*"></form:errors>

    name: <input type="text" name="userName"/>
    <form:errors path="userName"></form:errors>

    email: <input type="text" name="email"/>
    <form:errors path="email"></form:errors>

    <input type="submit" value="Submit"/>
</form:form>
// User
@DateTimeFormat(pattern="yyyy/MM/dd")
@Past
private Date birthday;

@NumberFormat(pattern="#,###.##")
@DecimalMax("9999")
@DecimalMin("1000")
private long salary;

@Pattern(regexp="\\w(4,30)")
private String userName;

@Email
private String email;
// 目标方法
@RequestMapping("/handle19")
public String handle19(@Vaild @ModelAttribute("user") User user, BindingResult bindingResult) {
    if(bindingResult.hasErrors()) {
        return "forward:/index.jsp";
    }else {
        System.out.println("验证通过!");
    }
    user.setId(1000);
    System.out.println(user);
    return "success";

}

提示消息的国际化

  1. 每个属性在数据绑定和数据校验发生时,都会生成一个对应的FieldError对象。
  2. 当一个属性校验失败后,校验框架会为该属性生成4个消息代码,这些代码以校验注解类名为前缀,结合modleAttribute、属性名及属性类型名生成多个对应的消息代码:例如User类中的password属性标注了一个@Pattern所定义的规则时,就会产生一下4个错误代码:
    1. Pattern.user.password
    2. Pattern.password
    3. Pattern.java.lang.String
    4. Pattern
  3. 当使用Spring MVC标签显示错误时,Spring MVC会查看WEB上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。
  4. 若数据类型转换或数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:
    1. required:必要的参数不存在。如:@RequiredParam(“param1”)标注了一个入参,但是该参数不存在。
    2. typeMismatch:在数据绑定时发生数据类型不匹配的问题
    3. methodInvocation:Spring MVC在调用处理方法时发生了错误
  5. 注册国际化资源文件
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
     <property name="basename" value=“i18n”></property>
    </bean>