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)的,該方案沒(méi)有用到反射和代理。
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;
}
// 注解上沒(méi)有有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;
// 沒(méi)有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è)人覺(jué)得參數(shù)校驗(yàn)在controller或者對(duì)外暴露的服務(wù)中去做就好了,因?yàn)檫@些都是對(duì)外提供服務(wù)的,controller層也應(yīng)該去做這些,所以參數(shù)需要校驗(yàn)好。
沒(méi)必要在自己內(nèi)部調(diào)用的service中加校驗(yàn)。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Java使用DateTimeFormatter實(shí)現(xiàn)格式化時(shí)間
這篇文章主要介紹了Java使用DateTimeFormatter實(shí)現(xiàn)格式化時(shí)間,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-01-01
詳解spring cloud Feign使用中遇到的問(wèn)題總結(jié)
本篇文章主要介紹了詳解spring cloud Feign使用中遇到的問(wèn)題總結(jié),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-01-01
springboot中spring.profiles.include的妙用分享
這篇文章主要介紹了springboot中spring.profiles.include的妙用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-08-08
JavaWeb入門教程之分頁(yè)查詢功能的簡(jiǎn)單實(shí)現(xiàn)
這篇文章主要介紹了JavaWeb入門教程之分頁(yè)查詢功能的簡(jiǎn)單實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Sharding-jdbc報(bào)錯(cuò):Missing the data source
在使用MyBatis-plus進(jìn)行數(shù)據(jù)操作時(shí),新增Order實(shí)體屬性后,出現(xiàn)了數(shù)據(jù)源缺失的提示錯(cuò)誤,原因是因?yàn)閡serId屬性值使用了隨機(jī)函數(shù)生成的Long值,這與sharding-jdbc的路由規(guī)則計(jì)算不匹配,導(dǎo)致無(wú)法找到正確的數(shù)據(jù)源,通過(guò)調(diào)整userId生成邏輯2024-11-11
SpringBoot項(xiàng)目注入?traceId?追蹤整個(gè)請(qǐng)求的日志鏈路(過(guò)程詳解)
本文介紹了如何在單體SpringBoot項(xiàng)目中通過(guò)手動(dòng)實(shí)現(xiàn)過(guò)濾器或攔截器來(lái)注入traceId,以追蹤整個(gè)請(qǐng)求的日志鏈路,通過(guò)使用MDC和配置日志格式,可以在日志中包含traceId,便于問(wèn)題排查,同時(shí),還在返回的包裝類中注入traceId,以便用戶反饋問(wèn)題,感興趣的朋友一起看看吧2025-02-02

