SpringMVC day03

SpringMVC day03

1 数据类型转换:日期类型

SpringMVC默认可以转换 yyyy/MM/dd HH:mm:ss 格式的字符串转换为日期类型。如果使用其它格式,就需要自定义类型转换。

1.1 DateTimeFormat注解

@Controller
public class DateController {
    @RequestMapping("/date")
    public String date(@DateTimeFormat(pattern="yyyy-MM-dd HH:mm:ss") Date date){
        System.out.println("date = [" + date + "]");
        return "forward:/index.jsp";
    }
}

特点:简单灵活,但是编码繁琐(有多少处需要转换,就需要配置多少处)

1.2 Converter 类型转换器

  1. 编码 实现Converter接口

    public class DateConverter implements Converter<String, Date> {
       private String pattern;
       public void setPattern(String pattern){
           this.pattern = pattern;
       }
    
        @Override
        //将请求中的字符串参数解析为目标日期类型数据
        public Date convert(String source) {
            SimpleDateFormat simpleDateFormat = new SimpleDateFormat(pattern);
       		
            try {
                Date date = simpleDateFormat.parse(source);
                return date;
            }catch(Exception e){
                throw new IllegalArgumentException("必须提供合法的日期字符串数据");
            }
          
        }
    }
    
  2. 配置

    <!-- 配置自定义类型转换器-->
    <bean id="dateConverter" class="com.baizhi.converter.DateConverter">
        <property name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
    </bean>
    
    <!-- 配置类型转换器工厂 -->
    <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
        <property name="converters">
            <set>
                <ref bean="dateConverter"/>
            </set>
        </property>
    </bean>
    
    <!-- 配置mvc框架使用类型转换器工厂 -->
    <mvc:annotation-driven conversion-service="conversionService"/>
    

注意:

一旦配置Converter,注解将不再起作用。

1.3 Formatter类型转换器

  1. 编码

    public class DateFormatter implements Formatter<Date> {
        private String pattern;
        private SimpleDateFormat sdf;
    
        public void setPattern(String pattern) {
            this.pattern = pattern;
            this.sdf = new SimpleDateFormat(pattern);
        }
    
        @Override
        // 将请求中的字符串参数解析为日期类型数据
        public Date parse(String text, Locale locale) throws ParseException {
            System.out.println("DateFormatter.parse");
            Date date = null;
            try{
                date = sdf.parse(text);
            }catch (ParseException e){
                throw new IllegalArgumentException(e);
            }
            return date;
        }
    
        @Override
        // 设置日期类型数据解析为特定格式的字符串
        public String print(Date object, Locale locale) {
            return sdf.format(object);
        }
    }
    
  2. 配置

    <!-- 配置mvc框架使用类型转换器工厂 -->
    <mvc:annotation-driven conversion-service="conversionService2"/>
    
    <!-- 配置类型转换器工厂-->
    <bean id="conversionService2" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
        <property name="formatters">
            <set>
                <!-- 直接将DateFormatter的bean配置在set标签中,作为set的一个元素-->
                <bean class="com.baizhi.formatter.DateFormatter">
                    <property name="pattern" value="yyyy-MM-dd HH:mm:ss"/>
                </bean>
            </set>
        </property>
    </bean>
    

注意:

一旦配置Formatter类型转换器,Converter类型转换器将不再起作用

2 SpringMVC异常处理

程序在运行时不出现异常是很难得的。现在我们写的代码中,当代码里出现异常的时候,会跳转到 Tomcat 的 500 页面显示一堆的异常信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f2zVFoS2-1630975505208)(SpringMVC day03.assets/image-20210626232830096.png)]

现在的效果存在着2个问题:

  • 用户体验差:大多数的程序运行时的异常并不严重,但再小的错误对于小白用户的冲击都是极大的
  • 暴露代码信息:异常堆栈信息暴露了程序运行的流程、错误原因、使用的框架技术,为黑客攻击提供了信息

