你的开发利器Spring自定义注解

本文涉及的产品
密钥管理服务KMS,1000个密钥,100个凭据,1个月
简介: 自定义注解在开发中是一把利器,经常会被使用到。在上一篇文章中有提到了自定义校验注解的用法。 然而最近接到这样一个需求,主要是针对某些接口的返回数据需要进行一个加密操作。于是很自然的就想到了自定义注解+AOP去实现这样一个功能。但是对于自定义注解,只是停留在表面的使用,没有做到知其然,而知其所以然。所以这篇文章就是来了解自定义注解这把开发利器的。

前言

  自定义注解在开发中是一把利器,经常会被使用到。在上一篇文章中有提到了自定义校验注解的用法。 然而最近接到这样一个需求,主要是针对某些接口的返回数据需要进行一个加密操作。于是很自然的就想到了自定义注解+AOP去实现这样一个功能。但是对于自定义注解,只是停留在表面的使用,没有做到知其然,而知其所以然。所以这篇文章就是来了解自定义注解这把开发利器的。

什么是自定义注解?

官方定义

  An annotation is a form of metadata, that can be added to Java source code. Classes, methods, variables, parameters and packages may be annotated. Annotations have no direct effect on the operation of the code they annotate.

Google翻译一下

  注解是元数据的一种形式,可以添加到Java源代码中。 类,方法,变量,参数和包都可以被注释。 注解对其注释的代码的操作没有直接影响。

看完这个定义是不是有点摸不到头脑,不要慌实践出真知。

建立一个自定义注解

  我们先回顾一下需求的场景,是要针对xx接口的返回数据需要做一个加密操作。之前说到使用自定义注解+AOP来实现这个功能。所以我们先定义一个注解叫Encryption,被Encryption注解修饰后接口,返回的数据要被加密。

public @interface Encryption {
   
   
}

  你会发现创建自定义注解,就和建立普通的接口一样简单。只是所使用的关键字有所不同。在底层实现上,所有定义的注解都会自动继承java.lang.annotation.Annotation接口。

编写相应的接口

@Encryption
@GetMapping("/encrypt")
public ResultVo encrypt(){
   
   
    return ResultVoUtil.success("不一样的科技宅");
}

@GetMapping("/normal")
public ResultVo normal(){
   
   
    return ResultVoUtil.success("不一样的科技宅");
}

编写切面

@Around("@annotation(com.hxh.unified.param.check.annotation.Encryption)")
public ResultVo encryptPoint(ProceedingJoinPoint joinPoint) throws Throwable {
   
   
  ResultVo resultVo = (ResultVo) joinPoint.proceed();

  // 获取注解
  MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  Method method = methodSignature.getMethod();
  Encryption annotation = method.getAnnotation(Encryption.class);

  // 如果被标识了,则进行加密
  if(annotation != null){
   
   
    // 进行加密
    String encrypt = EncryptUtil.encryptByAes(JSON.toJSONString(resultVo.getData()));
    resultVo.setData(encrypt);
  }

  return resultVo;
}

测试结果

  这个时候,你会发现返回的数据并没有被加密。 那么这个是为啥呢?俗话说遇到问题不要慌,先掏出手机发个朋友圈(稍微有点跑题了)。出现这个原因是,缺少了@Retention@Encryption的修饰,让我们把它加上。

@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
   
   

}

继续测试

  这个时候返回的数据就被加密了,说明自定义注解生效了。

测试普通接口

  没有用@Encryption的接口,返回的数据没有被加密。到此需求就已经实现了,接下来就该了解原理了。

@Retention

@Retention作用是什么

  Retention的翻译过来就是"保留"的意思。也就意味着它的作用是,用来定义注解的生命周期的,并且在使用时需要指定RetentionPolicyRetentionPolicy有三种策略,分别是:

  • SOURCE - 注解只保留在源文件,当Java文件编译成class文件的时候,注解被遗弃。
  • CLASS - 注解被保留到class文件,但jvm加载class文件时候被遗弃,这是默认的生命周期。
  • RUNTIME - 注解不仅被保存到class文件中,jvm加载class文件之后,仍然存在。

