Java註解詳解和自定義註解實戰,用程式碼講解

語言: CN / TW / HK

關於我為啥突然又想要了解Java註解和反射

  1. 好奇心來啦
  2. 打算看原始碼(只是有想法,flag中,實現挺難)
  3. 鞏固Java基礎知識(基礎不牢,地動山搖)

一、邏輯思維圖🧐

第 1-5 小節均偏向於理論知識,若只是想要了解如何自定義註解和如何應用註解,請跳轉至第6小節開始閱讀。

在本篇中,主要是針對註解的概念執行時註解進行解釋說明,附帶有三個實戰的案例,儘可能的讓大家能夠理解透徹並且能夠加以應用。

二、什麼是註解👨‍🏫

Java註解(Annotation)用於為 Java 程式碼提供元資料。作為元資料,註解不直接影響你的程式碼執行,但也有一些型別的註解實際上可以用於這一目的。Java註解是從 Java5 開始新增到 Java 的。--官方文件

2.1、註解

Annotion(註解)是一個介面,程式可以通過反射來獲取指定程式元素的Annotion物件,然後通過Annotion物件來獲取註解裡面的元資料。

我們常常使用的註解,@Data、@Controller等等,這些都是註解,建立一個註解,也很簡單,建立一個類,然後將class改為 @interface就是一個註解啦。

2.2、註解出現的位置

Java程式碼中的包、型別、構造方法、方法、成員變數、引數、本地變數的宣告都可以用註解來修飾。註解本質上可以看作是一種特殊的標記,程式在編譯或者執行時可以檢測到這些標記而進行一些特殊的處理。

2.3、關於註解的處理

我們一般將利用反射來處理註解的方式稱之為執行時註解

另外一種則是編譯時註解,如我們常常使用的 lombok 裡的註解,@Data,它能夠幫我們省略set/get方法,我們在Class上加上這個註解後,在編譯的時候,lombok其實是修改了.class檔案的,將set/get方法放進去了,不然的話,你可以看看編譯完後的.class檔案。諸如這種,我們常稱為編譯時註解,也就是使用javac處理註解。

--圖:來自於極客學院

這幅圖就是從.java檔案到class檔案的,再到class檔案被 JVM 載入的過程。

而其中的註解抽象語法樹這一階段,就是去解析註解,然後根據定義的註解處理器進行相關的邏輯處理。

這一塊不是我的關注點,略過略過啦,朋友們,好奇可以去研究研究噢

三、註解的目的或作用💞

  • 生成文件。這是最常見的,也是 Java 最早提供的註解。如@param、@return等等
  • 跟蹤程式碼依賴性,實現替代配置檔案功能。作用就是減少配置,如 SpringBean的裝載注入,而且現在的框架基本上都是使用註解來減少配置檔案的數量,同時這樣也使得程式設計更加簡潔,程式碼更加清晰。
  • 在編譯時進行格式檢查。@Override放在方法前,如果你這個方法並不是覆蓋了超類方法,則編譯時就能檢查出;
  • 標識作用。Java編譯時或執行時,檢測到這裡的註解,做什麼的處理,自定義註解一般如此。
  • 攜帶資訊。 註解的成員提供了程式元素的關聯資訊,Annotation的成員在 Annotation型別中以無引數的方法的形式被宣告。其方法名和返回值定義了該成員的名字和型別。在此有一個特定的預設 語法:允許宣告任何Annotation成員的預設值。一個Annotation可以將name=value對作為沒有定義預設值的Annotation成員的值,當然也可以使用name=value對來覆蓋其它成員預設值。這一點有些近似類的繼承特性,父類的建構函式可以作為子類的預設建構函式,但是也 可以被子類覆蓋。
  • 這麼一大段話,其實就是關於註解中成員的解釋。

說了這麼多,其實一句話也能表達完。

註解就是一張便利貼,它貼在那裡,你看到的那一刻,就明白該做什麼事啦。

如出門前,門上貼著一張便利貼📌,上面寫著"出門記得帶鑰匙",當你看到的那一刻,你就會去檢查一下自己是否帶鑰匙啦。

