SpringBoot使用JSR303参数校验并进行全局异常处理

  • Post author:
  • Post category:其他


SpringBoot使用JSR303参数校验

开发web项目有时候我们需要对controller层传过来的参数进行一些基本的校验,比如非空,非null,整数值的范围,字符串的个数,日期,邮箱等等。最常见的就是我们直接写代码校验,这样以后比较繁琐,而且不够灵活。 不能总是写繁琐的代码来实现吧。

使用JSR303来做参数校验就方便并且整洁很多了。

pop引入依赖

1
2
3
4
5
<!--JSR303校验的依赖-->
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-validation</artifactId>
    </dependency>

Controller中使用

1
2
3
4
5
@RequestMapping("/login")
//@Valid是JSR303校验
public Result<Boolean> login(@Valid LoginVo loginVo){
    
}

校验实体

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package com.springboot.SecKill.vo;

import com.springboot.SecKill.validator.IsMobile;
import org.hibernate.validator.constraints.Length;

import javax.validation.constraints.NotNull;

/**
 * @author WilsonSong
 * @date 2018/8/2/002
 */
public class LoginVo {

    @NotNull
    @IsMobile
    private String mobile;

    @NotNull
    @Length(min=32)
    private String password;

    public String getMobile() {
        return mobile;
    }

    public void setMobile(String mobile) {
        this.mobile = mobile;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    @Override
    public String toString() {
        return "LoginVo{" +
                "mobile='" + mobile + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
}

自定义校验

JSR303中给我们定义了一些常用的校验注解,如本文最后常用常用注解中所示,但是要是还不能满足学习怎么去自己定义注解呢?

参照@NotNull这个校验注解的定义方法,@NotNull是这么定义的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by Fernflower decompiler)
//

package javax.validation.constraints;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import javax.validation.Constraint;
import javax.validation.Payload;

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Repeatable(NotNull.List.class)
@Documented
@Constraint(
    validatedBy = {}
)
public @interface NotNull {
    String message() default "{javax.validation.constraints.NotNull.message}";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface List {
        NotNull[] value();
    }
}

参照上面,我们实现自己的注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package com.springboot.SecKill.validator;

import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.*;

/**
 * @author WilsonSong
 * @date 2018/8/2/002
 */

@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(
        validatedBy = {IsMobileValidator.class}
)
public @interface IsMobile {

    boolean required() default true;
    //校验不通过,提示默认的错误信息
    String message() default "手机号码格式错误";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

实现具体校验器

上面只是实现了自己定义的校验器的接口,具体的实现类如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.springboot.SecKill.validator;

import com.springboot.SecKill.util.ValidatorUtil;
import org.springframework.util.StringUtils;

import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;

/**
 * JSR303具体的校验器
 * @author WilsonSong
 * @date 2018/8/2/002
 */
public class IsMobileValidator  implements ConstraintValidator<IsMobile, String> {

    public boolean required = false;
    //初始化
    @Override
    public void initialize(IsMobile constraintAnnotation) {
        required = constraintAnnotation.required();
    }

    //校验
    @Override
    public boolean isValid(String s, ConstraintValidatorContext constraintValidatorContext) {
        if(required){       //值是必须的就判断是否合法
            return ValidatorUtil.isMobile(s);     //不为空就判断格式
        }else {  //若不必须就判断是否有值
            if (StringUtils.isEmpty(s)){
                return true;
            }else {
                return ValidatorUtil.isMobile(s);     //不为空就判断格式
            }
        }
    }
}

异常处理

参数校验不通过就会产生错误信息,显示一大串例如

1
{"timestamp":"2018-08-02T13:07:50.890+0000","status":400,"error":"Bad Request","errors":[{"codes":["IsMobile.loginVo.mobile","IsMobile.mobile","IsMobile.java.lang.String","IsMobile"],"arguments":[{"codes":["loginVo.mobile","mobile"],"arguments":null,"defaultMessage":"mobile","code":"mobile"},true],"defaultMessage":"手机号码格式错误","objectName":"loginVo","field":"mobile","rejectedValue":"22111111111","bindingFailure":false,"code":"IsMobile"}],"message":"Validation failed for object='loginVo'. Error count: 1","path":"/login/do_login"}

为了方便查看,统一进行异常处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.springboot.SecKill.exception;

import com.springboot.SecKill.result.CodeMsg;
import com.springboot.SecKill.result.Result;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;
import org.springframework.validation.BindException;

import java.util.List;

/**
 * @author WilsonSong
 * @date 2018/8/2/002
 */
@ControllerAdvice
@ResponseBody
public class GlobalExceptionHandler {

    @ExceptionHandler(value = Exception.class)       //拦截所有的异常
    public Result<String> exceptionHandler(HttpServletRequest httpServletRequest, Exception e){

        // 参数校验异常
        if(e instanceof BindException){
            BindException ex = (BindException)e;
            List<ObjectError> errors = ex.getAllErrors();
            ObjectError error= errors.get(0);
            String msg = error.getDefaultMessage();
            return Result.error(CodeMsg.BIND_ERROR.fillArgs(msg));
        }else {
            //其他异常
            return Result.error(CodeMsg.SERVER_ERROR);
        }
    }
}

全局的异常处理

上面的知识参数检验时候的异常处理,但是在工程中很多的异常,用全局的异常处理更加方便维护。

首先定义一个全局的异常

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package com.springboot.SecKill.exception;

import com.springboot.SecKill.result.CodeMsg;

/**
 * @author WilsonSong
 * @date 2018/8/2/002
 */
public class GlobalException extends RuntimeException {


    private static final long serialVersionUID = 1L;

    private CodeMsg cm;

    public GlobalException (CodeMsg cm){
        super(cm.toString());
        this.cm = cm;
    }

    public static long getSerialVersionUID() {
        return serialVersionUID;
    }

    public CodeMsg getCm() {
        return cm;
    }

    public void setCm(CodeMsg cm) {
        this.cm = cm;
    }
}

然后在全局异常处理器中添加这个全局的异常,也就是在GlobalExceptionHandler类中添加

1
2
3
4
if(e instanceof GlobalException){
            GlobalException ex = (GlobalException) e;
            return Result.error(ex.getCm());
        }

然后在产生异常的地方直接抛出全局异常就可以了

1
2
3
if (loginVo == null){
           throw new GlobalException(CodeMsg.SERVER_ERROR);
       }

CodeMsg.SERVER_ERROR是自己定义的异常信息。

常用注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@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) 被注释的元素必须符合指定的正则表达式。
@Email 被注释的元素必须是电子邮件地址
@Length 被注释的字符串的大小必须在指定的范围内
@NotEmpty  被注释的字符串必须非空
@Range  被注释的元素必须在合适的范围内



版权声明:本文为weixin_41945228原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。