选择合适的生命周期

  首先要明确生命周期 RUNTIME > CLASS > SOURCE 。一般如果需要在运行时去动态获取注解信息,只能使用RUNTIME。如果要在编译时进行一些预处理操作,比如生成一些辅助代码就用CLASS。如果只是做一些检查性的操作,比如 @Override和@SuppressWarnings,则可选用 SOURCE。

我们实际开发中的自定义注解几乎都是使用的RUNTIME

  最开始@Encryption没有使用@Retention对其生命周期进行定义。所以导致AOP在获取的时候一直为空,如果为空就不会对数据进行加密。

  是不是感觉这个注解太简陋。那再给他加点东西,加上个@Target

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
   
   

}

@Target

  @Target注解是限定自定义注解可以使用在哪些地方。这就和参数校验一样,约定好规则,防止乱用而导致问题的出现。针对上述的需求可以限定它只能用方法上。根据不同的场景,还可以使用在更多的地方。比如说属性、包、构造器上等等。

  • TYPE - 类,接口(包括注解类型)或枚举
  • FIELD - 字段(包括枚举常量)
  • METHOD - 方法
  • PARAMETER - 参数
  • CONSTRUCTOR - 构造函数
  • LOCAL_VARIABLE - 局部变量
  • ANNOTATION_TYPE -注解类型
  • PACKAGE - 包
  • TYPE_PARAMETER - 类型参数
  • TYPE_USE - 使用类型

  上面两个是比较常用的元注解,Java一共提供了4个元注解。你可能会问元注解是什么?元注解的作用就是负责注解其他注解。

@Documented

  @Documented的作用是对自定义注解进行标注,如果使用@Documented标注了,在生成javadoc的时候就会把@Documented注解给显示出来。没什么实际作用,了解一下就好了。

@Inherited

  被@Inherited修饰的注解,被用在父类上时其子类也拥有该注解。 简单的说就是,当在父类使用了被@Inherited修饰的注解@InheritedTest时,继承它的子类也拥有@InheritedTest注解。

这个可以单独讲下

注解元素类型

  参照我们在定义接口的经验,在接口中能定义方法和常量。但是在自定义注解中,只能定义一个东西:注解类型元素Annotation type element

其实可以简单的理解为只能定义方法,但是和接口中的方法有区别。

定义注解类型元素时需要注意如下几点:

  • 访问修饰符必须为public,不写默认为public。
  • 元素的类型只能是基本数据类型、String、Class、枚举类型、注解类型。
  • type()括号中不能定义方法参数,仅仅只是一个特殊的语法。但是可以通过default关键字设置"默认值"。
  • 如果没有默认值,则使用注解时必须给该类型元素赋值。

继续改造

  需求这个东西经常都在变动。原本需要加密的接口只使用AES进行加密,后面又告知有些接口要使用DES加密。针对这样的情况,我们可以在注解内,添加一下配置项,来选择使用何种方式加密。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
   
   

    /**
     * 加密类型
     */
    String value() default "AES";

}

调整接口

@Encryption
@GetMapping("/encrypt")
public ResultVo encrypt(){
   
   
    return ResultVoUtil.success("不一样的科技宅");
}

@Encryption(value = "DES")
@GetMapping("/encryptDes")
public ResultVo encryptDes(){
   
   
    return ResultVoUtil.success("不一样的科技宅");
}

@GetMapping("/normal")
public ResultVo normal(){
   
   
    return ResultVoUtil.success("不一样的科技宅");
}

调整AOP

@Around("@annotation(com.hxh.unified.param.check.annotation.Encryption)")
public ResultVo encryptPoint(ProceedingJoinPoint joinPoint) throws Throwable {
   
   
  ResultVo resultVo = (ResultVo) joinPoint.proceed();

  // 获取注解
  MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
  Method method = methodSignature.getMethod();
  Encryption annotation = method.getAnnotation(Encryption.class);

  // 如果被标识了,则进行加密
  if(annotation != null){
   
   
    // 进行加密
    String encrypt = null;
    switch (annotation.value()){
   
   
      case "AES":
        encrypt = EncryptUtil.encryptByAes(JSON.toJSONString(resultVo.getData()));
        break;
      case "DES":
        encrypt = EncryptUtil.encryptByDes(JSON.toJSONString(resultVo.getData()));
        break;
      default:
        break;
    }
    resultVo.setData(encrypt);
  }

  return resultVo;
}

  至此就改造完了。可以发现注解元素类型,在使用的时候,操作元素类型像在操作属性。解析的时候,操作元素类型像在操作方法。