在Java中也是一樣的,你定義了一個註解,註解上可以寫一些東西,然後你再將它貼在某個上面,說明白執行規則,當編譯到這裡的時候需要幹嘛幹嘛,又或者是當執行到這裡的時候需要幹嘛幹嘛。

因為註解寫的東西的不同,或者是處理註解的規則不同,而產生了不同的註解及作用。

四、JDK內建註解💫

Java中 內建的註解有5類,具體包括:

@Deprecated:過時註解,用於標記已過時 & 被拋棄的元素(類、方法等)

@Override:複寫註解,用於標記該方法需要被子類複寫

@SuppressWarnings:阻止警告註解,用於標記的元素會阻止編譯器發出警告提醒

@SafeVarargs:引數安全型別註解,用於提醒開發者不要用引數做不安全的操作 & 阻止編譯器產生 unchecked警告,Java 1.7 後引入

五、元註解 🎯

何為元註解?就是註解的註解,就是給你自己定義的註解添加註解,你自己定義了一個註解,但你想要你的註解有什麼樣的功能,此時就需要用元註解對你的註解進行說明了。

接著上一個比喻

註解有很多很多嗎,門上貼一個,冰箱上貼一個,書桌上貼一個等等

元註解勒就是把他們整合起來稱呼的,像上面這些可以統稱為生活類註解啊。所以也就是註解的註解。

5.1、@Target

在 @Target 註解中指定的每一個 ElementType 就是一個約束,它告訴編譯器,這 個自定義的註解只能用於指定的型別。

說明了註解所修飾的物件範圍:註解可被用於 packages、types(類、介面、列舉、Annotation型別)、型別成員(方法、構造方法、成員變數、列舉值)、方法引數和本地變數(如迴圈變數、catch引數)。

5.2、@Retention

定義了該註解的生命週期:

  1. 某些註解僅出現在原始碼中,而被編譯器丟棄; (原始碼級)
  2. 而另一些卻被編譯在class檔案中; (位元組碼級)
  3. 編譯在class檔案中的註解可能會被虛擬機器忽略,而另一些在class被裝載時將被讀取(請注意並不影響class的執行,因為註解與class在使用上是被分離的)。絕大多數開發者都是使用RUNTIME,因為我們期望在程式執行時,能夠獲取到這些註解,並乾點有意思的事兒,而只有RetentionPolicy.RUNTIME,能確保自定義的註解在執行時依然可見。(執行級)

使用這個元註解可以對自定義註解的“生命週期”進行限制。

RetentionPolicy.SOURCE 一般開發者很少用到,大都是Java內建的註解。如@Override

