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项目
-
首先,新建一个Android项目,然后File–>New–>New Module,打开如上图所示的面板,选择Java Library即可。一个APT项目至少应该由两个Java Library模块:
- 首先需要一个Annotation模块,这个用来存放自定义的注解。
- 另外需要一个Compiler模块,这个模块依赖Annotation模块。
-
项目的App模块和其它的业务模块都需要依赖Annotation模块,同时需要通过annotationProcessor依赖Compiler模块。
app模块的gradle中依赖关系如下:implementation project(':annotation') annotationProcessor project(':factory-compiler')
为什么要强调上述两个模块一定要是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); } }
参考: