JSR303在项目controller及service层中的应用

一、前言

项目中我们经常要对接口的入参做校验,如果在代码中写判断逻辑,不仅不美观且代码冗余,我们可以使用JSR303标注注解的方式来解决这样的问题。不仅在controller层可以使用JSR303做校验,service层也可以使用JSR303做校验。

二、版本问题

实战中,使用springboot2.7.2版本时需要导入以下依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

当使用springboot2.2.5.RELEASE则不需要另外导入依赖

结论:不同的springboot版本对validation-api jar的集成情况不同,实战中需要注意版本问题。

本文采用springboot2.2.5.RELEASE

三、controller层接口入参校验

1、接口入参为一个实体json

实体代码

@Data
public class User {

  private Integer id;

  @NotBlank private String name;

  @NotNull private Integer age;
}

接口代码

 @PostMapping("/save")
  public CommonRes save(@Valid @RequestBody User user) {

    CommonRes commonRes = new CommonRes();
    System.out.println("user = " + user);

    commonRes.setCode(200);
    commonRes.setData(user);
    return commonRes;
  }

请求接口

{
    "name":"",
    "age":""
}

接口响应结果

{
    "timestamp": "2022-09-28T05:11:03.192+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotNull.user.age",
                "NotNull.age",
                "NotNull.java.lang.Integer",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "不能为null",
            "objectName": "user",
            "field": "age",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "NotBlank.user.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "不能为空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": "",
            "bindingFailure": false,
            "code": "NotBlank"
        },
        {
            "codes": [
                "NotNull.user.age",
                "NotNull.age",
                "NotNull.java.lang.Integer",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "不能为null",
            "objectName": "user",
            "field": "age",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 3",
    "path": "/user/save"
}

结果信息冗余,并不是我们想要的格式

后台日志

2022-09-28 13:11:03.190  WARN 21711 --- [io-10092-exec-4] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.web.bind.MethodArgumentNotValidException: Validation failed for argument [0] in public com.example.jsr303demo.util.CommonRes com.example.jsr303demo.controller.UserController.save(com.example.jsr303demo.entity.User) with 3 errors: [Field error in object 'user' on field 'age': rejected value [null]; codes [NotNull.user.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [不能为null]] [Field error in object 'user' on field 'name': rejected value []; codes [NotBlank.user.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [不能为空]] [Field error in object 'user' on field 'age': rejected value [null]; codes [NotNull.user.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [不能为null]] ]

可添加一个全局异常处理,专门捕获并处理这个异常

@Slf4j
@RestControllerAdvice
public class MyExceptionControllerAdvice {

  /**
   * MethodArgumentNotValidException 异常捕获处理
   *
   * @param e
   * @return
   */
  @ExceptionHandler(value = MethodArgumentNotValidException.class)
  public CommonRes methodArgumentNotValidExceptionHandle(MethodArgumentNotValidException e) {

    CommonRes commonRes = new CommonRes();

    log.error("数据校验异常{},异常类型:{}", e.getMessage(), e.getClass());

    Map<String, String> errorMap = new HashMap<>();
    e.getBindingResult()
        .getFieldErrors()
        .forEach(
            item -> {
              errorMap.put(item.getField(), item.getDefaultMessage());
            });

    commonRes.setCode(400);
    commonRes.setMsg("数据校验异常 MethodArgumentNotValidException");
    commonRes.setData(errorMap);
    return commonRes;
  }


}

再次请求结果为

{
    "code": 400,
    "msg": "数据校验异常 MethodArgumentNotValidException",
    "data": {
        "name": "不能为空",
        "age": "不能为null"
    }
}

2、接口入参是非json实体

去掉@RequestBody注解

@PostMapping("/save")
  public CommonRes save(@Valid User user) {

    CommonRes commonRes = new CommonRes();
    System.out.println("user = " + user);

    commonRes.setCode(200);
    commonRes.setData(user);
    return commonRes;
  }

结果为

{
    "timestamp": "2022-09-28T05:18:55.149+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "NotBlank.user.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.name",
                        "name"
                    ],
                    "arguments": null,
                    "defaultMessage": "name",
                    "code": "name"
                }
            ],
            "defaultMessage": "不能为空",
            "objectName": "user",
            "field": "name",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotBlank"
        },
        {
            "codes": [
                "NotNull.user.age",
                "NotNull.age",
                "NotNull.java.lang.Integer",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "不能为null",
            "objectName": "user",
            "field": "age",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        },
        {
            "codes": [
                "NotNull.user.age",
                "NotNull.age",
                "NotNull.java.lang.Integer",
                "NotNull"
            ],
            "arguments": [
                {
                    "codes": [
                        "user.age",
                        "age"
                    ],
                    "arguments": null,
                    "defaultMessage": "age",
                    "code": "age"
                }
            ],
            "defaultMessage": "不能为null",
            "objectName": "user",
            "field": "age",
            "rejectedValue": null,
            "bindingFailure": false,
            "code": "NotNull"
        }
    ],
    "message": "Validation failed for object='user'. Error count: 3",
    "path": "/user/save"
}

