一、数据校验

  项目中涉及到数据校验,如果只做前端校验是不安全的,我们可以绕过前端校验,重要数据一定要加上后端校验;

  1、通过程序,每个数据取出,进行校验,如果失败直接来到添加页面,提示其重新填写;(不推荐)

  2、SpringMVC:可以利用 JSR303 做数据校验;

 

二、如何校验

  1、JSR 303 

    JSR 303 是 Java 为 Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0 中 。

    JSR 303 (Java Specification Requests意思是Java 规范提案)通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证:

    

 

 

 

  2、Hibernate Validator 扩展注解

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

    

 

 

 

  3、Spring MVC 数据校验

  Spring 4.0 拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架;

  Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在 Spring MVC 中,可直接通过注解驱动的方式进行数据校验

  Spring 的 LocalValidatorFactroyBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在 Spring 容器中定义了一个 LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean 中;

  Spring 本身并没有提供 JSR303 的实现,所以必须将 JSR303 的实现者的 jar 包放到类路径下;

  <mvc:annotation-driven /> 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Valid 注解即可让 Spring MVC 在完成数据绑定后执行数据校验的工作;

  在已经标注了 JSR303 注解的表单/命令对象前标注一个 @Valid,Spring MVC 框架在将请求参数绑定到该入参对象后,就会调用校验框架根据注解声明的校验规则实施校验;

 

三、实现数据校验

  1、使用JSR 303验证标准

  2、加入hibernate validator验证框架

  3、在SpringMVC配置文件中增加< mvc:annotation-driven />

  4、需要在bean的属性上增加对应验证的注解

  5、在目标方法bean类型的前面增加@Valid注解

四、实验代码

  1、添加依赖

    Maven 方式:

            <dependency>
                <groupId>org.hibernate</groupId>
                <artifactId>hibernate-validator</artifactId>
                <version>5.2.4.Final</version>
            </dependency>

 

    jar 包方式:

hibernate-validator-5.0.0.CR2.jar
hibernate-validator-annotation-processor-5.0.0.CR2.jar
classmate-0.8.0.jar
jboss-logging-3.1.1.GA.jar
validation-api-1.1.0.CR1.jar

 

    注意:推荐使用 Tomcat7.0Tomcat7.0以上的 el 表达式比较强大;

  2、给验证属性上增加验证注解

public class Employee {

    private Integer id;
    @NotBlank
    @NotEmpty
    @Length(min = 6, max = 10, message = "姓名超出长度限制")
    private String lastName;

    @NotBlank
    @Email
    @Length(max = 15, message = "电子邮件超出长度限制")
    private String email;

    private Integer gender;

    //可以规定页面提交的日期格式
    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Past     //必须是一个过去的时间
    //@Future //必须是一个未来的时间
    private Date birth;

