Java 注解與反射實戰(zhàn)之自定義注解從入門到精通
前言:注解到底是什么?
你是否經(jīng)常在 Java 代碼中看到@Override、@Deprecated這樣的標記?這些就是注解 —— 一種給代碼 "貼標簽" 的機制。注解本身不直接影響代碼執(zhí)行,但能通過工具(如編譯器)或框架(如 Spring)賦予代碼額外含義。
自定義注解則是讓我們根據(jù)業(yè)務需求創(chuàng)建專屬 "標簽",結合反射機制能實現(xiàn)強大的動態(tài)邏輯(比如日志記錄、權限校驗、ORM 映射等)。本文將從基礎到實戰(zhàn),帶你掌握自定義注解的定義、元注解的作用,以及如何通過反射讓注解 "生效"。
一、自定義注解基礎:@interface關鍵字
自定義注解使用 @interface 關鍵字定義,本質上是一種特殊的接口(編譯后會生成繼承 java.lang.annotation.Annotation 的接口)。
1.1 最簡單的自定義注解
// 定義一個空注解
public @interface MyFirstAnnotation {
}
這個注解沒有任何屬性,僅作為標記使用??梢灾苯訕俗⒃陬悺⒎椒ǖ仍厣希?/p>
@MyFirstAnnotation
public class Demo {
@MyFirstAnnotation
public void test() {}
}
1.2 帶屬性的注解
注解可以包含 "屬性"(類似接口的抽象方法),使用時需要為屬性賦值(除非有默認值)。
public @interface UserInfo {
// 字符串屬性
String name();
// 整數(shù)屬性,帶默認值
int age() default 18;
// 數(shù)組屬性
String[] hobbies() default {"coding"};
}
使用時的語法(屬性名 = 值):
@UserInfo(name = "張三", age = 20, hobbies = {"籃球", "游戲"})
public class Person {}
?? 特殊規(guī)則:
- 若屬性名是
value,且只有這一個屬性需要賦值,可省略屬性名:@MyAnnotation("test") - 數(shù)組屬性若只有一個元素,可省略大括號:
hobbies = "足球"
二、元注解:注解的 "注解"
元注解是用于修飾注解的注解,規(guī)定了自定義注解的使用范圍、生命周期等特性。Java 內置了 4 種元注解:@Target、@Retention、@Documented、@Inherited。
2.1@Target:指定注解能修飾哪些元素
@Target 限制注解可標注的目標(如類、方法、字段等),參數(shù)是 ElementType 枚舉數(shù)組,常用值:
| ElementType | 作用范圍 |
|---|---|
| TYPE | 類、接口、枚舉 |
| METHOD | 方法 |
| FIELD | 成員變量(包括枚舉常量) |
| PARAMETER | 方法參數(shù) |
| CONSTRUCTOR | 構造方法 |
| LOCAL_VARIABLE | 局部變量 |
示例:限制注解只能用于類和方法
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;
@Target({ElementType.TYPE, ElementType.METHOD}) // 可修飾類和方法
public @interface Log {
}如果把 @Log 標注在字段上,編譯器會直接報錯:
public class Demo {
@Log // 編譯錯誤:@Log不適用于字段
private String name;
}
?? 圖示:@Target 的作用范圍限制

2.2@Retention:指定注解的生命周期
@Retention 決定注解保留到哪個階段(源碼、字節(jié)碼、運行時),參數(shù)是 RetentionPolicy 枚舉,必須指定:
| RetentionPolicy | 生命周期說明 | 能否被反射獲取 |
|---|---|---|
| SOURCE | 僅存在于源碼中,編譯后丟棄(如@Override) | 不能 |
| CLASS | 保留到字節(jié)碼中,但 JVM 運行時不加載(默認值) | 不能 |
| RUNTIME | 保留到運行時,JVM 加載,可通過反射獲取 | 能 |
示例:讓注解在運行時可被反射獲取
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME) // 關鍵:保留到運行時
public @interface Permission {
String value();
}?? 為什么 RUNTIME 重要?反射是在程序運行時動態(tài)獲取類信息的機制,只有 RUNTIME 級別的注解才能被反射讀取,這是注解與反射結合的核心前提。
?? 圖示:注解的生命周期流程

2.3@Documented:讓注解出現(xiàn)在 API 文檔中
默認情況下,javadoc 生成的文檔不會包含注解信息。@Documented 修飾的注解會被包含在文檔中。
示例:
import java.lang.annotation.Documented;
@Documented // 生成文檔時包含該注解
public @interface Description {
String value();
}
/**
* 測試類
* @Description 這是一個測試類
*/
@Description("測試類")
public class Test {}生成的 javadoc 中,Test 類的文檔會顯示 @Description("測試類")。
2.4@Inherited:讓注解可被繼承
@Inherited 表示注解具有繼承性:如果父類被該注解標注,子類會自動繼承該注解(僅對類注解有效,方法 / 字段注解不繼承)。
示例:
import java.lang.annotation.Inherited;
@Inherited // 允許繼承
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {}
// 父類標注注解
@InheritedAnnotation
class Parent {}
// 子類未標注,但會繼承父類的@InheritedAnnotation
class Child extends Parent {}通過反射驗證:
public class Test {
public static void main(String[] args) {
System.out.println(Child.class.isAnnotationPresent(InheritedAnnotation.class)); // 輸出:true
}
}
?? 圖示:@Inherited 的繼承效果

