java jdk: 10.0.2

注解的定义

注解通过 @interface 关键字进行定义.

public @interface TestAnnotation {
}

它的形式跟接口很类似,不过前面多了一个 @ 符号. 上面的代码就创建了一个名字为 TestAnnotaion 的注解.

你可以简单理解为创建了一张名字为 TestAnnotation 的标签.

注解的应用

上面创建了一个注解, 那么注解的的使用方法是什么呢.

@TestAnnotation
public class Test {
}

创建一个类 Test, 然后在类定义的地方加上 @TestAnnotation 就可以用 TestAnnotation 注解这个类了.

你可以简单理解为将 TestAnnotation 这张标签贴到 Test 这个类上面.

不过, 要想注解能够正常工作, 还需要介绍一下一个新的概念那就是元注解.

元注解

元注解是什么意思呢?

元注解是可以注解到注解上的注解, 或者说元注解是一种基本注解, 但是它能够应用到其它的注解上面.

如果难于理解的话, 你可以这样理解. 元注解也是一张标签, 但是它是一张特殊的标签, 它的作用和目的就是给其他普通的标签进行解释说明的.

元标签有 @Retention@Documented@Target@Inherited@Repeatable 5 种.

@Retention

Retention 的英文意为保留期的意思. 当 @Retention 应用到一个注解上的时候, 它解释说明了这个注解的的存活时间.

它的取值如下:

  • RetentionPolicy.SOURCE 注解只在源码阶段保留, 在编译器进行编译时它将被丢弃忽视.
  • RetentionPolicy.CLASS 注解只被保留到编译进行的时候, 它并不会被加载到 JVM 中.
  • RetentionPolicy.RUNTIME 注解可以保留到程序运行的时候, 它会被加载进入到 JVM 中, 所以在程序运行时可以获取到它们.

我们可以这样的方式来加深理解, @Retention 去给一张标签解释的时候, 它指定了这张标签张贴的时间.

@Retention 相当于给一张标签上面盖了一张时间戳, 时间戳指明了标签张贴的时间周期.

@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
}

上面的代码中, 我们指定 TestAnnotation 可以在程序运行周期被获取到, 因此它的生命周期非常的长.

@Documented

顾名思义, 这个元注解肯定是和文档有关. 它的作用是能够将注解中的元素包含到 Javadoc 中去.

@Target

Target 是目标的意思, @Target 指定了注解运用的地方.

你可以这样理解, 当一个注解被 @Target 注解时, 这个注解就被限定了运用的场景.

  • ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
  • ElementType.CONSTRUCTOR 可以给构造方法进行注解
  • ElementType.FIELD 可以给属性(字段)进行注解
  • ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
  • ElementType.METHOD 可以给方法进行注解
  • ElementType.PACKAGE 可以给一个包进行注解
  • ElementType.PARAMETER 可以给一个方法内的参数进行注解
  • ElementType.TYPE 可以给一个类型进行注解, 比如类、接口、枚举

@Inherited

Inherited 是继承的意思, 但是它并不是说注解本身可以继承, 而是说如果一个超类被 @Inherited 注解过的注解进行注解的话, 那么如果它的子类没有被任何注解应用的话, 那么这个子类就继承了超类的注解.
说的比较抽象. 代码来解释.

@Inherited
@Retention(RetentionPolicy.RUNTIME)
@interface Test {}


@Test
public class A {}


public class B extends A {}

注解 Test@Inherited 修饰, 之后类 A 被 Test 注解, 类 B 继承 A, 类 B 也拥有 Test 这个注解.

@Repeatable

Repeatable 自然是可重复的意思.

什么样的注解会多次应用呢? 通常是注解的值可以同时取多个.

举个例子, 一个人他既是程序员又是产品经理, 同时他还是个画家.

@interface Persons {
    Person[]  value();
}


@Repeatable(Persons.class)
@interface Person{
    String role default "";
}


@Person(role="artist")
@Person(role="coder")
@Person(role="PM")
public class SuperMan{

}

注意上面的代码, @Repeatable 注解了 Person. 而 @Repeatable 后面括号中的类相当于一个容器注解.

什么是容器注解呢? 就是用来存放其它注解的地方. 它本身也是一个注解.

我们再看看代码中的相关容器注解.

@interface Persons {
    Person[]  value();
}

按照规定, 它里面必须要有一个 value 的属性, 属性类型是一个被 @Repeatable 注解过的注解数组, 注意它是数组.

注解的属性

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {

    int id();

    String msg();

}

上面代码定义了 TestAnnotation 这个注解中拥有 idmsg 两个属性. 在使用的时候, 我们应该给它们进行赋值.

赋值的方式是在注解的括号内以 value=”” 形式, 多个属性之前用 , 隔开.

@TestAnnotation(id=3,msg="hello annotation")
public class Test {

}
⚠️ 在注解中定义属性时它的类型必须是 8 种基本数据类型外加 类、接口、注解及它们的数组.

判断类上是否使用指定注解

Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");

stringClassMap.forEach((key, value) ->{
        ReflectionClass annotation = value.getAnnotation(ReflectionClass.class);
        if (annotation != null) {
                System.out.println("class name: " + value.getSimpleName() + ", 注解 value : " + annotation.Value());
        }
});

判断字段上是否使用指定注解

        Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");

        stringClassMap.forEach((key, value) -> {

            for (Field declaredField : ClassUtil.getAnnotatedDeclaredFields(value, ReflectionField.class, false)) {
                ReflectionField annotation = declaredField.getAnnotation(ReflectionField.class);
                if (annotation != null) {

                    this.put(annotation.Value(), declaredField);

                    System.out.println("class name: " + value.getSimpleName() +
                            ", 字段名: " + declaredField.getName() +
                            ", 字段类型:" + declaredField.getType().getSimpleName() +
                            ", 注解值:" + annotation.Value()
                    );
                }

            }

        });

判断方法上是否使用指定注解

        Map<String, Class<?>> stringClassMap = ClassUtil.getClasses("org.itzhizhe.testReflection");

        stringClassMap.forEach((key, value) -> {

            for (Method declaredField : ClassUtil.getAnnotatedDeclaredMethods(value, ReflectionMethod.class,false)) {
                ReflectionMethod annotation = declaredField.getAnnotation(ReflectionMethod.class);
                if (annotation != null) {
                    System.out.println("class name: " + value.getSimpleName() +
                            ", 方法名: " + declaredField.getName() +
                            ", 方法返回类型:" + declaredField.getReturnType() +
                            ", 注解值:" + annotation.id()
                    );
                }

            }

        });

创建对象

value.getConstructor().newInstance();

上面代码是使用无参构造来创建对象, 当然也可以使用有参构造来创建对象.

getConstructor() 参数填写构造参数的参数类型.

newInstance() 参数填写传给构造参数的值.

调用方法

可以通过 MethodsetAccessible 方法设置为 true, 就可以调用对象的 private 方法.

declaredField.setAccessible(true);
declaredField.invoke(value);

然后可以使用 invoke 方法来进行方法调用. 第一个参数为要调用方法的对象. 剩下的参数是方法参数.

总结

如果想实现像 Spring 这种框架, 实现上面几步是必须.

比如依赖注入, 我们需要知道字段的类型, 然后到 IOC 容器中找到对应的对象进行赋值.

而方法调用你可以先将注解的方法进行保存, 比如保存到 Map 集合中.

例如客户端发送指定数据帧后, 然后通过 key 取出要执行的方法后执行就可以.


sc_ik
127 声望8 粉丝

我用时间换到了什么