簡單易懂講注解


注解是什么

簡單的說,注解就是一種將元數據信息從 xml 剝離開來,然后保存在 java 源代碼中,這將使得代碼更加清晰易懂,無需維護兩個地方: java 源代碼以及 xml 配置文件。

典型的場景就是 spring 框架,我們都知道,spring 框架將一個 bean 保存在容器里有兩種方式,一種是采用配置文件的方式生成 bean 並且保存在容器中,使用的時候通過 bean 工廠拿對應的 bean 實例即可。這種方式很繁瑣,不僅需要維護 java 源代碼,還需要在 xml 配置里再維護一遍。另一種方式是采用注解的方式,在類名上使用 @Component或者@Service(當然還有其他方式,但不是本篇文章的重點)。然后在使用的時候采用 @Autowired 形式注入即可。這樣就無需繁瑣的 xml 配置。(例子)

當然,采用傳統 xml 維護元素據還是使用注解,各有優劣,需要根據實際場景進行評估。

如何使用注解

接下來我們先從一個簡單的注解定義開始,然后介紹一些注解的關鍵屬性

定義注解

如下例子,Test 注解看起來很像接口的定義,注解和其他接口和類一樣,都會被編譯成 class 文件。像這種不含任何元素的被稱為標記注解,如 java 8 新加入的用於聲明一個接口時函數式接口的注解:@FunctionalInterface。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    
}

當然,注解也是可以定義一些屬性的。如下:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    int value();
    String name() default "-- default --";
}

其中 value() 以及 name() 就是該注解的屬性,其中 value() 沒有默認值,那么在使用該注解的時候,必須指定 value 屬性,name 有個默認值,使用的時候可以不需要指定默認值。

如下例子,就是該注解的使用方式,在 1 處,由於沒有指定 value 屬性,所以編譯失敗。

public class AnnotationDemo {

    @Test(1)
    private int value;
    
    @Test() // 1 編譯失敗
    private int withoutValue;

    @Test(2)
    private String withoutName;

    @Test(value = 3, name = "name")
    private String name;
}

這個注解現在來說是沒有一絲絲意義的,因為我們還沒有為其編寫注釋處理器,注釋處理器在后面會介紹。

@Test 注解中使用到的 @Target 注解、@Retention 注解以及他們的參數枚舉,會在下文的元注解中介紹。

常見注解

常見的注解這里主要介紹 jdk 的注解

  • @Override:表示當前的方法定義將覆蓋基類的方法。如果你不小心拼寫錯誤,或者方法簽名被錯誤拼寫的時候,編譯器就會發出錯誤提示。
  • @Deprecated:表示當前類 or 方法 or 字段被棄用了,不應該再使用了,使用會產生告警
  • @SuppressWarnings:關閉不當的編譯器警告信息。
  • @FunctionalInterface:Java 8 中加入用於表示類型聲明為函數式接口

元注解

上文的 @Test 注解中,我們使用到了 @Target 注解、@Retention 注解,這兩個注解為元注解,

目前一共有 5 個元注解:

注解 解釋
@Target 表示注解可以用於哪些地方。可能的 ElementType 參數包括:CONSTRUCTOR:構造器的聲明 FIELD:字段聲明(包括 enum 實例) LOCAL_VARIABLE:局部變量聲明 METHOD:方法聲明 PACKAGE:包聲明 PARAMETER:參數聲明 TYPE:類、接口(包括注解類型)或者 enum 聲明
@Retention 表示注解信息保存的時長。可選的 RetentionPolicy 參數包括: SOURCE:注解將被編譯器丟棄 CLASS:注解在 class 文件中可用,但是會被 VM 丟棄。 RUNTIME:VM 將在運行期也保留注解,因此可以通過反射機制讀取注解的信息。
@Documented 將此注解保存在 Javadoc 中
@Inherited 允許子類繼承父類的注解
@Repeatable 允許一個注解可以被使用一次或者多次(Java 8)

使用最多的其實就是 @Target 以及 @Retention。

@Targe:注解中指定的每一個 ElementType 就是一個約束,它告訴編譯器,這個自定義的注解只能用於指定的類型。你可以指定 enum ElementType 中的一個值,或者以逗號分割的形式指定多個值。如果想要將注解應用於所有的 ElementType,那么可以省去 @Target 注解,但是這並不常見。

