前言
自定义注解在开发中是一把利器,常常会被应用到。在上一篇文章中有提到了 自定义校验注解 的用法。然而最近接到这样一个需要,次要是针对某些接口的返回数据须要进行一个加密操作。于是很天然的就想到了 自定义注解 +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 的翻译过去就是 ” 保留 ” 的意思。也就意味着它的作用是,用来定义注解的生命周期的,并且在应用时须要指定 RetentionPolicy
,RetentionPolicy
有三种策略,别离是:
- 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 对象下来获取。总之用在哪里,就去哪里获取。
总结
注解能够了解为就是一个标识。能够在程序代码中的要害节点上打上这些标识,它不会扭转原有代码的执行逻辑。而后程序在编译时或运行时能够检测到这些标记,在做出相应的操作。联合下面的小场景,能够得出自定义注解应用的根本流程:
- 定义注解 –> 依据业务进行创立。
- 应用注解 –> 在相应的代码中进行应用。
- 解析注解 –> 在编译期或运行时检测到标记,并进行非凡操作。
上期回顾
- SpringBoot 分组校验及自定义校验注解
结尾
如果感觉对你有帮忙,能够多多评论,多多点赞哦,也能够到我的主页看看,说不定有你喜爱的文章,也能够顺手点个关注哦,谢谢。
我是不一样的科技宅,每天提高一点点,体验不一样的生存。咱们下期见!