
前言
??自定義注解在開(kāi)發(fā)中是一把利器,經(jīng)常會(huì)被使用到。在上一篇文章中有提到了自定義校驗(yàn)注解的用法。 然而最近接到這樣一個(gè)需求,主要是針對(duì)某些接口的返回?cái)?shù)據(jù)需要進(jìn)行一個(gè)加密操作。于是很自然的就想到了自定義注解+AOP去實(shí)現(xiàn)這樣一個(gè)功能。但是對(duì)于自定義注解,只是停留在表面的使用,沒(méi)有做到知其然,而知其所以然。所以這篇文章就是來(lái)了解自定義注解這把開(kāi)發(fā)利器的。
什么是自定義注解?
官方定義
??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翻譯一下
??注解是元數(shù)據(jù)的一種形式,可以添加到Java源代碼中。 類,方法,變量,參數(shù)和包都可以被注釋。 注解對(duì)其注釋的代碼的操作沒(méi)有直接影響。
看完這個(gè)定義是不是有點(diǎn)摸不到頭腦,不要慌實(shí)踐出真知。
建立一個(gè)自定義注解
??我們先回顧一下需求的場(chǎng)景,是要針對(duì)xx接口的返回?cái)?shù)據(jù)需要做一個(gè)加密操作。之前說(shuō)到使用自定義注解+AOP來(lái)實(shí)現(xiàn)這個(gè)功能。所以我們先定義一個(gè)注解叫Encryption,被Encryption注解修飾后接口,返回的數(shù)據(jù)要被加密。
public @interface Encryption {
}
??你會(huì)發(fā)現(xiàn)創(chuàng)建自定義注解,就和建立普通的接口一樣簡(jiǎn)單。只是所使用的關(guān)鍵字有所不同。在底層實(shí)現(xiàn)上,所有定義的注解都會(huì)自動(dòng)繼承java.lang.annotation.Annotation接口。
編寫(xiě)相應(yīng)的接口
@Encryption
@GetMapping("/encrypt")
public ResultVo encrypt(){
return ResultVoUtil.success("不一樣的科技宅");
}
@GetMapping("/normal")
public ResultVo normal(){
return ResultVoUtil.success("不一樣的科技宅");
}
編寫(xiě)切面
@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);
// 如果被標(biāo)識(shí)了,則進(jìn)行加密
if(annotation != null){
// 進(jìn)行加密
String encrypt = EncryptUtil.encryptByAes(JSON.toJSONString(resultVo.getData()));
resultVo.setData(encrypt);
}
return resultVo;
}
測(cè)試結(jié)果

??這個(gè)時(shí)候,你會(huì)發(fā)現(xiàn)返回的數(shù)據(jù)并沒(méi)有被加密。 那么這個(gè)是為啥呢?俗話說(shuō)遇到問(wèn)題不要慌,先掏出手機(jī)發(fā)個(gè)朋友圈(稍微有點(diǎn)跑題了)。出現(xiàn)這個(gè)原因是,缺少了@Retention對(duì)@Encryption的修飾,讓我們把它加上。
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
}
繼續(xù)測(cè)試

??這個(gè)時(shí)候返回的數(shù)據(jù)就被加密了,說(shuō)明自定義注解生效了。
測(cè)試普通接口

