最后我們寫一個測試類
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());
}
}
結果:
this person class this person field private this person field public this person method
我們通過反射讀取api時,一般會先去校驗這個注解存不存在:
if(c1.isAnnotationPresent(MyAnnotation.class)) {
//存在 MyAnnotation 注解
}else {
//不存在 MyAnnotation 注解
}
我們發現反射真的很強大,不僅可以讀取類的屬性、方法、構造器等信息,還可以讀取類的注解相關信息。
那反射是如何實現工作的?
我們來看下源碼:
從 c1.getAnnotation(MyAnnotation.class);
通過idea點進去查看源碼,把重點的給貼出來,其他的就省略了
Map<Class extends Annotation>, Annotation> declaredAnnotations =
AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);
parseAnnotations()去 分析注解 ,其第一個參數是 獲取原始注解,第二個參數是獲取常量池內容
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)
創建動態代理,此處var0參數是由常量池獲取的數據轉換而來。
我們監聽此處的var0:
image-20220616225518195
可以推斷出注解相關的信息 是存放在常量池中的
我們來總結一下,反射調用getAnnotations(MyAnnotation.class)
方法的背后主要操作:
解析注解parseAnnotations()的時候 從該注解類的常量池中取出注解相關的信息,將其轉換格式后,通過newProxyInstance(注解的類加載器,注解的class實例 ,AnotationInvocationHandler實例)
來創建代理對象,作為參數傳進去,最后返回一個代理實例。
其中AnotationInvocationHandler類是一個典型的動態代理類, 這邊先挖個坑,暫不展開,不然這篇文章是寫不完了
關于動態代理類我們只需先知道: 對象的執行方法,交給代理來負責
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) {
...
//調用委托類對象的方法,具體等等一些操作
}
...
}
反射調用getAnnotations(MyAnnotation.class)
,返回一個代理實例,我們可以通過這個實例來操作該注解
示例:注解 模擬訪問權限控制
當我們引入springsecurity來做安全框架,然后只需添加@PreAuthorize**(**"hasRole('Admin')"**)**
注解,就能實現權限的控制,簡簡單單地一行代碼,就優雅地實現了權限控制,覺不覺得很神奇?讓我們一起模擬一個出來吧
@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 {
//模擬 用戶的權限
String role = "User";
//模擬 需要的權限
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("騷瑞,你無權訪問該資源");
}
//訪問資源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("騷瑞,你無權訪問該資源");
}
}
}
結果:
騷瑞,你無權訪問該資源 資源B
尾語
注解 是一種 標記、標簽 來修飾代碼, 但它不是代碼本身的一部分,即 注解本身對代碼邏輯沒有任何影響 ,如何使用注解完全取決于我們開發者用Java反射來讀取和使用。
我們發現反射真的很強大,不僅可以讀取類的屬性、方法、構造器等信息,還可以讀取類的注解相關信息,以后還會經常遇到它。
注解一般用于
- 編譯器可以利用注解來探測錯誤和檢查信息,像
@override
檢查是否重寫 - 適合工具類型的軟件用的,避免繁瑣的代碼,生成代碼配置,比如jpa自動生成sql,日志注解,權限控制
- 程序運行時的處理:某些注解可以在程序運行的時候接受代碼的讀取,比如我們可以自定義注解
平時我們只知道如何使用注解,卻不知道其是如何起作用的,理所當然的往往是我們所忽視的。
參考資料:
《Java核心技術 卷一》
https://blog.csdn.net/qq_20009015/article/details/106038023
https://zhuanlan.zhihu.com/p/258429599
-
JAVA
+關注
關注
19文章
2966瀏覽量
104702 -
spring
+關注
關注
0文章
340瀏覽量
14338 -
注解
+關注
關注
0文章
18瀏覽量
2672
發布評論請先 登錄
相關推薦
評論