@Validated和@Valid

By | 2021年12月31日

1 方法参数中使用 @Valid + BindingResult 

在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别,也就是说,下面的 @Valid可以改成@Validated:

@RequestMapping(value = "{uuid}", method = {RequestMethod.PUT}, produces = {"application/json"})
public JsonWrapper update(@PathVariable String uuid, @RequestBody @Valid ScheduleObjectDto scheduleObjectDto,BindingResult result) {
    ValidationUtil.validateFieldErrors(result);
    ......
}

以上这种方式我们需要在每个controller中处理参数验证,不是特别理想。

2 捕捉 MethodArgumentNotValidException (推荐)

下面我们采用统一处理的方法:把 @Validated 贴在类上(@Valid不能用在类上),然后捕捉 MethodArgumentNotValidException 异常后统一处理,相关代码如下:

ScheduleObjectDto.class

@Validated
public class ScheduleObjectDto extends AuditBaseEntity implements BaseDto {
    @NotEmpty
    private String name;
}

ScheduleObjectController.class

@RequestMapping(value = "{uuid}", method = {RequestMethod.PUT}, produces = {"application/json"})
public JsonWrapper update(@PathVariable String uuid, @RequestBody @Validated ScheduleObjectDto scheduleObjectDto) {
    scheduleObjectDto.setUuid(uuid);
    ScheduleObject scheduleObject = posScheduleObjectService.saveScheduleObject(scheduleObjectDto);
    return new JsonWrapper(scheduleObject);
}

请注意,上面的方法里有@Validated(或@Valid),但没有 BindingResult  参数。

ExceptionControllerAdvice.class

import com.falsec.nsm.common.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import com.falsec.nsm.common.util.JsonWrapper;

import java.text.MessageFormat;

@ControllerAdvice
public class ExceptionControllerAdvice {
    private static final Logger log = LoggerFactory.getLogger(ExceptionControllerAdvice.class);

    @ResponseBody
    @ExceptionHandler(value = MethodArgumentNotValidException.class)
    public ResponseEntity methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException ex) {
        log.error(StringUtil.getStackTrace(ex));

        BindingResult bindingResult = ex.getBindingResult();

        StringBuilder message = new StringBuilder();
        if (bindingResult.hasErrors()) {
            for (ObjectError objectError : bindingResult.getAllErrors()) {
                FieldError fieldError = ((FieldError) objectError);
                message.append(MessageFormat.format("{0} {1}!", fieldError.getField(), fieldError.getDefaultMessage()));
                break;
            }
        }

        JsonWrapper jsonWrapper = JsonWrapper.createErrorResponse(null, HttpStatus.OK.toString(), message.toString(), ex);
        return new ResponseEntity(jsonWrapper, HttpStatus.OK);
    }

    @ResponseBody
    @ExceptionHandler(value = Throwable.class)
    public ResponseEntity exceptionHandler(Exception ex) {
        log.error(StringUtil.getStackTrace(ex));

        String message = ex.getMessage();
        JsonWrapper jsonWrapper = JsonWrapper.createErrorResponse(null, HttpStatus.OK.toString(), message, ex);
        return new ResponseEntity(jsonWrapper, HttpStatus.OK);
    }
}

3 @Validated和@Valid区别

Spring Validation验证框架对参数的验证机制提供了@Validated。javax提供了@Valid,配合BindingResult可以直接提供参数验证结果。
其中对于字段的特定验证注解比如@NotNull等网上到处都有,这里不详述。

在检验Controller的入参是否符合规范时,使用@Validated或者@Valid在基本验证功能上没有太多区别,但在分组、注解地方、嵌套验证等功能上两个有所不同:

3.1 注解地方

  • @Validated:可以用在、方法和方法参数上。但是不能用在成员属性(字段)上
  • @Valid:可以用在方法、构造函数、方法参数和成员属性(字段)上
    两者是否能用于成员属性(字段)上直接影响能否提供嵌套验证的功能。

3.2 分组

后台校验有很多的工具,最开始用的是@Valid,这个是比较简单的,不支持分组校验。
@Validated提供了一个分组功能,可以在入参验证时,根据不同的分组采用不同的验证机制。
下面使用Demo演示下。

3.2.1 创建分组接口

public interface CreateScheduleObject {
}

public interface UpdateScheduleObject {
}

3.2.2 DTO字段上标记分组

@Validated
public class ScheduleObjectDTO extends AuditBaseEntity implements BaseDto {
    @NotEmpty(groups = {UpdateScheduleObject.class})
    private String name;

    ......
}

3.2.3 Controller方法上指定分组验证

public class ScheduleObjectController {
    @RequestMapping(value = "{uuid}", method = {RequestMethod.PUT}, produces = {"application/json"})
    public JsonWrapper update(@PathVariable String uuid, @RequestBody @Validated({CreateScheduleObject.class,UpdateScheduleObject.class}) ScheduleObjectDto scheduleObjectDto) throws Exception {
        scheduleObjectDto.setUuid(uuid);
        ScheduleObject scheduleObject = posScheduleObjectService.saveScheduleObject(scheduleObjectDto);
        return new JsonWrapper(scheduleObject);
    }
}

这个方法会验证包含在 CreateScheduleObject和UpdateScheduleObject  组中的字段。

3.3 嵌套验证

嵌套验证必须使用 @Valid,下面的 subItemList字段上就使用了@Valid来进行嵌套验证。@Validated是不支持放字段上的。下面使用Demo演示下。

3.3.1 父对象

import org.springframework.validation.annotation.Validated;

import javax.validation.Valid;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import java.util.List;

@Validated
public class Item {

    @NotNull(message = "id不能为空")
    @Min(value = 1, message = "id必须为正整数")
    private long id;

    @Valid
    @NotNull(message = "subItemList不能为空")
    @Size(min = 1, message = "至少要有一个值")
    private List<SubItem> subItemList;
}

3.3.2 子对象

import org.springframework.validation.annotation.Validated;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;

@Validated
public class SubItem {
    @NotNull(message = "parentId 不能为空")
    private long parentId;

    @NotBlank(message = "name不能为空")
    private String name;
}

3.3.3 Controller

@RestController
public class ItemController {
    @RequestMapping(value = "/item/add", method = {RequestMethod.POST}, produces = {"application/json"})
    public void addItem(@RequestBody @Validated Item item) {
        System.out.println(">>> 已通过验证.");
    }
}

3.3.4 测试

由于子对象没有输入name值,因此测试报错:subItemList[0].name name不能为空!。说明嵌套验证起作用了。
子对象虽然贴有@Validated,但对于嵌套对象验证来说是不起作用的,必须在主对象的属性上使用@Valid。

发表评论

您的电子邮箱地址不会被公开。