??沒(méi)有用@Encryption的接口,返回的數(shù)據(jù)沒(méi)有被加密。到此需求就已經(jīng)實(shí)現(xiàn)了,接下來(lái)就該了解原理了。
@Retention
@Retention作用是什么
??Retention的翻譯過(guò)來(lái)就是"保留"的意思。也就意味著它的作用是,用來(lái)定義注解的生命周期的,并且在使用時(shí)需要指定RetentionPolicy,RetentionPolicy有三種策略,分別是:
- SOURCE - 注解只保留在源文件,當(dāng)Java文件編譯成class文件的時(shí)候,注解被遺棄。
- CLASS - 注解被保留到class文件,但jvm加載class文件時(shí)候被遺棄,這是默認(rèn)的生命周期。
- RUNTIME - 注解不僅被保存到class文件中,jvm加載class文件之后,仍然存在。
選擇合適的生命周期
??首先要明確生命周期 RUNTIME > CLASS > SOURCE 。一般如果需要在運(yùn)行時(shí)去動(dòng)態(tài)獲取注解信息,只能使用RUNTIME。如果要在編譯時(shí)進(jìn)行一些預(yù)處理操作,比如生成一些輔助代碼就用CLASS。如果只是做一些檢查性的操作,比如 @Override和@SuppressWarnings,則可選用 SOURCE。
我們實(shí)際開(kāi)發(fā)中的自定義注解幾乎都是使用的RUNTIME
??最開(kāi)始@Encryption沒(méi)有使用@Retention對(duì)其生命周期進(jìn)行定義。所以導(dǎo)致AOP在獲取的時(shí)候一直為空,如果為空就不會(huì)對(duì)數(shù)據(jù)進(jìn)行加密。
??是不是感覺(jué)這個(gè)注解太簡(jiǎn)陋。那再給他加點(diǎn)東西,加上個(gè)@Target。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
}
@Target
??@Target注解是限定自定義注解可以使用在哪些地方。這就和參數(shù)校驗(yàn)一樣,約定好規(guī)則,防止亂用而導(dǎo)致問(wèn)題的出現(xiàn)。針對(duì)上述的需求可以限定它只能用方法上。根據(jù)不同的場(chǎng)景,還可以使用在更多的地方。比如說(shuō)屬性、包、構(gòu)造器上等等。
- TYPE - 類,接口(包括注解類型)或枚舉
- FIELD - 字段(包括枚舉常量)
- METHOD - 方法
- PARAMETER - 參數(shù)
- CONSTRUCTOR - 構(gòu)造函數(shù)
- LOCAL_VARIABLE - 局部變量
- ANNOTATION_TYPE -注解類型
- PACKAGE - 包
- TYPE_PARAMETER - 類型參數(shù)
- TYPE_USE - 使用類型
??上面兩個(gè)是比較常用的元注解,Java一共提供了4個(gè)元注解。你可能會(huì)問(wèn)元注解是什么?元注解的作用就是負(fù)責(zé)注解其他注解。
@Documented
??@Documented的作用是對(duì)自定義注解進(jìn)行標(biāo)注,如果使用@Documented標(biāo)注了,在生成javadoc的時(shí)候就會(huì)把@Documented注解給顯示出來(lái)。沒(méi)什么實(shí)際作用,了解一下就好了。
@Inherited
??被@Inherited修飾的注解,被用在父類上時(shí)其子類也擁有該注解。 簡(jiǎn)單的說(shuō)就是,當(dāng)在父類使用了被@Inherited修飾的注解@InheritedTest時(shí),繼承它的子類也擁有@InheritedTest注解。
這個(gè)可以單獨(dú)講下
注解元素類型
??參照我們?cè)诙x接口的經(jīng)驗(yàn),在接口中能定義方法和常量。但是在自定義注解中,只能定義一個(gè)東西:注解類型元素Annotation type element。
其實(shí)可以簡(jiǎn)單的理解為只能定義方法,但是和接口中的方法有區(qū)別。
定義注解類型元素時(shí)需要注意如下幾點(diǎn):
- 訪問(wèn)修飾符必須為public,不寫(xiě)默認(rèn)為public。
- 元素的類型只能是基本數(shù)據(jù)類型、String、Class、枚舉類型、注解類型。
- type()括號(hào)中不能定義方法參數(shù),僅僅只是一個(gè)特殊的語(yǔ)法。但是可以通過(guò)
default關(guān)鍵字設(shè)置"默認(rèn)值"。 - 如果沒(méi)有默認(rèn)值,則使用注解時(shí)必須給該類型元素賦值。
繼續(xù)改造
??需求這個(gè)東西經(jīng)常都在變動(dòng)。原本需要加密的接口只使用AES進(jìn)行加密,后面又告知有些接口要使用DES加密。針對(duì)這樣的情況,我們可以在注解內(nèi),添加一下配置項(xiàng),來(lái)選擇使用何種方式加密。
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Encryption {
/**
* 加密類型
*/
String value() default "AES";
}
調(diào)整接口
@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("不一樣的科技宅");
}
調(diào)整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);
// 如果被標(biāo)識(shí)了,則進(jìn)行加密
if(annotation != null){
// 進(jìn)行加密
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;
}
??至此就改造完了??梢园l(fā)現(xiàn)注解元素類型,在使用的時(shí)候,操作元素類型像在操作屬性。解析的時(shí)候,操作元素類型像在操作方法。。
小技巧
- 當(dāng)注解沒(méi)有注解類型元素,使用時(shí)候可直接寫(xiě)為
@Encryption和@Encryption()等效。 - 當(dāng)注解只有一個(gè)注解類型元素,并且命名是value。在使用時(shí)
@Encryption("DES")和@Encryption(value = "DES")等效。
注意的點(diǎn)
- 需要根據(jù)實(shí)際情況指定注解的生命周期
@Retention。 - 使用
@Target來(lái)限制注解的使用范圍,防止注解被亂用。 - 如果注解是配置在方法上的,那么我們要從Method對(duì)象上獲取。如果是配置在屬性上,就需要從該屬性對(duì)應(yīng)的Field對(duì)象上去獲取??傊迷谀睦?,就去哪里獲取。
總結(jié)
??注解可以理解為就是一個(gè)標(biāo)識(shí)。可以在程序代碼中的關(guān)鍵節(jié)點(diǎn)上打上這些標(biāo)識(shí),它不會(huì)改變?cè)写a的執(zhí)行邏輯。然后程序在編譯時(shí)或運(yùn)行時(shí)可以檢測(cè)到這些標(biāo)記,在做出相應(yīng)的操作。結(jié)合上面的小場(chǎng)景,可以得出自定義注解使用的基本流程:
- 定義注解 --> 根據(jù)業(yè)務(wù)進(jìn)行創(chuàng)建。
- 使用注解 --> 在相應(yīng)的代碼中進(jìn)行使用。
- 解析注解 --> 在編譯期或運(yùn)行時(shí)檢測(cè)到標(biāo)記,并進(jìn)行特殊操作。
上期回顧
結(jié)尾
??如果覺(jué)得對(duì)你有幫助,可以多多評(píng)論,多多點(diǎn)贊哦,也可以到我的主頁(yè)看看,說(shuō)不定有你喜歡的文章,也可以隨手點(diǎn)個(gè)關(guān)注哦,謝謝。
??我是不一樣的科技宅,每天進(jìn)步一點(diǎn)點(diǎn),體驗(yàn)不一樣的生活。我們下期見(jiàn)!