Java 注解工作原理解析

原创 2018年04月15日 21:16:52

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:因为是在编译期进行处理注解,所以会对编译速度

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

注解(一)——注解入门

注解了解注解及java提供的几个基本注解 @SuppressWarnings 首先编写一个AnnotationTest类,先通过@SuppressWarnings的应用让大家认识和了解一下注解,通过...
  • yerenyuan_pku
  • yerenyuan_pku
  • 2016-09-19 10:43:10
  • 4109

Java自定义注解和运行时靠反射获取注解

java自定义注解 Java注解是附加在代码中的一些元信息,用于一些工具在编译、运行时进行解析和使用,起到说明、配置的功能。 注解不会也不能影响代码的实际逻辑,仅仅起到辅助性的作用。包含在 jav...
  • bao19901210
  • bao19901210
  • 2013-12-08 16:05:17
  • 152489

Java 8 新特性:扩展注解(类型注解和重复注解)

一.注解(JDK1.5) 二.注解更新(JDK1.8) 类型注解和重复注解…………
  • Sun_2134
  • Sun_2134
  • 2016-05-04 18:11:09
  • 7825

注解(Annotation)

注解 Java类提供的基本注解: · @SuppressWarnings:取消警告。例如:当你调用了已经过时的方法时,编译器会警告你该方法已经过时。如果你认为这不是问题,可以加上@SuppressWa...
  • zy_tiger
  • zy_tiger
  • 2016-03-05 18:53:56
  • 295

JUnit4注解基本介绍

@After If you allocate external resources in a Before method you need to release them after the test...
  • a19881029
  • a19881029
  • 2013-08-01 09:28:44
  • 19301

秒懂,Java 注解 (Annotation)你可以这样学

文章开头先引入一处图片。 这处图片引自老罗的博客。为了避免不必要的麻烦,首先声明我个人比较尊敬老罗的。至于为什么放这张图,自然是为本篇博文服务,接下来我自会说明。好了,可以开始今天的博文了。 ...
  • briblue
  • briblue
  • 2017-06-27 21:48:30
  • 33782

深入理解Java注解类型(@Annotation)

【版权申明】未经博主同意,谢绝转载!(请尊重原创,博主保留追究权) http://blog.csdn.net/javazejian/article/details/71860633 出自【...
  • javazejian
  • javazejian
  • 2017-05-21 10:51:43
  • 33166

IDEA下从零开始搭建SpringBoot工程

SpringBoot的具体介绍可以参看其他网上介绍,这里就不多说了,就这几天的学习,个人理解,简而言之: (1)它是Spring的升级版,Spring容器能做到的事情,它都能做到,而且更简便,从...
  • u013248535
  • u013248535
  • 2017-02-15 20:00:14
  • 48624

shiro注解权限控制-5个权限注解

shiro注解权限控制-5个权限注解Shiro共有5个注解,接下来我们就详细说说吧 RequiresAuthentication: 使用该注解标注的类,实例,方法在访问或调用时,当前Subject...
  • w_stronger
  • w_stronger
  • 2017-06-12 15:45:46
  • 16229

基础篇:带你从头到尾玩转注解

一起玩转高端大气上档次的Annotation。要玩就玩的痛快,从头到位带你了解Annotation以及自己动手编写注解处理器。...
  • dd864140130
  • dd864140130
  • 2016-12-25 22:35:45
  • 6496
收藏助手
不良信息举报
您举报文章:Java 注解工作原理解析
举报原因:
原因补充:

(最多只允许输入30个字)