java--最全注解解析


github地址:

https://github.com/a18792721831/studyjdk.git

model:

image-20200816173009379

先来一张图总览:

image-20200814181610326

注解

Annotation(注解)是 Java 提供的一种对元程序中元素关联信息和元数据(metadata)的途径和方法。Annatation(注解)是一个接口,程序可以通过反射来获取定程序中元素的 Annotation对象,然后通过该 Annotation 对象来获取注解中的元数据信息。

标准注解

  • @Override:表示当前的方法定义将覆盖超类中的方法。如果子类方法拼写错误,或者方法签名对不上被覆盖的方法,编译器就会发出错误提示。
  • @Deprecated:如果使用了注解为它的元素,编译器会发出警告信息。
  • @SuppressWWarnings:关闭不当的编译器警告信息。

元注解

元注解的作用是负责注解其他注解。 Java5.0 定义了 4 个标准的 meta-annotation 类型,它们被用来提供对其它 annotation 类型作说明。

Target 范围

@Target说明了Annotation所修饰的对象范围: Annotation可被用于 packages、types(类、接口、枚举、Annotation 类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch 参数)。在 Annotation 类型的声明中使用了 target 可更加明晰其修饰的目标。

image-20200814183336033

Retention 声明周期

Retention 定义了该 Annotation 被保留的时间长短:表示需要在什么级别保存注解信息,用于描述注解的生命周期(即:被描述的注解在什么范围内有效),取值(RetentionPoicy)由:

  • SOURCE:在源文件中有效(即源文件保留)
  • CLASS:在 class 文件中有效(即 class 保留)
  • RUNTIME:在运行时有效(即运行时保留)

Documented 说明文档

@ Documented 用于描述其它类型的 annotation 应该被作为被标注的程序成员的公共 API,因此可以被例如 javadoc 此类的工具文档化。

Inherited 传播

@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的 annotation 类型被用于一个 class,则这个 annotation 将被用于该class 的子类。

注解元素

  • 所有的基本类型
  • String
  • Class
  • enum
  • Annotation

注意,注解中不允许使用任何包装类型。 但是因为自动拆装箱的存在,这个基本上不是问题,只是需要注意,定义时,定义为基本类型就行。

image-20200814192032917

注解元素的值

注解元素要么有值,要么有默认值。

换句话说,只要定义了注解元素,那么在使用时,就不能使注解元素空着。

image-20200814192016545

注解元素的默认名称

一般来说 ,注解元素的值在指定的时候都必须指明是哪个注解元素。

唯一例外:注解元素的名称是value,而且本次使用只指定这一个注解元素(其他注解元素有默认值,或者只有一个注解元素)。

image-20200814191940058

image-20200814191957716

注解不支持继承

不能使用关键字extends来继承某个@interface。不过,注解元素可以是注解,可以使用注解嵌套的方式,复用已有的注解。

但是这样会大大的增加注解使用的难度。

注解解析器

注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。

最后根据注解元素的值,做不同的操作。

实例–定义注解

注解

@Target(ElementType.ANNOTATION_TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "this")
public @interface StudyAnno {

    public String name() default "studyAnno";

}

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@StudyAnno(name = "studyClass")
@StudyClass(name = "this")
public @interface StudyClass {
    
    

    public String name() default "studyClass";

}

构造方法

@Target(ElementType.CONSTRUCTOR)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyCons")
@StudyClass(name = "studyCons")
public @interface StudyCons {
    
    

    @StudyMethod
    public String name() default "studyCons";

}

属性

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyField")
@StudyClass(name = "studyField")
public @interface StudyField {
    
    

    @StudyMethod(name = "studyField")
    public String name() default "studyField";

}

方法

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyMethod")
@StudyClass(name = "studyMethod")
public @interface StudyMethod {
    
    

    @StudyMethod(name = "name")
    public String name() default "studyMethod";

}

参数

@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyParam")
@StudyClass(name = "studyParam")
public @interface StudyParam {
    
    

    @StudyMethod(name = "studyParam")
    public String value() default "";

}

变量