三、注解 + 反射:讓注解 "生效"
注解本身只是標記,必須通過反射獲取注解信息并執(zhí)行邏輯,才能真正發(fā)揮作用。反射提供了以下核心方法(在 Class、Method、Field 等類中):
| 方法 | 作用 |
|---|---|
getAnnotation(Class) | 獲取指定類型的注解實例 |
getAnnotations() | 獲取所有注解(包括繼承的) |
isAnnotationPresent(Class) | 判斷是否存在指定注解 |
實戰(zhàn)案例:用注解實現(xiàn)方法權限校驗
需求:定義 @RequiresPermission 注解,標記方法需要的權限;通過反射調用方法前檢查當前用戶是否有權限,無權限則拋出異常。
步驟 1:定義注解
import java.lang.annotation.*;
@Target(ElementType.METHOD) // 僅用于方法
@Retention(RetentionPolicy.RUNTIME) // 運行時可反射獲取
public @interface RequiresPermission {
String[] value(); // 所需權限列表
}步驟 2:使用注解標注方法
public class UserService {
// 需要"user:query"權限
@RequiresPermission("user:query")
public void queryUser() {
System.out.println("查詢用戶成功");
}
// 需要"user:add"或"admin"權限
@RequiresPermission({"user:add", "admin"})
public void addUser() {
System.out.println("新增用戶成功");
}
}步驟 3:反射 + 注解實現(xiàn)權限校驗
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class PermissionChecker {
// 模擬當前用戶擁有的權限
private static final Set<String> CURRENT_USER_PERMISSIONS = new HashSet<>(Arrays.asList("user:query"));
// 反射調用方法并校驗權限
public static void invokeWithCheck(Object obj, String methodName) throws Exception {
// 1. 獲取方法對象
Method method = obj.getClass().getMethod(methodName);
// 2. 檢查方法是否有@RequiresPermission注解
if (method.isAnnotationPresent(RequiresPermission.class)) {
// 3. 獲取注解實例
RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
// 4. 獲取注解的權限列表
String[] requiredPermissions = annotation.value();
// 5. 校驗權限
boolean hasPermission = false;
for (String permission : requiredPermissions) {
if (CURRENT_USER_PERMISSIONS.contains(permission)) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
throw new SecurityException("權限不足,需要:" + Arrays.toString(requiredPermissions));
}
}
// 6. 權限通過,調用方法
method.invoke(obj);
}
public static void main(String[] args) throws Exception {
UserService service = new UserService();
invokeWithCheck(service, "queryUser"); // 成功:查詢用戶成功
invokeWithCheck(service, "addUser"); // 失敗:拋出SecurityException
}
}執(zhí)行結果:
查詢用戶成功 Exception in thread "main" java.lang.SecurityException: 權限不足,需要:[user:add, admin]
四、底層原理:注解本質與反射獲取機制
注解的本質:@interface 編譯后會生成一個繼承 java.lang.annotation.Annotation 的接口,例如:
// 編譯后自動生成的代碼(簡化)
public interface MyAnnotation extends Annotation {
String value();
int age() default 18;
}
- 注解實例的生成:當 JVM 加載被注解的類時,會通過動態(tài)代理生成注解接口的實現(xiàn)類實例(保存注解屬性值)。
- 反射獲取注解的過程:反射通過
getAnnotation()方法從類 / 方法的元數(shù)據(jù)中獲取代理實例,從而讀取屬性值。
五、應用場景總結
注解 + 反射的組合在框架中被廣泛使用:
- 日志記錄:通過注解標記需要記錄日志的方法,反射攔截并打印日志(如 Spring 的
@Log)。 - ORM 映射:用注解關聯(lián) Java 類與數(shù)據(jù)庫表(如 JPA 的
@Entity、@Column)。 - 依賴注入:標記需要注入的對象(如 Spring 的
@Autowired)。 - AOP 切面:通過注解定義切入點(如 Spring 的
@Before、@After)。 - 參數(shù)校驗:驗證方法參數(shù)合法性(如 Jakarta 的
@NotNull、@Size)。
結語
自定義注解是 Java 中 "聲明式編程" 的核心體現(xiàn),結合反射能極大簡化代碼邏輯、提高靈活性。掌握元注解的作用(尤其是@Target和@Retention)是定義有效注解的前提,而反射則是讓注解從 "標記" 變?yōu)?"可執(zhí)行邏輯" 的橋梁。
嘗試在項目中用注解解決重復邏輯(如日志、權限),你會感受到它的強大!
到此這篇關于Java 注解與反射實戰(zhàn)之自定義注解從入門到精通的文章就介紹到這了,更多相關Java 注解與反射內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
prometheus數(shù)據(jù)遠程寫入elasticsearch方式
這篇文章主要介紹了prometheus數(shù)據(jù)遠程寫入elasticsearch方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-06-06
Java實現(xiàn)CORS跨域請求的實現(xiàn)方法
本篇文章主要介紹了Java實現(xiàn)CORS跨域請求的實現(xiàn)方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09
使用arthas命令redefine實現(xiàn)Java熱更新(推薦)
今天分享一個非常重要的命令 redefine ,主要作用是加載外部的 .class 文件,用來替換 JVM 已經(jīng)加載的類,總結起來就是實現(xiàn)了 Java 的熱更新,感興趣的朋友跟隨小編一起看看吧2020-05-05
升級springboot中spring框架的版本的實現(xiàn)方法
本文主要介紹了升級springboot中spring框架的版本的實現(xiàn)方法,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2024-08-08

