@Validated
统一参数检验,统一异常处理
基本测试条件
DTO对象:requestBody
@Data
public class XxxUserGroupCreateReqVO {
@NotNull(message = "组名不能为空")
@Length(min = 6, max = 20, message = "组名长度不能超过 20 个字符")
private String name;
@NotEmpty(message = "描述不能为空")
private String description;
@NotNull(message = "成员编号数组不能为空")
private Set<Long> memberUserIds;
@NotNull(message = "状态不能为空")
private Integer status;
}
参数接收:requestParam
/PathVariable
请求参数①:JSON对象
{
"name": "1",
"memberUserIds": [1,2],
"status": 0
}
请求参数②:param键值对传值
?name=1&status=0
测试分析
测试①:
@Validated
注解添加在Controller上,方法请求参数不添加@Validated
- 参数验证无效
@RestController
@RequestMapping("/xxx/user-group")
@Validated
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(XxxUserGroupCreateReqVO createReqVO) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试②:
@Validated
注解添加在Controller的方法请求参数,但是请求参数没有添加@RequestBody
- 触发异常:
org.springframework.validation.BindException
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Validated XxxUserGroupCreateReqVO createReqVO) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试③:
@Validated
注解添加在Controller的方法请求参数,且请求参数添加@RequestBody
- 触发异常:
org.springframework.web.bind.MethodArgumentNotValidException
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Validated @RequestBody XxxUserGroupCreateReqVO createReqVO) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试④:
@Validated
注解添加在Controller的方法请求参数,但是请求参数没有添加@RequestBody
,且传递的是请求参数②
- 触发异常:
org.springframework.validation.BindException
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Validated XxxUserGroupCreateReqVO createReqVO) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试⑤:不添加
@Validated
,请求参数直接添加@NotNull
等,传递的是请求参数②
- 参数验证不生效
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Length(min = 6, max = 20) @NotNull(message = "组名不能为空") String name, Integer status) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试⑥:
@Validated
注解添加在Controller的方法请求参数,请求参数再添加@NotNull
等,传递的是请求参数②
- 参数验证不生效
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Validated @Length(min = 6, max = 20) @NotNull(message = "组名不能为空") String name, Integer status) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试⑦:
@Validated
注解添加在Controller上,请求参数再添加@NotNull
等,传递的是请求参数②
- 触发异常:
javax.validation.ConstraintViolationException
@RestController
@RequestMapping("/xxx/user-group")
public class XxxUserGroupController {
@Resource
private XxxUserGroupService userGroupService;
@PostMapping("/create")
public CommonResult<Long> createUserGroup(@Validated @Length(min = 6, max = 20) @NotNull(message = "组名不能为空") String name, Integer status) {
return success(userGroupService.createUserGroup(createReqVO));
}
}
测试②和③的差异原因分析:@RequestBody
对于@Validated
的影响(测试④同理)
在Spring框架中,
@Validated
注解的使用及其与@RequestBody
注解的结合会影响数据绑定和验证过程中抛出的异常类型。理解这一差异的关键在于明白Spring如何处理HTTP请求中的数据以及如何应用验证逻辑。
没有添加
@RequestBody
的情况当
@Validated
注解用于控制器方法的参数,但没有与@RequestBody
一起使用时,Spring
MVC会将HTTP请求参数(如查询参数、表单数据等)映射到方法参数对象。在这个过程中,Spring使用WebDataBinder
来进行数据绑定,即将请求中的数据填充到Java对象的属性中。如果在数据绑定过程中发现问题(如类型不匹配、格式错误等),或者绑定后的对象违反了通过
@Validated
启用的验证约束,Spring会抛出org.springframework.validation.BindException
。这是因为在这种情况下,验证是作为数据绑定过程的一部分进行的,且直接关联于表单提交和URL查询参数的处理。
添加了
@RequestBody
的情况当
@Validated
与@RequestBody
一起使用时,@RequestBody
告诉Spring
MVC需要将HTTP请求体(通常是JSON或XML格式)反序列化为Java对象。这个过程通过HttpMessageConverter
实现,与WebDataBinder
的数据绑定过程是分开的。在这种情况下,如果反序列化成功,但对象不符合
@Validated
启用的验证约束,Spring
MVC会抛出org.springframework.web.bind.MethodArgumentNotValidException
。这个异常专门用于处理通过@RequestBody
接收并验证的对象失败的场景,它与@Validated
结合使用,确保了请求体中的对象在转换和验证过程中的问题能够被捕获和处理。
为什么会触发不同的异常
- 处理流程不同:没有添加
@RequestBody
时,参数绑定和验证是通过WebDataBinder
一起完成的,因此使用BindException
来表示错误。而添加了@RequestBody
后,参数的解析(反序列化)和验证是两个分开的步骤,这里使用MethodArgumentNotValidException
来专门表示验证失败的情况。- 数据源不同:没有
@RequestBody
时,数据源是来自URL的查询参数或表单数据;而@RequestBody
指示数据源是请求体中的内容,通常是JSON或XML,这需要不同的处理方式。- 异常设计理念:Spring设计不同的异常类型,以便于开发者更精确地理解和处理错误。
BindException
关注于表单和参数绑定,而MethodArgumentNotValidException
专注于处理请求体内容的验证问题。
测试⑤⑥⑦结论:requestParam/PathVariable参数校验必须在Controller类上标注@Validated注解
GET请求一般会使用
requestParam/PathVariable
传参。如果参数比较多(比如超过6个),还是推荐使用DTO对象接收。
否则,推荐将一个个参数平铺到方法入参中。在这种情况下,必须在Controller类上标注@Validated注解
,并在入参上声明约束注解(如@Min等)。如果校验失败,会抛出ConstraintViolationException
异常。
统一异常处理
/**
* 全局异常处理器
*
* @author ruoyi
*/
@RestControllerAdvice
public class GlobalExceptionHandler
{
/**
* 自定义验证异常
*/
@ExceptionHandler(BindException.class)
public AjaxResult handleBindException(BindException e)
{
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
log.error(msg);
return AjaxResult.error(msg);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(MethodArgumentNotValidException.class)
public Object handleMethodArgumentNotValidException(MethodArgumentNotValidException e)
{
BindingResult bindingResult = e.getBindingResult();
StringBuilder sb = new StringBuilder("校验失败:");
for (FieldError fieldError : bindingResult.getFieldErrors()) {
sb.append(fieldError.getField()).append(":").append(fieldError.getDefaultMessage()).append(", ");
}
String msg = sb.toString();
log.error(msg);
return AjaxResult.error(msg);
}
/**
* 自定义验证异常
*/
@ExceptionHandler(ConstraintViolationException.class)
public AjaxResult handleConstraintViolationException(ConstraintViolationException e)
{
log.error(e.getMessage(), e);
return AjaxResult.error(e.getMessage());
}
}
扩展:@Validated
在service中的应用
测试①:
@Validated
注解添加在Service上,方法请求参数不添加@Validated
- 触发异常:
javax.validation.ConstraintViolationException
@Service
@Validated
public class XxxUserGroupServiceImpl implements BpmUserGroupService {
@Resource
private XxxUserGroupMapper userGroupMapper;
@Override
public Long createUserGroup( XxxUserGroupCreateReqVO createReqVO) {
// 插入
XxxUserGroupDO userGroup = XxxUserGroupConvert.INSTANCE.convert(createReqVO);
userGroupMapper.insert(userGroup);
// 返回
return userGroup.getId();
}
}
测试②:
@Validated
注解添加在Service的方法请求参数上
- 参数验证无效
@Service
public class XxxUserGroupServiceImpl implements BpmUserGroupService {
@Resource
private XxxUserGroupMapper userGroupMapper;
@Override
public Long createUserGroup(@Validated XxxUserGroupCreateReqVO createReqVO) {
// 插入
XxxUserGroupDO userGroup = XxxUserGroupConvert.INSTANCE.convert(createReqVO);
userGroupMapper.insert(userGroup);
// 返回
return userGroup.getId();
}
}