@Target(ElementType.LOCAL_VARIABLE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@StudyAnno(name = "studyVar")
@StudyClass(name = "studyVar")
public @interface StudyVar {
    
    

    @StudyMethod(name = "studyVar")
    public String name() default "studyVar";

}

实例–解析注解

创建普通类,也就是使用注解的类

image-20200814201347696

image-20200814201409219

这两个普通的类有这样的一个特点:

子类没有使用任何注解

这个主要是验证注解的继承特性。

这是注解和类的一些描述类型的关系:

image-20200816151529329

可以看到最顶层的是AnnotatedElelment接口。

注解解析器的处理流程都相同:注解解析器说白了就是使用反射,获取对象的注解,然后通过反射得到的注解对象,执行注解元素的方法,最终获取注解元素的值。

所以,创建一个公共的方法,这个公共的方法,就是处理的是AnnotatedElelment的对象。

    /**
     * 注解解析
     *
     * @param annotatedElement 需要处理的AnnotatedElement对象
     * @param annClazz         需要处理的注解类型
     */
    private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
    
    
        // 获取使用注解的对象上面的注解 (StudyClass对象)
        Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
        // 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
        Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
        // 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
        Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
    
    
            try {
    
    
                // 获取注解对象中的注解元素 (name方法)
                Method name = x.getClass().getDeclaredMethod("name", null);
                // 执行注解元素,获取注解元素值 ("people")
                Object nameValue = name.invoke(x, null);
                // 获取到注解元素值之后做的操作
                System.out.println(annClazz.getSimpleName() + " , name = " + nameValue);
            } catch (NoSuchMethodException e) {
    
    
                e.printStackTrace();
            } catch (IllegalAccessException e) {
    
    
                e.printStackTrace();
            } catch (InvocationTargetException e) {
    
    
                e.printStackTrace();
            }
        });
    }

这个方法是注解解析的核心,我们创建了这个公共的方法后,只需要传入需要解析的对象即可:类、注解、构造、属性、方法、参数等等。

为了方便在控制台观察,我们增加一个日志方法,日志方法是注解解析方法的前置方法。但是这两个方法同时可用:

    /**
     * 记录日志
     *
     * @param annotatedElement
     * @param annClass
     * @param objClass
     */
    private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
    
    
        System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
        parse(annotatedElement, annClass);
    }

日志方法打印一些友好查看的信息,然后转调核心解析方法。

注解

    /**
     * @param object 普通对象
     */
    private static void parseAnno(Object object) {
    
    
        // 获取普通的所有的注解
        Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
    
    
            // 获取注解的注解对象
            // 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
            // 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
            // 将含有注解信息的代理对象进行解析
            parse(x.annotationType(), StudyAnno.class, object.getClass());
        });
    }

解析注解需要注意,传入核心解析的对象应该是注解的注解对象。

在主方法调用:

    public static void main(String[] args) {
    
    

        People people = new People("xiaomeiS");
        Man man = new Man("hah");
        // getDeclaredAnnotations:获取直接标注在指定对象上面的注解
        // getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
        parseAnno(people);
        parseAnno(man);
    }

输出如下:

image-20200816154513928

    /**
     * @param object 普通对象
     */
    private static void parseClass(Object object) {
    
    
        // 获取普通对象的class对象
        parse(object.getClass(), StudyClass.class, object.getClass());
    }

执行结果

image-20200814201316556

构造方法

    /**
     * 解析构造
     *
     * @param object
     */
    private static void parseCons(Object object) {
    
    
        // 获取普通对象的Constructor对象
        Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
    }

执行结果

image-20200816160549734

属性

    /**
     * 解析属性
     *
     * @param object
     */
    private static void parseField(Object object) {
    
    
        // 获取对象的全部属性对象
        Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> {
    
    
            parse(x, StudyField.class, object.getClass());
        });
    }

执行结果:

image-20200816154719072

因为People里面的属性都是private的,Man继承于People,但是私有属性没有被继承。

我们在People中增加其他三个权限的属性:

image-20200816155007764

然后重新执行解析:

image-20200816155054704

方法

    /**
     * 解析方法
     *
     * @param object
     */
    private static void parseMethod(Object object) {
    
    
        // 获取对象的全部方法
        Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> {
    
    
            parse(x, StudyMethod.class, object.getClass());
        });
    }

执行结果:

image-20200816155640659

虽然Peopl的方法是公有的,Man继承于People,那么,Man也能够调用公有方法,但是StudyMenthod注解是不可被继承的。