在日常的项目开发中,我们一定要web层对dao和service层中不能处理的错误和异常进行处理,这就需要一个统一的异常处理机制处理异常。保证客户端能够收到友好的提示,这么做的目的主要是给用户一个较好的使用体验,避免一些错误信息或者错误页面直接暴露到用户面前。

2.1 实战中异常处理套路

实战中,在dao和service层未能及时处理的异常,一定在web层处理。

  • 实战中,为了精细化分辨各种异常通常我们会自定义异常。在dao和service中如果不能处理异常,将其将异常包装成自定义异常,然后再抛出。

    public class DaoException extends RuntimeException{
        //此处省略的是构造方法
        ...
    }
    
    public class ServiceException extends RuntimeException{
      	//此处省略的是构造方法
        ...
    }
    
  • 在controller中的服务方法中不使用try{}catch…处理异常,而是通过SpringMVC的全局异常处理机制,统一处理程序中抛出的所有异常。

SpringMVC中提供了全局异常处理机制

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oZ39Pttf-1630975505210)(SpringMVC day03.assets/image-20210626235444538.png)]

2.2 HandlerExceptionResolver接口

第1种方式,编码方式:实现HanlderInterceptor接口

  1. 在Controller中模拟程序中各种异常

    @Controller
    @RequestMapping("ex")
    public class ExceptionController {
    
        @RequestMapping("throw")
        public String testThrow(){
            System.out.println("ExceptionController.testThrow");
            throw new RuntimeException("Controller运行时异常");
        }
    
        @RequestMapping("dao")
        public String testDao(){
            System.out.println("ExceptionController.testDao");
            throw new DaoException("运行时,dao出现异常");
        }
    
        @RequestMapping("service")
        public String testService(){
            System.out.println("ExceptionController.testService");
            throw new ServiceException("运行时,service出现异常");
        }
    }
    
  2. 编码:定义全局异常处理类

    public class MyHandlerExceptionResolver implements HandlerExceptionResolver {
        @Override
        /*
            request 请求
            response 响应
            handler 服务方法
            ex 异常对象
         */
        public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
            System.out.println("MyHandlerExceptionResolver.resolveException");
            
            //将异常信息保存到request作用域
            request.setAttribute("message",ex.getMessage());
            //根据异常类型,进行针对性的处理
            if(ex instanceof DaoException){
                System.out.println("Dao层产生的异常");
    
                return new ModelAndView("forward:/error-dao.jsp");
            }else if(ex instanceof ServiceException){
                System.out.println("Service层产生的异常");
    
                return new ModelAndView("forward:/error-service.jsp");
            }
            //其他异常,统一跳转到error.jsp
            return new ModelAndView("forward:/error.jsp");
        }
    }
    
  3. 配置

    <!-- 配置自定义的全局异常解析器-->
    <bean class="com.baizhi.exception.MyHandlerExceptionResolver"/>
    

2.3 ExceptionHandler和ControllerAdvice注解

第2种方式:使用ExceptionHandler注解

2.3.1 ExceptionHandler注解基本使用
  1. 在Controller类中定义一个处理异常的方法

    @Controller
    @RequestMapping("ex")
    public class ExceptionController {
    
        @RequestMapping("throw")
        public String testThrow(){
            System.out.println("ExceptionController.testThrow");
            throw new RuntimeException("Controller运行时异常");
        }
    
        @RequestMapping("dao")
        public String testDao(){
            System.out.println("ExceptionController.testDao");
            throw new DaoException("运行时,dao出现异常");
        }
    
        @RequestMapping("service")
        public String testService(){
            System.out.println("ExceptionController.testService");
            throw new ServiceException("运行时,service出现异常");
        }
    
        //处理异常的方法
        public ModelAndView handleException(){
            System.out.println("ExceptionController.handleException");
            return "forward:/error.jsp";
        }
    }
    
  2. 在处理异常的方法上添加ExceptionHandler注解

    //添加注解,并明确处理方法要处理的异常类型
    @ExceptionHandler(Exception.class)
    public ModelAndView handleException(){
        System.out.println("ExceptionController.handleException");
        return new ModelAndView("forward:/error.jsp");
    }
    