@Target(ElementType.METHOD) @Retention(RetentionPolicy.SOURCE) public @interface Override { }

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE}) @Retention(RetentionPolicy.SOURCE) public @interface SuppressWarnings {

這些註解只是在編譯的時候用到,一旦編譯完成後,執行時沒有任何意義,所以他們被稱作原始碼級別註解。

如果有了解過 lombok 一些簡單原理的開發者, 都知道它是通過註解在編譯時自動生成一部分程式碼,讓原始碼看起來更簡潔,位元組碼卻很強大。

當然,這種方式有它自身的缺陷,譬如不一致性,問題排解時的困擾,以及依賴問題,不是本篇重點,扯回來。

  • 提供資訊給編譯器: 編譯器可以利用註解來檢測出錯誤或者警告資訊,打印出日誌。
  • 編譯階段時的處理: 軟體工具可以用來利用註解資訊來自動生成程式碼、文件或者做其它相應的自動處理。
  • 執行時處理: 某些註解可以在程式執行的時候接受程式碼的提取,自動做相應的操作。

5.3、@Documented

用於描述其它型別的annotation應該被作為被標註的程式成員的公共API,因此可以被例如 javadoc此類的工具文件化。是一個標記註解,沒有成員。

5.4、@Inherited

是一個標記註解闡述了某個被標註的型別是被繼承的。使用了@Inherited修飾的註解型別被用於一個class時該class的子類也有了該註解

5.5、@Repeatable

允許一個註解可以被使用一次或者多次(Java 8)。

六、自定義註解📸

自定義註解實際上就是一種型別而已,也就是引用型別(Java中除了8種基本型別之外,我們見到的任何型別都是引用型別)

6.1、定義註解

自定義註解過程:

  1. 宣告一個類MyAnnotation
  2. 把class關鍵字改為@interface

這樣我們就聲明瞭一個自定義的註解,當我們用@interface宣告一個註解的時候,實際上是聲明瞭一個介面,這個介面自動的繼承了java.lang.annotation.Annotation,但是我們只需要@interface這個關鍵字來宣告註解,編譯器會自動的完成相關的操作,不需要我們手動的指明繼承Annotation介面

另外在定義註解時,不能再繼承其他的註解或介面。

我舉了四個例子,這四個註解分別是放在 類(介面、列舉類上)、建構函式、方法級別、成員屬性上的。

@Documented //定義可以被文件工具文件化 @Retention(RetentionPolicy.RUNTIME)//宣告週期為runtime,執行時可以通過反射拿到 @Target(ElementType.TYPE)//註解修飾範圍為類、介面、列舉 public @interface ClassAnnotation { public String name() default "defaultService"; public String version() default "1.1.0"; }

@Documented @Target(ElementType.CONSTRUCTOR) @Retention(RetentionPolicy.RUNTIME) public @interface ConstructorAnnotatin { String constructorName() default ""; String remark() default "構造器"; }

``` @Documented @Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface FieldAnnotation { public String name() default "defaultName";

public String value() default "defaultValue";

} ```

@Documented @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface MethodAnnotation { public String name() default "defaultName"; public MethodTypeEnum type() default MethodTypeEnum.TYPE1; }

public enum MethodTypeEnum { TYPE1,TYPE2 }

6.2、註解的成員變數

  1. 成員以無引數無異常的方式宣告 String constructorName() default "";
  2. 可以使用default為成員指定一個預設值public String name() default "defaultName";
  3. 成員型別是受限的,合法的型別包括原始型別以及String、Class、Annotation、Enumeration (JAVA的基本資料型別有8種:byte(位元組)、short(短整型)、int(整數型)、long(長整型)、float(單精度浮點數型別)、double(雙精度浮點數型別)、char(字元型別)、boolean(布林型別)
  4. public MethodTypeEnum type() default MethodTypeEnum.TYPE1;
  5. 註解類可以沒有成員,沒有成員的註解稱為標識註解,例如JDK註解中的@Override、@Deprecation
  6. 如果註解只有一個成員,並且把成員取名為value(),則在使用時可以忽略成員名和賦值號“=”
  7. 例如JDK註解的@SuppviseWarnings ;如果成員名 不為value,則使用時需指明成員名和賦值號"="

6.3、使用註解

因為我們在註解中聲明瞭屬性,所以在使用註解的時候必須要指明屬性值 ,多個屬性之間沒有順序,多個屬性之間通過逗號分隔

``` @ClassAnnotation(name = "personBean", version = "1.2.1") public class Person {

// 告訴大家是可以用的,但是影響我測試,我就又註釋掉了. // @ConstructorAnnotatin(constructorName="Person()") // public Person(String description) { // this.description = description; // }

@FieldAnnotation(name = "description", value = "This is my personal annotation")
private String description;

public String getDescription() {
    return description;
}

public void setDescription(String description) {
    this.description = description;
}

@MethodAnnotation(name = "sayHello", type = MethodTypeEnum.TYPE2)
public void sayHello() {
    System.out.println("Hello Annotation!");
}

} ```

6.4、淺提一下反射

想要去獲取註解就不得不提到反射啦,但 Java 反射會帶來一定的耗時,因此使用執行註解需要考慮對效能的影響。

我們宣告一個Student類用來描述學生物件的資訊的

class Student{ String name; String school; //...set/get }

當我們建立一個學生物件時,學生物件的資訊是儲存在 Student 類中,所以 Student 類會提供獲取這些資訊的方法。

在Java類中,每個類都會有對應的Class,要想執行反射操作,必須先要獲取指定類名的Class


瞭解Class物件

類是程式的一部分,每個類都有一個 Class 物件。換言之,每當我們編寫並且編譯 了一個新類,就會產生一個 Class 物件(更恰當的說,是被儲存在一個同名的 .class 檔案中)。為了生成這個類的物件,Java 虛擬機器 (JVM) 先會呼叫 “類載入器” 子系統把 這個類載入到記憶體中。

Class類:簡單說就是用來描述類物件的資訊的

類物件的資訊包括:

  1. 類的基本資訊:包名、修飾符、類名、基類,實現的介面
  2. 屬性的資訊:修飾符、屬性型別、屬性名稱、屬性值,
  3. 方法的資訊:修飾符、返回型別、方法名稱、引數列表、丟擲的異常
  4. 構造方法的資訊:修飾符、類名、引數列表、丟擲的異常
  5. 註解的相關資訊:
  6. 因為:類物件的相關資訊全部儲存在Class類
  7. 所以:Class類會提供獲取這些資訊的方法

一旦某個類的 Class 物件被載入記憶體,它就可以用來建立這個類的所有物件。


通過 Class 獲取類的相關資訊,通過Class建立物件,通過 Class 呼叫物件上面的屬性,呼叫物件上面的方法,這種操作就稱為反射,要想執行反射操作,必須先要獲取到指定的類名的 Class

獲取Class的不同方式

  • 獲取基本型別的Class
  • 1)基本型別Class:如 int.Class獲取的就是 int 型別的 Class
  • 獲取引用型別的Class:
  • 1)引用型別的Class:如String.Class獲取的就是String類對應的Class
  • 2)通過物件來獲取:如:String obj="hello",Class calz = obj.getClass(),獲取的就是String類對應的Class
  • 3)Class.forName("java.lang.String"),獲取的就是對應的Class

