刨根问底,Java注解的底层实现

大家有没有过这样的疑惑,我们只需要定义一个这样的注解,然后以方法的形式定义属性,然后我们在其他的类中使用注解,就可以获取标签中的值,那这是为什么呢。
通过如下代码我们获取到了blog类author属性上的注解的值。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface PersonInfoAnnotation {

    public String name();

    public int age();

    public String gender();

    public String[] language();
}

public class Blog {

    @PersonInfoAnnotation(name = "liyl", age = 24, gender = "男", language = {"java", "Go"})
    private String author;
}

public class AnnotationTest {

    public static void main(String[] args) {
        Blog blog = new Blog();
        try {
            parseFieldAnnotation("com.liyl.study.java.annotation.Blog");
        } catch(ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    public static void parseFieldAnnotation(String classFullName) throws ClassNotFoundException {
        Class clazz = Class.forName(classFullName);
        //这里获取的是class对象的注解,而不是其里面的方法和成员变量的注解
        Field[] declaredFields = clazz.getDeclaredFields();
        for(Field field : declaredFields) {
            boolean has = field.isAnnotationPresent(PersonInfoAnnotation.class);
            if(has) {
                PersonInfoAnnotation personInfoAnnotation = field.getAnnotation(PersonInfoAnnotation.class);
                System.out.println("名字:"+personInfoAnnotation.name()+"\n" +
                        "年龄:"+personInfoAnnotation.age()+"\n" +
                        "性别:"+personInfoAnnotation.gender()+"\n");
                for(String language:personInfoAnnotation.language()){
                    System.out.println("课程名:"+language);
                }

            }
        }
    }
}

反编译,揭开注解真面目


我们先看看注解编译文件中是什么样的

如图,我们在类路径下先javac进行编译,然后javap反编译查看,发现我们自定义的注解是继承自annotation接口的一个接口。

那么我们是如何从一个接口上拿到定义的属性值的呢。
是因为JVM在运行时生成了中间对象,我们可以通过一些途径查看这些中间对象。

在main方法中首行加入该代码,就会保存jvm运行时生成的代理类如图:
System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");


代理类.png

看到这个代理类熟悉jdk动态代理应该不会陌生,这正是jdk在运行时为注解生成了代理类。
我们打开Proxy1,发现有8个static Method 属性,然后我们在最下面看他们的实例,是不是感觉很熟悉。
除了继承自Object的toString equals 和 haseCode方法,其余的就是我们 定义的属性方法了。


代理类属性.png
属性实例.png

然后我们找其中一个属性,看看他的值怎么获取,在代理类中往上翻一翻页面,发现是调用了super的h的invoke方法。


invoke.png

那super的h是什么呢,我们点进去看看。正是InvocationHandler,如果你熟悉jdk动态代理的话,那看到这个你一定会很亲切,而且应该有一丝丝的明白了吧。


Proxy.png

InvocationHandler的实现类

那么还有一个问题,InvocationHandler本身也是一个interface,那么它的实现类是什么呢。我们可以在Proxy的构造函数打一个断点,看看在对h赋值时它到底是什么。是AnnotationInvocationHandler


微信截图_20200719221056.png

我们去查看AnnotationInvocationHandler的invoke方法的实现,所有注解中定义的属性值都通过memberValues得到。key是注解定义的属性方法名,value就是我们使用注解时给的值。


1.png
2.png

由此可知,这一切都是在jvm运行时使用了动态代理的机制来为我们获取注解的值,加载类时会扫描注解,将注解信息写入元素属性表,并在运行时取出这些值存入map,然后实例化AnnotationInvocationHandler,将map引用传递给AnnotationInvocationHandler。

这也是为什么注解的属性要以方法的形式定义,而且不能是private的,因为要用到反射和动态代理。
因为Class的getMethods()方法只能得到所有public的方法包括父类的public方法。如果注解中定义了private那肯定是要报错的。

你可能感兴趣的:(刨根问底,Java注解的底层实现)