说明:

  • ExceptionHandler注解描述处理方法,在服务方法发生设置类型的异常时执行处理方法
  • ExceptionHandler注解方式优先于HandlerExceptionResolver接口方式执行
2.3.2 ExceptionHandler注解详解
  1. 处理方法的形参列表很灵活,可以定义 HttpServletRequestHttpServletResponseModel 和 各种异常类型参数;返回值类型也可以定义为String

    
    /*
            request 请求
            response 响应
            model 模型,传递数据
            e 程序运行出现的异常
         */
    @ExceptionHandler(Exception.class)
    public String handleException(HttpServletRequest request, HttpServletResponse response, Model model,Exception e){
        System.out.println("ExceptionController.handleException");
    
        model.addAttribute("message", e.getMessage());
        if(e instanceof DaoException){
            System.out.println("Dao层产生的异常");
    
            return  "forward:/error-dao.jsp";
        }else if(e instanceof ServiceException){
            System.out.println("Service层产生的异常");
    
            return  "forward:/error-service.jsp";
        }
        return "forward:/error.jsp";
    }
    

    需要提升的地方:在处理方法中instanceof判断异常类型,提供针对性解决方案,过程比较麻烦。

  2. 一个Controller中可以定义多个ExceptionHandler方法,根据ExceptionHandler注解中设置的异常类型进行匹配。

    //处理DaoException异常
    @ExceptionHandler(DaoException.class)
    public String handleDaoException(HttpServletRequest req,HttpServletResponse resp,Model model,DaoException daoException){
        System.out.println("ExceptionController.handleDaoException");
        model.addAttribute("message",daoException.getMessage());
        return "error-dao";
    }
    
    //处理ServiceException异常
    @ExceptionHandler(ServiceException.class)
    public String handleServiceException(HttpServletRequest req,HttpServletResponse resp,Model model,ServiceException serviceException){
        System.out.println("ExceptionController.handleServiceException");
        model.addAttribute("message",serviceException.getMessage());
        return "error-service";
    }
    
    //处理Exception异常
    @ExceptionHandler(Exception.class)
    public String handleException(HttpServletRequest req,HttpServletResponse resp,Model model,Exception exception){
        System.out.println("ExceptionController.handleException");
        model.addAttribute("message",exception.getMessage());
        return "error";
    }
    
2.3.3 ControllerAdvice注解

单独使用 ExceptionHandler 注解,存在很明显的问题:多个Controller类处理相同异常时,需要将处理方法在多个Controller类中重复定义。这时候,就需要使用 ControllerAdvice注解配合来解决问题。

  1. 将所有的处理方法,单独抽取到一个类中

    public class ExceptionHandlerAdvice {
    	//处理DaoException异常
        @ExceptionHandler(DaoException.class)
        public String handleDaoException(HttpServletRequest req, HttpServletResponse resp, Model model, DaoException daoException) {
            System.out.println("ExceptionHandlerAdvice.handleDaoException");
            model.addAttribute("message", daoException.getMessage());
            return "error-dao";
        }
        
    	//处理ServiceException异常
        @ExceptionHandler(ServiceException.class)
        public String handleServiceException(HttpServletRequest req, HttpServletResponse resp, Model model, ServiceException serviceException) {
            System.out.println("ExceptionHandlerAdvice.handleServiceException");
            model.addAttribute("message", serviceException.getMessage());
            return "error-service";
        }
    
        //处理Exception异常
        @ExceptionHandler(Exception.class)
        public String handleException(HttpServletRequest req, HttpServletResponse resp, Model model, Exception exception) {
            System.out.println("ExceptionHandlerAdvice.handleException");
            model.addAttribute("message", exception.getMessage());
            return "error";
        }
    }
    
  2. 在这个异常处理类上添加 ControllerAdvice 注解

    @ControllerAdvice
    public class ExceptionHandlerAdvice {
        ...
    }
    

说明:

  • ControllerAdvice注解描述的类,默认会增强所有 @Controller 类(当前是异常处理的增强);可以为注解赋值,明确要增强哪个包下的 @Controller 类。

    @ControllerAdvice("com.baizhi.controller")
    public class ExceptionHandlerAdvice {
        ...
    }
    
  • 处理异常时,Controller类中的处理方法优先级更高