小技巧

  • 当注解没有注解类型元素,使用时候可直接写为@Encryption@Encryption()等效。
  • 当注解只有一个注解类型元素,并且命名是value。在使用时@Encryption("DES")@Encryption(value = "DES")等效。

注意的点

  • 需要根据实际情况指定注解的生命周期@Retention
  • 使用@Target来限制注解的使用范围,防止注解被乱用。
  • 如果注解是配置在方法上的,那么我们要从Method对象上获取。如果是配置在属性上,就需要从该属性对应的Field对象上去获取。总之用在哪里,就去哪里获取。

总结

  注解可以理解为就是一个标识。可以在程序代码中的关键节点上打上这些标识,它不会改变原有代码的执行逻辑。然后程序在编译时或运行时可以检测到这些标记,在做出相应的操作。结合上面的小场景,可以得出自定义注解使用的基本流程:

  1. 定义注解 --> 根据业务进行创建。
  2. 使用注解 --> 在相应的代码中进行使用。
  3. 解析注解 --> 在编译期或运行时检测到标记,并进行特殊操作。

结尾

  如果觉得对你有帮助,可以多多评论,多多点赞哦,也可以到我的主页看看,说不定有你喜欢的文章,也可以随手点个关注哦,谢谢。

  我是不一样的科技宅,每天进步一点点,体验不一样的生活。我们下期见!

相关文章
|
15天前
|
XML Java 数据格式
SpringBoot入门(8) - 开发中还有哪些常用注解
SpringBoot入门(8) - 开发中还有哪些常用注解
36 0
|
22天前
|
XML JSON Java
SpringBoot必须掌握的常用注解!
SpringBoot必须掌握的常用注解!
45 4
SpringBoot必须掌握的常用注解!
|
12天前
|
存储 运维 安全
Spring运维之boot项目多环境(yaml 多文件 proerties)及分组管理与开发控制
通过以上措施,可以保证Spring Boot项目的配置管理在专业水准上,并且易于维护和管理,符合搜索引擎收录标准。
25 2
|
24天前
|
存储 缓存 Java
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
Spring缓存注解【@Cacheable、@CachePut、@CacheEvict、@Caching、@CacheConfig】使用及注意事项
79 2
|
24天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
35 1
|
1月前
|
XML Java 数据格式
提升效率!Spring Boot 开发中的常见失误轻松规避
本文深入探讨了在 Spring Boot 开发中常见的失误,包括不当使用注解、不良异常处理、低效日志记录等,提供了有效的规避策略,帮助开发者提升代码质量和系统性能,构建更健壮、高效的应用程序。
|
17天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
31 0
|
18天前
|
存储 安全 Java
springboot当中ConfigurationProperties注解作用跟数据库存入有啥区别
`@ConfigurationProperties`注解和数据库存储配置信息各有优劣,适用于不同的应用场景。`@ConfigurationProperties`提供了类型安全和模块化的配置管理方式,适合静态和简单配置。而数据库存储配置信息提供了动态更新和集中管理的能力,适合需要频繁变化和集中管理的配置需求。在实际项目中,可以根据具体需求选择合适的配置管理方式,或者结合使用这两种方式,实现灵活高效的配置管理。
13 0
|
1月前
|
存储 Java 数据管理
强大!用 @Audited 注解增强 Spring Boot 应用,打造健壮的数据审计功能
本文深入介绍了如何在Spring Boot应用中使用`@Audited`注解和`spring-data-envers`实现数据审计功能,涵盖从添加依赖、配置实体类到查询审计数据的具体步骤,助力开发人员构建更加透明、合规的应用系统。
|
6月前
|
Java API Spring
Spring容器如何使用一个注解来指定一个类型为配置类型
Spring容器如何使用一个注解来指定一个类型为配置类型
53 0
下一篇
无影云桌面