SpringBoot(二十六)SpringBoot自定义注解

注解在springboot日常开发中使用的频率是很高的,官方为我们提供了很多注解,比如:@Autowired、@GetMapping等......

但是我们有些特定的需求官方提供的注解是没有的。我们可以自定义注解。

下面我们来了解一下自定义注解的过程。

一:元注解

Java为我们提供了几个元注解来自定义注解。分别是:

  1. @Target:用于描述注解的使用范围(即:被描述的注解可以用在什么地方)
arduino 复制代码
TYPE,   // 类,接口(包括注解类型)或枚举声明
FIELD,  // 字段声明(包括枚举常量)
METHOD, // 方法声明
PARAMETER,  // 方法参数
CONSTRUCTOR,  // 构造方法
LOCAL_VARIABLE,  // 局部变量
ANNOTATION_TYPE, // 注解类型
PCKAGE,  // 包
TYPE_PARAMETER,
TYPE_USE
  1. @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期
取值 描述 作用范围 使用场景
SOURCE 表示注解只保留在源文件,当java文件编译成class文件,就会消失 源文件 只是做一些检查性的操作,,比如 @Override 和 @SuppressWarnings
CLASS 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期 class文件(默认) 要在编译时进行一些预处理操作,比如生成一些辅助代码(如 ButterKnife)
RUNTIME 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在 运行时也存在 需要在运行时去动态获取注解信息
  1. @Document:说明该注解将被包含在javadoc中

  2. @lnherited:说明子类可以继承父类中的该注解

二:自定义注解

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口。

分析:

@interface用来声明一个注解,格式:public@interface注解名{定义内容}

其中的每一个方法实际上是声明了一个配置参数

方法的名称就是参数的名称

返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)

可以通过default来声明参数的默认值

如果只有一个参数成员,一般参数名为value

注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值,

自定义注解分为两部分:

1 :自定义注解

(1):只有一个参数的注解

less 复制代码
// 只能在方法上使用
@Target(ElementType.METHOD)
// Runtime级别注解
@Retention(RetentionPolicy.RUNTIME)
@interface MyCustomAnnotation {
    // 如果只有一个参数,直接用value就可以,可以添加默认值
    String value() default "";
}

调用:

less 复制代码
@GetMapping("get")
@MyCustomAnnotation("test")
public void myMethod() {
    // 业务逻辑
    System.out.println("进入了我的自定义方法!");
}

当注解只有一个参数的时候,调用注解直接传参就可以。

(2):多个参数的注解

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyCustomAnnotation {
    // 可以定义一些属性,如果不需要,可以留空
    String name();
    String age() default "";// 这个可以不传参数
}

调用:

less 复制代码
@GetMapping("get")
@MyCustomAnnotation(name="test",age="20")
public void myMethod() {
    // 业务逻辑
    System.out.println("进入了我的自定义方法!");
}

至此,自定义注解其实就已经完成了,但是,这么作没有什么实际的意义。他没有任何功能,怎么给他添加功能呢?

很简单,使用Springboot的切面类。

2:创建自定义注解对应的切面类

java 复制代码
/**
 *
 * 配置 AOP 切面   ---  aspectj 相关注解的作用:
 *
 * 对于那些性能要求较高的应用,不想在生产环境中打印日志,只想在开发环境或者测试环境中使用,要怎么做呢?我们只需为切面添加 @Profile 就可以
 * @Profile({"dev", "test"})  这样就指定了只能作用于 dev 开发环境和 test 测试环境,生产环境 prod 是不生效的!
 *
 * @Aspect:声明该类为一个注解类;
 * @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;
 * 切点定义好后,就是围绕这个切点做文章了:
 *
 * @Before: 在切点之前,织入相关代码;
 * @After: 在切点之后,织入相关代码;
 * @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
 * @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
 * @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
 *
 *
 * attention:!!
 *      web项目会有多切面,如何指定切面的优先级(执行顺序)?
 *      可以通过 @Order(i) 注解指定优先级, 注意i值越小,优先级越高 ------- 执行顺序类似栈,先进后出
 *      切点之前 Before: 中 @Order(i) i值越小  执行优先级越高
 *      切点之后 After: 中 @Otder(i) i值越小  优先级越低
 *
 *
 */
@Aspect   // 声明该类为一个注解类;
@Component
@Profile({"dev", "test"})  // 这样就指定了只能作用于 dev 开发环境和 test 测试环境,生产环境 prod 是不生效的!
public class MyCustomAspect
{
private final static Logger logger = LoggerFactory.getLogger(MyCustomAspect.class);
    // 自定义注解参数
    private String name;
    private String age;

    // /** 以自定义 @MyCustomAnnotation 注解为切点 --- @annotation里配置的 @MyCustomAnnotation的自定义注解的全路径名 */
    @Pointcut("@annotation(com.modules.controller.test.MyCustomAnnotation)")
    public void myCustomAnnotationPointcut() {}