后台日志

2022-09-28 13:18:55.149  WARN 22953 --- [io-10092-exec-5] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.validation.BindException: org.springframework.validation.BeanPropertyBindingResult: 3 errors
Field error in object 'user' on field 'name': rejected value [null]; codes [NotBlank.user.name,NotBlank.name,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.name,name]; arguments []; default message [name]]; default message [不能为空]
Field error in object 'user' on field 'age': rejected value [null]; codes [NotNull.user.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [不能为null]
Field error in object 'user' on field 'age': rejected value [null]; codes [NotNull.user.age,NotNull.age,NotNull.java.lang.Integer,NotNull]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.age,age]; arguments []; default message [age]]; default message [不能为null]]

没有得到我们想要的结果,是因为此时抛出的异常不是MethodArgumentNotValidException,而是BindException

因此我们需要添加BindException的全局异常处理

/**
     * 只打了@Valid在参数前,没有@RequestBody 验证错误后返回的异常
     * 
     * @param e 
     * @return
     */
    @ExceptionHandler(BindException.class)
    public CommonRes bindExceptionHandle(BindException e) {
      CommonRes commonRes = new CommonRes();

      log.error("数据校验异常{},异常类型:{}", e.getMessage(), e.getClass());

      Map<String, String> errorMap = new HashMap<>();
      e.getBindingResult()
          .getFieldErrors()
          .forEach(
              item -> {
                errorMap.put(item.getField(), item.getDefaultMessage());
              });

      commonRes.setCode(400);
      commonRes.setMsg("数据校验异常 bindExceptionHandle");
      commonRes.setData(errorMap);
      return commonRes;
    }

再次请求结果为

{
    "code": 400,
    "msg": "数据校验异常 bindExceptionHandle",
    "data": {
        "name": "不能为空",
        "age": "不能为null"
    }
}

3、接口的入参不是一个实体

@GetMapping("/getUserByParam")
  public CommonRes getUserByParam(@NotNull Integer id) {

    CommonRes commonRes = new CommonRes();

    System.out.println("id = " + id);

    commonRes.setCode(200);
    commonRes.setData(id);
    return commonRes;
  }

此时请求id为空

localhost:10092/user/getUserByParam?id=

@NotNull并不起作用

在类上加入@Validated注解才会校验生效

@Validated
@RestController
@RequestMapping("/user")
public class UserController {
   // ...
}

再次请求结果为

{
    "timestamp": "2022-09-28T05:30:11.616+0000",
    "status": 500,
    "error": "Internal Server Error",
    "message": "getUserByParam.id: 不能为null",
    "path": "/user/getUserByParam"
}

后台日志抛出异常

2022-09-28 13:30:11.614 ERROR 24560 --- [io-10092-exec-4] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is javax.validation.ConstraintViolationException: getUserByParam.id: 不能为null] with root cause

javax.validation.ConstraintViolationException: getUserByParam.id: 不能为null


...