    //假设页面,为了显示方便提交的工资 10000.98,---》 ¥10,000.98
    @NumberFormat(pattern="#,###,###.##")
    private Double salary;
    private Department department;
}

 

  3、在SpringMVC封装对象的时候,告诉SpringMVC这个 JavaBean 需要校验

    /**
     * 保存员工
     * 增加@Valid注解,验证失败会报错。
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee) {
        System.out.println("employee = " + employee);
        return "success";
    }

 

  4、如何知道校验结果?

  给需要校验的 JavaBean 后面紧跟一个 BindingResult,这个BindingResult 就是封装了前一个Bean的校验结果而且不能间隔任何参数。

    /**
     * 保存员工
     * 增加@Valid注解,验证失败会报错。
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result) {
        System.out.println("employee = " + employee);
        //是否有校验错误
        if (result.hasErrors()) {
            System.out.println("有校验错误!!!");
            return "add";
        } else {
            //employeeDao.save(employee);
            System.out.println("保存成功!");
            return "success";
        }
    }

 

    根据不同的校验结果然后决定如何操作

 

五、错误信息的显示

  1、关于错误信息

public interface BindingResult extends Errors

  

  Spring MVC 是通过对处理方法签名的规约来保存校验结果的:前一个表单/命令对象的校验结果保存到随后的入参中,这个保存校验结果的入参必须是 BindingResult 或 Errors 类型,这两个类都位于 org.springframework.validation 包中;

  需校验的 Bean 对象和其绑定结果对象或错误对象是成对出现的,它们之间不允许声明其他的入参

  Errors 接口提供了获取错误信息的方法,如 getErrorCount() 或 getFieldErrors(String field)

  BindingResult 扩展了 Errors 接口

  

 

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

    在表单/命令对象类的属性中标注校验注解,在处理方法对应的入参前添加 @Valid,Spring MVC 就会实施校验并将校验结果保存在被校验入参对象之后的 BindingResult 或 Errors 入参中。

    常用方法:

FieldError getFieldError(String field)
List<FieldError> getFieldErrors()
Object getFieldValue(String field)
Int getErrorCount()

 

 

  2、使用表单标签回显错误结果

    Spring MVC 除了会将表单/命令对象的校验结果保存到对应的 BindingResult 或 Errors 对象中外,还会将所有校验结果保存到 “隐含模型”;

    即使处理方法的签名中没有对应于表单/命令对象的结果入参,校验结果也会保存在 “隐含对象” 中;

    隐含模型中的所有数据最终将通过 HttpServletRequest 的属性列表暴露给 JSP 视图对象,因此在 JSP 中可以获取错误信息

    在 JSP 页面上可通过 <form:errors path=“userName”> 显示错误消息

    使用 <form:errors> 标签可以显示该属性的错误提示:

<form:errors path="属性"/>

     示例:

    在表单上页面上显示所有的错误消息:

<!-- 显示所有的错误消息 -->
<form:errors path="*"/>

  

    显示某一个表单域的错误消息:

<form:errors  path="lastName"/>  

  

    显示校验的提示信息:

<form:form action="${ctx}/emp" modelAttribute="employee" method="POST">
    lastName:<form:input path="lastName" /> <form:errors path="lastName"/> <br/>
    email:<form:input path="email" /> <form:errors path="email"/><br/>
    gender:<br><form:radiobutton path="gender" value="1"/><br/><form:radiobutton path="gender" value="0" /><br/>
    birth:<form:input path="birth" /> <form:errors path="birth"/><br/>
    salary:<form:input path="salary" /><br/>
    dept:
    <form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
    <br>
    <input type="submit" value="保存">
</form:form>

 

  3、将校验信息回写

    BindingResult 中封装了类型转换与格式化的错误信息,可以把错误信息放在请求域中,带回给页面。

    示例:

    /**
     * 保存员工
     * 增加@Valid注解,验证失败会报错。
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result, Model model) {
        System.out.println("employee = " + employee);
        Map<String, Object> errorsMap = new HashMap<>();
        //是否有校验错误
        if (result.hasErrors()) {
            System.out.println("有校验错误!!!");
            List<FieldError> errors = result.getFieldErrors();
            for (FieldError fieldError : errors) {
                System.out.println("错误消息提示:" + fieldError.getDefaultMessage()); //错误信息
                System.out.println("错误的字段是:" + fieldError.getField());          //错误字段
                System.out.println(fieldError);
                errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage());
            }
            model.addAttribute("errorInfo", errorsMap);
            return "add";
        } else {
            //employeeDao.save(employee);
            System.out.println("保存成功!");
            return "success";
        }
    }

 

五、提示消息的国际化 

  每个属性在数据绑定和数据校验发生错误时,都会生成一个对应的 FieldError 对象。

  当一个属性校验失败后,校验框架会为该属性生成 4 个消息代码,这些代码以校验注解类名为前缀结合 modleAttribute、属性名及属性类型名生成多个对应的消息代码:

  例如 User 类中的 password 属性标注了一个 @Pattern 注解,当该属性值不满足 @Pattern 所定义的规则时, 就会产生以下 4 个错误代码:

Pattern.user.password

Pattern.password

Pattern.java.lang.String

Pattern

  当使用 Spring MVC 标签显示错误消息时, Spring MVC 会查看 WEB 上下文是否装配了对应的国际化消息,如果没有,则显示默认的错误消息,否则使用国际化消息。

  若数据类型转换数据格式转换时发生错误,或该有的参数不存在,或调用处理方法时发生错误,都会在隐含模型中创建错误消息。其错误代码前缀说明如下:

required:必要的参数不存在。如 @RequiredParam(“param1”) 标注了一个入参,但是该参数不存在

typeMismatch:在数据绑定时,发生数据类型不匹配的问题

methodInvocation:Spring MVC 在调用处理方法时发生了错误

  

  注册国际化资源文件

  

 

 

   国际化资源文件怎么写呢?对应的 key 是什么?

  每一个字段发生错误以后,都会有自己的错误代码;国际化文件中错误消息的 key 必须对应一个错误代码;

  错误信息:

Field error in object 'employee' on field 'email': rejected value [aa];
codes [Email.employee.email,Email.email,Email.java.lang.String,Email];
arguments [org.springframework.context.support.DefaultMessageSourceResolvable:
codes [employee.email,email]; arguments []; default message [email],[Ljavax.validation.constraints.Pattern$Flag;@4e69aec9,.*];
default message [不是一个合法的电子邮件地址]

  codes:

Email.employee.email        校验规则(@Email).隐含模型中这个对象的key.对象的属性
Email.email                 校验规则.属性名
Email.java.lang.String      校验规则.属性类型
Email                       校验规则

  

  说明:

1、如果隐含模型中 employee 对象的 email 字段发生了 @Email 校验错误,就会生成 Email.employee.email;
2、Email.email:所有的 email 属性只要发生了  @Email 校验错误;
3、Email.java.lang.String:只要是 String 类型发生了 @Email 校验错误;
4、Email:只要发生了 @Email 校验错误;

   注意:如果根据 codes 配置了多个值,精确匹配的 codes 优先生效;

六、提示消息的国际化实验

  1、定义国际化资源文件

    中文:errors_zh_CN.properties

Email.email=邮箱错误!!
Email=邮箱不正确!!
NotEmpty=不允许为空!!
Length.java.lang.String=长度错误!!
Past=必须是一个过去时间!!
typeMismatch.employee.birth=生日格式不正确!!
typeMismatch=格式不正确!!
Length=长度错误!!

 

    英文:errors_en_US.properties

Email.email=email incorrect~~
Email=email error~~
NotEmpty=must not empty~~
Length.java.lang.String=length incorrect {0},must between {2} and {1}~~
Past=must a past time~~
typeMismatch.employee.birth=birthday incorrect~~
typeMismatch=formatting incorrect~~
Length=length incorrect~~

 

  2、声明国际化资源配置(让SpringMVC来管理国际化资源文件)

    <!--管理国际化资源文件-->
    <bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
        <property name="basename" value="errors"/>
    </bean>

 

  3、错误信息国际化回显

<form:form action="${ctx}/emp" modelAttribute="employee" method="POST">
    lastName:<form:input path="lastName" />
            <form:errors path="lastName"/> --->  ${errorInfo.lastName}<br/>
    email:<form:input path="email" />
            <form:errors path="email"/> ---> ${errorInfo.email}<br/>
    gender:<br><form:radiobutton path="gender" value="1"/><br/><form:radiobutton path="gender" value="0" /><br/>
    birth:<form:input path="birth" />
            <form:errors path="birth"/> ---> ${errorInfo.birth}<br/>
    salary:<form:input path="salary" /><br/>
    dept:
    <form:select path="department.id" items="${depts}" itemLabel="departmentName" itemValue="id"></form:select>
    <br>
    <input type="submit" value="保存">
</form:form>

 

    格式或者校验错误后,<form:erros path=""> 就会显示定制的国际化信息。

 

  4、高级国际化

    如何在国际化文件中动态的传入参数信息?

    示例:

Length.java.lang.String=length incorrect {0},must between {2} and {1}~~

     说明:

{0} 永远都是当前属性名
{1} {2} 才是参数信息,并且是按照属性大小写排序的顺序

  

 

  5、message 指定错误消息

     注释上有个属性是message,直接写上错误信息即可,缺点不能国际化

    例如:

    @NotBlank
    @NotEmpty
    @Length(min = 6, max = 10, message = "姓名超出长度限制")
    private String lastName;

    @NotBlank
    @Email
    @Length(max = 15, message = "电子邮件超出长度限制")
    private String email;

    @DateTimeFormat(pattern = "yyyy-MM-dd")
    @Past //必须是一个过去的时间
    //@Future //必须是一个未来的时间
    private Date birth;

 

  6、国际化信息的回显

    根据错误信息,然后通过国际化文件,把信息回显到页面

    /**
     * 保存员工
     * 增加@Valid注解,验证失败会报错。
     * @param employee
     * @return
     */
    @RequestMapping(value = "/emp", method = RequestMethod.POST)
    public String addEmp(@Valid Employee employee, BindingResult result, Model model,
                         @RequestHeader("Accept-Language") String language) {
        Locale locale = getLocale(language);
        System.out.println("locale = " + locale);
        System.out.println("employee = " + employee);

        Map<String, Object> errorsMap = new HashMap<>();
        //是否有校验错误
        if (result.hasErrors()) {
            System.out.println("有校验错误!!!");
            List<FieldError> errors = result.getFieldErrors();
            for (FieldError fieldError : errors) {
                System.out.println("错误消息提示:" + fieldError.getDefaultMessage()); //错误信息
                System.out.println("错误的字段是:" + fieldError.getField());          //错误字段
                System.out.println(fieldError);
                System.out.println(fieldError.getCode());  //Length
                System.out.println(Arrays.toString(fieldError.getCodes())); //[Length.employee.lastName, Length.lastName, Length.java.lang.String, Length]
                String message = messageSource.getMessage(fieldError.getCode(), new Object[0], locale);
                System.out.println("message = " + message);
                errorsMap.put(fieldError.getField(), fieldError.getDefaultMessage());
            }
            model.addAttribute("errorInfo", errorsMap);
            return "add";
        } else {
            //employeeDao.save(employee);
            System.out.println("保存成功!");
            return "success";
        }
    }

    private Locale getLocale(String language) {
        System.out.println("language = " + language);
        Locale locale = null;
        if (!StringUtils.isEmpty(language)) {
            //locale = en_US,EN;Q=0.9,ZH
            String[] split = language.split("-");
            locale = new Locale(split[0], split[1]);
        }
        System.out.println("locale = " + locale);
        return locale;
    }

 

 

  错误信息都封装在 FieldError 对象里,其中有 getCodes() 和 getCode() 方法,分别用于获取所有的 codes 值和最后一个 code 值:

  

   同时把国际化文件 MessageSource 注入进来,然后根据请求头的语言信息和 code 来读取国际化信息。

 

posted on 2021-12-02 22:20  格物致知_Tony  阅读(823)  评论(0编辑  收藏  举报