注解是什么
簡單的說,注解就是一種將元數據信息從 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 編程思想