实战中,推荐使用注解方式处理程序中的异常。

3 参数校验

在Controller接受请求的参数时,为保证程序运行的正确性,必须要对请求中的参数进行数据格式的校验,比如说非空、长度限制…。当参数格式不满足要求时,Controller中要返回对应的错误信息。

传统的参数校验就在Controller服务方法中添加if语句做判断,有多少参数需要校验,就添加多个if语句:

@RequestMapping("add")
public String addStudent(Student s,Model model){
    
    if(s.getStudentName() == null || s.getStudentName().isEmpty()){
        model.addAttribute("studentName","学员姓名名不能为空");
    }
    if(s.getAge() == null || s.getAge()  <= 0){
        errors.put("age","学员年龄不能为空,且必须大于0");
    }
    ...
    if(errors.size() > 0){
        model.addAttribute("errors",map);
        return "forward:/addStudent.jsp";
    }
    
    System.out.println("s = " + s);
    return "redirect:/index.jsp";
}

优化方案:使用JSR303注解方式简化参数校验。

JSR303 是一套 JavaBean 参数校验的标准,它简化了参数的校验操作。典型代码如下:

public class Student {
    private Integer id;
    //NotBlank 用于字符串,限制不能为null、空串、空格字符
    @NotBlank(message="学员姓名不能为空")
    private String studentName;
    //NotNull 限制不能为null
    @NotNull(message="年龄不能为空")
    private Integer age;
    ...
}

3.1 JSR303校验基本使用

JSR303是JavaEE 6 中提出的参数校验规范。当然,只有规范不行,还得有落地实现,SpringWebMvc 选择了 Hibernate-Validator 作为 JSR-303 的落地实现。

开发步骤:

  1. 导入依赖

     <dependency>
         <groupId>org.hibernate.validator</groupId>
         <artifactId>hibernate-validator</artifactId>
         <version>6.1.5.Final</version>
    </dependency>
    
  2. 配置springmvc-servlet.xml,开启注解校验

    <!-- 将校验器注入到springmvc中 -->
    <mvc:annotation-driven conversion-service="conversionService" validator="validator"/>
    
    <!-- 配置校验器 -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
    </bean>
    
  3. 实体属性添加校验注解

    public class Student implements Serializable {
        private Integer studentId;
        @NotNull(message = "学员姓名不能为空")
        private String studentName;
        @NotNull(message = "学员年龄不能为空")
        private Integer age;
        ...
    }
    
  4. 服务方法形参前添加Validated注解,在校验的形参后添加 BindingResult类型参数

    @RequestMapping("add")
    //添加ModelAttribute注解原因:将student保存到model中,一旦参数校验失败,方便在jsp页面回显原有的学员数据
    public String addStudent(@ModelAttribute("s") @Validated Student student, BindingResult bindingResult, Model model){
        //如果bindingResult中校验错误,说明有参数校验失败
        if (bindingResult.hasErrors()) {
            System.out.println("bindingResult.hasErrors()");
            //遍历获取所有的校验错误信息,保存到map中
            bindingResult.getAllErrors().forEach(objectError -> {
                if(objectError instanceof FieldError) {
                    FieldError fieldError = (FieldError)objectError;
                    model.addAttribute(fieldError.getField(), fieldError.getDefaultMessage());
                    System.out.println(fieldError.getField()+":"+fieldError.getDefaultMessage());
                }else{
                    model.addAttribute(objectError.getObjectName(), objectError.getDefaultMessage());
                    System.out.println(objectError.getObjectName()+":"+objectError.getDefaultMessage());
                }
            });
    
            //将异常信息保存到model中,在addStudent.jsp回显错误信息
            return "forward:/addStudent.jsp";
        }
    
        System.out.println("s = " + student);
        return "redirect:/index.jsp";
    }
    

    注意:

    • 在实体类型形参前添加@Validated注解
    • 在实体类型形参后紧跟BindingResult形参,用来保存校验信息。每多添加一个校验的参数,就要配对紧跟一个BindingResult形参。
    • 检验的数据类型只能是复杂的对象类型参数,不能校验单类型参数。
  5. addStudent.jsp回显校验失败信息

    <form action="${pageContext.request.contextPath}/student/add.do" method="post">
        学员姓名: <input type="text" name="studentName" value="${s.studentName}"> 
        											<span>${requestScope.studentName}</span> <br>
        学员年龄: <input type="text" name="age" value="${s.age}"> 
        											<span>${requestScope.age}</span> <br>
        <input type="submit" value="添加">
    </form>
    

