注解是什么
簡單的說,注解就是一種將元資料資訊從 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 = https://www.cnblogs.com/cyrus-s/p/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 編程思想
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/340304.html
標籤:Java
上一篇:Nginx常用配置,避坑指南!
