最后我們寫一個(gè)測(cè)試類
public class TestAn {
public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
//獲取Person class 實(shí)例
Class c1 = Person.class;
//反射獲取 類上的注解
MyAnnotation classAnnotation = c1.getAnnotation(MyAnnotation.class);
System.out.println(classAnnotation.msg());
//反射獲取 private屬性上的注解
Field we = c1.getDeclaredField("weight");
MyAnnotation fieldAnnotation = we.getAnnotation(MyAnnotation.class);
System.out.println(fieldAnnotation.msg());
//反射獲取 public屬性上的注解
Field he = c1.getDeclaredField("height");
MyAnnotation field2Annotation = he.getAnnotation(MyAnnotation.class);
System.out.println(field2Annotation.msg());
//反射獲取 方法上的注解
Method me = c1.getMethod("dance",null);
MyAnnotation methodAnnotation = me.getAnnotation(MyAnnotation.class);
System.out.println(methodAnnotation.msg());
}
}
結(jié)果:
this person class this person field private this person field public this person method
我們通過(guò)反射讀取api時(shí),一般會(huì)先去校驗(yàn)這個(gè)注解存不存在:
if(c1.isAnnotationPresent(MyAnnotation.class)) {
//存在 MyAnnotation 注解
}else {
//不存在 MyAnnotation 注解
}
我們發(fā)現(xiàn)反射真的很強(qiáng)大,不僅可以讀取類的屬性、方法、構(gòu)造器等信息,還可以讀取類的注解相關(guān)信息。
那反射是如何實(shí)現(xiàn)工作的?
我們來(lái)看下源碼:
從 c1.getAnnotation(MyAnnotation.class);
通過(guò)idea點(diǎn)進(jìn)去查看源碼,把重點(diǎn)的給貼出來(lái),其他的就省略了
Map<Class extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
parseAnnotations()去 分析注解 ,其第一個(gè)參數(shù)是 獲取原始注解,第二個(gè)參數(shù)是獲取常量池內(nèi)容
public static Annotation annotationForMap(final Class extends Annotation> var0, final Map<String, Object> var1) {
return (Annotation)AccessController.doPrivileged(new PrivilegedAction() {
public Annotation run() {
return (Annotation)Proxy.newProxyInstance(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1));
}
});
}
Proxy._newProxyInstance_(var0.getClassLoader(), new Class[]{var0}, new AnnotationInvocationHandler(var0, var1)
創(chuàng)建動(dòng)態(tài)代理,此處var0參數(shù)是由常量池獲取的數(shù)據(jù)轉(zhuǎn)換而來(lái)。
我們監(jiān)聽此處的var0:
image-20220616225518195
可以推斷出注解相關(guān)的信息 是存放在常量池中的
我們來(lái)總結(jié)一下,反射調(diào)用getAnnotations(MyAnnotation.class)
方法的背后主要操作:
解析注解parseAnnotations()的時(shí)候 從該注解類的常量池中取出注解相關(guān)的信息,將其轉(zhuǎn)換格式后,通過(guò)newProxyInstance(注解的類加載器,注解的class實(shí)例 ,AnotationInvocationHandler實(shí)例)
來(lái)創(chuàng)建代理對(duì)象,作為參數(shù)傳進(jìn)去,最后返回一個(gè)代理實(shí)例。
其中AnotationInvocationHandler類是一個(gè)典型的動(dòng)態(tài)代理類, 這邊先挖個(gè)坑,暫不展開,不然這篇文章是寫不完了
關(guān)于動(dòng)態(tài)代理類我們只需先知道: 對(duì)象的執(zhí)行方法,交給代理來(lái)負(fù)責(zé)
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
...
private final Map<String, Object> memberValues;//存放該注解所有屬性的值
private transient volatile Method[] memberMethods = null;
AnnotationInvocationHandler(Class extends Annotation> var1, Map<String, Object> var2) {
...
}
public Object invoke(Object var1, Method var2, Object[] var3) {
...
//調(diào)用委托類對(duì)象的方法,具體等等一些操作
}
...
}
反射調(diào)用getAnnotations(MyAnnotation.class)
,返回一個(gè)代理實(shí)例,我們可以通過(guò)這個(gè)實(shí)例來(lái)操作該注解
示例:注解 模擬訪問(wèn)權(quán)限控制
當(dāng)我們引入springsecurity來(lái)做安全框架,然后只需添加@PreAuthorize**(**"hasRole('Admin')"**)**
注解,就能實(shí)現(xiàn)權(quán)限的控制,簡(jiǎn)簡(jiǎn)單單地一行代碼,就優(yōu)雅地實(shí)現(xiàn)了權(quán)限控制,覺(jué)不覺(jué)得很神奇?讓我們一起模擬一個(gè)出來(lái)吧
@Retention(RetentionPolicy.RUNTIME)
public @interface MyPreVer {
String value() default "no role";
}
public class ResourceLogin {
private String name;
@MyPreVer(value = "User")
private void rsA() {
System.out.println("資源A");
}
@MyPreVer(value = "Admin")
private void rsB() {
System.out.println("資源B");
}
}
public class TestLogin {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
//模擬 用戶的權(quán)限
String role = "User";
//模擬 需要的權(quán)限
final String RoleNeeded = "Admin";
//獲取Class實(shí)例
Class c1 = ResourceLogin.class;
//訪問(wèn)資源A
Method meA = c1.getDeclaredMethod("rsA",null);
MyPreVer meAPre = meA.getDeclaredAnnotation(MyPreVer.class);
if(meAPre.value().equals(RoleNeeded)) {//模擬攔截器
meA.setAccessible(true);
meA.invoke(c1.newInstance(),null);//模擬訪問(wèn)資源
}else {
System.out.println("騷瑞,你無(wú)權(quán)訪問(wèn)該資源");
}
//訪問(wèn)資源B
Method meB = c1.getDeclaredMethod("rsB",null);
MyPreVer meBPre = meB.getDeclaredAnnotation(MyPreVer.class);
if(meBPre.value().equals(RoleNeeded)) {//模擬攔截器
meB.setAccessible(true);
meB.invoke(c1.newInstance());//模擬訪問(wèn)資源
}else {
System.out.println("騷瑞,你無(wú)權(quán)訪問(wèn)該資源");
}
}
}
結(jié)果:
騷瑞,你無(wú)權(quán)訪問(wèn)該資源 資源B
尾語(yǔ)
注解 是一種 標(biāo)記、標(biāo)簽 來(lái)修飾代碼, 但它不是代碼本身的一部分,即 注解本身對(duì)代碼邏輯沒(méi)有任何影響 ,如何使用注解完全取決于我們開發(fā)者用Java反射來(lái)讀取和使用。
我們發(fā)現(xiàn)反射真的很強(qiáng)大,不僅可以讀取類的屬性、方法、構(gòu)造器等信息,還可以讀取類的注解相關(guān)信息,以后還會(huì)經(jīng)常遇到它。
注解一般用于
- 編譯器可以利用注解來(lái)探測(cè)錯(cuò)誤和檢查信息,像
@override
檢查是否重寫 - 適合工具類型的軟件用的,避免繁瑣的代碼,生成代碼配置,比如jpa自動(dòng)生成sql,日志注解,權(quán)限控制
- 程序運(yùn)行時(shí)的處理:某些注解可以在程序運(yùn)行的時(shí)候接受代碼的讀取,比如我們可以自定義注解
平時(shí)我們只知道如何使用注解,卻不知道其是如何起作用的,理所當(dāng)然的往往是我們所忽視的。
參考資料:
《Java核心技術(shù) 卷一》
https://blog.csdn.net/qq_20009015/article/details/106038023
https://zhuanlan.zhihu.com/p/258429599
-
JAVA
+關(guān)注
關(guān)注
20文章
2987瀏覽量
108184 -
spring
+關(guān)注
關(guān)注
0文章
340瀏覽量
14965 -
注解
+關(guān)注
關(guān)注
0文章
18瀏覽量
2755
發(fā)布評(píng)論請(qǐng)先 登錄
如何通過(guò)注解來(lái)優(yōu)化我們的Java代碼
java經(jīng)典面試題深度解析
詳細(xì)介紹了Java泛型、注解、并發(fā)編程
HarmonyOS注解的使用方法分享
微信ANR原理解析與解決方案

分析java注解基本概念
Java底層實(shí)現(xiàn),CPU還有10個(gè)術(shù)語(yǔ)!
注解定義Bean及開發(fā)
Java注解及其底層原理解析 1

容器配置及Spring Boot注解

SpringBoot的核心注解2

JAVA中注解是怎么做到的(上)
JAVA中注解是怎么做到的(下)

3分鐘純Java注解搭個(gè)管理系統(tǒng)

評(píng)論