Java 注解工作原理解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_36385172/article/details/79953410

1、背景

在Android开发中会经常使用到Java注解这个知识点,如:重写父类方法时使用@Override注解、阅读框架源码时常看到@Deprecated注解。
特别现在有很多优秀的Android开源框架都是使用注解,如EventBus、ButterKnife、GreenDao等。 Java注解是Java开发一个很重要的知识点,所以觉得有必要对Java注解这块知识点有一个深入的学习。

2、注解的简介

注解属于Java语言的特性,是在Java5.0引入的新特征 ,位于java.lang.annotation包中 。
注解概念:
注解是用于给Java代码附加元数据,可在编译时或运行时解析并处理这些元数据。Java代码可以是包名、类、方法、成员变量、参数等,且附加的元数据不会影响源代码的执行。
我们也可以这样通俗的理解Java注解:想像Java代码如包名、类、方法、成员变量、参数等都是具有生命,注解就是给代码中某些元素贴上去的一张标签。通俗点来讲,注解如同一张标签。这样理解有助于你快速地理解

3、注解的基本语法

很多开发人员会认为注解的地位不高。其实同 classs 和 interface 一样,注解也属于一种类型。通过 @interface 关键字进行定义。
注解的定义:
定义的格式如下

[@Target]
[@Retention]
[@Documented]
[@Inherited]
public @interface [名称] {
    // 元素
}

形式跟接口很类似,不过前面多了一个@符号。
下面的创建了一个名为Test的注解,你可以简单理解为创建了一张名字为Test的标签。

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

}

定义注解时可给注解添加属性,也叫注解的成员变量。注解只有成员变量,没有方法。
注解的成员变量的以“无形参的方法”形式来声明。

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value();
}

还可给注解的属性设定默认值