6.5、提取註解

``` public class TestClassAnnotation {

private static Person person = new Person();

public static void main(String[] args) {
    Class<?> clazz = person.getClass();
    //因為註解是作用於類上面的,所以可以通過isAnnotationPresent來判斷是否是一個具有指定註解的類
    if (clazz.isAnnotationPresent(ClassAnnotation.class)) {
        System.out.println("This is a class with annotation ClassAnnotation!");
        //通過getAnnotation可以獲取註解物件
        ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class);
        if (null != annotation) {
            System.out.println("BeanName = " + annotation.name());
            System.out.println("BeanVersion = " + annotation.version());
        } else {
            System.out.println("the annotation that we get is null");
        }
    } else {
        System.out.println("This is not the class that with ClassAnnotation");
    }
}

} ```

This is a class with annotation ClassAnnotation! BeanName = personBean BeanVersion = 1.2.1

``` public class AnnotationTest {

public static void main(String[] args) throws ClassNotFoundException { Class<?> clazz = Class.forName("com.nzc.my_annotation.shang.Person"); System.out.println("==============類註解解析=============="); printClassAnno(clazz);

System.out.println("==============成員變數註解解析==============");
printFieldAnno(clazz);

System.out.println("==============成員方法註解解析==============");
printMethodAnno(clazz);

System.out.println("==============構造器註解解析==============");
printConstructorAnno(clazz);

}

/* * 列印類的註解 / private static void printClassAnno(Class<?> clazz) throws ClassNotFoundException { //判斷是否有AuthorAnnotatin註解 if(clazz.isAnnotationPresent(ClassAnnotation.class)) { //獲取AuthorAnnotatin型別的註解 ClassAnnotation annotation = clazz.getAnnotation(ClassAnnotation.class); System.out.println(annotation.name()+"\t"+annotation.version()); } }

/* * 列印成員變數的註解 / private static void printFieldAnno(Class<?> clazz) throws ClassNotFoundException { Field[] fields = clazz.getDeclaredFields(); for (Field field : fields) { if(field.isAnnotationPresent(FieldAnnotation.class)) { FieldAnnotation annotation = field.getAnnotation(FieldAnnotation.class); System.out.println(annotation.name()+"\t"+annotation.value()); } } }

/* * 列印成員變數的註解 / private static void printMethodAnno(Class<?> clazz) throws ClassNotFoundException { Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { if(method.isAnnotationPresent(MethodAnnotation.class)) { MethodAnnotation annotation = method.getAnnotation(MethodAnnotation.class); System.out.println(annotation.name()+"\t"+annotation.type()); } } }

/* * 列印成員變數的註解 / private static void printConstructorAnno(Class<?> clazz) throws ClassNotFoundException { Constructor<?>[] constructors = clazz.getDeclaredConstructors(); for (Constructor<?> constructor : constructors) { if(constructor.isAnnotationPresent(ConstructorAnnotatin.class)) { ConstructorAnnotatin annotation = constructor.getAnnotation(ConstructorAnnotatin.class); System.out.println(annotation.constructorName()+"\t"+annotation.remark()); } } System.out.println("無"); }

} ```

