JAVA 注解Annotation開發(fā)流程從原理到實戰(zhàn)應(yīng)用
1.1 本章學(xué)習(xí)目標(biāo)與重點
?? 掌握注解的核心概念與分類,理解注解在Java開發(fā)中的核心價值。
?? 熟練使用JDK內(nèi)置注解,掌握自定義注解的定義、解析與使用流程。
?? 掌握注解的元注解配置方式,理解不同元注解對自定義注解的約束作用。
?? 結(jié)合反射機制實現(xiàn)注解的實戰(zhàn)應(yīng)用,掌握注解在框架開發(fā)中的核心用法。
?? 本章重點是 自定義注解的開發(fā)流程 和 注解與反射結(jié)合的實戰(zhàn)應(yīng)用,這是Java高級開發(fā)與框架設(shè)計的必備技能。
1.2 注解的核心概念與價值
1.2.1 什么是注解
?? 注解(Annotation) 是Java 5引入的一種特殊標(biāo)記,它可以在編譯期、類加載期、運行時被讀取,并執(zhí)行相應(yīng)的處理邏輯。注解本身不直接影響代碼的執(zhí)行邏輯,而是通過元數(shù)據(jù)的方式為程序提供額外信息,這些信息可以被編譯器、虛擬機或自定義的注解處理器解析和使用。
注解的本質(zhì)是一個繼承了 java.lang.annotation.Annotation 接口的特殊接口,我們定義的每一個注解,最終都會被編譯器生成對應(yīng)的接口實現(xiàn)類,供程序在運行時調(diào)用。
1.2.2 注解的核心價值
在傳統(tǒng)Java開發(fā)中,我們需要通過配置文件(如XML)來描述程序的元數(shù)據(jù)信息,而注解的出現(xiàn),讓元數(shù)據(jù)可以直接嵌入到代碼中,實現(xiàn)了代碼與配置的一體化。注解的核心價值主要體現(xiàn)在以下幾個方面:
- 簡化配置:替代XML等外部配置文件,減少配置文件的數(shù)量,降低維護成本。
- 增強代碼可讀性:元數(shù)據(jù)與代碼緊密結(jié)合,開發(fā)者可以直接在代碼中看到配置信息。
- 編譯期檢查:部分注解可以在編譯期進行語法檢查,提前發(fā)現(xiàn)潛在問題。
- 框架開發(fā)基石:是Spring、MyBatis等主流框架的核心技術(shù),用于實現(xiàn)依賴注入、AOP、ORM映射等功能。
1.2.3 注解的分類
根據(jù)注解的使用范圍和生命周期,我們可以將注解分為以下三類:
| 分類標(biāo)準 | 具體類型 | 特點 | 典型示例 |
|---|---|---|---|
| JDK內(nèi)置注解 | 基本注解 | 用于修飾代碼元素(類、方法、屬性等) | @Override、@Deprecated、@SuppressWarnings |
| 元注解 | 注解的注解 | 用于修飾自定義注解,約束注解的使用范圍和生命周期 | @Target、@Retention、@Documented、@Inherited |
| 自定義注解 | 業(yè)務(wù)注解 | 開發(fā)者根據(jù)業(yè)務(wù)需求自行定義的注解,用于實現(xiàn)特定功能 | @RequestMapping、@Autowired、@Test |
? 核心結(jié)論:注解是一種元數(shù)據(jù)標(biāo)記,本身不執(zhí)行任何邏輯,需要通過注解處理器(如反射、APT工具)解析后,才能發(fā)揮實際作用。
1.3 JDK內(nèi)置注解詳解
JDK提供了三個最基礎(chǔ)的內(nèi)置注解,用于輔助代碼開發(fā),這三個注解也是我們?nèi)粘i_發(fā)中使用頻率最高的注解。
1.3.1@Override:重寫檢查注解
?? 作用:用于標(biāo)記方法是重寫自父類或接口的方法,編譯器會檢查該方法的簽名是否與父類完全一致,如果不一致則編譯報錯。
?? 注意事項:該注解只能修飾方法,且只能用于重寫場景,不能用于重載。
代碼實操
/**
* @Override 注解使用示例
*/
class ParentClass {
public void sayHello() {
System.out.println("父類的sayHello方法");
}
}
class ChildClass extends ParentClass {
// 使用@Override標(biāo)記該方法是重寫自父類
@Override
public void sayHello() {
System.out.println("子類重寫的sayHello方法");
}
// 錯誤示例:方法簽名與父類不一致,編譯報錯
// @Override
// public void sayHello(String name) {
// System.out.println("Hello " + name);
// }
}
public class OverrideDemo {
public static void main(String[] args) {
ParentClass child = new ChildClass();
child.sayHello(); // 輸出:子類重寫的sayHello方法
}
}1.3.2@Deprecated:過時標(biāo)記注解
?? 作用:用于標(biāo)記類、方法、屬性等元素已過時,不推薦使用。編譯器會對使用該元素的代碼發(fā)出警告,提示開發(fā)者更換新的API。
?? 注意事項:該注解標(biāo)記的元素仍然可以使用,但存在被移除的風(fēng)險,建議開發(fā)者使用替代方案。
代碼實操
/**
* @Deprecated 注解使用示例
*/
public class DeprecatedDemo {
// 標(biāo)記該方法已過時
@Deprecated
public static void oldMethod() {
System.out.println("這個方法已經(jīng)過時了,請使用newMethod替代");
}
// 新的替代方法
public static void newMethod() {
System.out.println("這是新的推薦方法");
}
public static void main(String[] args) {
// 使用過時方法,編譯器會發(fā)出警告
oldMethod();
// 使用推薦方法
newMethod();
}
}1.3.3@SuppressWarnings:抑制警告注解
?? 作用:用于抑制編譯器產(chǎn)生的特定類型警告,讓代碼編譯時不再輸出警告信息。
?? 注意事項:該注解需要傳入?yún)?shù),指定要抑制的警告類型,常用參數(shù)有 unchecked(抑制未檢查轉(zhuǎn)換警告)、deprecation(抑制過時元素警告)、all(抑制所有警告)。
代碼實操
import java.util.ArrayList;
import java.util.List;
/**
* @SuppressWarnings 注解使用示例
*/
public class SuppressWarningsDemo {
public static void main(String[] args) {
// 抑制未檢查轉(zhuǎn)換警告
@SuppressWarnings("unchecked")
List<String> list = new ArrayList(); // 原始類型ArrayList,默認會產(chǎn)生警告
list.add("Hello Annotation");
System.out.println(list.get(0));
// 抑制過時方法警告
@SuppressWarnings("deprecation")
void test() {
DeprecatedDemo.oldMethod(); // 調(diào)用過時方法,不會產(chǎn)生警告
}
test();
}
}? 核心結(jié)論:JDK內(nèi)置注解主要用于代碼檢查和提示,幫助開發(fā)者寫出更規(guī)范的代碼,提升代碼質(zhì)量。
1.4 元注解詳解
元注解是用于修飾自定義注解的注解,JDK提供了四個標(biāo)準元注解,它們分別從不同維度約束自定義注解的行為。
1.4.1@Target:注解目標(biāo)約束
?? 作用:指定自定義注解可以修飾的代碼元素類型,如類、方法、屬性等。
?? 注意事項:如果不指定 @Target,則該注解可以修飾所有類型的代碼元素。
@Target 的參數(shù)是一個 ElementType 枚舉數(shù)組,常用枚舉值如下:
| 枚舉值 | 可修飾的代碼元素 |
|---|---|
TYPE | 類、接口、枚舉 |
METHOD | 方法 |
FIELD | 屬性(成員變量) |
PARAMETER | 方法參數(shù) |
CONSTRUCTOR | 構(gòu)造方法 |
LOCAL_VARIABLE | 局部變量 |
ANNOTATION_TYPE | 注解 |
1.4.2@Retention:注解生命周期約束
?? 作用:指定注解的生命周期,即注解在哪個階段有效。
?? 注意事項:這是自定義注解必須指定的元注解,否則注解默認在編譯期就會被丟棄,無法在運行時通過反射獲取。
@Retention 的參數(shù)是一個 RetentionPolicy 枚舉值,三種枚舉值對應(yīng)注解的三個生命周期:
| 枚舉值 | 生命周期 | 特點 | 適用場景 |
|---|---|---|---|
SOURCE | 源碼階段 | 注解僅存在于源碼中,編譯成字節(jié)碼后被丟棄 | 編譯器檢查,如 @Override |
CLASS | 字節(jié)碼階段 | 注解存在于源碼和字節(jié)碼中,類加載時被丟棄 | 類加載器處理,如字節(jié)碼增強 |
RUNTIME | 運行時階段 | 注解存在于源碼、字節(jié)碼和運行時,可通過反射獲取 | 框架開發(fā),如Spring注解 |
1.4.3@Documented:文檔生成標(biāo)記
?? 作用:指定該注解會被 javadoc 工具提取到API文檔中。
?? 注意事項:默認情況下,注解不會出現(xiàn)在API文檔中,添加該元注解后,注解信息會被包含在文檔里。
1.4.4@Inherited:注解繼承標(biāo)記
?? 作用:指定該注解可以被子類繼承,如果父類使用了該注解,子類會自動繼承該注解。
?? 注意事項:該注解僅對類級別注解有效,對方法、屬性等注解無效。
代碼實操:元注解使用示例
import java.lang.annotation.*;
/**
* 自定義注解 + 元注解使用示例
* 該注解用于標(biāo)記類的作者信息
*/
// 該注解只能修飾類
@Target(ElementType.TYPE)
// 注解在運行時有效,可通過反射獲取
@Retention(RetentionPolicy.RUNTIME)
// 注解信息會被javadoc提取
@Documented
// 注解可以被子類繼承
@Inherited
public @interface Author {
// 注解屬性:作者姓名,必填
String name();
// 注解屬性:作者郵箱,可選,默認值為空字符串
String email() default "";
}? 核心結(jié)論:元注解是自定義注解的規(guī)則約束器,通過元注解可以精準控制自定義注解的使用范圍和生命周期。
1.5 自定義注解的開發(fā)流程
自定義注解是注解開發(fā)的核心,掌握自定義注解的定義、使用和解析流程,是實現(xiàn)注解驅(qū)動開發(fā)的關(guān)鍵。
1.5.1 步驟1:定義自定義注解
定義自定義注解的語法格式與定義接口類似,只是在 interface 關(guān)鍵字前加了一個 @ 符號。注解中可以定義屬性,屬性的定義方式與接口方法類似,支持指定默認值。
語法格式
[元注解]
public @interface 注解名稱 {
// 注解屬性定義
屬性類型 屬性名() [default 默認值];
}
代碼實操:定義兩個常用自定義注解
import java.lang.annotation.*;
/**
* 注解1:用于標(biāo)記業(yè)務(wù)日志,記錄方法的操作信息
*/
@Target(ElementType.METHOD) // 僅修飾方法
@Retention(RetentionPolicy.RUNTIME) // 運行時有效
public @interface Log {
// 操作描述
String desc();
// 操作類型,默認值為"OTHER"
String type() default "OTHER";
}
/**
* 注解2:用于標(biāo)記數(shù)據(jù)校驗,校驗方法參數(shù)的合法性
*/
@Target(ElementType.PARAMETER) // 僅修飾方法參數(shù)
@Retention(RetentionPolicy.RUNTIME) // 運行時有效
public @interface Validate {
// 參數(shù)不能為空
boolean notNull() default true;
// 參數(shù)長度最小值,默認值為0
int minLength() default 0;
}1.5.2 步驟2:使用自定義注解
定義好自定義注解后,就可以在對應(yīng)的代碼元素上使用該注解,使用時需要為注解的必填屬性賦值,可選屬性可以使用默認值。
代碼實操:使用自定義注解
/**
* 自定義注解使用示例:用戶服務(wù)類
*/
public class UserService {
// 使用@Log注解標(biāo)記方法,記錄操作日志
@Log(desc = "根據(jù)ID查詢用戶", type = "QUERY")
public String getUserById(
// 使用@Validate注解標(biāo)記參數(shù),校驗參數(shù)合法性
@Validate(notNull = true, minLength = 1) String userId) {
if (userId == null || userId.length() < 1) {
return "參數(shù)不合法";
}
return "用戶ID:" + userId + ",用戶名:張三";
}
@Log(desc = "添加用戶", type = "ADD")
public String addUser(
@Validate(notNull = true, minLength = 2) String username) {
if (username == null || username.length() < 2) {
return "用戶名長度不能小于2";
}
return "添加用戶成功:" + username;
}
}1.5.3 步驟3:解析自定義注解
注解本身不會執(zhí)行任何邏輯,必須通過注解解析器來讀取注解信息,并執(zhí)行相應(yīng)的處理邏輯。在Java中,最常用的注解解析方式是反射機制。
解析流程
① 獲取目標(biāo)類的 Class 對象。
② 獲取目標(biāo)方法或參數(shù)的注解對象。
③ 讀取注解的屬性值,執(zhí)行自定義業(yè)務(wù)邏輯。
代碼實操:反射解析自定義注解
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
/**
* 注解解析器:解析@Log和@Validate注解
*/
public class AnnotationParser {
/**
* 解析@Log注解,輸出方法操作日志
*/
public static void parseLogAnnotation(Class<?> clazz) {
// 獲取類中所有方法
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
// 判斷方法是否有@Log注解
if (method.isAnnotationPresent(Log.class)) {
Log log = method.getAnnotation(Log.class);
System.out.println("===== 方法操作日志 =====");
System.out.println("方法名:" + method.getName());
System.out.println("操作描述:" + log.desc());
System.out.println("操作類型:" + log.type());
System.out.println("-----------------------");
}
}
}
/**
* 解析@Validate注解,校驗方法參數(shù)合法性
*/
public static Object invokeAndValidate(Object obj, String methodName, Object... args) throws Exception {
// 獲取目標(biāo)方法
Method method = obj.getClass().getDeclaredMethod(methodName, getParameterTypes(args));
Parameter[] parameters = method.getParameters();
// 校驗參數(shù)
for (int i = 0; i < parameters.length; i++) {
Parameter param = parameters[i];
if (param.isAnnotationPresent(Validate.class)) {
Validate validate = param.getAnnotation(Validate.class);
Object arg = args[i];
String paramName = param.getName();
// 非空校驗
if (validate.notNull() && arg == null) {
throw new IllegalArgumentException(paramName + " 不能為null");
}
// 長度校驗
if (arg instanceof String && ((String) arg).length() < validate.minLength()) {
throw new IllegalArgumentException(paramName + " 長度不能小于" + validate.minLength());
}
}
}
// 調(diào)用目標(biāo)方法
return method.invoke(obj, args);
}
// 獲取參數(shù)類型數(shù)組
private static Class<?>[] getParameterTypes(Object... args) {
if (args == null || args.length == 0) {
return new Class[0];
}
Class<?>[] types = new Class[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
}
return types;
}
// 測試方法
public static void main(String[] args) throws Exception {
UserService userService = new UserService();
// 解析@Log注解
parseLogAnnotation(UserService.class);
// 解析@Validate注解并調(diào)用方法
System.out.println("正常調(diào)用:" + invokeAndValidate(userService, "getUserById", "1001"));
System.out.println("參數(shù)非法調(diào)用:" + invokeAndValidate(userService, "getUserById", ""));
}
}輸出結(jié)果
===== 方法操作日志 =====
方法名:getUserById
操作描述:根據(jù)ID查詢用戶
操作類型:QUERY
-----------------------
===== 方法操作日志 =====
方法名:addUser
操作描述:添加用戶
操作類型:ADD
-----------------------
正常調(diào)用:用戶ID:1001,用戶名:張三
Exception in thread "main" java.lang.IllegalArgumentException: arg0 長度不能小于1
? 核心結(jié)論:自定義注解的開發(fā)流程是 定義 → 使用 → 解析,其中解析是核心步驟,反射是最常用的解析方式。
1.6 注解的高級應(yīng)用場景
注解在Java開發(fā)中應(yīng)用廣泛,尤其是在框架開發(fā)和業(yè)務(wù)系統(tǒng)設(shè)計中,以下是幾個典型的高級應(yīng)用場景。
1.6.1 場景1:基于注解的AOP實現(xiàn)
?? 核心思想:通過注解標(biāo)記需要增強的方法,結(jié)合動態(tài)代理實現(xiàn)AOP(面向切面編程),在方法執(zhí)行前后添加通用邏輯(如日志、事務(wù)、權(quán)限校驗)。
代碼實操:注解+動態(tài)代理實現(xiàn)日志切面
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 基于注解的AOP日志切面
*/
// 定義切面注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface LogAspect {
}
// 業(yè)務(wù)接口
interface OrderService {
@LogAspect
void createOrder(String orderId);
}
// 業(yè)務(wù)實現(xiàn)類
class OrderServiceImpl implements OrderService {
@Override
public void createOrder(String orderId) {
System.out.println("創(chuàng)建訂單:" + orderId);
}
}
// 日志切面處理器
class LogAspectHandler implements InvocationHandler {
private Object target;
public LogAspectHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 方法執(zhí)行前:判斷是否有@LogAspect注解
if (method.isAnnotationPresent(LogAspect.class)) {
System.out.println("[前置日志] 方法 " + method.getName() + " 開始執(zhí)行,參數(shù):" + args[0]);
}
// 執(zhí)行目標(biāo)方法
Object result = method.invoke(target, args);
// 方法執(zhí)行后
if (method.isAnnotationPresent(LogAspect.class)) {
System.out.println("[后置日志] 方法 " + method.getName() + " 執(zhí)行完畢");
}
return result;
}
// 創(chuàng)建代理對象
public static <T> T createProxy(T target) {
return (T) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
target.getClass().getInterfaces(),
new LogAspectHandler(target)
);
}
}
// 測試類
public class AnnotationAopDemo {
public static void main(String[] args) {
OrderService orderService = new OrderServiceImpl();
OrderService proxy = LogAspectHandler.createProxy(orderService);
proxy.createOrder("ORDER_20260129");
}
}輸出結(jié)果
[前置日志] 方法 createOrder 開始執(zhí)行,參數(shù):ORDER_20260129
創(chuàng)建訂單:ORDER_20260129
[后置日志] 方法 createOrder 執(zhí)行完畢
1.6.2 場景2:基于注解的ORM映射
?? 核心思想:通過注解標(biāo)記實體類與數(shù)據(jù)庫表的映射關(guān)系,結(jié)合反射實現(xiàn)自動生成SQL語句,簡化數(shù)據(jù)庫操作。
代碼實操:注解實現(xiàn)ORM映射
import java.lang.annotation.*;
/**
* 基于注解的ORM映射示例
*/
// 表映射注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Table {
String name();
}
// 字段映射注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface Column {
String name();
String type() default "VARCHAR";
int length() default 255;
}
// 實體類
@Table(name = "t_user")
class User {
@Column(name = "user_id", type = "INT", length = 10)
private Integer userId;
@Column(name = "user_name", type = "VARCHAR", length = 50)
private String userName;
@Column(name = "age", type = "INT", length = 3)
private Integer age;
// 省略getter/setter方法
public Integer getUserId() { return userId; }
public void setUserId(Integer userId) { this.userId = userId; }
public String getUserName() { return userName; }
public void setUserName(String userName) { this.userName = userName; }
public Integer getAge() { return age; }
public void setAge(Integer age) { this.age = age; }
}
// ORM工具類:生成SQL語句
class OrmUtil {
public static String generateSelectSql(Class<?> clazz) {
// 獲取表名
Table table = clazz.getAnnotation(Table.class);
if (table == null) {
throw new IllegalArgumentException("實體類未添加@Table注解");
}
String tableName = table.name();
// 獲取字段名
StringBuilder columns = new StringBuilder();
java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
for (java.lang.reflect.Field field : fields) {
Column column = field.getAnnotation(Column.class);
if (column != null) {
columns.append(column.name()).append(",");
}
}
// 移除最后一個逗號
columns.deleteCharAt(columns.length() - 1);
// 生成查詢SQL
return "SELECT " + columns + " FROM " + tableName;
}
public static void main(String[] args) {
System.out.println("生成的查詢SQL:" + generateSelectSql(User.class));
}
}輸出結(jié)果
生成的查詢SQL:SELECT user_id,user_name,age FROM t_user
1.6.3 場景3:基于注解的單元測試
?? 核心思想:通過注解標(biāo)記測試方法,測試框架自動識別并執(zhí)行這些方法,實現(xiàn)自動化測試。這正是JUnit框架的核心實現(xiàn)原理。
代碼實操:簡化版JUnit框架
import java.lang.annotation.*;
import java.lang.reflect.Method;
/**
* 簡化版JUnit框架:基于注解的單元測試
*/
// 測試方法注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@interface TestCase {
}
// 測試類
class MathTest {
@TestCase
public void testAdd() {
int result = 1 + 2;
assert result == 3 : "加法測試失敗";
System.out.println("testAdd 測試通過");
}
@TestCase
public void testMultiply() {
int result = 2 * 3;
assert result == 6 : "乘法測試失敗";
System.out.println("testMultiply 測試通過");
}
// 非測試方法,不會被執(zhí)行
public void normalMethod() {
System.out.println("這是普通方法,不會被測試");
}
}
// 測試運行器
class TestRunner {
public static void run(Class<?> testClass) throws Exception {
Object obj = testClass.getDeclaredConstructor().newInstance();
Method[] methods = testClass.getDeclaredMethods();
int passCount = 0;
int failCount = 0;
for (Method method : methods) {
if (method.isAnnotationPresent(TestCase.class)) {
try {
method.invoke(obj);
passCount++;
} catch (Exception e) {
failCount++;
System.out.println(method.getName() + " 測試失?。? + e.getCause().getMessage());
}
}
}
System.out.println("\n===== 測試結(jié)果 ======");
System.out.println("總測試數(shù):" + (passCount + failCount));
System.out.println("通過數(shù):" + passCount);
System.out.println("失敗數(shù):" + failCount);
}
public static void main(String[] args) throws Exception {
TestRunner.run(MathTest.class);
}
}輸出結(jié)果
testAdd 測試通過
testMultiply 測試通過===== 測試結(jié)果 ======
總測試數(shù):2
通過數(shù):2
失敗數(shù):0
1.7 注解的優(yōu)缺點與最佳實踐
1.7.1 注解的優(yōu)點
- 簡化開發(fā):減少冗余配置,提高開發(fā)效率。
- 增強代碼可讀性:元數(shù)據(jù)與代碼緊密結(jié)合,一目了然。
- 提高代碼靈活性:通過注解可以動態(tài)控制程序行為,無需修改核心代碼。
- 框架友好:是現(xiàn)代Java框架的標(biāo)配,便于集成主流框架。
1.7.2 注解的缺點
- 侵入性強:注解直接嵌入代碼,修改注解需要修改源碼。
- 可讀性差:過多的注解會導(dǎo)致代碼臃腫,影響閱讀。
- 性能損耗:運行時注解需要通過反射解析,存在一定的性能開銷。
- 調(diào)試困難:注解的邏輯在運行時執(zhí)行,調(diào)試時難以跟蹤。
1.7.3 注解的最佳實踐
- 合理選擇注解生命周期:僅在需要反射解析時使用
RUNTIME,否則使用SOURCE或CLASS,減少性能損耗。 - 避免過度使用注解:不要用注解替代所有配置,對于經(jīng)常變化的配置,建議使用外部配置文件。
- 做好注解文檔:為自定義注解添加詳細的JavaDoc注釋,說明注解的作用和使用方式。
- 結(jié)合設(shè)計模式使用:如結(jié)合工廠模式、代理模式,實現(xiàn)注解的靈活解析和擴展。
- 優(yōu)先使用成熟框架的注解:如Spring的
@Autowired、@RequestMapping,避免重復(fù)造輪子。
1.8 實戰(zhàn)案例:基于注解的權(quán)限校驗框架
1.8.1 需求分析
?? 實現(xiàn)一個輕量級的權(quán)限校驗框架,要求:
- 通過注解標(biāo)記需要權(quán)限校驗的方法。
- 支持指定方法所需的權(quán)限角色。
- 在方法執(zhí)行前自動校驗用戶權(quán)限,無權(quán)限則拋出異常。
- 框架具備良好的擴展性,支持自定義權(quán)限校驗邏輯。
1.8.2 代碼實現(xiàn)
import java.lang.annotation.*;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
/**
* 基于注解的權(quán)限校驗框架
*/
// 權(quán)限校驗注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermission {
// 所需權(quán)限角色
String[] value();
}
// 用戶上下文類:存儲當(dāng)前登錄用戶信息
class UserContext {
private static ThreadLocal<String> currentUser = new ThreadLocal<>();
private static Set<String> userRoles = new HashSet<>();
// 設(shè)置當(dāng)前用戶
public static void setCurrentUser(String username, String... roles) {
currentUser.set(username);
userRoles.clear();
userRoles.addAll(Arrays.asList(roles));
}
// 獲取當(dāng)前用戶角色
public static Set<String> getUserRoles() {
return userRoles;
}
// 清除當(dāng)前用戶
public static void clear() {
currentUser.remove();
userRoles.clear();
}
}
// 權(quán)限校驗切面類
class PermissionAspect {
/**
* 執(zhí)行方法并校驗權(quán)限
*/
public static Object invoke(Object obj, String methodName, Object... args) throws Exception {
Method method = obj.getClass().getDeclaredMethod(methodName, getParameterTypes(args));
// 權(quán)限校驗
if (method.isAnnotationPresent(RequiresPermission.class)) {
RequiresPermission annotation = method.getAnnotation(RequiresPermission.class);
String[] requiredRoles = annotation.value();
Set<String> userRoles = UserContext.getUserRoles();
// 檢查是否有匹配的角色
boolean hasPermission = false;
for (String role : requiredRoles) {
if (userRoles.contains(role)) {
hasPermission = true;
break;
}
}
if (!hasPermission) {
throw new SecurityException("無權(quán)限訪問該方法,所需角色:" + Arrays.toString(requiredRoles));
}
}
// 執(zhí)行目標(biāo)方法
return method.invoke(obj, args);
}
private static Class<?>[] getParameterTypes(Object... args) {
if (args == null || args.length == 0) {
return new Class[0];
}
Class<?>[] types = new Class[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i].getClass();
}
return types;
}
}
// 業(yè)務(wù)服務(wù)類
class AdminService {
@RequiresPermission({"ADMIN", "SUPER_ADMIN"})
public String deleteUser(String userId) {
return "刪除用戶成功:" + userId;
}
@RequiresPermission({"USER", "ADMIN", "SUPER_ADMIN"})
public String queryUser(String userId) {
return "查詢用戶信息:" + userId;
}
}
// 測試類
public class PermissionFrameworkDemo {
public static void main(String[] args) throws Exception {
AdminService adminService = new AdminService();
// 測試1:管理員用戶,擁有ADMIN角色
UserContext.setCurrentUser("admin", "ADMIN");
System.out.println(PermissionAspect.invoke(adminService, "deleteUser", "1001"));
System.out.println(PermissionAspect.invoke(adminService, "queryUser", "1001"));
// 測試2:普通用戶,只有USER角色
UserContext.setCurrentUser("user", "USER");
System.out.println(PermissionAspect.invoke(adminService, "queryUser", "1001"));
try {
PermissionAspect.invoke(adminService, "deleteUser", "1001");
} catch (Exception e) {
System.out.println(e.getCause().getMessage());
}
UserContext.clear();
}
}輸出結(jié)果
刪除用戶成功:1001
查詢用戶信息:1001
查詢用戶信息:1001
無權(quán)限訪問該方法,所需角色:[ADMIN, SUPER_ADMIN]
1.8.3 案例總結(jié)
? 這個權(quán)限校驗框架綜合運用了注解定義、反射解析、線程本地存儲等技術(shù),核心亮點如下:
- 使用
@RequiresPermission注解標(biāo)記需要權(quán)限校驗的方法,配置簡單。 - 通過
UserContext存儲當(dāng)前用戶角色,支持多線程環(huán)境。 - 利用反射解析注解,在方法執(zhí)行前自動校驗權(quán)限,無侵入性。
- 框架擴展性強,可通過修改
PermissionAspect類實現(xiàn)自定義權(quán)限校驗邏輯。
1.9 本章總結(jié)
- 注解是Java的一種元數(shù)據(jù)標(biāo)記,本身不執(zhí)行邏輯,需要通過解析器(反射、APT)處理后才能發(fā)揮作用。
- JDK內(nèi)置注解用于代碼檢查,元注解用于約束自定義注解,自定義注解是業(yè)務(wù)開發(fā)的核心。
- 自定義注解的開發(fā)流程是 定義 → 使用 → 解析,反射是最常用的運行時解析方式。
- 注解是框架開發(fā)的基石,廣泛應(yīng)用于AOP、ORM、權(quán)限校驗、單元測試等場景。
- 注解的優(yōu)點是簡化配置、增強可讀性,缺點是侵入性強、有性能損耗,使用時需平衡利弊。
- 注解的最佳實踐是“合理選擇生命周期、避免過度使用、做好文檔注釋”,結(jié)合設(shè)計模式提升擴展性。
到此這篇關(guān)于JAVA 注解Annotation開發(fā)流程從原理到實戰(zhàn)應(yīng)用的文章就介紹到這了,更多相關(guān)java注解annotation內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java DataInputStream和DataOutputStream詳解及實例代碼
這篇文章主要介紹了java DataInputStream和DataOutputStream詳解及實例代碼的相關(guān)資料,需要的朋友可以參考下2017-01-01
SpringBoot2.x 整合 AntiSamy防御XSS攻擊的簡單總結(jié)
本文主要對SpringBoot2.x集成AntiSamy防御XSS攻擊進行簡單總結(jié),其中SpringBoot使用的2.4.5版本,通過示例代碼給大家介紹的非常詳細,需要的朋友參考下吧2021-08-08
Kafka中的producer攔截器與consumer攔截器詳解
這篇文章主要介紹了Kafka中的producer攔截器與consumer攔截器詳解,Producer 的Interceptor使得用戶在消息發(fā)送前以及Producer回調(diào)邏輯前有機會對消息做 一些定制化需求,比如修改消息等,需要的朋友可以參考下2023-12-12
SpringBoot3應(yīng)用中集成和使用Spring Retry的實踐記錄
SpringRetry為SpringBoot3提供重試機制,支持注解和編程式兩種方式,可配置重試策略與監(jiān)聽器,適用于臨時性故障場景,需合理設(shè)置次數(shù)、退避策略并做好監(jiān)控,本文給大家詳細介紹如何在 SpringBoot 3 應(yīng)用中集成和使用 Spring Retry,感興趣的朋友一起看看吧2025-06-06
獲取Spring的上下文環(huán)境ApplicationContext的最簡單方式
這篇文章主要介紹了獲取Spring的上下文環(huán)境ApplicationContext的最簡單方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08
IDEA中使用Gradle構(gòu)建項目中文報GBK錯誤的解決方案
這篇文章主要介紹了IDEA中使用Gradle構(gòu)建項目中文報GBK錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2025-04-04

