注解Annotation实现原理与自定义注解浅析

什么是注解?

对于很多初次接触的开发者来说应该都有这个疑问?Annontation是Java5开始引入的新特征,中文名称叫注解。它提供了一种安全的类似注释的机制,用来将任何的信息或元数据(metadata)与程序元素(类、方法、成员变量等)进行关联。为程序的元素(类、方法、成员变量)加上更直观更明了的说明,这些说明信息是与程序的业务逻辑无关,并且供指定的工具或框架使用。Annontation像一种修饰符一样,应用于包、类型、构造方法、方法、成员变量、参数及本地变量的声明语句中。

Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 java.lang.annotation 包中。

注解的用处:

1、生成文档。这是最常见的,也是java 最早提供的注解。常用的有@param @return 等

2、跟踪代码依赖性,运行时动态处理,获取信息,实现替代配置文件功能。比如Dagger 2 依赖注入,未来java 开发,将大量注解配置,具有很大用处;

3、在编译时进行格式检查。如@override 放在方法前,如果你这个方法并不是覆盖了超类方法,则编译时就能检查出。

注解的原理:
注解Annotation实现原理与自定义注解浅析_第1张图片
Java也为我们提供了一些常用的注解:

@Override,代表某方法是重写父类中的方法
@Deprecated,表示被标记的内容已经过时、不建议被使用,如果被使用编译器会报警告,但程序也能正常运行。
@SuppressWarnings,由于内容被@Deprecated标记后,编译器会有警告,如果想忽略警告可以使用@SuppressWarnings

自定义注解::
自定义注解类编写的一些规则:

  1. Annotation 型定义为@interface, 所有的Annotation 会自动继承java.lang.Annotation这一接口,并且不能再去继承别的类或是接口.
  2. 参数成员只能用public 或默认(default) 这两个访问权修饰
  3. 参数成员只能用基本类型byte、short、char、int、long、float、double、boolean八种基本数据类型和String、Enum、Class、annotations等数据类型,以及这一些类型的数组.
  4. 要获取类方法和字段的注解信息,必须通过Java的反射技术来获取 Annotation 对象,因为你除此之外没有别的获取注解对象的方法
  5. 注解也可以没有定义成员,,不过这样注解就没啥用了
    PS:自定义注解需要使用到元注解

如何定义一个注解呢?可以先看看这些已有的注解是如何实现的,我们先以@Override为例:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

可以看到注解是通过@interface关键字来定义的,和接口的定义类似,但是又多了@Target()@Retention(),这些是java中的元注解,元注解可以理解为内置的基础注解,用来限定、说明自定义注解。除了这两个元注解外,还有三个元注解@Inherited@Repeatable@Documented,后边会详细解释这些元注解的作用!
再看看@SuppressWarnings注解的实现:

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

@Override相比最大的区别就是注解体中多了String[] value();,它代表注解的属性!关于注解属性也会在后边详细的介绍!

所以定义注解时,除了使用@interface还需要考虑元注解、注解属性,一个自定义注解的伪码如下:

@元注解0
@元注解1
@元注解2
public @interface 注解名称 {
    类型 attr0();
    类型 attr1();
}

元注解:

java.lang.annotation 提供了四种元注解,专门注解其他的注解(在自定义注解的时候,需要使用到元注解):
@Documented – 注解是否将包含在JavaDoc中
@Retention – 什么时候使用该注解
@Target – 注解用于什么地方
@Inherited – 是否允许子类继承该注解

1.)@Retention – 定义该注解的生命周期
● RetentionPolicy.SOURCE : 在编译阶段丢弃。这些注解在编译结束之后就不再有任何意义,所以它们不会写入字节码。@Override, @SuppressWarnings都属于这类注解。
● RetentionPolicy.CLASS : 在类加载的时候丢弃。在字节码文件的处理中有用。注解默认使用这种方式
● RetentionPolicy.RUNTIME : 始终不会丢弃,运行期也保留该注解,因此可以使用反射机制读取该注解的信息。我们自定义的注解通常使用这种方式。

2.)Target – 表示该注解用于什么地方。默认值为任何元素,表示该注解用于什么地方。可用的ElementType 参数包括
● ElementType.CONSTRUCTOR: 用于描述构造器
● ElementType.FIELD: 成员变量、对象、属性(包括enum实例)
● ElementType.LOCAL_VARIABLE: 用于描述局部变量
● ElementType.METHOD: 用于描述方法
● ElementType.PACKAGE: 用于描述包
● ElementType.PARAMETER: 用于描述参数
● ElementType.TYPE: 用于描述类、接口(包括注解类型) 或enum声明