3.2 嵌套校验

当属性又是一个实体类型时,如果需要级联嵌套校验,则需要在实体属性上添加 Valid 注解。

实体类:

public class Student implements Serializable {
    private Integer studentId;
    @NotBlank(message = "学员姓名不能为空")
    private String studentName;
    @NotNull(message = "学员年龄不能为空")
    private Integer age;

    @NotNull(message = "学员的地址不能为空")
    @Valid//必须添加该注解,否则不会对addr的子属性进行校验
    private Address addr;
    ...
}

public class Address implements Serializable {
    @NotBlank(message = "城市不能为空")
    private String city;
    @NotBlank(message = "街道不能为空")
    private String street;
    ...
}

addStudent.jsp回显addr子属性的校验信息时,需要特殊处理:

<form action="${pageContext.request.contextPath}/student/add.do" method="post">
    学员姓名: <input type="text" name="studentName" value="${s.studentName}" id="">
    								<span>${requestScope.studentName}</span> <br>
    学员年龄: <input type="text" name="age" value="${s.age}"> 
        							<span>${requestScope.age}</span> <br>
    城市: <input type="text" name="addr.city" value="${s.addr.city}"/> 
    
    <!-- 这里要特殊处理-->
        							<span>${requestScope["addr.city"]}</span> <br> 
    街道: <input type="text" name="addr.street" value="${s.addr.street}"/> 
    								<span>${requestScope["addr.street"]}</span>  <br/>

    <input type="submit" value="添加">
</form>

3.3 分组校验

