Java进阶_3 注解、APT

1 篇文章 0 订阅
1 篇文章 0 订阅

Java进阶_3 注解、APT

一、注解的概念

注解(Annotation)

​ 也叫元数据。一种代码级别的说明。它是JDK1.5及以后版本引入的一个特性,与类、接口、枚举是在同一个层次。它可以声明在包、类、字段、方法、局部变量、方法参数等的前面,用来对这些元素进行说明,注释。

三种标准注解

  • @Override

    用于重写父类方法,或者实现接口对应的方法

  • @Deprecated

    用于申明函数,变量,类等过时,后续的版本中可能会被删除,不推荐使用,申明了该注解的对象会被打上破折号

  • @SuppressWarnings

    注解元素上的编译器警告应该被压制. 在类上的压制会同样作用到类的方法上.

四种元注解

​ 简单来说,就是注解的注解,元注解的作用就是负责注解其他注解。Java5.0定义了4个标准的meta-annotation类型,它们被用来提供对其它 annotation类型作说明

  • @Target

    该标识用于描述注解的使用范围(即:被描述的注解可以用在什么地方): packages、types(类、接口、枚举、Annotation类型)、类型成员(方法、构造方法、成员变量、枚举值)、方法参数和本地变量(如循环变量、catch参数)。在Annotation类型的声明中使用了target可更加明晰其修饰的目标。

    取值(ElementType)有:
      1.CONSTRUCTOR:用于描述构造器
      2.FIELD:用于描述域
      3.LOCAL_VARIABLE:用于描述局部变量
      4.METHOD:用于描述方法
      5.PACKAGE:用于描述包
      6.PARAMETER:用于描述参数
      7.TYPE:用于描述类、接口(包括注解类型) 或enum声明

  • @Retention

    该标示定义了该Annotation被保留的时间长短:某些Annotation仅出现在源代码中,而被编译器丢弃;而另一些却被编译在class文件中;编译在class文件中的Annotation可能会被虚拟机忽略,而另一些在class被装载时将被读取(请注意并不影响class的执行,因为Annotation与class在使用上是被分离的)。

    取值(ElementType)有:
     1.SOURCE:在源文件中有效(即源文件保留)
     2.CLASS:在class文件中有效(即class保留)
     3.RUNTIME:在运行时有效(即运行时保留)

  • @Documented

    Documented用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化。Documented是一个标记注解,没有成员

  • @Inherited

    @Inherited 元注解是一个标记注解,@Inherited阐述了某个被标注的类型是被继承的。如果一个使用了@Inherited修饰的annotation类型被用于一个class,则这个annotation将被用于该class的子类。

    注意:@Inherited annotation类型是被标注过的class的子类所继承。类并不从它所实现的接口继承annotation,方法并不从它所重载的方法继承annotation。

    当@Inherited annotation类型标注的annotation的Retention是RetentionPolicy.RUNTIME,则反射API增强了这种继承性。如果我们使用java.lang.reflect去查询一个@Inherited annotation类型的annotation时,反射代码检查将展开工作:检查class和其父类,直到发现指定的annotation类型被发现,或者到达类继承结构的顶层。

自定义注解

  • 格式:

    注解
    public @interface 注解名 {定义体}
    

使用@interface自定义注解时,自动继承了java.lang.annotation.Annotation接口,由编译程序自动完成其他细节。在定义注解时,不能继承其他的注解或接口。@interface用来声明一个注解,其中的每一个方法实际上是声明了一个配置参数。方法的名称就是参数的名称,返回值类型就是参数的类型(返回值类型只能是基本类型、Class、String、enum)。可以通过default来声明参数的默认值。

注解参数的可支持数据类型:

​ 1.所有基本数据类型(int,float,boolean,byte,double,char,long,short)
​ 2.String类型
​ 3.Class类型
​ 4.enum类型
​ 5.Annotation类型
​ 6.以上所有类型的数组

Annotation类型里面的参数设定:

1.只能用public或默认(default)这两个访问权修饰.例如,String value();这里把方法设为defaul默认类型; 
2.参数成员只能用基本类型byte,short,char,int,long,float,double,boolean八种基本数据类型和 String,Enum,Class,annotations等数据类型,以及这一些类型的数组.例如,String value();这里的参数成员就为String;  
 3.如果只有一个参数成员,最好把参数名称设为”value”,后加小括号.例:下面的例子FruitName注解就只有一个参数成员。

下面是一个简单的自定义注解的栗子:

package annotation;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 动物名称注解
 */
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface AnimalName {
    String value();
}

​ 定义了注解,并在需要的时候给相关类,类属性加上注解信息,如果没有响应的注解信息处理流程,注解可以说是没有实用价值,接下来要介绍的是java的APT(Annotation Process Tool)技术,用于处理自定义注解。

APT技术

概念

APT即为Annotation Processing Tool,它是javac的一个工具,中文意思为编译时注解处理器。APT可以用来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来自动的生成一些代码,省去了手动编写。注意,获取注解及生成代码都是在代码编译时候完成的,相比反射在运行时处理注解大大提高了程序性能。APT的核心是AbstractProcessor类。