==============類註解解析============== personBean 1.2.1 ==============成員變數註解解析============== description This is my personal annotation ==============成員方法註解解析============== sayHello TYPE2 ==============構造器註解解析============== 無

七、自定義註解實戰🐱‍🏍

註解大多時候與反射或者 AOP 切面結合使用,它的作用有很多,比如標記和檢查,最重要的一點就是簡化程式碼,降低耦合性,提高執行效率

7.1、自定義註解 + SpringMVC 攔截器實現許可權控制功能

還有一種應用場景,許可權判斷或者說是登入校驗。

這個是我當時還沒有學習市面上的許可權框架,就是使用了這種自定義註解+攔截器的方式來實現簡單的許可權控制。

注意:此案例不可CV直接執行,程式碼很容易實現,大家理解思路即可。


定義註解:

@Target({ElementType.METHOD,ElementType.TYPE}) // 這個註解可以放在也可以放在方法上的。 @Retention(RetentionPolicy.RUNTIME) public @interface Authority { Role[] roles() ; }

public enum Role { SADMIN, ADMIN, TEACHER, STUDENT }


使用註解:

``` @Authority(roles = {Role.ADMIN, Role.SADMIN}) // 放在類上 說明這個類下所有的方法都需要有這個許可權才可以進行訪問 @RestController @RequestMapping("/admin") public class AdminController {

@GetMapping("/hello")
public String Hello(){
    return "hello 你最近還好嗎";
}

} ```

``` @Controller @RequestMapping("/student") public class StudentController {

@Authority(roles = {Role.STUDENT}) // 放在方法上則說明此方法需要註解上的許可權才能進行訪問 @GetMapping("/test") public String test(){ return "你好,我已經不是一名學生啦"; }

} ```


編寫 SpringMVC攔截器及處理註解的Handler

在其中進行 Token 的判斷,和訪問方法的許可權判斷,看方法上是否有註解,有的話,

就和當前使用者對比,成功就可以訪問,失敗就直接拒絕。

當時用的是SSM框架,所以才會看到有 response.sendRedirect(contextPath + "/login");這樣的。

``` public class LoginInterceptor extends HandlerInterceptorAdapter {

private static final Logger log = LoggerFactory.getLogger(WebExceptionHandler.class);

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

    String url = request.getRequestURI();

// log.info(request.getMethod()+" 請求URL:"+url);

    //從Token中解析User資訊
    User user = TokenUtil.verifyToken(request);

    String contextPath = request.getContextPath();
    //user 為空則 表示 Token 不存在
    if (user != null) {
        if (user.getRole().equals("sadmin")) {
            //檢查方法上 是否有註解的 Role.SADMIN 或者 Role.ADMIN 許可權 , 沒有則檢查類上有沒有 如果符合要求則放行
            if (HandlerUitl.checkAuthority(handler, new Role[]{Role.SADMIN, Role.ADMIN})) {
                request.setAttribute("user", user);
                return true;
            }
        }
        if (user.getRole().equals("admin")) {
            if (HandlerUitl.checkAuthority(handler, new Role[]{Role.ADMIN})) {
                request.setAttribute("user", user);
                return true;
            }else {
                response.sendRedirect(contextPath + "/login");
            }
        }

        if (user.getRole().equals("teacher")) {
            if (HandlerUitl.checkAuthority(handler, new Role[]{Role.TEACHER})) {

                return true;
            } else {
                response.sendRedirect(contextPath + "/login");
            }
        }
        if (user.getRole().equals("student")) {
            if (HandlerUitl.checkAuthority(handler, new Role[]{Role.STUDENT})) {

                return true;
            } else {

                response.sendRedirect(contextPath + "/student/login");
            }
        }
    }else {
        response.sendRedirect(contextPath + "/login");
    }


    return false;
}

} ```

  • 用於檢查 方法 或者 類 是否需要許可權
  • 並和 擁有的許可權做對比
  • 如果方法上有 ,則以方法的 優先

