Java注解及其底层原理解析2

电子说

1.2w人已加入

描述

最后我们写一个测试类

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<Classextends Annotation>, Annotation> declaredAnnotations =
            AnnotationParser.parseAnnotations(getRawAnnotations(), getConstantPool(), this);

parseAnnotations()去 分析注解 ,其第一个参数是 获取原始注解,第二个参数是获取常量池内容

public static Annotation annotationForMap(final Classextends 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:

spring

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(Classextends 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

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分