你的開發利器Spring自定義註解

前言

  自定義註解在開發中是一把利器,常常會被使用到。在上一篇文章中有提到了自定義校驗註解的用法。 然而最近接到這樣一個需求,主要是針對某些接口的返回數據須要進行一個加密操做。因而很天然的就想到了自定義註解+AOP去實現這樣一個功能。可是對於自定義註解,只是停留在表面的使用,沒有作到知其然,而知其因此然。因此這篇文章就是來了解自定義註解這把開發利器的。java

什麼是自定義註解?

官方定義segmentfault

  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.app

Google翻譯一下jvm

  註解是元數據的一種形式,能夠添加到Java源代碼中。 類,方法,變量,參數和包均可以被註釋。 註解對其註釋的代碼的操做沒有直接影響。ide

看完這個定義是否是有點摸不到頭腦,不要慌實踐出真知。

創建一個自定義註解

  咱們先回顧一下需求的場景,是要針對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 {

}

繼續測試spa

  這個時候返回的數據就被加密了,說明自定義註解生效了。翻譯

測試普通接口

  沒有用@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. 解析註解 --> 在編譯期或運行時檢測到標記,並進行特殊操做。

上期回顧

結尾

  若是以爲對你有幫助,能夠多多評論,多多點贊哦,也能夠到個人主頁看看,說不定有你喜歡的文章,也能夠隨手點個關注哦,謝謝。

  我是不同的科技宅,天天進步一點點,體驗不同的生活。咱們下期見!

相關文章
相關標籤/搜索