看到最后,你才发现Java中注解原来是这么做到的!

开发 前端
用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。简而言之,元注解是用来修饰注解的。

​概述

注解想必大家在项目中经常使用,比如Spring框架中常用的一些注解:@Controller、@Service、@RequestMapping等等,它是JDK1.5及以后版本引入的一个特性。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。那么你知道JDK什么是元注解吗?注解有哪些分类吗?以及注解在Java中最本质究竟是什么东西,是如何实现的呢?

注解的分类

根据注解的使用场景,主要分为三类,元注解、内置注解和自定义注解。

元注解

用于定义注解的注解,通常用于注解的定义上,标明该注解的使用范围、生效范围等。简而言之,元注解是用来修饰注解的。

@Retention

指定注解信息保留到哪个阶段,分别为源代码阶段、编译Class阶段、运行阶段。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Retention {
    /**
     * Returns the retention policy.
     * @return the retention policy
     */
    RetentionPolicy value();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • SOURCE: 保留在源代码java中,被编译器丢弃,也就是说在class文件中不包含注解信息,通常用来标记源码,引起大家的注意,比如自定义一个注解例如@ThreadSafe,用来标识一个类时线程安全的。
  • CLASS: 编译后的class文件中包含注解信息,但是会被jvm丢弃
  • RUNTIME: 注解信息在运行期(JVM)保留(.class也有),可以通过反射机制读取注解的信息

@Target

指定注解的使用范围,如类、方法、属性、局部属性、参数等, 可以多选。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.

具体可选的枚举如下:

public enum ElementType {
    /** 适用范围:类、接口、注解类型,枚举类型enum */
    TYPE,
    /** 作用于类属性 (includes enum constants) */
    FIELD,
    /** 作用于方法 */
    METHOD,
    /** 作用于参数声明 */
    PARAMETER,
    /** 作用于构造函数声明 */
    CONSTRUCTOR,
    /** 作用于局部变量声明 */
    LOCAL_VARIABLE,
    /** 作用于注解声明 */
    ANNOTATION_TYPE,
    /** 作用于包声明 */
    PACKAGE,
    /** 作用于类型参数(泛型参数)声明 */
    TYPE_PARAMETER,
    /** 作用于使用类型的任意语句(不包括class) */
    TYPE_USE
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

@Inherited

加上该注解的注解,表示可以被标注的类子类继承,比如A上标记了带有@Inherited的注解,那么类B继承了A, 那么B也会有这个注解,默认情况下注解是不支持继承的。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Inherited {
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@Document

将此注解包含在 javadoc 中 ,它代表着此注解会被javadoc工具提取成文档。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Documented {
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@Repeatable

1.8中加入的元注解,用来标记是否可以重复标记。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Repeatable {
    /**
     * Indicates the <em>containing annotation type</em> for the
     * repeatable annotation type.
     * @return the containing annotation type
     */
    Class<? extends Annotation> value();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.

内置注解

java提供了一些内置注解,可以配合编译器来检查代码的正确性, 我们可以关注他们的元注解。

@Override

标记当前方法是覆写父类的方法。

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
  • 1.
  • 2.
  • 3.
  • 4.

@Deprecated

标记一个元素为已过期,不要在使用了

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(value={CONSTRUCTOR, FIELD, LOCAL_VARIABLE, METHOD, PACKAGE, PARAMETER, TYPE})
public @interface Deprecated {
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@SuppressWarnings

用来关闭编译器输出的警告信息

@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    String[] value();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

@FunctionalInterface

java8中引入,标记是一个函数式接口,也就是说有且只有一个抽象方法的接口

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}
  • 1.
  • 2.
  • 3.
  • 4.

自定义注解

注解遵循的格式一般如下:

//元注解部分 xxxx
@Retention(xxxx)
@Target(xxxx)
public @interface 注解名 {
  返回值 属性名() 默认值;
  返回值 属性名() 默认值;
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 返回值支持的类型如下:java的8种基础类型(不支持包装类型)、String、Class、Enum、Annotation、以及上面类型的数组。
  • 默认值可选,非必有。

举个项目中自定义的栗子:

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented  
@Inherited
public @interface DataAuthorize {
    /**
     * 资产ID
     * @return
     */
    String assetId();

    /**
     * 资产类型
     * @return
     */
    String assetType();

    /**
     * 权限代码
     * @return
     */
    String authCode() default "";

    /**
     * 使用的类型
     * @return
     */
    Class[] useType();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.

使用反射操作注解

大部分情况下,我们的项目或者开源框架中都定义了大量的注解,而且都是@Retention(RetentionPolicy.RUNTIME)运行时阶段,我们可以通过反射获取注解中的信息,所以整体遵循下面的一个范式。

  1. 自定义注解
  2. 扫描注解
  3. 通过反射获取注解的信息,执行相应的逻辑。

下面我们重点使用下如何用反射来获取注解的信息。

  1. 定义target是注解的注解
@Inherited
@Retention( value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.ANNOTATION_TYPE})
public @interface AnnoTest {

    String value() default "anno";

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  1. 定义一个几乎全量信息的注解
@AnnoTest("alvinAnno")
@Inherited
@Retention( value = RetentionPolicy.RUNTIME)
@Target(value = {ElementType.TYPE_USE,ElementType.PACKAGE,ElementType.FIELD,
        ElementType.TYPE_PARAMETER,ElementType.CONSTRUCTOR,ElementType.LOCAL_VARIABLE})
@Documented
public @interface FullAnnoTest {

    String value() default  "FullAnnoTest";

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  1. 定义测试类和反射代码
@FullAnnoTest("package")
package com.alvin.java.core.anno;

public class ParentObj {
}



@FullAnnoTest("testAnnoReflect")
public class TestAnnoReflect<@FullAnnoTest("parameter") T > extends @FullAnnoTest("parent")ParentObj {

    @FullAnnoTest("constructor")
    TestAnnoReflect() {
    }

    //注解字段域
    private @FullAnnoTest("name") String name;
    //注解泛型字段域
    private @FullAnnoTest("value") T value;
    //注解通配符
    private @FullAnnoTest("list") List<@FullAnnoTest("generic") ?> list;
    //注解方法
    @FullAnnoTest("method")                       //注解方法参数
    public String hello(@FullAnnoTest("methodParameter") String name)
            throws @FullAnnoTest("Exception") Exception { // 注解抛出异常
        //注解局部变量,现在运行时暂时无法获取(忽略)
        @FullAnnoTest("result") String result;
        result = "siting";
        System.out.println(name);
        return  result;
    }

    public static void main(String[] args) throws Exception {

        TestAnnoReflect<String>  TestAnnoReflect = new TestAnnoReflect<> ();
        Class<TestAnnoReflect<Object>> clazz = (Class<TestAnnoReflect<Object>>) TestAnnoReflect.getClass();
        //class的注解
        Annotation[] annotations = clazz.getAnnotations();
        FullAnnoTest testTmp = (FullAnnoTest) annotations[0];
        System.out.println("修饰TestAnnoReflect.class注解value: "+testTmp.value());
        //构造器的注解
        Constructor<TestAnnoReflect<Object>> constructor = (Constructor<TestAnnoReflect<Object>>) clazz.getDeclaredConstructors()[0];
        testTmp = constructor.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰构造器的注解value: "+testTmp.value());
        //继承父类的注解
        AnnotatedType annotatedType = clazz.getAnnotatedSuperclass();
        testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰继承父类的注解value: "+testTmp.value());
        //注解的注解
        AnnoTest AnnoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
        System.out.println("修饰注解的注解AnnoTest-value: "+AnnoTest.value());
        //泛型参数 T 的注解
        TypeVariable<Class<TestAnnoReflect<Object>>> variable = clazz.getTypeParameters()[0];
        testTmp = variable.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰泛型参数T注解value: "+testTmp.value());
        //普通字段域 的注解
        Field[] fields = clazz.getDeclaredFields();
        Field nameField = fields[0];
        testTmp = nameField.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰普通字段域name注解value: "+testTmp.value());
        //泛型字段域 的注解
        Field valueField = fields[1];
        testTmp = valueField.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰泛型字段T注解value: "+testTmp.value());
        //通配符字段域 的注解
        Field listField = fields[2];
        AnnotatedParameterizedType annotatedPType = (AnnotatedParameterizedType)listField.getAnnotatedType();
        testTmp = annotatedPType.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰泛型注解value: "+testTmp.value());
        //通配符注解 的注解
        AnnotatedType[] annotatedTypes = annotatedPType.getAnnotatedActualTypeArguments();
        AnnotatedWildcardType annotatedWildcardType = (AnnotatedWildcardType) annotatedTypes[0];
        testTmp = annotatedWildcardType.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰通配符注解value: "+testTmp.value());
        //方法的注解
        Method method = clazz.getDeclaredMethod("hello", String.class);
        annotatedType = method.getAnnotatedReturnType();
        testTmp = annotatedType.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰方法的注解value: "+testTmp.value());
        //异常的注解
        annotatedTypes = method.getAnnotatedExceptionTypes();
        testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
        System.out.println("修饰方法抛出错误的注解value: "+testTmp.value());
        //方法参数的注解
        annotatedTypes = method.getAnnotatedParameterTypes();
        testTmp = annotatedTypes[0].getAnnotation(FullAnnoTest.class);
        System.out.println("修饰方法参数注解value: "+testTmp.value());
        //包的注解
        Package p = Package.getPackage("com.alvin.java.core.anno");
        testTmp = p.getAnnotation(FullAnnoTest.class);
        System.out.println("修饰package注解value: "+testTmp.value());
        TestAnnoReflect.hello("hello");
    }

}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.
  • 58.
  • 59.
  • 60.
  • 61.
  • 62.
  • 63.
  • 64.
  • 65.
  • 66.
  • 67.
  • 68.
  • 69.
  • 70.
  • 71.
  • 72.
  • 73.
  • 74.
  • 75.
  • 76.
  • 77.
  • 78.
  • 79.
  • 80.
  • 81.
  • 82.
  • 83.
  • 84.
  • 85.
  • 86.
  • 87.
  • 88.
  • 89.
  • 90.
  • 91.
  • 92.
  • 93.
  • 94.
  • 95.
  1. 查看对应的执行结果
修饰TestAnnoReflect.class注解value: testAnnoReflect
修饰构造器的注解value: constructor
修饰继承父类的注解value: parent
修饰注解的注解AnnoTest-value: alvinAnno
修饰泛型参数T注解value: parameter
修饰普通字段域name注解value: name
修饰泛型字段T注解value: value
修饰泛型注解value: list
修饰通配符注解value: generic
修饰方法的注解value: method
修饰方法抛出错误的注解value: Exception
修饰方法参数注解value: methodParameter
修饰package注解value: package
hello
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

注解的本质和底层实现

大家有没有想过注解的本质是什么?

我们先通过反编译查看注解生成的字节码,可以通过javap -v FullAnnoTest.class查看如下:

图片

可以看到,我们的注解是继承自Annotation接口。

public interface Annotation {
    boolean equals(Object obj);

    int hashCode();

    String toString();

    /**
     * Returns the annotation type of this annotation.
     * @return the annotation type of this annotation
     */
    Class<? extends Annotation> annotationType();
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.

所以注解相当于一个语法糖一样,可以方便我们使用,本质上它是继承自Annotation的一个接口。

那大家有没有想过它的实现类在哪里?比如下面的代码,获取到注解,按照上面的解释,它是一个接口,那调用value()方法时,它具体调用的哪个实现类呢?我们并没有写实现类啊.....

答案当然就是动态代理生成的实现类。

AnnoTest annoTest = testTmp.annotationType().getAnnotation(AnnoTest.class);
System.out.println("修饰注解的注解AnnoTest-value: "+annoTest.value());
  • 1.
  • 2.

我们可以在启动参数添加如下命令可以查看生成的代理类:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true

图片

执行后,生成代理类如下,

图片

代理大致的代码如下:

public final class $Proxy2 extends Proxy implements FullAnnoTest {
    private static Method m1;
    private static Method m2;
    private static Method m4;
    private static Method m0;
    private static Method m3;

    public $Proxy2(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

  

    public final Class annotationType() throws  {
        try {
            return (Class)super.h.invoke(this, m4, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String value() throws  {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m4 = Class.forName("com.alvin.java.core.anno.FullAnnoTest").getMethod("annotationType");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.alvin.java.core.anno.FullAnnoTest").getMethod("value");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.
  • 34.
  • 35.
  • 36.
  • 37.
  • 38.
  • 39.
  • 40.
  • 41.
  • 42.
  • 43.
  • 44.
  • 45.
  • 46.
  • 47.
  • 48.
  • 49.
  • 50.
  • 51.
  • 52.
  • 53.
  • 54.
  • 55.
  • 56.
  • 57.

我们看value()​方法,这里调用了super.h对象,也就是InvocationHandler​对象,而我们注解用的是AnnotationInvocationHandler这个子类,我们在invoke方法中打个断点,就明白了~~

责任编辑:武晓燕 来源: JAVA旭阳
相关推荐

2022-01-14 14:19:38

ReactTS前端

2020-02-23 15:55:00

疫情AI人工智能

2021-02-07 08:13:18

@DateTimeFo@NumberFormSpring

2017-11-12 21:12:34

HPC

2019-10-08 12:32:07

运维架构技术

2020-06-30 08:12:32

VMwareKVMDocker

2022-08-15 08:01:00

三色标记JVM算法

2021-07-29 16:56:59

微信腾讯注册

2024-10-11 11:59:03

2017-06-06 15:13:07

2022-12-14 07:32:40

InnoDBMySQL引擎

2023-07-26 00:32:33

注解抽象spring

2020-05-26 08:52:36

Java JVM多态

2015-08-05 10:50:01

Facebook缓存网页

2021-08-17 07:00:00

双重检查锁Nacos

2013-03-29 09:54:05

创业创业者

2020-09-24 06:44:54

HTTPS网站 HTTP

2024-02-21 23:03:56

代码系统

2022-11-02 19:08:48

微服务轮询消费者

2018-04-02 15:13:21

网络
点赞
收藏

51CTO技术栈公众号