@Retention:表明注解存在的時長,使用最多的是 RUNTIME,使用 RUNTIME 的時候,注解在運行期也保留着,這時就可以通過反射機制讀取注解信息,如果使用 SOURCE,CLASS,那么就無法通過反射獲取。

注解處理器

單獨定義一個注釋是沒什么意義的,我們要給一個注釋賦予意義,那么就得 coding,給這個注釋編寫一個注解處理器。這里我僅演示最簡單的注解處理器。

這個列子很簡單,定義了一個注解 @Test,該注解可以在方法上使用,可以被帶入到運行時。AnnotationDemo 類實現了 Interface 接口,demo1()、demo2()、demo3()使用了注解,其中 demo3() 使用默認值,demo4() 沒有引入注解。這里實現接口的原因是為了使用動態代理來調用方法,處理注解的邏輯寫在動態代理里。動態代理類 InvokeClass,可以看到 invoke 方法里拿到 obj 對應的方法(這里不直接用入參的 method 字段是因為該字段代表接口方法,接口方法沒有加注解,獲取到的 Test annotation 會為空),這里拿到了方法上的注解信息后可以編寫自己想要的處理邏輯,我這邊就簡單把 @Test 注解的 value() 值打印出來。

動態代理文章可以看:簡單易懂將反射

(這里字符串判空寫的有點丑了,是因為我沒引入對應工具類)

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "--- default ---";
}
interface Interface {

    void demo1();

    void demo2();

    void demo3();

    void demo4();
}


public class AnnotationDemo implements Interface {

    @Test("demo1")
    @Override
    public void demo1() {

    }

    @Test("demo2")
    @Override
    public void demo2() {

    }

    @Test
    @Override
    public void demo3() {

    }

    @Override
    public void demo4() {

    }
}
class InvokeClass implements InvocationHandler {

    Object obj;

    public InvokeClass(Object obj) {
        this.obj = obj;
    }


    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Method objectMethod = obj.getClass().getMethod(method.getName());
        Test annotation = objectMethod.getAnnotation(Test.class);
        if (Objects.nonNull(annotation) && !"".equals(annotation.value())) {
            System.out.println(annotation.value());
        }
        return method.invoke(obj, args);
    }
}


public class Main {

    public static void main(String[] args) {

        Interface anInterface = (Interface) Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Interface.class}, new InvokeClass(new AnnotationDemo()));
        anInterface.demo1();
        anInterface.demo2();
        anInterface.demo3();
        anInterface.demo4();
    }

}

輸出:

demo1
demo2
--- default ---

Spring 如何自定義注解

在 spring 中使用自定義注解一般是配合 aop 使用的。

如下,還是注解 @Test ,有個 AnnotationDemo 類,在方法上使用了注解,並且將自身注入 spring 容器 (@Service),並且通過實現 BeanFactoryAware 接口,在初始化的時候調用 setBeanFactory 方法,這里通過傳入的 bean 工廠獲取到 bean 並且調用方法。

定義一個切面 AspectDemo,切點 pointcut 為我們自定義的注解類,增強 advice 是打印了 @Test 注解的 value() 信息。這樣當調用了使用了 @Test 的注解的方法的時候,就是會打印對應的 value() 信息。啟動項目,由於在 setBeanFactory 方法中調用了 AnnotationDemo 類的幾個方法,因此打印出了對應的注解的 value 信息。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "--- default ---";
}
@Service
public class AnnotationDemo implements BeanFactoryAware {

    @Test("demo1")
    public void demo1() {

    }

    @Test("demo2")
    public void demo2() {

    }

    @Test
    public void demo3() {

    }

    public void demo4() {

    }

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        AnnotationDemo demo = beanFactory.getBean(AnnotationDemo.class);
        demo.demo1();
        demo.demo2();
        demo.demo3();
        demo.demo4();
    }
}
@Component
@Aspect
public class AspectDemo {

    @Pointcut("@annotation(com.example.spring_project.Test)")
    private void pointcut() {}

    @Before("pointcut() && @annotation(test)")
    public void advice(Test test) {
        System.out.println(test.value());
    }

}

輸出:

demo1
demo2
--- default ---

文章為本人學習過程中的一些個人見解,漏洞是必不可少的,希望各位大佬多多指教,幫忙修復修復漏洞!!!

進入本人語雀文檔體驗更好哦
https://www.yuque.com/docs/share/d1d3f7bb-0918-4844-a870-fc50eb0da707?# 《注解》

參考資料:java 編程思想


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM