首页 > 其他分享 >实现自定义注解校验方法参数(AOP+自定义注解+自定义异常+全局异常捕获)

实现自定义注解校验方法参数(AOP+自定义注解+自定义异常+全局异常捕获)

时间:2024-04-14 16:12:08浏览次数:24  
标签:自定义 com atguigu 校验 import 注解 异常 annotation

一、实现目的

在编写接口的时候,通常会先对参数进行一次校验,这样业务逻辑代码就略显冗杂,如果可以把校验参数的代码进行统一管理,在方法或者属性上直接添加注解就可以实现参数的校验,就可以提升代码编写的效率。

二、实现原理

通过自定义注解,注解在入参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)&&regex.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

相关文章

  • scanf 中给 double 用 %f 时赋值异常, float lf, char s 同理
    结论scanf的变量要匹配对应的格式化字符串。floatf,doublelf,charc编译器提示的错误要消除,不消除不能运行;同时尽量消除警告doublefc语言中,给double类型的变量用scanf%f输入赋值时,会发生逻辑上的错误,请看代码#include<stdio.h>intmain(){doublevalue......
  • Windows 自定义服务(Windows Service)管理
    Windows自定义服务(WindowsService)管理在Windows系统中,你可以使用sc.exe命令或者PowerShell来创建自定义服务。以下是两种方法:使用sc.exe命令:打开命令提示符(以管理员身份运行)。使用sc.exe命令创建服务,语法如下:sccreate<ServiceName>binPath="<PathtoE......
  • @Resource和@Autowired注解
    @Resource和@Autowired注解都是用来实现依赖注入的。只是@AutoWried按bytype自动注入,而@Resource默认按byName自动注入。@Resource有两个重要属性,分别是name和typespring将name属性解析为bean的名字,而type属性则被解析为bean的类型。所以如果使用name属性,则使用byName的自动注......
  • Ant - Form 自定义组件 form.getFiledsValue 如何获取值
    import{FC,useState}from'react';importtype{SelectProps}from'antd';import{Select,Space,Flex,Input,Button}from'antd';/***扩展选择器组件,可以通过键盘enter输入一个Option*/constInputSelect:FC<{defaultOptio......
  • 一种新的姿势:程序try/catch抛出异常之绕过canary pwn121
    一种新的姿势:程序try/catch抛出异常之绕过canary我前面发了不少关于绕过canary的姿势,先总结一下,现在绕过canary的姿势有泄露,爆破,格式化字符串绕过,多线程劫持TLS绕过,stack_smashing,今天介绍一种新的姿势,就是程序处理异常时,如果异常被上一个函数的catch捕获,那么上个函数的rbp就会......
  • C语言10-指针(多级指针、空指针、野指针),自定义数据类型(枚举enum、结构体struct)
    第12章指针pointer12.6多级指针指向指针的指针称为多级指针eg:int*ptr1=&num; int**ptr2=&ptr1; int***ptr3=&ptr2;12.7空指针应用场景:1.暂时不确定指向的指针,可以在定义的时候先赋值为NULL2.有些指针函数,如果内部出现异常无法成功实现功能,可以返回NUL......
  • WPF自定义Window
    前言我们使用WPF开发客户端软件时,一般来讲都不会直接使用默认的Window样式,因为很难符合项目的风格,所以我们一般会自定义Window,一般有两种方式。WindowStyle=None和自定义Window,本文主要介绍第二种。一、WindowStyle=NoneWindowStyle="None"将Window的整个边框就去掉了,好处是......
  • docker network之 自定义网络(重点,多容器时都是使用这个)
    原来的默认使用bridge模式,创建好容器以后,2个容器使用ip地址去ping对方的ip是ok的,但是按照容器的服务名字取ping就失败: 我们知道容器在重启后,ip是可能变化的。所以那总不可能按照ip去访问吧,最好是按照服务名去访问,那怎么处理呢,请看下方:dockernetworklsdockernetworkcrea......
  • SpringBoot starter 原理及如何自定义 starter
     前言项目的开发要求是不断进化的,而随着时间以及技术的推移,在项目中除了基本的编程语言外,还需要进行大量的应用服务整合。例如,在项目中使用MySQL数据库进行持久化存储,同时会利用Redis作为缓存存储,以及使用RocketMQ实现异构系统整合服务等。但在早先使用Spring开发的......
  • 实用技巧:排查数据异常/数据波动问题,该如何下手?
    前言在我做开发的这些年,让我很头痛的一类问题,不是线上故障,而是数据异常,不知道有没有程序员跟我感同身受。大多数的服务故障都有较为直观的异常日志,再结合产品表象,相对排查起来还有迹可循,但数据异常的原因就太多了,很多时候连报错日志都没有,排查起来简直无从下手。在一个微服务、......