Java使用@Validated注解进行参数验证的方法
目前项目中大部分代码进行参数验证都是写代码进行验证,为了提升方便性和代码的简洁性,所以整理了下使用注解进行参数验证。使用效果如下:
//要验证的实体类 @Data publicclassUserimplementsSerializable{ @NotBlank(message="id不能为空!",groups=Update.class) protectedStringid=""; @NotBlank(message="商户id不能为空!") protectedStringtenantId; @NotBlank(message="名称不能为空!") protectedStringname=""; } //controller //在要验证的参数上添加@Validated注解即可 @PostMapping("add") publicbooleanaddUser(@Validated@RequestBodyUseruser){ ProdAllotStandardinfo=allotStandardService.getProdAllotStandardWithDetailById(id); returninfo; } //修改则需要比添加的基础上多验证一个id,可以通过group的方式进行区分 //实体类上不设置groups,默认会是Default,所以修改的时候,我们需要指定Update和Default这两个组 //group是可以自定义的,我默认定义了Add,Update,Delete,Search这四个组 @PostMapping("update") publicbooleanupdateUser(@Validated({Update.class,Default.class})@RequestBodyUseruser){ ProdAllotStandardinfo=allotStandardService.getProdAllotStandardWithDetailById(id); returninfo; } //当然该注解也支持service上实现,同样的使用方法,在service的实现类的参数上加上对应注解即可,如: publicbooleanaddUser(@ValidatedUseruser){ } //通过不同group的搭配,我们就可以灵活的在实体类上配置不同场景的验证了 //group为一个接口,用以下方式创建 publicinterfaceAdd{ }
下面看一下具体的实现,验证框架的核心实现采用的是hibernate-validator,我采用的是5.4.3.Final版本。
controller和service的实现方式不同,下面分别介绍下。
不管哪种实现,我们都需要先定义一个异常和一个异常拦截器,当参数校验出现问题时,我们就抛出对应异常。
//为了简短,省略了部分代码 publicclassParamsExceptionextendsRuntimeException{ privateStringcode; publicBusinessException(){ } publicParamsException(Stringcode,Stringmessage){ super(message); this.code=code; } publicParamsException(Stringmessage){ super(message); } } //定义异常处理类 @ControllerAdvice publicclassExceptionAdvice{ privatestaticLoggerL=LoggerFactory.getLogger(ExceptionAdvice.class); //对所有的ParamsException统一进行拦截处理,如果捕获到该异常,则封装成MessageBody返回给前端 @ExceptionHandler(value=ParamsException.class) @ResponseStatus(HttpStatus.OK) @ResponseBody publicMessageBodyhandleParamsException(HttpServletRequestrequest,BusinessExceptione){ L.error(e.getMessage(),e); returngetErrorMessageBody(e.getData(),e.getMessage(),e.getMessage(), StringUtils.isEmpty(e.getCode())?ResponseCode.BUSINESS_ERROR:e.getCode()); } privateMessageBodygetErrorMessageBody(Objectdata,Stringmessage,StringerrorInfo,Stringcode){ MessageBodybody=newMessageBody(); body.setCode(code); body.setData(data); body.setErrorCode(Integer.parseInt(code)); body.setErrorInfo(errorInfo); body.setMessage(message); body.setSuccess(false); returnbody; } }
controller的验证的实现:
主要是通过实现springmvc给我们留下的接口进行实现的,该方案没有用到反射和代理。
1. 实现spring提供的SmartValidator接口
publicclassParamsValidatorimplementsSmartValidator{ privatejavax.validation.Validatorvalidator=Validation.buildDefaultValidatorFactory().getValidator(); @Override publicbooleansupports(Class>clazz){ returntrue; } //注解上没有有group的验证逻辑 @Override publicvoidvalidate(Objecttarget,Errorserrors){ validate(target,errors,null); } //注解上带有group的验证逻辑 //第一个参数为我们要验证的参数,第二个不用管,第三个为注解上设置个groups @Override publicvoidvalidate(Objecttarget,Errorserrors,Object...validationHints){ //这里面为验证实现,可以根据自己的需要进行完善与修改 if(target==null){ thrownewParamsException("参数不能为空!"); }else{ if(targetinstanceofString){ if(StringUtils.isEmpty(target)){ thrownewParamsException("参数不能为空!"); } }elseif(targetinstanceofCollection){ for(Objecto:(Collection)target){ validate(o,validationHints); } }else{ validate(target,validationHints); } } } privatevoidvalidate(Objecttarget,Object...objs){ Set>violations; //没有groups的验证 if(objs==null||objs.length==0){ violations=validator.validate(target); }else{ //基于groups的验证 Set >groups=newLinkedHashSet >(); for(Objecthint:objs){ if(hintinstanceofClass){ groups.add((Class>)hint); } } violations=validator.validate(target,ClassUtils.toClassArray(groups)); } //若为空,则验证通过 if(violations==null||violations.isEmpty()){ return; } //验证不通过则抛出ParamsException异常。 for(ConstraintViolationitem:violations){ thrownewParamsException(item.getMessage()); } } }
2.配置并设置Validator验证器
@Configuration publicclassWebMvcConfigurerextendsWebMvcConfigurerAdapter{ //我们在这里重写spring的一个方法,返回我们自定义的验证器 @Override publicValidatorgetValidator(){ returncreateValidator(); } @Bean publicParamsValidatorcreateValidator(){ returnnewParamsValidator(); } }
非常简单,通过上面配置,就可以在controller上使用注解了。
springmvc对这个注解的处理主要是在RequestResponseBodyMethodProcessor这个类中的resolveArgument方法实现的,该类主要处理方法的参数和返回值。
springmvc调用的一段代码。
protectedvoidvalidateIfApplicable(WebDataBinderbinder,MethodParameterparameter){ Annotation[]annotations=parameter.getParameterAnnotations(); for(Annotationann:annotations){ ValidatedvalidatedAnn=AnnotationUtils.getAnnotation(ann,Validated.class); if(validatedAnn!=null||ann.annotationType().getSimpleName().startsWith("Valid")){ Objecthints=(validatedAnn!=null?validatedAnn.value():AnnotationUtils.getValue(ann)); Object[]validationHints=(hintsinstanceofObject[]?(Object[])hints:newObject[]{hints}); binder.validate(validationHints); break; } } }
service的验证的实现:
该方案主要通过aop进行拦截处理的。如下配置:
@Component @Aspect publicclassParamsValidateAdvice{ //这里重用上面定义的那个validator privateParamsValidatorvalidator=newParamsValidator(); /** *拦截参数上加了@Validated的注解的方法 *排除掉controller,因为controller有自己的参数校验实现不需要aop */ @Pointcut("execution(*com.choice..*(..,@org.springframework.validation.annotation.Validated(*),..))&&"+ "!execution(*com.choice..api..*(..))&&"+ "!execution(*com.choice..controller..*(..))") publicvoidpointCut(){} @Before("pointCut()") publicvoiddoBefore(JoinPointjoinPoint){ Object[]params=joinPoint.getArgs(); MethodSignaturesignature=(MethodSignature)joinPoint.getSignature(); Methodmethod=signature.getMethod(); Parameter[]parameters=method.getParameters(); //验证参数上的注解 for(inti=0;i0){ validator.validate(params[i],null,validated.value()); }else{ validator.validate(params[i],null); } } } }
这样就可以在service使用验证注解了,具体的Pointcut可以自己进行配置。
个人觉得参数校验在controller或者对外暴露的服务中去做就好了,因为这些都是对外提供服务的,controller层也应该去做这些,所以参数需要校验好。
没必要在自己内部调用的service中加校验。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持毛票票。