``` public class HandlerUitl {

public static boolean checkAuthority(Object handler, Role[] roles1){
        if (handler instanceof HandlerMethod) {
        HandlerMethod handlerMethod = (HandlerMethod) handler;
        // 獲取方法上的註解
        Authority authority = handlerMethod.getMethod().getAnnotation(Authority.class);
        // 如果方法上的註解為空 則獲取類的註解
        if (authority == null) {
            authority = handlerMethod.getMethod().getDeclaringClass().getAnnotation(Authority.class);
        }
        // 如果標記了註解,則判斷許可權
        if (authority != null) {
            Role[] roles = authority.roles();
            //如果 方法許可權為 0 則通過
            if(roles.length==0){
                return true;
            }
            //判斷 擁有的許可權 是否 符合 方法所需許可權
            for(int i = 0; i < roles.length; i++){
                for(int j = 0; j < roles1.length; j++){
                    if(roles[i]==roles1[j]){

// System.out.println("可以訪問"); return true; } } }

        }
        return false;
    }
    return true;

}

} ```

7.2、自定義註解+AOP+Redis 防止重複提交

先簡單說一下防止重複提交註解的邏輯:

  1. 在需要防止重複提交的介面的方法,加上註解。
  2. 傳送請求寫介面攜帶 Token
  3. 請求的路徑+ Token 拼接程 key,value 值為生成的 UUID 碼
  4. 然後 set Redis 分散式鎖,能獲取到就順利提交(分散式鎖預設 5 秒過期),不能獲取就是重複提交了,報錯。

定義註解

``` import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;

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

/**
 * 設定請求鎖定時間
 * @return
 */
int lockTime() default 5;

} ```

定義處理註解的切面類

``` import com.eshop.api.ApiResult; import com.eshop.common.aop.NoRepeatSubmit; import com.eshop.common.util.RedisLock; import com.eshop.common.util.RequestUtils; import lombok.extern.slf4j.Slf4j; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.util.Assert;

import javax.servlet.http.HttpServletRequest; import java.util.UUID;

/* * 重複提交aop / @Aspect @Component @Slf4j public class RepeatSubmitAspect {

@Autowired
private RedisLock redisLock;

@Pointcut("@annotation(noRepeatSubmit)")
public void pointCut(NoRepeatSubmit noRepeatSubmit) {
}

@Around("pointCut(noRepeatSubmit)")
public Object around(ProceedingJoinPoint pjp, NoRepeatSubmit noRepeatSubmit) throws Throwable {
    int lockSeconds = noRepeatSubmit.lockTime();

    HttpServletRequest request = RequestUtils.getRequest();
    Assert.notNull(request, "request can not null");

    String bearerToken = request.getHeader("Authorization");
    String[] tokens = bearerToken.split(" ");
    String token = tokens[1];
    String path = request.getServletPath();
    String key = getKey(token, path);
    String clientId = getClientId();

    boolean isSuccess = redisLock.tryLock(key, clientId, lockSeconds);
    log.info("tryLock key = [{}], clientId = [{}]", key, clientId);

    if (isSuccess) {
        log.info("tryLock success, key = [{}], clientId = [{}]", key, clientId);
        // 獲取鎖成功
        Object result;

        try {
            // 執行程序
            result = pjp.proceed();
        } finally {
            // 解鎖
            redisLock.releaseLock(key, clientId);
            log.info("releaseLock success, key = [{}], clientId = [{}]", key, clientId);
        }
        return result;
    } else {
        // 獲取鎖失敗,認為是重複提交的請求
        log.info("tryLock fail, key = [{}]", key);
        return  ApiResult.fail("重複請求,請稍後再試");
    }
}

private String getKey(String token, String path) {
    return token + path;
}

private String getClientId() {
    return UUID.randomUUID().toString();
}

} ```