换句话说:因为StudyMenthod没有使用Inherited修饰,所以Man从People继承了方法,但是没有继承得到注解。

image-20200816160121607

image-20200816160146127

参数

在People中,只有构造方法有参数,而且我们只在这一个地方用到了StudyParam注解。所以,我们应该首先得到构造方法,然后在获取参数对象。

    /**
     * 解析参数
     *
     * @param object
     */
    private static void parseParam(Object object) {
    
    
        // 获取普通对象的构造器、方法(全部可以有参数的方法)
        Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
        Method[] declaredMethods = object.getClass().getDeclaredMethods();
        Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
    
    
            // 获取参数
            Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
        });
        Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
    
    
            // 获取参数
            Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
        });
    }

执行结果

image-20200816161602497

纳尼,竟然异常了。嗯嗯。

看看异常堆栈吧,是没有找到方法的异常。

为什么呢?

调试一次吧:

image-20200816161750377

原来我们在注解中定义的方法名称不是name了,而是value了。

但是在代码中直接写死使用name,所以就异常了。

所以这里我们获取注解对象中全部的方法,因为注解中的方法都是没有参数的,所以获取全部的方法,过滤掉有参数的方法。

==还有一点,因为获取到的注解对象是代理对象,java中对象都继承了Object,那么就同时继承了Object的公共方法,同时还有annotationType方法。==这个方法是调试的时候发现的,可能和jdk代理有关吧。

image-20200816164309128

然后我们将注解中的方法名,也就是注解参数名也输出:

image-20200816164720257

再次执行

image-20200816164759461

这种方式,对于原来的name也是支持的:

image-20200816164853000

=前面的就是注解的元素的名字,= 后面就是注解元素的值。
(吐槽一句:高亮显示=,结果因为高亮的markdown的表示是= = ,结果直接显示5个 =?)

同样因为StudyParam注解没有被Inherited修饰,所以Man中不会继承有这个注解。

变量

因为放在变量上面的注解,在类编译的过程中,局部变量就会被编译器处理,所以,局部变量的注解,即使设置它的生命周期是RUNTIME或者是CLASS,注解实际生效的生命周期还是SOURCE

首先我们找到class文件,然后进行反编译查看:

image-20200816171813399

image-20200816171826203

不要尝试使用javac编译单独的People类,会出现注解类找不到的异常。

然后使用javap -V 反编译查看:

image-20200816172003583

我们的局部变量在编译时,就被处理了。也就是说,在编译时,已经没有了StudyVar注解了。

image-20200816172114081

局部变量被放到了局部变量表中:

image-20200816172255490

至此,全部完成。

最后附上全部解析源码:

package com.study.studyannotation;

import com.study.studyannotation.anno.*;
import com.study.studyannotation.beans.Man;
import com.study.studyannotation.beans.People;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;

/**
 * @author 
 * @Date 2020/8/14
 */
public class Main {
    
    

    public static void main(String[] args) {
    
    

        People people = new People("xiaomeiS");
        Man man = new Man("hah");
        // getDeclaredAnnotations:获取直接标注在指定对象上面的注解
        // getAnnotations:获取指定对象上面的注解(可以获取继承的注解)
        parseAnno(people);
        parseAnno(man);
        System.out.println();
        parseClass(people);
        parseClass(man);
        System.out.println();
        parseCons(people);
        parseCons(man);
        System.out.println();
        parseField(people);
        parseField(man);
        System.out.println();
        parseMethod(people);
        parseMethod(man);
        System.out.println();
        parseParam(people);
        parseParam(man);
    }

    /**
     * 解析组合注解
     *
     * @param object 普通对象
     */
    private static void parseAnno(Object object) {
    
    
        // 获取普通的所有的注解
        Arrays.stream(object.getClass().getAnnotations()).filter(x -> x != null).forEach(x -> {
    
    
            // 获取注解的注解对象
            // 注意,这里不能使用反射。因为注解本质上是接口,所以,实际使用中,都是代理对象,当你使用反射获取的时候,获取得到的代理对象,这个代理对象没有注解信息了
            // 使用annotationType方法,获取的是带有注解信息的注解对象(代理对象)
            // 将含有注解信息的代理对象进行解析
            parse(x.annotationType(), StudyAnno.class, object.getClass());
        });
    }