@Target({ElementType.FIELD, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Test {
    String value() default "hello";
}

注解的属性可支持数据类型有如下:
1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
2.String类型
3.Class类型
4.enum类型
5.Annotation类型
6.以上所有类型的数组

注解的使用:
下面定义注解@DbString, 注解中拥有 fieldName 和 isPrimaryKey 两个属性,并为isPrimaryKey设定了默认值。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface DbString {
    String fieldName();
    boolean isPrimaryKey() default false;
}

在使用的时候,必须给没有设置默认值的属性赋值。赋值的方式是在注解的括号内以 value=”” 形式进行赋值。

public class Person {
    private int id;

    @DbString(fieldName = "name")
    private String name;
}

public class Person {
    @DbInt(fieldName = "id", isPrimaryKey = true)
    private int id;

    @DbString(fieldName = "name", isPrimaryKey = true)
    private String name;
}

内置注解:
JDK5.0加入了下面三个内置注解:

1. @Override:表示当前的方法定义将覆盖父类中的方法
2. @Deprecated:表示代码被弃用,如果使用了被@Deprecated注解的代码则编译器将发出警告
3. @SuppressWarnings:表示关闭编译器警告信息

元注解:
要想让定义的注解能很好的工作,需要学习元注解的使用。Java5.0还提供了四个元注解。

概念:元注解的作用就是负责注解其他注解。或者说元注解是一种基本注解,但是它能够应用到其它注解上面。
如果难于理解的话,可这样理解:元注解也是一张标签,但它是一张特殊的标签,它的作用就是给其他普通的标签进行解释说明的。

元注解 作用 取值 备注
@Target 指定了注解运用的地方 下面给出 你可以这样理解,当一个注解被 @Target 注解时,这个注解就被限定了运用的场景。类比到标签,原本标签是你想张贴到哪个地方就到哪个地方,但是因为 @Target 的存在,它张贴的地方就非常具体了,比如只能张贴到方法上、类上、方法参数上等等。
@Retention 说明注解的存活时间 下面给出 我们可以这样的方式来加深理解,@Retention 去给一张标签解释的时候,它指定了这张标签张贴的时间。@Retention 相当于给一张标签上面盖了一张时间戳,时间戳指明了标签张贴的时间周期。
@Document 说明注解是否能被文档化 不常用
@Inhrited 说明注解能否被继承 不常用
* ElementType.CONSTRUCTOR 可以给构造方法进行注解
* ElementType.FIELD 可以给属性进行注解
* ElementType.LOCAL_VARIABLE 可以给局部变量进行注解
* ElementType.METHOD 可以给方法进行注解
* ElementType.PACKAGE 可以给一个包进行注解
* ElementType.PARAMETER 可以给一个方法内的参数进行注解
* ElementType.ANNOTATION_TYPE 可以给一个注解进行注解
* ElementType.TYPE 可以给一个类型进行注解,比如类、接口、枚举

4、注解的处理

现在我们已知道如何定义和使用注解,但这是注解还是没有意义的,接下来就是学习如何提取注解,并使用这些注解做一些有用的事情。
我通过用标签来比作注解,前面的内容是讲怎么写注解,然后贴到哪个地方去,而现在我们要做的工作就是在某个时刻拿到这些标签内容。并根据标签的内容做一些处理。

我们可以在两时期对注解进行处理:

1. 编译时处理
2. 运行时处理

注解的处理方式是如何划分的?
回顾一下元注解@Retention,当@Retention的值设定为RetentionPolicy.SOURCE时注解只存在.java源文件中, 但设定为RetentionPolicy.CLASS时注解只存在于.class文件中,所以无法在运行时去获取注解的信息,只能在编译时处理。
设定为RetentionPolicy.RUNTIME注解信息会存在.class文件中,通知单JVM加载.class文件时会把注解也加载到JVM中,所以就可在运行时获取注解的信息

运行时处理:
运行时处理是通过反射机制获取注解。
定义一个@FindView注解

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface FindView {
    int value();
}

使用注解

public class MainActivity extends AppCompatActivity {
    @FindView(R.id.text)
    TextView mTextView;

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

处理注解

public class MainActivity extends AppCompatActivity {

    @FindView(R.id.text)
    TextView mTextView;

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

public static void bindView(Activity activity) {
    Class classObj = activity.getClass();
    Field [] fields = classObj.getDeclaredFields();

    for (int i = 0; i < fields.length; i++) {
        Field field = fields[i];
        if (field.isAnnotationPresent(FindView.class)) {
            FindView findView = field.getAnnotation(FindView.class);
            int resId = findView.value();
            View view = activity.findViewById(resId);
            field.setAccessible(true);
            Class<?> targetType = field.getType();
            Class<?> viewType = view.getClass();
            if (!targetType.isAssignableFrom(viewType)) {
                continue;
            }
            try {
                field.set(activity, view);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    }
}

运行时处理的缺点:
1:通过反射会影响运行效率
2:如果注解无法保存到运行时的话,是无法使用运行时处理的
编译时处理:
编译时处理需要使用到APT技术,该技术提供了一套编译期的注解处理流程。

这里写图片描述
在编译期扫描.java文件的注解,并传递到注解处理器,注解处理器可根据注解生成新的.java文件,这些新的.java问和原来的.java一起被javac编译。
这里写图片描述
这里需要引入注解处理器这个概念,注解处理器是一个在javac编译期处理注解的工具,你可以创建注解处理器并注册,在编译期你创建的处理器以Java代码作为输入,生成文件.java文件作为输出。
注意:注解处理器不能修改已经存在的Java类(即不能向已有的类中添加方法)。只能生成新的Java类。
下面定义注解处理器的四个重要的方法

public class CustomProcessor extends AbstractProcessor {
    /**
    * 初始化注解处理器
    * @param processingEnv APT框架的环境对象,通过该对象可以获取到很多工具类
    */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
    }

    /**
    * 配置该注解处理器需要处理的注解类型
    * @return
    */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
        return super.getSupportedAnnotationTypes();
    }

    /**
    * 配置该注解处理器支持的最新版本
    * @return
    */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        return super.getSupportedSourceVersion();
    }

    /**
    * 用于处理注解,并生成.java文件
    * @param annotations
    * @param roundEnv
    * @return
    */
    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                          RoundEnvironment roundEnv) {
        return false;
    }
}

优点:
1:不在运行时进行操作,所以对程序的性能不会有什么影响

缺点
1:无法对原来的.java文件进行修改
2:生成额外的.java文件
3:因为是在编译期进行处理注解,所以会对编译速度

阅读更多 登录后自动展开
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页