JAVA注解-Annotation學習
本文目的:項目開發過程中遇到自定義注解,想要弄清楚其原理,但是自己的基礎知識不足以支撐自己去探索此問題,所以先記錄問題,然后補充基礎知識,然后解決其問題。記錄此學習過程。
項目中遇到的注解:
//使用注解的地方
@ServiceScan({"com.sinosoft.lis.pubfun"})
public class CodeQuerySQL {}
//注解類 ServiceScan
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceScan {
String[] value() default {};
}
//這個com.sinosoft.lis.pubfun包下的類
@CodeQuery
public interface CodeQuery_Framework {}
//注解類CodeQuery
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface CodeQuery {
}
問題描述: 開發中,我們需要自己新建一個codequeryframework_nb類,這個類是這樣使用的,放在com.sinosoft.lis.pubfun包下就行,然后自定義方法,就會自動被掃描到,然后會自動加載定義的接口方法類,去實現我們的查詢下拉的功能。注解使用正確,包放在正確的位置就可以使用了,但是為什么不會和之前的codequeryframework沖突?具體是怎么實現的,我們組內的成員都沒搞明白。我決定把注解這個知識點往深處挖一挖。
我們的問題:
- 功能這是怎么實現的。
- 為什么不會和之前創建的類沖突。
- 其實就是,怎么實現的。
學習目的:
- 能夠解釋:為什么這個類是通過注解如何實現的
- 了解注解是什么,注解怎么用,注解的實現原理
注解基礎知識補充:
學習過程:
- 首先,先找一個資料,大概的對注解有一定的認識。 https://www.runoob.com/w3cnote/java-annotation.html
- 查詢一些博主的博文,看看他們都提到哪些大的點,基本上都是一樣的,所以就能定位到自己需要首先了解哪些是需要學習和了解的
- 對於過程中的疑問,進行查漏補缺。為自己解惑
其他人提到的知識點:java5,元注解,自定義注解,注解的實現,注解的屬性,注解的作用,在反射中使用注解
注解的本質
//「java.lang.annotation.Annotation」接口中有這么一句話,用來描述『注解』。
The common interface extended by all annotation types
所有的注解類型都繼承自這個普通的接口(Annotation)
我們先隨便點開一個JDK內的注解,查看一下是如何定義的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這是注解 @Override 的定義,其實它本質上就是:
public interface Override extends Annotation{
}
沒錯,注解的本質就是一個繼承了 Annotation 接口的接口。有關這一點,你可以去反編譯任意一個注解類,你會得到結果的。
為什么要用注解?
這里找到了兩位博主的解析,感覺簡單易懂很到位。先來一個整體上的認識。
在平時不知道我們是否都用過便利貼,在一張紙上寫好幾句話,貼在我們需要的地方.還有一個情況,大多數人都叫我們程序猿(錢多話少死得快),這也是給我們貼了一個標簽。像這兩種情況基本上就是注解。你可以把這兩種情況聯想到代碼的注解上。比如我們定義了一個方法,這個方法要實現加法的運算,那么我們就可以定義一個@ADD標簽。表示這個方法就是實現加法的。我們程序員一看到這個@ADD,就能很容易理解這個方法是干嘛的。簡單而言。注解就是對於代碼中某些鮮活個體的貼上去的一張標簽。簡化來講,注解如同一張標簽。因為,如果你之前還未正式的學習過注解,你就可以把他當成便利貼標簽就好了,這能幫你理解注解的大部分內容。
以前,『XML』是各大框架的青睞者,它以松耦合的方式完成了框架中幾乎所有的配置,但是隨着項目越來越龐大,『XML』的內容也越來越復雜,維護成本變高。於是就有人提出來一種標記式高耦合的配置方式,『注解』。方法上可以進行注解,類上也可以注解,字段屬性上也可以注解,反正幾乎需要配置的地方都可以進行注解。關於『注解』和『XML』兩種不同的配置模式,爭論了好多年了,各有各的優劣,注解可以提供更大的便捷性,易於維護修改,但耦合度高,而 XML 相對於注解則是相反的。追求低耦合就要拋棄高效率,追求效率必然會遇到耦合。本文意不再辨析兩者誰優誰劣,而在於以最簡單的語言介紹注解相關的基本內容。
元注解
元注解的作用:
『元注解』是用於修飾注解的注解,通常用在注解的定義上
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
這是我們 @Override 注解的定義,你可以看到其中的 @Target,@Retention 兩個注解就是我們所謂的『元注解』,『元注解』一般用於指定某個注解生命周期以及作用目標等信息。
那么元注解分別有哪些
//目前jdk官方提供的元注解有4個
@Target:定義注解的作用目標
@Retention:定義注解的生命周期
@Documented:定義注解是否應當被包含在 JavaDoc 文檔中
@Inherited:定義是否允許子類繼承該注解
元注解詳解
- @Target -用於指明被修飾的注解最終可以作用的目標是誰,也就是指明,你的注解到底是用來修飾方法的?修飾類的?還是用來修飾字段屬性的。Target 的定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
/**
* Returns an array of the kinds of elements an annotation type
* can be applied to.
* @return an array of the kinds of elements an annotation type
* can be applied to
*/
ElementType[] value();
}
我們可以通過以下的方式來為這個 value 傳值:
@Target(value = {ElementType.FIELD})
被這個 @Target 注解修飾的注解將只能作用在成員字段上,不能用於修飾方法或者類。其中,ElementType 是一個枚舉類型,有以下一些值:
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE, //允許被修飾的注解作用在類、接口和枚舉上
/** Field declaration (includes enum constants) */
FIELD, //允許作用在屬性字段上
/** Method declaration */
METHOD, //允許作用在方法上
/** Formal parameter declaration */
PARAMETER, //允許作用在方法參數上
/** Constructor declaration */
CONSTRUCTOR, //允許作用在構造器上
/** Local variable declaration */
LOCAL_VARIABLE, //允許作用在本地局部變量上
/** Annotation type declaration */
ANNOTATION_TYPE, //允許作用在注解上
/** Package declaration */
PACKAGE, //允許作用在包上
/**
* Type parameter declaration
* 表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)。
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
* 表示該注解能寫在使用類型的任何語句中。
* @since 1.8
*/
TYPE_USE
}
注意:上述中文翻譯為自己翻譯的,如果有錯誤,請自行查閱官方文檔
最后從jdk1.8添加的兩個枚舉類型的作用,是通過搜索網絡資料查詢得來
類型注解: JDK1.8之后,關於元注解@Target的參數類型ElementType枚舉值多了兩個:
TYPE_PARAMETER和TYPE_USE。
在Java8之前,注解只能是在聲明的地方所使用,Java8開始,注解可以應用在任何地方。
ElementType.TYPE_PARAMETER 表示該注解能寫在類型變量的聲明語句中(如:泛型聲明)。
ElementType.TYPE_USE 表示該注解能寫在使用類型的任何語句中。
- @Retention - 標識這個注解怎么保存,是只在代碼中,還是編入class文件中,或者是在運行時可以通過反射訪問。它的基本定義如下:
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
/**
* Returns the retention policy.
* @return the retention policy
*/
RetentionPolicy value();
}
同樣的,它也有一個 value 屬性:
@Retention(value = RetentionPolicy.RUNTIME
這里的 RetentionPolicy 依然是一個枚舉類型,它有以下幾個枚舉值可取:
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE, //當前注解編譯期可見,不會寫入 class 文件
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS, //類加載階段丟棄,會寫入 class 文件
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME //永久保存,可以反射獲取
}
@Retention 注解指定了被修飾的注解的生命周期,一種是只能在編譯期可見,編譯后會被丟棄,一種會被編譯器編譯進class文件中,無論是類或是方法,乃至字段,他們都是有屬性表的,而 JAVA 虛擬機也定義了幾種注解屬性表用於存儲注解信息,但是這種可見性不能帶到方法區,類加載時會予以丟棄,最后一種則是永久存在的可見性。
如何驗證生命周期?
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface TestAnnotation {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation2 {
}
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.SOURCE)
public @interface TestAnnotation3 {
}
@TestAnnotation
@TestAnnotation2
@TestAnnotation3
public class TestJava {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> testJava = Class.forName("com.sinosoft.lis.pubfun.TestJava");
Annotation[] annotations = testJava.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println(annotation.annotationType());
}
}
你明白我的意思吧。
- @Inherited - 標記這個注解是繼承於哪個注解類(默認 注解並沒有繼承於任何子類).
- @Documented - 標記這些注解是否包含在用戶文檔中.
剩下兩種類型的注解我們日常用的不多,也比較簡單,這里不再詳細的進行介紹了,只需要知道他們各自的作用即可.
@Documented 注解修飾的注解,當我們執行 JavaDoc 文檔打包時會被保存進 doc 文檔,反之將在打包時丟棄.
@Inherited 注解修飾的注解是具有可繼承性的,也就說我們的注解修飾了一個類,而該類的子類將自動繼承父類的該注解.
JAVA提供的三大內置注解
#### 除了上述四種元注解外,JDK 還為我們預定義了另外三種注解,它們是:
1. @Override
2. @Deprecated
3. @SuppressWarnings
JAVA提供的三大內置注解-詳解
1. @Override 注解想必是大家很熟悉的了,標記為方法為重寫,它的定義如下:
/**
* Indicates that a method declaration is intended to override a
* method declaration in a supertype. If a method is annotated with
* this annotation type compilers are required to generate an error
* message unless at least one of the following conditions hold:
*
* <ul><li>
* The method does override or implement a method declared in a
* supertype.
* </li><li>
* The method has a signature that is override-equivalent to that of
* any public method declared in {@linkplain Object}.
* </li></ul>
*
* @author Peter von der Ahé
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
它沒有任何的屬性,所以並不能存儲任何其他信息。它只能作用於方法之上,編譯結束后將被丟棄。所以你看,它就是一種典型的『標記式注解』,僅被編譯器可知,編譯器在對 java 文件進行編譯成字節碼的過程中,一旦檢測到某個方法上被修飾了該注解,就會去匹對父類中是否具有一個同樣方法簽名的函數,如果不是,自然不能通過編譯。
2. @Deprecated : 主要用來標記該Element已經過時,基本定義如下
/**
* A program element annotated @Deprecated is one that programmers
* are discouraged from using, typically because it is dangerous,
* or because a better alternative exists. Compilers warn when a
* deprecated program element is used or overridden in non-deprecated code.
*
* @author Neal Gafter
* @since 1.5
* @jls 9.6.3.6 @Deprecated
*/
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
依然是一種『標記式注解』,永久存在,可以修飾所有的類型,作用是,標記當前的類或者方法或者字段等已經不再被推薦使用了,可能下一次的 JDK 版本就會刪除。當然,編譯器並不會強制要求你做什么,只是告訴你 JDK 已經不再推薦使用當前的方法或者類了,建議你使用某個替代者。
3. @SuppressWarnings:主要用來壓制 java 的警告,它的基本定義如下:
/**
* Indicates that the named compiler warnings should be suppressed in the
* annotated element (and in all program elements contained in the annotated
* element). Note that the set of warnings suppressed in a given element is
* a superset of the warnings suppressed in all containing elements. For
* example, if you annotate a class to suppress one warning and annotate a
* method to suppress another, both warnings will be suppressed in the method.
*
* <p>As a matter of style, programmers should always use this annotation
* on the most deeply nested element where it is effective. If you want to
* suppress a warning in a particular method, you should annotate that
* method rather than its class.
*
* @author Josh Bloch
* @since 1.5
* @jls 4.8 Raw Types
* @jls 4.12.2 Variables of Reference Type
* @jls 5.1.9 Unchecked Conversion
* @jls 5.5.2 Checked Casts and Unchecked Casts
* @jls 9.6.3.5 @SuppressWarnings
*/
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
/**
* The set of warnings that are to be suppressed by the compiler in the
* annotated element. Duplicate names are permitted. The second and
* successive occurrences of a name are ignored. The presence of
* unrecognized warning names is <i>not</i> an error: Compilers must
* ignore any warning names they do not recognize. They are, however,
* free to emit a warning if an annotation contains an unrecognized
* warning name.
*
* <p> The string {@code "unchecked"} is used to suppress
* unchecked warnings. Compiler vendors should document the
* additional warning names they support in conjunction with this
* annotation type. They are encouraged to cooperate to ensure
* that the same names work across multiple compilers.
* @return the set of warnings to be suppressed
*/
String[] value();
}
它有一個 value 屬性需要你主動的傳值,這個 value 代表一個什么意思呢,這個 value 代表的就是需要被壓制的警告類型。例如:
public static void main(String[] args) {
Date date = new Date(2019, 12, 27);
}
這么一段代碼,程序啟動時編譯器會報一個警告。
Warning:(8, 21) java: java.util.Date 中的 Date(int,int,int) 已過時
而如果我們不希望程序啟動時,編譯器檢查代碼中過時的方法,就可以使用 @SuppressWarnings 注解並給它的 value 屬性傳入一個參數值來壓制編譯器的檢查。
@SuppressWarning(value = "deprecated")
public static void main(String[] args) {
Date date = new Date(2019, 12, 27);
}
這樣你就會發現,編譯器不再檢查 main 方法下是否有過時的方法調用,也就壓制了編譯器對於這種警告的檢查。
當然,JAVA 中還有很多的警告類型,他們都會對應一個字符串,通過設置 value 屬性的值即可壓制對於這一類警告類型的檢查。
自定義注解:
自定義注解的語法比較簡單,通過類似以下的語法即可自定義一個注解。
public @interface InnotationName{
}
當然,自定義注解的時候也可以選擇性的使用元注解進行修飾,這樣你可以更加具體的指定你的注解的生命周期、作用范圍等信息。
注解的屬性 && 注解的使用
注解的屬性也叫做成員變量。注解只有成員變量,沒有方法。注解的成員變量在注解的定義中以“無形參的方法”形式來聲明,其方法名定義了該成員變量的名字,其返回值定義了該成員變量的類型。
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
int id();
String msg();
}
上面代碼定義了 TestAnnotation 這個注解中擁有 id 和 msg 兩個屬性。在使用的時候,我們應該給它們進行賦值。
賦值的方式是在注解的括號內以 value=”” 形式,多個屬性之前用 ,隔開。
需要注意的是,在注解中定義屬性時它的類型必須是 8 種基本數據類型外加 類、接口、注解及它們的數組。
注解中屬性可以有默認值,默認值需要用 default 關鍵值指定。比如:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
public int id() default -1;
public String msg() default "Hi";
}
TestAnnotation 中 id 屬性默認值為 -1,msg 屬性默認值為 Hi。 它可以這樣應用。
@TestAnnotation()
public class Test {}
因為有默認值,所以無需要再在 @TestAnnotation 后面的括號里面進行賦值了,這一步可以省略。
最后,還需要注意的一種情況是一個注解沒有任何屬性。比如
public @interface Perform {}
那么在應用這個注解的時候,括號都可以省略。
到目前為止:我僅僅知道注解是如何定義的,具體用起來是怎么實現的呢?
- 比如@override是怎么去校驗的??畢竟點開源碼,看它定義起來挺簡單的
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
- 再比如,我的類上面添加一個 @Documented 注解,在生成文檔的時候就會自動根據我寫的 doc去生成文檔嗎?他是怎么實現的?通過掃描注解類來完成嗎?
- 再比如,之前用過的@bean 注解,我們在spring框架使用時候,在java類上定義之后,就會在加載的時候掃描加載到容器嗎?具體是怎么實現的呢?
- 我覺得我需要很明白的理解這寫問題。自己預估可能跟其他人提到的反射有關
那么。帶着這些個疑問,我們繼續向下學習。
注解與反射
上述內容我們介紹了注解使用上的細節,也簡單提到,「注解的本質就是一個繼承了 Annotation 接口的接口」,現在我們就來從虛擬機的層面看看,注解的本質到底是什么。
注解的使用實例
注解運用的地方太多了,如:
JUnit 這個是一個測試框架,典型使用方法如下:
public class ExampleUnitTest {
@Test
public void addition_isCorrect() throws Exception {
assertEquals(4, 2 + 2);
}
}
還有例如ssm框架,springboot,springcloud等運用了大量的注解。
總結
算是對注解有了基本的認知。談談自我總結吧。
- 如果注解難於理解,你就把它類同於標簽,標簽為了解釋事物,注解為了解釋代碼。
- 注解的基本語法,創建如同接口,但是多了個 @ 符號。
- 注解的元注解。
- 注解的屬性。
- 注解主要給編譯器及工具類型的軟件用的。
- 注解的提取需要借助於 Java 的反射技術,反射比較慢,所以注解使用時也需要謹慎計較時間成本(需研究反射,!important)。
我之前的問題:
- 我推論,我之前的問題,並不是出現在注解上面了。
- 我之前的疑問的功能是通過注解,反射來完成的.注解只是起到了注解該完成的功能。
- 接下來需要研究的是:反射。
通過反射機制會掃描出所有被@CodeQuery 修飾過的類或者接口並以bean對象的形式注入到自己的容器中來統一管理,根據被@CodeQuery修飾的接口或者類,就可以確定了被@CodeQuery修飾過得類都有哪些,遍歷所有Class文件,然后可以用反射中的Method類來獲取所有被@SQL修飾過的方法的名字,通過方法名字就可以在程序運行時調用對應的接口來執行sql語句了
參考文獻 :
- https://www.cnblogs.com/yangming1996/p/9295168.html
- https://www.cnblogs.com/love-menglong/p/11165469.html
- https://www.runoob.com/w3cnote/java-annotation.html
- https://blog.csdn.net/tainxiawuti/article/details/99644352
- https://www.cnblogs.com/skywang12345/ 《大佬》
擴展作業
- Class源碼查閱並了解里面的內置方法,如里面提供的有查看反射類的注解方法:
Class<TestJava> testJavaClass = TestJava.class;
testJavaClass.getAnnotations();
- 認識到了底層知識的重要性,感受到了Java底層的力量。
- 反射的理論知識,實際應用,應用場景分析。