    /**
     * 解析类
     *
     * @param object 普通对象
     */
    private static void parseClass(Object object) {
    
    
        // 获取普通对象的class对象
        parse(object.getClass(), StudyClass.class, object.getClass());
    }

    /**
     * 解析参数
     *
     * @param object
     */
    private static void parseParam(Object object) {
    
    
        // 获取普通对象的构造器、方法(全部可以有参数的方法)
        Constructor<?>[] declaredConstructors = object.getClass().getDeclaredConstructors();
        Method[] declaredMethods = object.getClass().getDeclaredMethods();
        Arrays.stream(declaredConstructors).filter(x -> x != null).forEach(x -> {
    
    
            // 获取参数
            Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
        });
        Arrays.stream(declaredMethods).filter(x -> x != null).forEach(x -> {
    
    
            // 获取参数
            Arrays.stream(x.getParameters()).filter(y -> y != null).forEach(y -> parse(y, StudyParam.class, object.getClass()));
        });
    }

    /**
     * 解析构造
     *
     * @param object
     */
    private static void parseCons(Object object) {
    
    
        // 获取普通对象的Constructor对象
        Arrays.stream(object.getClass().getDeclaredConstructors()).filter(x -> x != null).forEach(x -> parse(x, StudyCons.class, object.getClass()));
    }

    /**
     * 解析属性
     *
     * @param object
     */
    private static void parseField(Object object) {
    
    
        // 获取对象的全部属性对象
        Arrays.stream(object.getClass().getDeclaredFields()).filter(x -> x != null).forEach(x -> parse(x, StudyField.class, object.getClass()));
    }

    /**
     * 解析方法
     *
     * @param object
     */
    private static void parseMethod(Object object) {
    
    
        // 获取对象的全部方法
        Arrays.stream(object.getClass().getDeclaredMethods()).filter(x -> x != null).forEach(x -> parse(x, StudyMethod.class, object.getClass()));
    }

    /**
     * 记录日志
     *
     * @param annotatedElement
     * @param annClass
     * @param objClass
     */
    private static void parse(AnnotatedElement annotatedElement, Class annClass, Class objClass) {
    
    
        System.out.println("---------- parse\t" + annClass.getSimpleName() + ":\t" + objClass.getSimpleName() + "----------");
        parse(annotatedElement, annClass);
    }

    /**
     * 注解解析
     *
     * @param annotatedElement 需要处理的AnnotatedElement对象
     * @param annClazz         需要处理的注解类型
     */
    private static void parse(AnnotatedElement annotatedElement, Class annClazz) {
    
    
        // 获取使用注解的对象上面的注解 (StudyClass对象)
        Annotation declaredAnnotation = annotatedElement.getAnnotation(annClazz);
        // 判断是否存在继承的注解,如果有继承的注解,那么获取继承的注解对象,否则是null
        Annotation annotation = annotatedElement.isAnnotationPresent(annClazz) ? annotatedElement.getAnnotation(annClazz) : null;
        // 将本身的注解和继承的注解的对象,进行流化,然后去重,去除null,最后遍历处理
        Arrays.asList(declaredAnnotation, annotation).stream().distinct().filter(x -> x != null).forEach(x -> {
    
    
            // 获取注解对象中的注解元素 (name方法)
            // 注解中的方法都是没有参数的
            // 同时因为代理对象的缘故,还需要去掉Object的2个无参公共方法:toString,hashCode,以及class的annotationType方法
            List<String> methodList = Arrays.asList("toString", "hashCode", "annotationType");
            Arrays.stream(x.getClass().getDeclaredMethods()).filter(y -> y.getParameterCount() == 0)
                    .filter(y -> !methodList.contains(y.getName()))
                    .forEach(y -> {
    
    
                        // 执行注解元素,获取注解元素值 ("people")
                        try {
    
    
                            Object nameValue = y.invoke(x, null);
                            System.out.println(annClazz.getSimpleName() + " , " + y.getName() + " = " + nameValue);
                        } catch (IllegalAccessException e) {
    
    
                            e.printStackTrace();
                        } catch (InvocationTargetException e) {
    
    
                            e.printStackTrace();
                        }
                        // 获取到注解元素值之后做的操作
                    });
        });
    }
}

执行结果

image-20200816172720001

猜你喜欢

转载自blog.csdn.net/a18792721831/article/details/108039289