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。