Controller层代码这么写,简洁又优雅( 三 )

@RequestBody 参数校验Post、Put 请求的参数推荐使用 @RequestBody 请求体参数
对 @RequestBody 参数进行校验需要在 DTO 对象中加入校验条件后,再搭配 @Validated 即可完成自动校验 如果校验失败,会抛出
ConstraintViolationException 异常
//DTO@Datapublic class TestDTO {@NotBlankprivate String userName;@NotBlank@Length(min = 6, max = 20)private String password;@NotNull@Emailprivate String email;}//Controller@RestController(value = https://www.isolves.com/it/cxkf/bk/2022-08-02/"prettyTestController")@RequestMapping("/pretty")public class TestController {private TestService testService;@PostMapping("/test-validation")public void testValidation(@RequestBody @Validated TestDTO testDTO) {this.testService.save(testDTO);}@Autowiredpublic void setTestService(TestService testService) {this.testService = testService;}}校验原理声明约束的方式,注解加到了参数上面,可以比较容易猜测到是使用了 AOP 对方法进行增强
【Controller层代码这么写,简洁又优雅】而实际上 Spring 也是通过
MethodValidationPostProcessor 动态注册 AOP 切面,然后使用
MethodValidationInterceptor 对切点方法进行织入增强
public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor implements InitializingBean {//指定了创建切面的Bean的注解private Class<? extends Annotation> validatedAnnotationType = Validated.class;@Overridepublic void afterPropertiesSet() {//为所有@Validated标注的Bean创建切面Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);//创建Advisor进行增强this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));}//创建Advice,本质就是一个方法拦截器protected Advice createMethodValidationAdvice(@Nullable Validator validator) {return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());}}public class MethodValidationInterceptor implements MethodInterceptor {@Overridepublic Object invoke(MethodInvocation invocation) throws Throwable {//无需增强的方法,直接跳过if (isFactoryBeanMetadataMethod(invocation.getMethod())) {return invocation.proceed();}Class<?>[] groups = determineValidationGroups(invocation);ExecutableValidator execVal = this.validator.forExecutables();Method methodToValidate = invocation.getMethod();Set<ConstraintViolation<Object>> result;try {//方法入参校验,最终还是委托给Hibernate Validator来校验//所以Spring Validation是对Hibernate Validation的二次封装result = execVal.validateParameters(invocation.getThis(), methodToValidate, invocation.getArguments(), groups);}catch (IllegalArgumentException ex) {...}//校验不通过抛出ConstraintViolationException异常if (!result.isEmpty()) {throw new ConstraintViolationException(result);}//Controller方法调用Object returnValue = https://www.isolves.com/it/cxkf/bk/2022-08-02/invocation.proceed();//下面是对返回值做校验,流程和上面大概一样result = execVal.validateReturnValue(invocation.getThis(), methodToValidate, returnValue, groups);if (!result.isEmpty()) {throw new ConstraintViolationException(result);}return returnValue;}}自定义校验规则有些时候 JSR303 标准中提供的校验规则不满足复杂的业务需求,也可以自定义校验规则
自定义校验规则需要做两件事情

  • 自定义注解类,定义错误信息和一些其他需要的内容
  • 注解校验器,定义判定规则
//自定义注解类@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER})@Retention(RetentionPolicy.RUNTIME)@Documented@Constraint(validatedBy = MobileValidator.class)public @interface Mobile {/*** 是否允许为空*/boolean required() default true;/*** 校验不通过返回的提示信息*/String message() default "不是一个手机号码格式";/*** Constraint要求的属性,用于分组校验和扩展,留空就好*/Class<?>[] groups() default {};Class<? extends Payload>[] payload() default {};}//注解校验器public class MobileValidator implements ConstraintValidator<Mobile, CharSequence> {private boolean required = false;private final Pattern pattern = Pattern.compile("^1[34578][0-9]{9}$"); // 验证手机号/*** 在验证开始前调用注解里的方法,从而获取到一些注解里的参数** @param constraintAnnotation annotation instance for a given constraint declaration*/@Overridepublic void initialize(Mobile constraintAnnotation) {this.required = constraintAnnotation.required();}/*** 判断参数是否合法** @param valueobject to validate* @param context context in which the constraint is evaluated*/@Overridepublic boolean isValid(CharSequence value, ConstraintValidatorContext context) {if (this.required) {// 验证return isMobile(value);}if (StringUtils.hasText(value)) {// 验证return isMobile(value);}return true;}private boolean isMobile(final CharSequence str) {Matcher m = pattern.matcher(str);return m.matches();}}


推荐阅读