``` import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.RedisCallback; import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import redis.clients.jedis.Jedis; import redis.clients.jedis.params.SetParams;

import java.util.Collections;

/* * Redis 分散式鎖實現 / @Service public class RedisLock {

private static final Long RELEASE_SUCCESS = 1L;
private static final String LOCK_SUCCESS = "OK";
private static final String SET_IF_NOT_EXIST = "NX";
// 當前設定 過期時間單位, EX = seconds; PX = milliseconds
private static final String SET_WITH_EXPIRE_TIME = "EX";
// if get(key) == value return del(key)
private static final String RELEASE_LOCK_SCRIPT = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";

@Autowired
private StringRedisTemplate redisTemplate;

/**
 * 該加鎖方法僅針對單例項 Redis 可實現分散式加鎖
 * 對於 Redis 叢集則無法使用
 *
 * 支援重複,執行緒安全
 *
 * @param lockKey   加鎖鍵
 * @param clientId  加鎖客戶端唯一標識(採用UUID)
 * @param seconds   鎖過期時間
 * @return
 */
public boolean tryLock(String lockKey, String clientId, long seconds) {
    return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
        Jedis jedis = (Jedis) redisConnection.getNativeConnection();
        SetParams setParams = new SetParams();
        String result = jedis.set(lockKey, clientId, setParams.nx().px(seconds));
        if (LOCK_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    });
}

/**
 * 與 tryLock 相對應,用作釋放鎖
 *
 * @param lockKey
 * @param clientId
 * @return
 */
public boolean releaseLock(String lockKey, String clientId) {
    return redisTemplate.execute((RedisCallback<Boolean>) redisConnection -> {
        Jedis jedis = (Jedis) redisConnection.getNativeConnection();
        Object result = jedis.eval(RELEASE_LOCK_SCRIPT, Collections.singletonList(lockKey),
                Collections.singletonList(clientId));
        if (RELEASE_SUCCESS.equals(result)) {
            return true;
        }
        return false;
    });
}

} ```

使用註解

/** * 新增收藏 */ @NoRepeatSubmit @PostMapping("/collect/add") @ApiOperation(value = "新增收藏",notes = "新增收藏") public ApiResult<Boolean> collectAdd(@Validated @RequestBody StoreProductRelationQueryParam param){ // 處理業務邏輯 return ApiResult.ok(); }

7.3、自定義註解 + Aop 實現日誌收集

有關於這個,我之前有寫過一篇文章,就不再此處特意貼出來增加篇幅啦。

自定義註解 + Aop 實現日誌收集


八、自言自語💌

原本還想找點面試題的,但是到處找了找,面試大部分也就是面試上面這些知識點,所以就刪掉啦。

本篇主要是針對Java執行時的註解的講解及應用,但是你想一想,我們使用lombok的註解時,它的實現原理又是什麼樣的呢?為什麼可以幫我們自動生成程式碼呢?是誰給我們做了這件事情呢?

下篇主要是針對上述的幾個疑問來展開的,文章的大綱和構思倒是有點想法,但是不知道能不能寫好下篇。

另外Java註解的下半場,主要是圍繞著 AbstractProcessor相關來講,其實也算是冷門知識了,但是好奇心還是要有的。

也非常感謝大家的閱讀,覺得有所收穫的話,可以點點贊,或者留下評論,讓我收到你的反饋吧

下篇文章見。

參考

我正在參與掘金技術社群創作者簽約計劃招募活動,點選連結報名投稿