    /**
     * 在切点之前,织入相关代码;
     * @param joinPoint
     * @param myCustomAnnotation
     */
    @Before("@annotation(myCustomAnnotation)")
    public void beforeMethod(JoinPoint joinPoint, MyCustomAnnotation myCustomAnnotation) throws Exception
    {
        // 获取注解的参数
        this.name = myCustomAnnotation.name();
        this.age = myCustomAnnotation.age();
        System.out.println("注解的参数是: " + name);
        System.out.println("注解的参数是: " + age);
        // 其他逻辑...
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        // 获取 @WebLog 注解的描述信息
        String methodDescription = getAspectLogDescription(joinPoint);

        // 打印请求相关参数
        logger.info("===================== Start ===================");
        // 打印请求 url
        logger.info("URL            : {}", request.getRequestURL().toString());
        // 打印描述信息
        logger.info("Description    : {}", methodDescription);
        // 打印 Http method
        logger.info("HTTP Method    : {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        logger.info("IP             : {}", request.getRemoteAddr());
        // 打印请求入参
        logger.info("Request Args   : {}", new Gson().toJson(joinPoint.getArgs()));
    }

    /**
     * 获取切面注解的描述
     *
     * @param joinPoint 切点
     * @return 描述信息
     * @throws Exception
     */
    public String getAspectLogDescription(JoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        StringBuilder description = new StringBuilder("");
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] clazzs = method.getParameterTypes();
                if (clazzs.length == arguments.length) {
                    description.append(method.getAnnotation(MyCustomAnnotation.class).name());
                    break;
                }
            }
        }
        return description.toString();
    }

    /**
     * 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;
     * @param point
     * @return
     * @throws Throwable
     */
    @Around("myCustomAnnotationPointcut()")
    private Object testAop(ProceedingJoinPoint point) throws Throwable {
        System.out.println("======AopAspectJ执行环绕通知开始=========");
        Object obj = point.proceed();
        Object[] args = point.getArgs();
        //方法名
        String methodName = point.getSignature().getName();
        //对象
        Object target = point.getTarget();
        //类名
        String className = target.getClass().getName();
        System.out.println("类:" + className + ";方法:" + methodName + ";参数:" + JSONArray.toJSONString(args));
        System.out.println("======AopAspectJ执行环绕通知结束=========");
        return obj;
    }

    /**
     * 后置通知(在目标方法执行后调用,若目标方法出现异常,则不执行)
     */
    @AfterReturning("@annotation(myCustomAnnotation)")
    private void afterRunningAdvance(MyCustomAnnotation myCustomAnnotation){
        System.out.println("age=" + myCustomAnnotation.age());
        System.out.println("======AopAspectJ执行后置通知=========");
    }

    /**
     * 在切点之后,织入相关代码;
     * @param myCustomAnnotation
     */
    @After("@annotation(myCustomAnnotation)")
    public void afterAdvance(MyCustomAnnotation myCustomAnnotation){
        System.out.println("======AopAspectJ执行最终通知=========");
    }

    /**
     * 异常通知:目标方法抛出异常时执行
     */
    @AfterThrowing("@annotation(myCustomAnnotation)")
    public void afterThrowingAdvance(MyCustomAnnotation myCustomAnnotation) {
        System.out.println("======AopAspectJ执行异常通知=========");
    }
}

调用一下:

less 复制代码
@GetMapping("get")
@MyCustomAnnotation(name="test",age="20")
public void myMethod() {
    // 业务逻辑
    System.out.println("进入了我的自定义方法!");
}

控制台输出:

bash 复制代码
======AopAspectJ执行环绕通知开始=========
注解的参数是: test
注解的参数是: 20
m.c.t.AnnotateController$MyCustomAspect : ===================== Start ===================
m.c.t.AnnotateController$MyCustomAspect : URL            : http://localhost:7001/annotate/get
m.c.t.AnnotateController$MyCustomAspect : Description    : test
m.c.t.AnnotateController$MyCustomAspect : HTTP Method    : GET
m.c.t.AnnotateController$MyCustomAspect : Class Method   : com.modules.controller.test.AnnotateController.myMethod
m.c.t.AnnotateController$MyCustomAspect : IP             : 0:0:0:0:0:0:0:1
m.c.t.AnnotateController$MyCustomAspect : Request Args   : []
进入了我的自定义方法!
age=20
======AopAspectJ执行后置通知=========
======AopAspectJ执行最终通知=========
类:com.modules.controller.test.AnnotateController;方法:myMethod;参数:[]
======AopAspectJ执行环绕通知结束=========

以上大概就是在Springboot中自定义注解的全过程,有好的建议,请在下方输入你的评论。

相关推荐
浩宇软件开发3 分钟前
Android开发,实现一个简约又好看的登录页
android·java·android studio·android开发
brzhang3 分钟前
告别『上线裸奔』!一文带你配齐生产级 Web 应用的 10 大核心组件
前端·后端·架构
shepherd1114 分钟前
Kafka生产环境实战经验深度总结,让你少走弯路
后端·面试·kafka
南客先生10 分钟前
多级缓存架构设计与实践经验
java·面试·多级缓存·缓存架构
anqi2712 分钟前
如何在 IntelliJ IDEA 中编写 Speak 程序
java·大数据·开发语言·spark·intellij-idea
袋鱼不重17 分钟前
Cursor 最简易上手体验:谷歌浏览器插件开发3s搞定!
前端·后端·cursor
m0_7401546718 分钟前
maven相关概念深入介绍
java·maven
嘻嘻哈哈开森19 分钟前
Agent 系统技术分享
后端
用户40993225021220 分钟前
异步IO与Tortoise-ORM的数据库
后端·ai编程·trae
会有猫25 分钟前
LabelStudio使用阿里云OSS教程
后端