Java使用@Validated注解進(jìn)行參數(shù)驗(yàn)證的方法
目前項(xiàng)目中大部分代碼進(jìn)行參數(shù)驗(yàn)證都是寫代碼進(jìn)行驗(yàn)證,為了提升方便性和代碼的簡(jiǎn)潔性,所以整理了下使用注解進(jìn)行參數(shù)驗(yàn)證。使用效果如下:
// 要驗(yàn)證的實(shí)體類 @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 // 在要驗(yàn)證的參數(shù)上添加@Validated注解即可 @PostMapping("add") public boolean addUser(@Validated @RequestBody User user) { ProdAllotStandard info = allotStandardService.getProdAllotStandardWithDetailById(id); return info; } // 修改則需要比添加的基礎(chǔ)上多驗(yàn)證一個(gè)id,可以通過(guò)group的方式進(jìn)行區(qū)分 // 實(shí)體類上不設(shè)置groups,默認(rèn)會(huì)是Default,所以修改的時(shí)候,我們需要指定Update和Default這兩個(gè)組 // group是可以自定義的,我默認(rèn)定義了Add,Update,Delete,Search這四個(gè)組 @PostMapping("update") public boolean updateUser(@Validated({Update.class, Default.class}) @RequestBody User user) { ProdAllotStandard info = allotStandardService.getProdAllotStandardWithDetailById(id); return info; } // 當(dāng)然該注解也支持service上實(shí)現(xiàn),同樣的使用方法,在service的實(shí)現(xiàn)類的參數(shù)上加上對(duì)應(yīng)注解即可,如: public boolean addUser(@Validated User user) { } // 通過(guò)不同group的搭配,我們就可以靈活的在實(shí)體類上配置不同場(chǎng)景的驗(yàn)證了 // group為一個(gè)接口,用以下方式創(chuàng)建 public interface Add { }
下面看一下具體的實(shí)現(xiàn),驗(yàn)證框架的核心實(shí)現(xiàn)采用的是hibernate-validator,我采用的是5.4.3.Final版本。
controller和service的實(shí)現(xiàn)方式不同,下面分別介紹下。
不管哪種實(shí)現(xiàn),我們都需要先定義一個(gè)異常和一個(gè)異常攔截器,當(dāng)參數(shù)校驗(yàn)出現(xiàn)問(wèn)題時(shí),我們就拋出對(duì)應(yīng)異常。
// 為了簡(jiǎn)短,省略了部分代碼 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); // 對(duì)所有的ParamsException統(tǒng)一進(jìn)行攔截處理,如果捕獲到該異常,則封裝成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的驗(yàn)證的實(shí)現(xiàn):
主要是通過(guò)實(shí)現(xiàn)spring mvc給我們留下的接口進(jìn)行實(shí)現(xiàn)的,該方案沒有用到反射和代理。
1. 實(shí)現(xiàn)spring提供的SmartValidator接口
public class ParamsValidator implements SmartValidator { private javax.validation.Validator validator = Validation.buildDefaultValidatorFactory().getValidator(); @Override public boolean supports(Class<?> clazz) { return true; } // 注解上沒有有g(shù)roup的驗(yàn)證邏輯 @Override public void validate(Object target, Errors errors) { validate(target,errors,null); } // 注解上帶有g(shù)roup的驗(yàn)證邏輯 // 第一個(gè)參數(shù)為我們要驗(yàn)證的參數(shù),第二個(gè)不用管,第三個(gè)為注解上設(shè)置個(gè)groups @Override public void validate(Object target, Errors errors, Object... validationHints) { // 這里面為驗(yàn)證實(shí)現(xiàn),可以根據(jù)自己的需要進(jìn)行完善與修改 if (target == null) { throw new ParamsException("參數(shù)不能為空!"); } else { if(target instanceof String) { if(StringUtils.isEmpty(target)) { throw new ParamsException("參數(shù)不能為空!"); } }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; // 沒有g(shù)roups的驗(yàn)證 if(objs==null || objs.length==0) { violations = validator.validate(target); } else { // 基于groups的驗(yàn)證 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)); } // 若為空,則驗(yàn)證通過(guò) if(violations==null||violations.isEmpty()) { return; } // 驗(yàn)證不通過(guò)則拋出ParamsException異常。 for(ConstraintViolation item:violations) { throw new ParamsException(item.getMessage()); } } }
2. 配置并設(shè)置Validator驗(yàn)證器
@Configuration public class WebMvcConfigurer extends WebMvcConfigurerAdapter{ // 我們?cè)谶@里重寫spring的一個(gè)方法,返回我們自定義的驗(yàn)證器 @Override public Validator getValidator() { return createValidator(); } @Bean public ParamsValidator createValidator(){ return new ParamsValidator(); } }
非常簡(jiǎn)單,通過(guò)上面配置,就可以在controller上使用注解了。
spring mvc對(duì)這個(gè)注解的處理主要是在RequestResponseBodyMethodProcessor這個(gè)類中的resolveArgument方法實(shí)現(xiàn)的,該類主要處理方法的參數(shù)和返回值。
spring mvc調(diào)用的一段代碼。
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的驗(yàn)證的實(shí)現(xiàn):
該方案主要通過(guò)aop進(jìn)行攔截處理的。如下配置:
@Component @Aspect public class ParamsValidateAdvice { // 這里重用上面定義的那個(gè)validator private ParamsValidator validator = new ParamsValidator(); /** * 攔截參數(shù)上加了@Validated的注解的方法 * 排除掉controller,因?yàn)閏ontroller有自己的參數(shù)校驗(yàn)實(shí)現(xiàn) 不需要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(); // 驗(yàn)證參數(shù)上的注解 for(int i=0;i<parameters.length;i++) { Parameter p = parameters[i]; // 獲取參數(shù)上的注解 Validated validated = p.getAnnotation(Validated.class); if(validated==null) { continue; } // 如果設(shè)置了group if(validated.value()!=null && validated.value().length>0) { validator.validate(params[i],null,validated.value()); } else { validator.validate(params[i],null); } } } }
這樣就可以在service使用驗(yàn)證注解了,具體的Pointcut可以自己進(jìn)行配置。
個(gè)人覺得參數(shù)校驗(yàn)在controller或者對(duì)外暴露的服務(wù)中去做就好了,因?yàn)檫@些都是對(duì)外提供服務(wù)的,controller層也應(yīng)該去做這些,所以參數(shù)需要校驗(yàn)好。
沒必要在自己內(nèi)部調(diào)用的service中加校驗(yàn)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
阿里云部署SpringBoot項(xiàng)目啟動(dòng)后被殺進(jìn)程的問(wèn)題解析
這篇文章主要介紹了阿里云部署SpringBoot項(xiàng)目啟動(dòng)后被殺進(jìn)程的問(wèn)題,本文給大家分享問(wèn)題原因所在及解決步驟,需要的朋友可以參考下2023-09-09springboot使用redisRepository和redistemplate操作redis的過(guò)程解析
本文給大家介紹springboot整合redis/分別用redisRepository和redistemplate操作redis,本文結(jié)合實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2022-05-05Springboot2.x+ShardingSphere實(shí)現(xiàn)分庫(kù)分表的示例代碼
這篇文章主要介紹了Springboot2.x+ShardingSphere實(shí)現(xiàn)分庫(kù)分表的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10java中構(gòu)造方法及this關(guān)鍵字的用法實(shí)例詳解(超詳細(xì))
大家都知道,java作為一門內(nèi)容豐富的編程語(yǔ)言,其中涉及的范圍是十分廣闊的,下面這篇文章主要給大家介紹了關(guān)于java中構(gòu)造方法及this關(guān)鍵字用法的相關(guān)資料,需要的朋友可以參考下2022-04-04SpringBoot整合日志功能(slf4j+logback)詳解(最新推薦)
Spring使用commons-logging作為內(nèi)部日志,但底層日志實(shí)現(xiàn)是開放的,可對(duì)接其他日志框架,這篇文章主要介紹了SpringBoot整合日志功能(slf4j+logback)詳解,需要的朋友可以參考下2024-08-08基于java實(shí)現(xiàn)租車管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了基于java實(shí)現(xiàn)租車管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-12-12使用java反射將結(jié)果集封裝成為對(duì)象和對(duì)象集合操作
這篇文章主要介紹了使用java反射將結(jié)果集封裝成為對(duì)象和對(duì)象集合操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-08-08