zoukankan      html  css  js  c++  java
  • 第五节:SpringMVC 数据校验

    一、数据校验

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

      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 来读取国际化信息。

  • 相关阅读:
    Spring AOP (下)
    C#和C++性能差距巨大,在高耗能和低性能设备上强烈建议不要使用
    [MOSS]Microsoft Office SharePoint Server 2007 安装与配置
    [MOSS]使用Microsoft Office SharePoint Server 2007过程中遇到的问题与解决方法
    博客索引
    springweb里面的post和get请求
    Nginx 之在windows下的安装与启动
    某外企招聘程序员部分面试题,求作答!!!
    解决Asp.net Web网站发布到服务器上运行时,某界面传参数过多,造成的“此请求的查询字符串的长度超过配置的 maxQueryStringLength 值”错误。
    使用”Sql Server 代理“下的”作业“定时执行期望的任务
  • 原文地址:https://www.cnblogs.com/niujifei/p/15635693.html
Copyright © 2011-2022 走看看