JAVA自定義注解的步驟示例詳解
簡介
在 Java 中,注解(Annotation) 是一種特殊的元數(shù)據(jù)(Metadata),可以標(biāo)記在類、方法、字段、參數(shù)等元素上,用于傳遞額外信息。自定義注解允許開發(fā)者根據(jù)業(yè)務(wù)需求定義自己的注解,結(jié)合反射、AOP 等技術(shù)實(shí)現(xiàn)靈活的功能(如日志記錄、權(quán)限校驗(yàn)、參數(shù)校驗(yàn)等)。
一、注解的核心概念
1. 注解的本質(zhì)
注解本質(zhì)是一個(gè)繼承了 java.lang.annotation.Annotation 接口的特殊接口,編譯器會(huì)自動(dòng)為其生成實(shí)現(xiàn)類。
2. 元注解(Meta-Annotation)
元注解是用于修飾注解的注解,定義了自定義注解的生命周期、作用范圍等屬性。Java 內(nèi)置 4 個(gè)核心元注解:
| 元注解 | 作用說明 |
|---|---|
@Retention | 指定注解的生命周期(必須聲明) |
@Target | 指定注解可作用的元素類型(如類、方法、字段等,必須聲明) |
@Documented | 標(biāo)記注解會(huì)被 javadoc 工具提取到文檔中(可選) |
@Inherited | 標(biāo)記注解可被子類繼承(僅對(duì)類注解有效,可選) |
@Repeatable(Java8+) | 允許注解在同一元素上重復(fù)使用(可選) |
二、自定義注解的步驟
1. 定義注解(使用@interface關(guān)鍵字)
格式:
// 元注解
元注解1
元注解2
...
public @interface 注解名 {
// 注解屬性(本質(zhì)是接口的抽象方法)
類型 屬性名() default 默認(rèn)值; // 可選默認(rèn)值,無默認(rèn)值則使用時(shí)必須指定
}關(guān)鍵說明:
- 注解屬性的類型只能是:基本類型(
int/String/boolean等)、枚舉、注解、數(shù)組(上述類型的數(shù)組)。 - 若屬性名是
value,且只有一個(gè)屬性時(shí),使用注解可省略屬性名(直接寫值)。 - 數(shù)組類型的屬性賦值時(shí),若只有一個(gè)元素,可省略
{}。
2. 常用元注解詳解
(1)@Retention:指定生命周期
取值(RetentionPolicy 枚舉):
SOURCE:僅保留在源代碼中,編譯時(shí)刪除(如@Override)。CLASS:保留到編譯后的.class文件中,但 JVM 運(yùn)行時(shí)不加載(默認(rèn)值)。RUNTIME:保留到 JVM 運(yùn)行時(shí),可通過反射獲?。ㄗ畛S?,如日志、校驗(yàn)注解)。
(2)@Target:指定作用范圍
取值(ElementType 枚舉):
TYPE:作用于類、接口、枚舉。METHOD:作用于方法。FIELD:作用于字段(成員變量)。PARAMETER:作用于方法參數(shù)。CONSTRUCTOR:作用于構(gòu)造器。ANNOTATION_TYPE:作用于注解本身。LOCAL_VARIABLE:作用于局部變量。MODULE:作用于模塊(Java9+)。
三、自定義注解示例
下面通過 3 個(gè)常見場景,演示自定義注解的定義與使用。
示例 1:基礎(chǔ)注解(無屬性)
定義一個(gè)標(biāo)記型注解(僅用于標(biāo)記,無額外屬性)。
1. 定義注解
import java.lang.annotation.*;
// 生命周期:運(yùn)行時(shí)(可通過反射獲?。?
@Retention(RetentionPolicy.RUNTIME)
// 作用范圍:類、方法
@Target({ElementType.TYPE, ElementType.METHOD})
// 生成 javadoc 文檔
@Documented
public @interface LogAnnotation {
// 無屬性(標(biāo)記型注解)
}2. 使用注解
// 作用于類
@LogAnnotation
public class UserService {
// 作用于方法
@LogAnnotation
public void addUser(String username) {
System.out.println("添加用戶:" + username);
}
}示例 2:帶屬性的注解(參數(shù)校驗(yàn))
定義一個(gè)用于字段非空校驗(yàn)的注解,支持自定義提示信息。
1. 定義注解
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD) // 僅作用于字段
@Documented
public @interface NotNull {
// 注解屬性:提示信息,默認(rèn)值為 "字段不能為空"
String message() default "字段不能為空";
}2. 使用注解(標(biāo)記實(shí)體類字段)
public class User {
@NotNull(message = "用戶名不能為空")
private String username;
@NotNull(message = "年齡不能為空")
private Integer age;
// getter/setter/構(gòu)造器省略
}3. 解析注解(通過反射實(shí)現(xiàn)校驗(yàn)邏輯)
import java.lang.reflect.Field;
public class ValidationUtil {
// 校驗(yàn)對(duì)象的注解字段
public static void validate(Object obj) throws IllegalAccessException {
// 獲取對(duì)象的類
Class<?> clazz = obj.getClass();
// 獲取所有字段(包括私有字段)
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
// 允許訪問私有字段
field.setAccessible(true);
// 判斷字段是否有 @NotNull 注解
if (field.isAnnotationPresent(NotNull.class)) {
// 獲取注解實(shí)例
NotNull notNull = field.getAnnotation(NotNull.class);
// 獲取字段值
Object value = field.get(obj);
// 校驗(yàn)邏輯:值為 null 則拋出異常
if (value == null) {
throw new IllegalArgumentException(notNull.message());
}
}
}
}
// 測試
public static void main(String[] args) throws IllegalAccessException {
User user1 = new User(null, 20); // 用戶名 null
try {
validate(user1);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 輸出:用戶名不能為空
}
User user2 = new User("張三", null); // 年齡 null
try {
validate(user2);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage()); // 輸出:年齡不能為空
}
}
}示例 3:重復(fù)注解(Java8+)
允許同一元素上重復(fù)使用注解(如多角色權(quán)限校驗(yàn))。
1. 定義 “容器注解”(用于存放重復(fù)的注解)
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Roles {
// 容器注解必須包含一個(gè)返回目標(biāo)注解數(shù)組的屬性(名稱固定為 value)
Role[] value();
}2. 定義可重復(fù)注解(使用@Repeatable關(guān)聯(lián)容器注解)
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Repeatable(Roles.class) // 指定容器注解
public @interface Role {
// 注解屬性:角色名稱
String value();
}3. 使用重復(fù)注解
public class PermissionService {
// 重復(fù)使用 @Role 注解(等價(jià)于 @Roles({@Role("admin"), @Role("manager")}))
@Role("admin")
@Role("manager")
public void deleteData() {
System.out.println("刪除數(shù)據(jù)成功");
}
}4. 解析重復(fù)注解
import java.lang.reflect.Method;
public class PermissionUtil {
public static void checkRole(Method method, String userRole) {
// 判斷方法是否有 @Roles 注解(重復(fù)注解會(huì)被包裝為容器注解)
if (method.isAnnotationPresent(Roles.class)) {
Roles roles = method.getAnnotation(Roles.class);
Role[] roleArr = roles.value();
// 校驗(yàn)用戶角色是否在允許范圍內(nèi)
boolean hasPermission = false;
for (Role role : roleArr) {
if (role.value().equals(userRole)) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
throw new SecurityException("無權(quán)限執(zhí)行該操作");
}
}
}
// 測試
public static void main(String[] args) throws NoSuchMethodException {
Method method = PermissionService.class.getMethod("deleteData");
// 測試有權(quán)限(admin)
checkRole(method, "admin");
System.out.println("admin 執(zhí)行成功");
// 測試無權(quán)限(user)
try {
checkRole(method, "user");
} catch (SecurityException e) {
System.out.println(e.getMessage()); // 輸出:無權(quán)限執(zhí)行該操作
}
}
}四、自定義注解的常見應(yīng)用場景
- 日志記錄:標(biāo)記需要打印日志的方法,通過 AOP 攔截并記錄請(qǐng)求參數(shù)、返回值、執(zhí)行時(shí)間等。
- 權(quán)限校驗(yàn):標(biāo)記需要權(quán)限的接口 / 方法,通過攔截器或 AOP 校驗(yàn)用戶角色。
- 參數(shù)校驗(yàn):標(biāo)記實(shí)體類字段,校驗(yàn)非空、長度、格式等(如 Spring 的
@NotNull、@Size)。 - 事務(wù)管理:標(biāo)記需要事務(wù)的方法(如 Spring 的
@Transactional)。 - 代碼生成:通過注解標(biāo)記類 / 字段,結(jié)合代碼生成工具(如 MyBatis Generator)生成模板代碼。
五、注意事項(xiàng)
- 注解本身不包含業(yè)務(wù)邏輯,需通過反射或AOP 解析注解并執(zhí)行相應(yīng)邏輯。
@Retention(RetentionPolicy.RUNTIME)是運(yùn)行時(shí)解析注解的前提,若僅用于編譯時(shí)檢查(如@Override),可設(shè)為SOURCE。- 注解屬性的默認(rèn)值不能是
null,需指定合理的默認(rèn)值(如空字符串、默認(rèn)枚舉值)。 - 重復(fù)注解需配合容器注解使用(Java8+ 特性),低版本需手動(dòng)包裝為容器注解。
通過自定義注解,開發(fā)者可以將通用邏輯(如日志、校驗(yàn))與業(yè)務(wù)邏輯解耦,提高代碼的復(fù)用性和可讀性。實(shí)際開發(fā)中,Spring、MyBatis 等框架大量使用自定義注解簡化配置(如 @Controller、@Mapper),核心原理與本文示例一致。
到此這篇關(guān)于JAVA自定義注解的文章就介紹到這了,更多相關(guān)java自定義注解內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
菜鳥學(xué)習(xí)java設(shè)計(jì)模式之單例模式
這篇文章主要為大家詳細(xì)介紹了java設(shè)計(jì)模式之單例模式的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-11-11
IDEA連接mysql數(shù)據(jù)庫報(bào)錯(cuò)的解決方法
這篇文章主要介紹了IDEA連接mysql數(shù)據(jù)庫報(bào)錯(cuò)的解決方法,文中有非常詳細(xì)的圖文示例,對(duì)出現(xiàn)Server returns invalid timezone. Go to ‘Advanced‘ tab and set ‘serverTimezone‘ prope報(bào)錯(cuò)的小伙伴們很有幫助喲,需要的朋友可以參考下2021-05-05
mybatis-generator生成多次重復(fù)代碼問題以及解決
在使用MySQL數(shù)據(jù)庫時(shí),如果多個(gè)數(shù)據(jù)庫中存在相同表名,即使在URL中配置了數(shù)據(jù)庫名,也可能導(dǎo)致數(shù)據(jù)互相影響,解決這一問題的方法是在mapper-generator-config.xml文件中添加catalog屬性,明確指定逆向工程代碼所涉及表的數(shù)據(jù)庫名2024-10-10
Java Lambda和Stream開發(fā)中20個(gè)高頻錯(cuò)誤案例分析與避坑指南
本篇文章將聚焦開發(fā)中最常見、最高頻的20個(gè)問題(含語法、函數(shù)式接口、Stream流、方法引用、并行流5大模塊),每個(gè)問題都配套問題描述+錯(cuò)誤案例+原因分析+正確解法,希望對(duì)大家有所幫助2026-04-04
聊聊SpringBoot使用Nacos進(jìn)行服務(wù)注冊發(fā)現(xiàn)與配置管理問題
Nacos支持基于DNS和基于RPC的服務(wù)發(fā)現(xiàn)(可以作為springcloud的注冊中心)、動(dòng)態(tài)配置服務(wù)(可以做配置中心)、動(dòng)態(tài)?DNS?服務(wù)。本文重點(diǎn)給大家介紹SpringBoot使用Nacos進(jìn)行服務(wù)注冊發(fā)現(xiàn)與配置管理,感興趣的朋友一起看看吧2022-01-01
SpringBoot使用ip2region獲取地理位置信息的方法
這篇文章主要介紹了SpringBoot使用ip2region獲取地理位置信息的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06
mybatis注解之@Mapper和@MapperScan的使用
這篇文章主要介紹了mybatis注解之@Mapper和@MapperScan的使用,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-10-10