构建APT项目

  1. 首先,新建一个Android项目,然后File–>New–>New Module,打开如上图所示的面板,选择Java Library即可。一个APT项目至少应该由两个Java Library模块:

    • 首先需要一个Annotation模块,这个用来存放自定义的注解。
    • 另外需要一个Compiler模块,这个模块依赖Annotation模块。
  2. 项目的App模块和其它的业务模块都需要依赖Annotation模块,同时需要通过annotationProcessor依赖Compiler模块。
    app模块的gradle中依赖关系如下:

    implementation project(':annotation')
    annotationProcessor project(':factory-compiler')
    

在这里插入图片描述

图1-APT项目模块结构图

为什么要强调上述两个模块一定要是Java Library?如果创建Android Library模块你会发现不能找到AbstractProcessor这个类,这是因为Android平台是基于OpenJDK的,而OpenJDK中不包含APT的相关代码。因此,在使用APT时,必须在Java Library中进行。

  • annotation模块自定义注解

    @Target({ElementType.TYPE})   \\作用范围 Class
    
    @Retention(RetentionPolicy.CLASS)  \\生命周期:仅保留到.class文件
    
    public @interface Route {
        /** Path of route*/
        String value();  //类似于成员变量
    }
    
  • compiler模块实现自己的注解处理器

    public class RouteProcessor extends AbstractProcessor {
        
        @Override
        public synchronized void init(ProcessingEnvironment env){ 
            // 主要做一些初始化操作
        }
    
        @Override
        public Set<String> getSupportedAnnotationTypes() { 
           // 支持处理的注解类型, 在这里就是@Route
    
        }
        
        @Override
            public boolean process(Set<? extends TypeElement> annoations, RoundEnvironment env) { 
            //具体处理注解的逻辑,控制代码的生成
            processAnnotations();
        }
    
        @Override
        public SourceVersion getSupportedSourceVersion() {
          //java版本 如:jdk1.6or jdk1.7
       }
    
    }
    

    (1) public SourceVersion getSupportedSourceVersion()
    这个方法非常简单,只有一个返回值,用来指定当前正在使用的Java版本,通常return SourceVersion.latestSupported()即可。

    (2) public Set getSupportedAnnotationTypes()
    这个方法的返回值是一个Set集合,集合中指要处理的注解类型的名称(这里必须是完整的包名+类名,例如Route.class.getCanonicalName())。由于在本例中只需要处理@Route注解,因此Set集合中只需要添加@Route的名称即可。

    (3) public synchronized void init(ProcessingEnvironment processingEnvironment)
    这个方法用于初始化处理器,方法中有一个ProcessingEnvironment类型的参数,ProcessingEnvironment是一个注解处理工具的集合。它包含了众多工具类。例如:

    • Filer可以用来编写新文件;
    • Messager可以用来打印错误信息;
    • Elements是一个可以处理Element的工具类。

    在Java语言中,Element是一个接口,表示一个程序元素,它可以指代包、类、方法或者一个变量。Element已知的子接口有如下几种:

    • PackageElement 表示一个包程序元素。提供对有关包及其成员的信息的访问。
      • ExecutableElement 表示某个类或接口的方法、构造方法或初始化程序(静态或实例),包括注释类型元素。
      • TypeElement 表示一个类或接口程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是一种类,而注解类型是一种接口。
      • VariableElement 表示一个字段、enum 常量、方法或构造方法参数、局部变量或异常参数。

    把Java类看作一个类似XML或者JSON一样的东西。有了这个概念之后我们就可以很容易的理解什么是Element了。带着这个概念来看下面的代码:

    package com.annotation.factory;  //    PackageElement
    
    public class Circle {  //  TypeElement
        private int i; //   VariableElement
        private Triangle triangle;  //  VariableElement
    
        public Circle() {} //    ExecuteableElement
    
        public void draw(   //  ExecuteableElement
                            String s)   //  VariableElement
        {
            System.out.println(s);
        }
    
        @Override
        public void draw() {    //  ExecuteableElement
            System.out.println("Draw a circle");
        }
    }
    

    不同类型Element其实就是映射了Java中不同的类元素。

    (4) public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment)
    FactoryProcessor是最重要的一个方法了。先看这个方法的返回值,是一个boolean类型,返回值表示注解是否由当前Processor 处理。如果返回 true,则这些注解由此注解来处理,后续其它的 Processor 无需再处理它们;如果返回 false,则这些注解未在此Processor中处理并,那么后续 Processor 可以继续处理它们。
    在这个方法的方法体中,我们可以校验被注解的对象是否合法、可以编写处理注解的代码,以及自动生成需要的java文件等。因此说这个方法是AbstractProcessor 中的最重要的一个方法。我们要处理的大部分逻辑都是在这个方法中完成。

  • 在AS中使用注解

    @Route(path = "/test/activity2")
    public class MainActivity extends AppCompatActivity {
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_test2);
        }
    }
    

参考:

(6条消息) java注解与APT技术_fengxingzhe001的专栏-CSDN博客

(6条消息) Java进阶–编译时注解处理器(APT)详解_smily的博客-CSDN博客

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值