对于同一个类型的实体参数,添加和修改的数据校验规则不同。例如:新增方法 实体id不能指定值可以为null,但是修改id不能为null,怎么来分情况进行校验呢?这时候就可以使用分组校验功能。

  1. 定义分组接口:创建两个标记接口(没有任何方法的空接口)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bWt7zxoA-1630975505212)(SpringMVC day03.assets/image-20210627234417644.png)]

  2. 在校验注解中,配置groups属性设置分组

    public class Student implements Serializable {
        @NotNull(message = "学员姓名不能为空",groups = {UpdateGroup.class})
        private Integer studentId;
        @NotNull(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
        private String studentName;
        @NotNull(message = "学员年龄不能为空",groups = {SaveGroup.class,UpdateGroup.class})
        private Integer age;
    
        @NotNull(message = "学员的地址不能为空",groups = {SaveGroup.class,UpdateGroup.class}) 
        @Valid//必须添加该注解,否则不会对addr的子属性进行校验
        private Address addr;
        ...
    }
    
  3. 在Controller服务方法的Validated注解中配置要使用的校验规则分组

    @RequestMapping("update")
    public String updateStudent(@ModelAttribute("s") @Validated({UpdateGroup.class}) Student student,
                                BindingResult bindingResult, Model model){
        ...
    }
    
    @RequestMapping("add")
    public String addStudent(@ModelAttribute("s") @Validated({SaveGroup.class}) Student student, 
                             BindingResult bindingResult, Model model){
        ...
    }
    

注意:

一旦开启分组,则所有校验注解都要明确自己使用的分组信息。否则,校验注解将无法发挥作用。

3.4 JSR303的常见注解

注解约束
@Null限制属性只能为 null
@NotNull限制属性不能为null(无法查检长度为0的字符串)
@NotBlank限制字符串值不能为null、空串(长度为0的字符串)、空白字符
@NotEmpty限制字符串、数组、集合不能为null,长度不能为0
@Size(min=, max=)限制字符串、数组、集合的长度范围
@Pattern限制字符串内容必须满足正则表达式的格式要求
@Past限制日期类型数据必须是一个过去的时间(比当前时间早)
@Future限制日期类型数据必须是一个未来的时间(比当前时间晚)
@Min限制数字、字符串的最小值(必须大于等于指定的值)
@Max限制数字、字符串的最大值(必须小于等于指定值)
@Range(min=,max=)限制整数数值必须在设定的范围内
@DecimalMin限制小数数值的最小值(必须大于等于指定的值)
@DecimalMax限制小数数值的最大值(必须小于等于指定值)

典型案例:

public class Student implements Serializable {
    @NotNull(message = "学员编号不能为空",groups = {UpdateGroup.class})
    private Integer studentId;
    
    @NotBlank(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    private String studentName;
    
    @NotNull(message = "学员年龄不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    @Range(min = 18,max = 100,message = "学员年龄必须在18~100之间",groups = {SaveGroup.class,UpdateGroup.class})
    private Integer age;
   
    @NotNull(message = "生日不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    @Past(message = "生日必须是过去的某一个时间",groups = {SaveGroup.class,UpdateGroup.class})
    private Date birthday;
    
    @NotNull(message = "分数不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    @DecimalMin(value = "0.5",message = "分数最低不能低于0.5",groups = {SaveGroup.class,UpdateGroup.class})
    @DecimalMax(value = "100.0",message = "分数再高不能高于100.0",groups = {SaveGroup.class,UpdateGroup.class})
    private Double score;
    
    @NotNull(message = "爱好不能为空")
    @Size(min = 1,max=4,message = "最少选择一个,最多四个爱好",groups = {SaveGroup.class,UpdateGroup.class})
    private List<Integer> favorites;
    
    @NotNull(message = "手机号不能为空",groups={SaveGroup.class,UpdateGroup.class})
    @Pattern(regexp = "^1(3[0-9]|5[0-3,5-9]|7[1-3,5-8]|8[0-9])\\d{8}$",
             message = "手机号格式不正确",groups = {SaveGroup.class,UpdateGroup.class})
    private String phone;

    @NotNull(message = "学员的地址不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    @Valid//必须添加该注解,否则不会对addr的子属性进行校验
    private Address addr;
    ...
}

addStudent.jsp

<form action="${pageContext.request.contextPath}/student/add.do" method="post">
    学员姓名: <input type="text" name="studentName" value="${s.studentName}" id=""> <span>${requestScope.studentName}</span> <br>
    学员年龄: <input type="text" name="age" value="${s.age}"> <span>${requestScope.age}</span> <br>
    出生日期:<input type="text" name="birthday" value="${s.birthday}"/> <span>${requestScope.birthday}</span><br>
    分数:<input type="text" name="score" value="${s.score}"/> <span>${requestScope.score}</span> <br>
    爱好:
    <input type="checkbox" name="favorites" value="1"> 唱
    <input type="checkbox" name="favorites" value="2"> 跳
    <input type="checkbox" name="favorites" value="3"> rap
    <input type="checkbox" name="favorites" value="4"> 打篮球
                        ${requestScope.favorites}<br>
    城市: <input type="text" name="addr.city" value="${s.addr.city}"/> <span>${requestScope["addr.city"]}</span> <br>
    街道: <input type="text" name="addr.street" value="${s.addr.street}"/> <span>${requestScope["addr.street"]}</span>


    <br/>

    <input type="submit" value="添加">
</form>

3.5 校验信息外部化

当前的校验方式存在一个明显的问题:校验提示信息硬编码在了程序中,不利于后续的维护。此时,就需要将校验信息抽取到外部配置文件中

  1. 将提示信息以 key=value 的形式,抽取到properties文件中。

    validatation-student-message.properties

    student.studentId.notNull=学员标号
    student.studentName.notblank=学员姓名不能为空
    student.age.notnull=学员年龄不能为空
    student.age.range=学员年龄必须在18~100之间
    student.birthday.notnull=生日不能为空
    student.birthday.past=生日必须是过去的某一个时间
    student.score.notnull=分数不能为空
    student.score.decimalmin=分数最低不能低于0.5
    student.score.decimalmax=分数最高不能高于100.0
    student.favorites.notnull=爱好不能为空
    student.favorites.size=最少选择一个,最多选择4个
    student.addr.notnull=学员地址不能为空
    

    validatation-address-message.properties

    address.city.notblank=城市不能为空
    address.street.notblank=街道不能为空
    
  2. 在springmvc-servlet.xml中配置使用properties文件

    <!-- 将校验器注入到springmvc中 -->
    <mvc:annotation-driven conversion-service="conversionService" validator="validator"/>
    
    <!-- 配置校验器 -->
    <bean id="validator" class="org.springframework.validation.beanvalidation.LocalValidatorFactoryBean">
        <property name="providerClass" value="org.hibernate.validator.HibernateValidator"/>
        <property name="validationMessageSource" ref="validationMessageSource"/>
    </bean>
    
    <!--配置 校验消息的路径-->
    <bean id="validationMessageSource"
          class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
        <property name="basenames">
            <array>
                <value>classpath:validatation-student-message</value>
                <value>classpath:validatation-address-message</value>
            </array>
        </property>
        <property name="defaultEncoding" value="utf-8"/>
    </bean>
    
  3. 在实体类中,将原有硬编码的校验信息改为占位符形式 {校验参数key} (注意这里不需要$)

    public class Student implements Serializable {
        @NotNull(message = "{student.studentId.notnull}",groups = {UpdateGroup.class})
        private Integer studentId;
    
    //    @NotNull(message = "学员姓名不能为空",groups = {SaveGroup.class,UpdateGroup.class})
        @NotBlank(message = "{student.studentName.notblank}",groups = {SaveGroup.class,UpdateGroup.class})
        private String studentName;
    
        @NotNull(message = "{student.age.notnull}",groups = {SaveGroup.class,UpdateGroup.class})
        @Range(min = 18,max = 100,message = "{student.age.range}",groups = {SaveGroup.class,UpdateGroup.class})
        private Integer age;
    
        @NotNull(message = "{student.birthday.notnull}",groups = {SaveGroup.class,UpdateGroup.class})
        @Past(message = "{student.birthday.past}",groups = {SaveGroup.class,UpdateGroup.class})
        private Date birthday;
    
        @NotNull(message = "{student.score.notnull}",groups = {SaveGroup.class,UpdateGroup.class})
    //    @Range(min=0,max = 100,message = "分数必须在0~100之间",groups = {SaveGroup.class,UpdateGroup.class})
        @DecimalMin(value = "0.5",message = "{student.score.decimalmin}",groups = {SaveGroup.class,UpdateGroup.class})
        @DecimalMax(value = "100.0",message = "{student.score.decimalmax}",groups = {SaveGroup.class,UpdateGroup.class})
        private Double score;
        @NotNull(message = "{student.favorites.notnull}",groups = {SaveGroup.class,UpdateGroup.class})
        @Size(min = 1,max=4,message = "{student.favorites.size}",groups = {SaveGroup.class,UpdateGroup.class})
        private List<Integer> favorites ;
    
        @NotNull(message = "{student.addr.notnull}",groups = {SaveGroup.class,UpdateGroup.class})
        @Valid//必须添加该注解,否则不会对addr的子属性进行校验
        private Address addr;
        ...
    }
    
    public class Address implements Serializable {
        @NotBlank(message = "{address.city.notblank}",groups = {SaveGroup.class, UpdateGroup.class})
        private String city;
        @NotBlank(message = "{address.street.notblank}",groups = {SaveGroup.class,UpdateGroup.class})
        private String street;
        ...
    }
    

3.6 自定义验证注解【了解】

JSR303和Hibernate-Validator中的校验注解常规情况足够使用,但是特殊情况下需要自定义校验规则。

以下代码以校验手机号为例

  1. 编写注解类

    package com.baizhi.annotation;
    
    import javax.validation.Constraint;
    import javax.validation.Payload;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    import static java.lang.annotation.ElementType.*;
    
    /**
     * 验证手机号码的注解类
     */
    @Target({ METHOD, FIELD, ANNOTATION_TYPE, CONSTRUCTOR, PARAMETER, TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    //指定校验规则的类
    @Constraint(validatedBy = PhoneValidator.class)
    public @interface Phone {
        /**
         * 写法固定
         */
        String message() default "手机号码格式错误";
    
        Class<?>[] groups() default {};
    
        Class<? extends Payload>[] payload() default {};
    }
    
    
  2. 编写自定义的规则校验类

    package com.baizhi.annotation;
    
    import javax.validation.ConstraintValidator;
    import javax.validation.ConstraintValidatorContext;
    import java.util.regex.Pattern;
    
    public class PhoneValidator implements ConstraintValidator<Phone, String> {
    
        //验证手机的正则表达式
        private String phoneReg = "^1(3[0-9]|5[0-3,5-9]|7[1-3,5-8]|8[0-9])\d{8}$";
    
        private Pattern phonePattern = Pattern.compile(phoneReg);
    
        public void initialize(Phone mobile) {
    
        }
    
        public boolean isValid(String value, ConstraintValidatorContext arg1) {
            //返回匹配结果 验证手机号的格式是否正确
            return phonePattern.matcher(value).matches();
    
        }
    
    }
    
  3. 在实体类上使用自己的校验注解

    public class Student implements Serializable {
        ...
        //使用自定义注解
        @NotNull(message="手机号码不能为空",groups = {SaveGroup.class,UpdateGroup.class})
        @Phone(groups = {SaveGroup.class,UpdateGroup.class})
        private String phone;
        ...
    }
    

4 静态资源的处理

Servlet的url-pattern的4种路径配置方式:

  • 精确配置: /开头后面跟路径
  • 路径配置:/前缀/* 匹配路径以指定前缀开头的请求
  • 后缀名配置:*.do 匹配路径以指定后缀结尾的请求
  • 缺省配置:/ 匹配所有请求

当springmvc的请求分发器url-pattern配置为/,图片、js、css等资源的请求也会被springmvc拦截,此时需要对静态资源处理。

springmvc-servlet.xml

<!--
	mapping: 匹配请求路径  **任意层级路径 *任意字符
	location: 静态资源在项目中的起始寻找路径
	注意:必须配置在mvc:annotation-driven前
-->
<mvc:resources mapping="/**/*.html" location="/" order="0"/>
<mvc:resources mapping="/**/*.js" location="/" order="0"/>

5 SpringMVC的执行流程

eturn phonePattern.matcher(value).matches();

   }

}


3. 在实体类上使用自己的校验注解

```java
public class Student implements Serializable {
    ...
    //使用自定义注解
    @NotNull(message="手机号码不能为空",groups = {SaveGroup.class,UpdateGroup.class})
    @Phone(groups = {SaveGroup.class,UpdateGroup.class})
    private String phone;
    ...
}

4 静态资源的处理

Servlet的url-pattern的4种路径配置方式:

  • 精确配置: /开头后面跟路径
  • 路径配置:/前缀/* 匹配路径以指定前缀开头的请求
  • 后缀名配置:*.do 匹配路径以指定后缀结尾的请求
  • 缺省配置:/ 匹配所有请求

当springmvc的请求分发器url-pattern配置为/,图片、js、css等资源的请求也会被springmvc拦截,此时需要对静态资源处理。

springmvc-servlet.xml

<!--
	mapping: 匹配请求路径  **任意层级路径 *任意字符
	location: 静态资源在项目中的起始寻找路径
	注意:必须配置在mvc:annotation-driven前
-->
<mvc:resources mapping="/**/*.html" location="/" order="0"/>
<mvc:resources mapping="/**/*.js" location="/" order="0"/>

5 SpringMVC的执行流程

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XgWMzE79-1630975505214)(SpringMVC day03.assets/image-20200602223850332.png)]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值