最后我们写一个测试类
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文章
2967浏览量
104744 -
spring
+关注
关注
0文章
340浏览量
14341 -
注解
+关注
关注
0文章
18浏览量
2674
发布评论请先 登录
相关推荐
评论