註解是什麼
簡單的說,註解就是一種將後設資料資訊從 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 程式設計思想