Java使用@Validated注解进行参数验证的方法
目前项目中大部分代码进行参数验证都是写代码进行验证,为了提升方便性和代码的简洁性,所以整理了下使用注解进行参数验证。使用效果如下:
// 要验证的实体类 @Data public class User implements Serializable { @NotBlank(message = "id不能为空!",groups = Update.class) protected String id = ""; @NotBlank(message = "商户id不能为空!") protected String tenantId; @NotBlank(message = "名称不能为空!") protected String name = ""; } // controller // 在要验证的参数上添加@Validated注解即可 @PostMapping("add") public boolean addUser(@Validated @RequestBody User user) { ProdAllotStandard info = allotStandardService.getProdAllotStandardWithDetailById(id); return info; } // 修改则需要比添加的基础上多验证一个id,可以通过group的方式进行区分 // 实体类上不设置groups,默认会是Default,所以修改的时候,我们需要指定Update和Default这两个组 // group是可以自定义的,我默认定义了Add,Update,Delete,Search这四个组 @PostMapping("update") public boolean updateUser(@Validated({Update.class, Default.class}) @RequestBody User user) { ProdAllotStandard info = allotStandardService.getProdAllotStandardWithDetailById(id); return info; } // 当然该注解也支持service上实现,同样的使用方法,在service的实现类的参数上加上对应注解即可,如: public boolean addUser(@Validated User user) { } // 通过不同group的搭配,我们就可以灵活的在实体类上配置不同场景的验证了 // group为一个接口,用以下方式创建 public interface Add { }
下面看一下具体的实现,验证框架的核心实现采用的是hibernate-validator,我采用的是5.4.3.Final版本。
controller和service的实现方式不同,下面分别介绍下。
不管哪种实现,我们都需要先定义一个异常和一个异常拦截器,当参数校验出现问题时,我们就抛出对应异常。
// 为了简短,省略了部分代码 public class ParamsException extends RuntimeException{ private String code; public BusinessException() { } public ParamsException(String code,String message) { super(message); this.code = code; } public ParamsException(String message) { super(message); } } // 定义异常处理类 @ControllerAdvice public class ExceptionAdvice { private static Logger L = LoggerFactory.getLogger(ExceptionAdvice.class); // 对所有的ParamsException统一进行拦截处理,如果捕获到该异常,则封装成MessageBody返回给前端 @ExceptionHandler(value = ParamsException.class) @ResponseStatus(HttpStatus.OK) @ResponseBody public MessageBody handleParamsException(HttpServletRequest request, BusinessException e){ L.error(e.getMessage(),e); return getErrorMessageBody(e.getData(),e.getMessage(),e.getMessage(), StringUtils.isEmpty(e.getCode())?ResponseCode.BUSINESS_ERROR:e.getCode()); } private MessageBody getErrorMessageBody(Object data,String message,String errorInfo,String code){ MessageBody body = new MessageBody(); body.setCode(code); body.setData(data); body.setErrorCode(Integer.parseInt(code)); body.setErrorInfo(errorInfo); body.setMessage(message); body.setSuccess(false); return body; } }
controller的验证的实现:
主要是通过实现spring mvc给我们留下的接口进行实现的,该方案没有用到反射和代理。
1. 实现spring提供的SmartValidator接口
public class ParamsValidator implements SmartValidator { private javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); @Override public boolean supports(Class<?> clazz) { return true; } // 注解上没有有group的验证逻辑 @Override public void validate(Object target, Errors errors) { validate(target,errors,null); } // 注解上带有group的验证逻辑 // 第一个参数为我们要验证的参数,第二个不用管,第三个为注解上设置个groups @Override public void validate(Object target, Errors errors, Object... validationHints) { // 这里面为验证实现,可以根据自己的需要进行完善与修改 if (target == null) { throw new ParamsException("参数不能为空!"); } else { if(target instanceof String) { if(StringUtils.isEmpty(target)) { throw new ParamsException("参数不能为空!"); } }else if(target instanceof Collection) { for(Object o:(Collection)target){ validate(o,validationHints); } }else { validate(target,validationHints); } } } private void validate(Object target,Object ... objs) { Set<ConstraintViolation<Object>> violations; // 没有groups的验证 if(objs==null || objs.length==0) { violations = validator.validate(target); } else { // 基于groups的验证 Set<Class<?>> groups = new LinkedHashSet<Class<?>>(); for (Object hint : objs) { if (hint instanceof Class) { groups.add((Class<?>) hint); } } violations = validator.validate(target, ClassUtils.toClassArray(groups)); } // 若为空,则验证通过 if(violations==null||violations.isEmpty()) { return; } // 验证不通过则抛出ParamsException异常。 for(ConstraintViolation item:violations) { throw new ParamsException(item.getMessage()); } } }
2. 配置并设置Validator验证器
@Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter{ // 我们在这里重写spring的一个方法,返回我们自定义的验证器 @Override public Validator getValidator() { return createValidator(); } @Bean public ParamsValidator createValidator(){ return new ParamsValidator(); } }
非常简单,通过上面配置,就可以在controller上使用注解了。
spring mvc对这个注解的处理主要是在RequestResponseBodyMethodProcessor这个类中的resolveArgument方法实现的,该类主要处理方法的参数和返回值。
spring mvc调用的一段代码。
protected void validateIfApplicable(WebDataBinder binder, MethodParameter parameter) { Annotation[] annotations = parameter.getParameterAnnotations(); for (Annotation ann : annotations) { Validated validatedAnn = AnnotationUtils.getAnnotation(ann, Validated.class); if (validatedAnn != null || ann.annotationType().getSimpleName().startsWith("Valid")) { Object hints = (validatedAnn != null ? validatedAnn.value() : AnnotationUtils.getValue(ann)); Object[] validationHints = (hints instanceof Object[] ? (Object[]) hints : new Object[] {hints}); binder.validate(validationHints); break; } } }
service的验证的实现:
该方案主要通过aop进行拦截处理的。如下配置:
@Component @Aspect public class ParamsValidateAdvice { // 这里重用上面定义的那个validator private ParamsValidator validator = new ParamsValidator(); /** * 拦截参数上加了@Validated的注解的方法 * 排除掉controller,因为controller有自己的参数校验实现 不需要aop */ @Pointcut("execution(* com.choice..*(..,@org.springframework.validation.annotation.Validated (*), ..)) && " + "!execution(* com.choice..api..*(..)) && " + "!execution(* com.choice..controller..*(..)) ") public void pointCut(){} @Before("pointCut()") public void doBefore(JoinPoint joinPoint){ Object[] params=joinPoint.getArgs(); MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); Parameter[] parameters = method.getParameters(); // 验证参数上的注解 for(int i=0;i<parameters.length;i++) { Parameter p = parameters[i]; // 获取参数上的注解 Validated validated = p.getAnnotation(Validated.class); if(validated==null) { continue; } // 如果设置了group if(validated.value()!=null && validated.value().length>0) { validator.validate(params[i],null,validated.value()); } else { validator.validate(params[i],null); } } } }
这样就可以在service使用验证注解了,具体的Pointcut可以自己进行配置。
个人觉得参数校验在controller或者对外暴露的服务中去做就好了,因为这些都是对外提供服务的,controller层也应该去做这些,所以参数需要校验好。
没必要在自己内部调用的service中加校验。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
赞 (0)