最后我們寫一個測試類
public class TestAn {
    public static void main(String[] args) throws NoSuchFieldException, NoSuchMethodException {
        //獲取Person class 實例
        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
我們通過反射讀取api時,一般會先去校驗這個注解存不存在:
if(c1.isAnnotationPresent(MyAnnotation.class)) {
    //存在 MyAnnotation 注解
}else {
    //不存在 MyAnnotation 注解
}
我們發(fā)現(xiàn)反射真的很強(qiáng)大,不僅可以讀取類的屬性、方法、構(gòu)造器等信息,還可以讀取類的注解相關(guān)信息。
那反射是如何實現(xiàn)工作的?
我們來看下源碼:
從 c1.getAnnotation(MyAnnotation.class);通過idea點進(jìn)去查看源碼,把重點的給貼出來,其他的就省略了
Map<Class extends Annotation>, Annotation> declaredAnnotations =
            AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
parseAnnotations()去 分析注解 ,其第一個參數(shù)是 獲取原始注解,第二個參數(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)建動態(tài)代理,此處var0參數(shù)是由常量池獲取的數(shù)據(jù)轉(zhuǎn)換而來。
我們監(jiān)聽此處的var0:

image-20220616225518195
可以推斷出注解相關(guān)的信息 是存放在常量池中的
我們來總結(jié)一下,反射調(diào)用getAnnotations(MyAnnotation.class)方法的背后主要操作:
解析注解parseAnnotations()的時候 從該注解類的常量池中取出注解相關(guān)的信息,將其轉(zhuǎn)換格式后,通過newProxyInstance(注解的類加載器,注解的class實例 ,AnotationInvocationHandler實例)來創(chuàng)建代理對象,作為參數(shù)傳進(jìn)去,最后返回一個代理實例。
其中AnotationInvocationHandler類是一個典型的動態(tài)代理類, 這邊先挖個坑,暫不展開,不然這篇文章是寫不完了
關(guān)于動態(tài)代理類我們只需先知道: 對象的執(zhí)行方法,交給代理來負(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)用委托類對象的方法,具體等等一些操作
    }
    ...
}
反射調(diào)用getAnnotations(MyAnnotation.class),返回一個代理實例,我們可以通過這個實例來操作該注解
示例:注解 模擬訪問權(quán)限控制
當(dāng)我們引入springsecurity來做安全框架,然后只需添加@PreAuthorize**(**"hasRole('Admin')"**)**注解,就能實現(xiàn)權(quán)限的控制,簡簡單單地一行代碼,就優(yōu)雅地實現(xiàn)了權(quán)限控制,覺不覺得很神奇?讓我們一起模擬一個出來吧
@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實例
        Class c1 = ResourceLogin.class;
        //訪問資源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);//模擬訪問資源
        }else {
            System.out.println("騷瑞,你無權(quá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());//模擬訪問資源
        }else {
            System.out.println("騷瑞,你無權(quán)訪問該資源");
        }
    }
}
 
結(jié)果:
騷瑞,你無權(quán)訪問該資源 資源B
尾語
注解 是一種 標(biāo)記、標(biāo)簽 來修飾代碼, 但它不是代碼本身的一部分,即 注解本身對代碼邏輯沒有任何影響 ,如何使用注解完全取決于我們開發(fā)者用Java反射來讀取和使用。
我們發(fā)現(xiàn)反射真的很強(qiáng)大,不僅可以讀取類的屬性、方法、構(gòu)造器等信息,還可以讀取類的注解相關(guān)信息,以后還會經(jīng)常遇到它。
注解一般用于
- 編譯器可以利用注解來探測錯誤和檢查信息,像
@override檢查是否重寫 - 適合工具類型的軟件用的,避免繁瑣的代碼,生成代碼配置,比如jpa自動生成sql,日志注解,權(quán)限控制
 - 程序運行時的處理:某些注解可以在程序運行的時候接受代碼的讀取,比如我們可以自定義注解
 
平時我們只知道如何使用注解,卻不知道其是如何起作用的,理所當(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文章
2994瀏覽量
115362 - 
                                spring
                                +關(guān)注
關(guān)注
0文章
341瀏覽量
15696 - 
                                注解
                                +關(guān)注
關(guān)注
0文章
18瀏覽量
2823 
發(fā)布評論請先 登錄
如何通過注解來優(yōu)化我們的Java代碼
java經(jīng)典面試題深度解析
詳細(xì)介紹了Java泛型、注解、并發(fā)編程
HarmonyOS注解的使用方法分享
微信ANR原理解析與解決方案
    
分析java注解基本概念
注解定義Bean及開發(fā)
Java注解及其底層原理解析 1
    
SpringBoot的核心注解2
    
JAVA中注解是怎么做到的(上)
JAVA中注解是怎么做到的(下)
    
3分鐘純Java注解搭個管理系統(tǒng)
    
          
        
        
Java注解及其底層原理解析2
                
 
    
           
            
            
                
            
評論