一、实现目的
在编写接口的时候,通常会先对参数进行一次校验,这样业务逻辑代码就略显冗杂,如果可以把校验参数的代码进行统一管理,在方法或者属性上直接添加注解就可以实现参数的校验,就可以提升代码编写的效率。
二、实现原理
通过自定义注解,注解在入参VO的属性上,设定需要满足的条件,然后通过面向切面编程,对待切入方法进行切入,对注有相关注解的属性进行校验,对比参数和条件,抛出异常统一处理返回。
三、代码详情
1.自定义注解
先定义一个用于标注哪些方法需要切入的注解(后面:在写一个切面类,会使得这个注解设置在哪个方法上,哪个方法就需要被切入) 其实就是设置那里作为切入点
package com.atguigu.gulimall.coupon.learn.annotation; import java.lang.annotation.*; /** * 自定义注解,用于标识是AOP的切点 * * 这个方法和@StrVal注解的区别:这个注解是标识 哪里是AOP的切点,而@StrVal 注解是为了注解在字段上做字段校验用的 */ @Target({ElementType.PARAMETER,ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface ParamValided { boolean open() default true; }
再定义一个参数校验注解,用于注解在某个入参实体的属性上;(注解在实体的属性上,实现对实体属性的校验)
package com.atguigu.gulimall.coupon.learn.annotation; import java.lang.annotation.*; /* 自定义注解,用于做参数校验 min() : 参数最小长度 max():参数最大长度 regex():正则表达式 info(): 参数名称 ifNull():是否允许为空 */ @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface StrVal { int min() default 0; int max() default 26; String regex() default ""; String info() default "参数"; boolean ifNull() default false; }
2.切面类
定义这个方法,将最上面自定义的注解:
@ParamValided 关联到哪些方法需要切入; 这里需要和 @ParamValided 定义那里一起看;
package com.atguigu.gulimall.coupon.learn.aspect; import com.atguigu.gulimall.coupon.learn.util.PcheckUtil; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Before; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * @author: jd * @create: 2024-03-28 */ @Aspect @Component public class ParamsCheckAspect { /** * 定义作为切入点的方法 ,并且将切入方法和@ParamValided 关联起来,通过这里就能使得注有这个注解的方法就需要被切入! */ @Pointcut("@annotation(com.atguigu.gulimall.coupon.learn.annotation.ParamValided)") public void pointcut(){}; @Before(value="pointcut()") //绑定到上面指定切入点的方法 public void before(JoinPoint joinPoint) throws Exception{ //获取方法参数 Object[] args = joinPoint.getArgs(); for (Object arg : args) { System.out.println("====我在注解方法前被执行了,代表切面切入进去====="); //对切入的方法的入参做参数校验 //调用校验参数的工具类 PcheckUtil.validate(arg); } } @After(value="pointcut()") //这个注解,在被切方法是否抛出异常的情况下都会执行,并切是在被切入方法执行之后去执行的 public void after(JoinPoint joinPoint){ System.out.println("====被切的方法发生异常之后,我在注解方法执行后又执行了,代表切面切入完成====="); } }
3.工具类(对入参的具体校验逻辑)
其中具体代码的作用,我都注明在了代码中。
package com.atguigu.gulimall.coupon.learn.util; import com.atguigu.gulimall.coupon.learn.annotation.StrVal; import com.atguigu.gulimall.coupon.learn.myexception.ParamsException; import com.mysql.cj.util.StringUtils; import org.springframework.stereotype.Component; import java.lang.annotation.Annotation; import java.lang.reflect.Field; /** * * 字符串校验工具类 * @author: jd * @create: 2024-03-28 */ @Component //加入到spring的管理中 public class PcheckUtil { public static void validate(Object arg) throws IllegalAccessException { //获取传的入参类中所有的属性 ,获取到入参类AddCooksParams 的所有属性 Field[] declaredFields = arg.getClass().getDeclaredFields(); for (Field declaredField : declaredFields) { //判断传入的参数AddCooksParams类的每个属性中是否有StrVal这个注解 if(declaredField.isAnnotationPresent(StrVal.class)){ //因为有@StrVal 注解 ,所以取这个注解中的值,进行校验处理, //这里 是拿到当前属性上的StrVal注解,因为可能在这个属性上有多个注解。所以指定注解类的名称 StrVal annotation = declaredField.getAnnotation(StrVal.class); int min = annotation.min(); int max = annotation.max(); String regex = annotation.regex(); String info = annotation.info(); boolean ifNull = annotation.ifNull(); //设置属性可见性 declaredField.setAccessible(true); //拿到 入参arg中当前属性declaredField对应的值 String value = (String) declaredField.get(arg); //先判断是否可以为空,就是判断ifNull 是否为true //如果可以为空,且当前属性的值为空,则不用进行其他的校验,因为没必要做注解中的空校验和 其他的校验了 if(ifNull && StringUtils.isNullOrEmpty(value)){ //直接继续下一个参数校验,继续循环,不走下面的逻辑 continue; } if(!ifNull&&StringUtils.isNullOrEmpty(value)){ //如果是注解中是指定不可为空的,而且值是空的,则进行异常抛出 throw new ParamsException(info+"不可为空!"); } //如果在注解中有指定正则表达式,则进行正则表达式正则匹配校验,不匹配则抛出指定异常提示 if(StringUtils.isNullOrEmpty(regex)&®ex.length()>0){ if(!value.matches(regex)){ throw new ParamsException(info+"格式不匹配!"); } } //最后做一下长度校验 if(value.length()<min || value.length()>max){ throw new ParamsException(info+"长度不符合标准,请填写"+min +"到"+max+"长度的内容"); } } } } }
4.全局异常拦截
【1】自定义异常
package com.atguigu.gulimall.coupon.learn.myexception; import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; /** * 自定义异常类 * @author: jd * @create: 2024-03-28 */ @EqualsAndHashCode(callSuper = true) @AllArgsConstructor @NoArgsConstructor @Data public class ParamsException extends RuntimeException { private static final long serialVersionUID = 7060237606941777850L; private String message; // 异常信息 /** * 重写父类的getMessage方法。获取用于获取异常信息 * @return */ @Override public String getMessage(){ return message; } public void setMessage(String message){ this.message =message; } }
【2】全局异常拦截
注:Result类是我自己封装的,可以自己写Map或者实体类返回
package com.atguigu.gulimall.coupon.learn.myexception; import com.atguigu.common.utils.R; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.ResponseBody; import javax.xml.transform.Result; /** * 全局异常拦截器 * @author: jd * @create: 2024-03-28 */ @ControllerAdvice //这个注解的作用如下: /*以下是 @ControllerAdvice 注解的一些主要用途: 1.全局异常处理:你可以使用 @ExceptionHandler 注解来定义异常处理方法,这些方法将应用于所有控制器。*/ public class GlobalExceptionHandler { /** * 拦截全部控制器范围内的 ParamsException异常的 参数错误返回 * @param paramsException * @return */ @ExceptionHandler(ParamsException.class) //指定用于处捕捉所有控制器,抛出的某个异常类 @ResponseBody public R handleMyException(ParamsException paramsException){ //捕捉到错误信息之后,将错误信息返回到前台 System.out.println("===========全局异常拦截器捕捉到了ParamsException异常=========="); return R.error(400,paramsException.getMessage()); } }
5.注解使用
【1】controller
package com.atguigu.gulimall.coupon.learn.controller; import com.atguigu.common.utils.R; import com.atguigu.gulimall.coupon.learn.annotation.ParamValided; import com.atguigu.gulimall.coupon.learn.myexception.ParamsException; import com.atguigu.gulimall.coupon.learn.params.AddCooksParams; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** 测试 切面请求方法 * @author: jd * @create: 2024-03-28 */ @RestController @Slf4j @RequestMapping("/coupon/learn") public class TestController { /** * * 请求参数:{"name":"大萝","src":"10","detail":"描述测试描述测试"} * * 切面切入的测试方法 * @param a * @return */ @ParamValided @PostMapping("/testASpect") public R addCooks(@RequestBody AddCooksParams a){ String result= null; //这里直接抛出异常: 是为了验证@After这个是不是,在被切入方法是否异常都会执行 int y =1/0 ; // 这里主动抛出 异常 try{ result = a.toString(); System.out.println(result); System.out.println("====我后面被执行了,代表切面切入完毕,返回到被切位置====="); }catch (RuntimeException runtimeException){ //这里可以验证到 切面里面抛出的异常,在被切的方法位置是捕捉不到这个异常的,只有在被切方法本身抛出的异常,则才会被这里catch捕捉到 log.error("*************>"+"主方法抛出的异常"); throw new ParamsException("主方法抛出的异常!!!"); } return R.ok().put("addCooksParams",result); } }
【2】入参实体类
注:我使用了lombok
package com.atguigu.gulimall.coupon.learn.params; import com.atguigu.gulimall.coupon.learn.annotation.StrVal; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import java.io.Serializable; /** 参数类 * @author: jd * @create: 2024-03-28 */ @Data @NoArgsConstructor @AllArgsConstructor public class AddCooksParams implements Serializable { private static final long serialVersionUID = 2145635852726787978L; @StrVal(info = "菜品名称",min = 2,max = 5) private String name; private String src; @StrVal(info = "菜品描述",max = 10) private String detail; @Override public String toString() { return "AddCooksParams{" + "name='" + name + '\'' + ", src='" + src + '\'' + ", detail='" + detail + '\'' + '}'; } }
6.返回效果
请求参数:
{"name":"大萝","src":"10","detail":"描述测试描述测试"} 正常的返回: XXX,忘记截图了,其实就是返回一个实体,其中的参数有:
"addCooksParams":XXX,
"code": 0,
"msg": "success"不满足校验条件的请求参数: {"name":"大萝","src":"10","detail":"描述测试描述测试描述测试"} 结果:postman会返回:
长度不符合标准,请填写"+min +"到"+max+"长度的内容
参考文章: https://blog.csdn.net/weixin_58973530/article/details/130596633 标签:自定义,com,atguigu,校验,import,注解,异常,annotation From: https://www.cnblogs.com/isme-zjh/p/18134258