Java 注解的底層原理解析(java基礎(chǔ)面試必問)
前言
注解是 Java 5 引入的一種元數(shù)據(jù)機制,可以在代碼中添加信息,這些信息可以被編譯器、開發(fā)工具或運行時環(huán)境使用。
注解的本質(zhì)是一個繼承了Annotation的接口
Java 注解(Annotation)是一種元數(shù)據(jù),它本身不直接影響代碼邏輯,但可以被編譯器、工具或框架在編譯時或運行時讀取并處理。
注解實際上是一個接口,它隱式地繼承自 java.lang.annotation.Annotation 接口。注解中定義的方法對應(yīng)注解的“屬性”。這些方法沒有參數(shù),返回值類型受限(基本類型、String、Class、枚舉、注解以及它們的數(shù)組)。
對于一個簡單的自定義注解,使用 @interface 關(guān)鍵字進行實現(xiàn)
public @interface MyAnnotation {
String value() default "";
}
用 javap -c MyAnnotation 反編譯后,會看到,
public interface MyAnnotation extends java.lang.annotation.Annotation {
public abstract java.lang.String value();
}
元注解
Java 提供了幾個元注解(即注解的注解),用來定義注解的生命周期、使用位置等。
| 元注解 | 作用 |
|---|---|
| @Target | 指定注解可以用在哪些地方(類、方法、字段、參數(shù)等)。 |
| @Retention | 指定注解保留到哪個階段(源碼、字節(jié)碼、運行時)。 |
| @Documented | 是否包含在 Javadoc 中。 |
| @Inherited | 是否允許子類繼承父類的注解。 |
| @Repeatable(Java 8) | 允許同一位置重復(fù)使用同一個注解。 |
@Target注解
@Target 接收一個 ElementType 數(shù)組,表示該注解可以出現(xiàn)在哪些地方。常用的 ElementType 枚舉值包括:
| ElementType | 說明 |
|---|---|
| TYPE | 類、接口、枚舉、注解類型 |
| FIELD | 成員變量(包括枚舉常量) |
| METHOD | 方法 |
| PARAMETER | 方法參數(shù) |
| CONSTRUCTOR | 構(gòu)造方法 |
| LOCAL_VARIABLE | 局部變量 |
| ANNOTATION_TYPE | 注解類型(用于元注解) |
| PACKAGE | 包 |
| TYPE_PARAMETER(Java 8) | 類型參數(shù)(如 class MyClass 中的 T) |
| TYPE_USE(Java 8) | 類型使用處(如 new @NonNull String()) |
@Retention注解
- SOURCE:注解只保留在源代碼中,編譯時被丟棄(如 @Override、@SuppressWarnings)。這類注解僅用于編譯期檢查,不會進入 .class 文件。
- CLASS(默認):注解保留在 .class 文件中,但加載到 JVM 時會被忽略(即運行時無法通過反射獲?。?。這種級別通常用于字節(jié)碼操作工具(如 Lombok)。
- RUNTIME:注解保留在 .class 文件中,并且在運行時可以被 JVM 讀?。ㄍㄟ^反射)。這是框架最常用的級別(如 @Autowired、@RequestMapping)。
注解在字節(jié)碼中的存儲
編譯階段
當編譯器處理帶有注解的代碼時,會根據(jù) @Retention 決定是否將注解信息寫入 .class 文件。對于 RUNTIME 或 CLASS 級別的注解,編譯器會在字節(jié)碼中添加專門的屬性表(Attribute)。
以 @MyAnnotation 標注一個類為例:
@MyAnnotation("hello")
public class Test {}
用 javap -v Test 查看字節(jié)碼,會看到類似:
RuntimeVisibleAnnotations:
0: #10(#11=s#12)
#10 = Utf8 "LMyAnnotation;"
#11 = Utf8 "value"
#12 = Utf8 "hello"
RuntimeVisibleAnnotations 是字節(jié)碼中的一種屬性,表示在運行時可見的注解列表。每個注解被編碼為:注解類型 + 屬性名 + 屬性值。例子中的類型為 LMyAnnotation;,屬性名是value,屬性值是hello。
類加載階段
JVM 在加載類時,會讀取 .class 文件中的這些屬性,將注解信息解析并存儲到類的元數(shù)據(jù)中(方法區(qū)的 Annotation 數(shù)據(jù)結(jié)構(gòu))。但對于 @Retention(CLASS) 的注解,在類加載后這些信息會被丟棄;而對于 RUNTIME 的注解,會保留在運行時。
運行時注解(反射API)
JVM自動生成動態(tài)代理對象來實現(xiàn)注解接口,可通過代理對象的 invoke 方法實現(xiàn)對注解中屬性對應(yīng)值的返回(自定義注解中的 value 的對應(yīng)屬性值)
當注解的 @Retention 為 RUNTIME 時,才可以通過反射 API 獲取注解信息
Annotation[] annotations = Test.class.getAnnotations(); MyAnnotation myAnno = Test.class.getAnnotation(MyAnnotation.class); String value = myAnno.value();
- Class.getAnnotation(Class) 最終會調(diào)用 JVM 內(nèi)部的 native 方法,遍歷類的 RuntimeVisibleAnnotations 表。
- 對于找到的每個注解,JVM 會動態(tài)生成一個代理對象(java.lang.reflect.Proxy)來實現(xiàn)該注解接口。
- 這個代理對象內(nèi)部有一個 AnnotationInvocationHandler(在 sun.reflect.annotation 包下),它維護了一個 Map<String, Object>,里面存放了解析出的注解屬性名和值(例如 {“value”: “hello”})
- 代理對象的 invoke 方法會根據(jù)注解屬性名返回對應(yīng)的值(這些值存儲在字節(jié)碼中,由 JVM 解析后保存)。
- 因此,myAnno.value() 實際執(zhí)行的是代理對象的方法調(diào)用,而不是某個實現(xiàn)類的實例方法。
@MyAnnotation(“hello”) 本質(zhì)上是 @MyAnnotation(value = “hello”),相當于給value賦值為 “hello”,調(diào)用myAnno.value()本質(zhì)上調(diào)用代理對象的 invoke 方法,最終返回屬性值。
到此這篇關(guān)于【Java基礎(chǔ)面經(jīng)】Java 注解的底層原理的文章就介紹到這了,更多相關(guān)Java 注解原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot中@RequestBody不能和Multipart同時傳遞的問題解決
本文主要介紹了SpringBoot中@RequestBody不能和Multipart同時傳遞的問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-04-04
Mybatis批量插入index out of range錯誤的解決(較偏的錯誤)
這篇文章主要介紹了Mybatis批量插入index out of range錯誤的解決(較偏的錯誤),具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-12-12
mybatisplus按條件修改實體單個字段實現(xiàn)方式
在MyBatis-Plus中,可以使用UpdateWrapper/LambdaUpdateWrapper按條件修改實體單個字段,推薦使用LambdaUpdateWrapper,它具有類型安全、清晰、高效等優(yōu)點,避免使用updateById()或傳入實體對象的方式,通過示例展示如何正確使用UpdateWrapper進行單個字段的更新2026-04-04
java.lang.Long cannot be cast to ja
本文主要介紹了java.lang.Long cannot be cast to java.lang.Integer數(shù)據(jù)類型轉(zhuǎn)換異常解決辦法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07
SpringBoot 多線程事務(wù)回滾的實現(xiàn)
本文是基于springboot的@Async注解開啟多線程,并通過自定義注解和AOP實現(xiàn)的多線程事務(wù),避免繁瑣的手動提交/回滾事務(wù),感興趣的可以了解一下2024-02-02
Java使用POI實現(xiàn)導(dǎo)出Excel的方法詳解
在項目開發(fā)中往往需要使用到Excel的導(dǎo)入和導(dǎo)出,導(dǎo)入就是從Excel中導(dǎo)入到DB中,而導(dǎo)出就是從DB中查詢數(shù)據(jù)然后使用POI寫到Excel上。本文將利用POI實現(xiàn)導(dǎo)出Excel,需要的可以參考一下2022-10-10
SpringBoot啟動執(zhí)行sql腳本的3種方法實例
在應(yīng)用程序啟動后,可以自動執(zhí)行建庫、建表等SQL腳本,下面這篇文章主要給大家介紹了關(guān)于SpringBoot啟動執(zhí)行sql腳本的3種方法,文中通過實例代碼介紹的非常詳細,需要的朋友可以參考下2022-01-01

