java web项目如何优雅的进行入参的校验

前言

之前看过有一个同事写的代码,他为了进行细粒度的返回值提示,针对每一种参数不合法的情况,都规定了一个code值,然后在service层去进行各种校验,捕捉各种异常,然后返回给controller不同的code码。controller再根据这些code码,返回不同的错误提示。

他这样做可以改进的地方有两处:

  1. service层先根据错误类型返回不同的code码,controller再根据不同的code码返回不同的文案。进行了两步处理,代码量显得特别大。
  2. 在service层捕捉了大量异常,造成事务失效,为后人埋下了很多坑。

javax.validation

介绍

javax.validation 是基于JSR-303标准开发出来的,使用注解方式实现,及其方便,但是这只是一个接口,没有具体实现。

Hibernate-Validator是一个hibernate独立的包,可以直接引用,他实现了javax.validation同时有做了扩展,比较强大。

SpringBoot在spring-boot-starter-web中引入了hibernate-validator,所以我们在springboot的web项目中可以直接使用。

具体使用

我就直接堆代码了, 由于代码比较多,所以用了一种折叠的方式,不过貌似只有chrome等少数的几个浏览器才支持。

返回值的包装类:

展开源码

public class ResponseData implements Serializable {

    private String code;

    private String message;

    private T data;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public static class ResponseDataBuilder {
        private ResponseData responseData;

        public ResponseDataBuilder() {
            responseData = new ResponseData<>();
        }

        public ResponseDataBuilder code(String code) {
            responseData.setCode(code);
            return this;
        }

        public ResponseDataBuilder message(String message) {
            responseData.setMessage(message);
            return this;
        }

        public ResponseDataBuilder data(T data) {
            responseData.setData(data);
            return this;
        }

        public ResponseDataBuilder success() {
            responseData.setCode("0000");
            responseData.setMessage("请求成功");
            return this;
        }

        public ResponseData build() {
            return responseData;
        }

    }
}


请求实体类:
展开源码

public class Phone {

    @NotEmpty(message = "手机名称不能为空")
    private String name;

    private String brand;

    @Min(value = 1, message = "电话尺寸最小为1")
    @Max(value = 10, message = "电话尺寸最大为10")
    private Integer size;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public Integer getSize() {
        return size;
    }

    public void setSize(Integer size) {
        this.size = size;
    }
}

可以看到上面的代码中对字段"name"进行了非空校验,对字段"size"进行的大小的限制。


controller:
展开源码

@RestController
@RequestMapping("/test")
public class TestController {

    @PostMapping("/a")
    public ResponseData test(@RequestBody @Validated Phone phone) {
        return new ResponseData.ResponseDataBuilder<>().success().build();
    }
}


统一的异常处理类,主要用来捕捉所有的validate异常,统一返回值的结构:
展开源码

@ControllerAdvice
public class MyExceptionHandler {

    private Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);

    @ExceptionHandler(ValidationException.class)
    @ResponseBody
    public ResponseData handleException(ValidationException exception) {
        String errorMessage = exception.getMessage();
        log.error(errorMessage);
        return new ResponseData.ResponseDataBuilder<>().code("9999").message(errorMessage).build();
    }


    @ExceptionHandler(BindException.class)
    @ResponseBody
    public ResponseData handleException(BindException exception) {
        String errorMessageStr = getErrorMessageStr(exception);
        log.error(errorMessageStr);

        return new ResponseData.ResponseDataBuilder<>().code("9999").message(errorMessageStr).build();
    }

    @ExceptionHandler(MethodArgumentNotValidException.class)
    @ResponseBody
    public ResponseData handleException(MethodArgumentNotValidException exception) {
        String errorMessageStr = getErrorMessageStr(exception);
        log.error(errorMessageStr);

        return new ResponseData.ResponseDataBuilder<>().code("9999").message(errorMessageStr).build();
    }

    private String getErrorMessageStr(BindException exception) {
        List errorMessages = exception.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        String errorMessageStr = errorMessages.stream().filter(message -> !StringUtils.isEmpty(message)).collect(Collectors.joining("--"));
        return errorMessageStr;
    }

    private String getErrorMessageStr(MethodArgumentNotValidException exception) {
        List errorMessages = exception.getBindingResult().getAllErrors().stream().map(ObjectError::getDefaultMessage).collect(Collectors.toList());
        String errorMessageStr = errorMessages.stream().filter(message -> !StringUtils.isEmpty(message)).collect(Collectors.joining("--"));
        return errorMessageStr;
    }

}

我们怎么简单的运用

上面介绍的方法,可以对入参进行非业务的校验。但是在实际的项目中,我们还会遇到业务型的校验,比如:注册的时候判断用户名是否重复,这个时候我们就可以借鉴上面的思想进行一些优雅的校验。

自定义一个校验异常

展开源码

public class MyValidateException extends RuntimeException {

    public MyValidateException() {
        super();
    }

    public MyValidateException(String message) {
        super(message);
    }

    public MyValidateException(String message, Throwable cause) {
        super(message, cause);
    }

    public MyValidateException(Throwable cause) {
        super(cause);
    }

    protected MyValidateException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}


在service层去进行业务校验
展开源码

@Service
public class TestService {

    List list = new ArrayList<>(Arrays.asList("Huawei"));

    public void test(Phone phone) {
        if (list.contains(phone.getName())) {
            throw new MyValidateException("手机的名字已存在");
        }
    }
}


统一的异常处理
展开源码

@ControllerAdvice
public class MyExceptionHandler {

    private Logger log = LoggerFactory.getLogger(MyExceptionHandler.class);

    @ExceptionHandler(MyValidateException.class)
    @ResponseBody
    public ResponseData handleException(MyValidateException exception) {
        String errorMessage = exception.getMessage();
        log.error(errorMessage);
        return new ResponseData.ResponseDataBuilder<>().code("9999").message(errorMessage).build();
    }

}

posted on 2020-11-07 11:06  斜月三星一太阳  阅读(610)  评论(0编辑  收藏  举报