添加ConstraintViolationException全局异常捕获

/**
     * constraintViolationExceptionHandle 验证错误后返回的异常
     *
     * @param e 
     * @return
     */
    @ExceptionHandler(ConstraintViolationException.class)
    public CommonRes constraintViolationExceptionHandle(ConstraintViolationException e) {
      CommonRes commonRes = new CommonRes();

      log.error("数据校验异常{},异常类型:{}", e.getMessage(), e.getClass());

      Map<String, String> errorMap = new HashMap<>();
      for (ConstraintViolation<?> constraintViolation : e.getConstraintViolations()) {
        errorMap.put(
            constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
      }

      commonRes.setCode(400);
      commonRes.setMsg("数据校验异常 constraintViolationExceptionHandle");
      commonRes.setData(errorMap);
      return commonRes;
    }

再次请求结果为

{
    "code": 400,
    "msg": "数据校验异常 constraintViolationExceptionHandle",
    "data": {
        "getUserByParam.id": "不能为null"
    }
}

以上基本涵盖了入参为实体json、非json实体、非实体非json的三种异常处理

同理其他校验异常也可添加异常处理捕获,进而统一返回校验错误的接口返回信息

四、service层方法入参校验

service入参也可使用JSR303校验统一返回校验结果json

如代码如下

@Slf4j
@Service
public class UserServiceImpl implements UserService {
  @Override
  public void save(User user) {

    ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
    Validator validator = vf.getValidator();
    Set<ConstraintViolation<Object>> set = validator.validate(user);
    for (ConstraintViolation<Object> constraintViolation : set) {
      log.error(constraintViolation.getPropertyPath() + ":" + constraintViolation.getMessage());
    }
  }
}

user字段为空时

控制台打印结果为

2022-09-28 13:49:09.107 ERROR 27242 --- [io-10092-exec-4] c.e.j.service.impl.UserServiceImpl       : name:不能为空
2022-09-28 13:49:09.107 ERROR 27242 --- [io-10092-exec-4] c.e.j.service.impl.UserServiceImpl       : age:不能为null

我们不妨将此段代码封装成工具类,在需要进行入参校验的实收调用一下即可

先定义一个自定义异常,用于此段逻辑

public class MyValidationException extends Exception {

  public MyValidationException() {
    super();
  }

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

工具类抛出自定义异常

@Slf4j
public class MyValidationUtil {

  public static void myServiceValidate(Object o) throws Exception {
    ValidatorFactory vf = Validation.buildDefaultValidatorFactory();
    Validator validator = vf.getValidator();
    Set<ConstraintViolation<Object>> set = validator.validate(o);
    Map<String, String> errorMap = new HashMap<>();
    if (set != null && set.size() > 0) {
      for (ConstraintViolation<Object> constraintViolation : set) {
        log.error(constraintViolation.getPropertyPath() + ":" + constraintViolation.getMessage());
        errorMap.put(
            constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage());
      }
      ObjectMapper objectMapper = new ObjectMapper();
      throw new MyValidationException(objectMapper.writeValueAsString(errorMap));
    }
  }
}

添加全局异常捕获MyValidationException

@ExceptionHandler(value = MyValidationException.class)
  public CommonRes handleServiceValidationException(MyValidationException e) {

    CommonRes commonRes = new CommonRes();

    log.error("数据校验异常:{}", e.getMessage());

    Map errorMap = null;
    if (!StringUtils.isEmpty(e.getMessage())) {
      ObjectMapper objectMapper = new ObjectMapper();
      try {
        errorMap = objectMapper.readValue(e.getMessage(), Map.class);
      } catch (JsonProcessingException ex) {
        ex.printStackTrace();
      }
    }

    commonRes.setCode(400);
    commonRes.setMsg("数据校验异常 service");
    commonRes.setData(errorMap);
    return commonRes;
  }

再次请求service方法,结果如下

{
    "code": 400,
    "msg": "数据校验异常 service",
    "data": {
        "name": "不能为空",
        "age": "不能为null"
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值