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。