3.)@Documented – 一个简单的Annotations 标记注解,表示是否将注解信息添加在java 文档中。

4.)@Inherited – 定义该注释和子类的关系
@Inherited 元注解是一个标记注解,@Inherited 阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited 修饰的annotation 类型被用于一个class,则这个annotation 将被用于该class 的子类。
例子:

@Retention(RetentionPolicy.RUNTIME) // 注解会在class字节码文件中存在,在运行时可以通过反射获取到
@Target({ElementType.FIELD,ElementType.METHOD})//定义注解的作用目标**作用范围字段、枚举的常量/方法
@Documented//说明该注解将被包含在javadoc中
@Inherited //这个注解将被用于该class 的子类
public @interface FieldMeta {
 
	/**
	 * 注解属性:下面介绍
	 * @return
	 */
	boolean id() default false;
	/**
	 * 注解属性:字段名称,default 为默认
	 * @return
	 */
	String name() default "";
}

注解属性
在注解中定义属性和在接口中定义方法的格式类似,例如:

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}

这样就给Test注解定义了name、age两个属性,并用default关键字指定age的默认值为18。可以这样使用定义好的注解:

@TestAnnotation(name = "Tom", age = 12, favour = {"music", "sports"})
public class Test {
}

由于age有默认值,可以在使用注解时不指定它的值。由于favour的类型为数组,所以当其有多个值时需要用{}包起来。
如果自定义注解没有属性或者属性有默认值,则使用时可以直接写@TestAnnotation,省略后边的括号。
注解的属性支持的数据类型如下:

  • 基本类型(byte、short、int、float、double、long、char、boolean),不包括其对应的包装类型

  • String

  • . Class,即Class

  • enum,例如enum staus {A, B, C}

  • 注解,例如Override test();

    及上述类型对应的数组
    

注解相关的语法糖就介绍到这里了,接下来要关注的是当一个类、方法、属性等使用了注解后,如何提取注解上的信息。

注解与反射
要提取注解上的信息,就要用到反射相关的知识了,下面看一个完整的例子,首先定义TestAnnotation注解,可以作用在类、字段、方法声明的地方,并可以在运行时被获取,以及三个属性:

@Target(value = {ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface TestAnnotation {
    String name();
    int age() default 18;
    String[] favour();
}

在Test的类、字段、方法声明的地方分别使用TestAnnotation注解:

@TestAnnotation(name = "Test", age = 20, favour = {"music", "sports"})
public class Test {

    @TestAnnotation(name = "testField", favour = {"reading", "sports"})
    private int testField;

    @TestAnnotation(name = "testMethod", age = 10, favour = {"dancing", "music"})
    public void testMethod() {

    }

    @TestAnnotation(name = "testMethod1", age = 12, favour = {"music"})
    public void testMethod1() {

    }
}

通过反射的方式提取注解信息:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        resolve();
    }

    private void resolve() {
        // 解析类上的注解
        boolean isPresent = Test.class.isAnnotationPresent(TestAnnotation.class);
        if (isPresent) {
            TestAnnotation annotation = Test.class.getAnnotation(TestAnnotation.class);
            showAnnotation(annotation);
        }
        // 解析字段上的注解
        Field[] fields = Test.class.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = field.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
        // 解析方法上的注解
        Method[] methods = Test.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.isAnnotationPresent(TestAnnotation.class)) {
                TestAnnotation annotation = method.getAnnotation(TestAnnotation.class);
                showAnnotation(annotation);
            }
        }
    }

    private void showAnnotation(TestAnnotation annotation) {
        Log.e("Annotation", annotation.name() + "#" + annotation.age() + "#" + Arrays.toString(annotation.favour()));
    }
}

运行后的效果如下:
注解Annotation实现原理与自定义注解浅析_第2张图片
其中涉及到了几个关键的方法,Class、Method、Field等类都有这样的方法:

boolean isAnnotationPresent(Class annotation),用来判断是否使用了某个注解。

public  A getAnnotation(Class annotation),获得指定名称的注解对象。

public Annotation[] getAnnotations(),返回对应元素的全部注解。

public Annotation[] getDeclaredAnnotations(),返回直接在对应元素上使用的注解,不包括父类的注解。

boolean isAnnotationPresent(Class annotation),用来判断是否使用了某个注解。

public  A getAnnotation(Class annotation),获得指定名称的注解对象。

public Annotation[] getAnnotations(),返回对应元素的全部注解。

public Annotation[] getDeclaredAnnotations(),返回直接在对应元素上使用的注解,不包括父类的注解。

你可能感兴趣的:(java)