Java进阶--编译时注解处理器(APT)详解

更新时间:2023-06-20 23:21:35 阅读: 评论:0

Java进阶--编译时注解处理器(APT)详解
本⽂同步发布在,未经本⼈允许不得转载
上篇⽂章我们使⽤注解+反射实现了⼀个仿ButterKnife功能的⽰例。考虑到反射是在运⾏时完成的,多少会影响程序性能。因
此,ButterKnife本⾝并⾮基于注解+反射来实现的,⽽是⽤APT技术在编译时处理的。APT什么呢?接下来⼀起来看。
⼀、APT简介
1.什么是APT?
APT即为Annotation Processing Tool,它是javac的⼀个⼯具,中⽂意思为编译时注解处理器。APT可以⽤来在编译时扫描和处理注解。通过APT可以获取到注解和被注解对象的相关信息,在拿到这些信息后我们可以根据需求来⾃动的⽣成⼀些代码,省去了⼿动编写。注意,获取注解及⽣成代码都是在代码编译时候完成的,相⽐反射在运⾏时处理注解⼤⼤提⾼了程序性能。APT的核⼼是AbstractProcessor类,关于AbstractProcessor类后⾯会做详细说明。
2.哪⾥⽤到了APT?
APT技术被⼴泛的运⽤在Java框架中,包括Android项以及Java后台项⽬,除了上⾯我们提到的ButterKnife之外,像EventBus 、Dagger2以及阿⾥的ARouter路由框架等都运⽤到APT技术,因此要想了解以、探究这些第三⽅框架的实现原理,APT就是我们必须要掌握的。
3.如何在Android Studio中构建⼀个APT项⽬?
APT项⽬需要由⾄少两个Java Library模块组成,不知道什么是Java Library?没关系,⼿把⼿来叫你如何创建⼀个Java Library。
⾸先,新建⼀个Android项⽬,然后File–>New–>New Module,打开如上图所⽰的⾯板,选择Java Library即可。刚才说到⼀个APT项⽬⾄少应该由两个Java Library模块。那么这两个模块分别是什么作⽤呢?
1.⾸先需要⼀个Annotation模块,这个⽤来存放⾃定义的注解。
2. 另外需要⼀个Compiler模块,这个模块依赖Annotation模块。
3.项⽬的App模块和其它的业务模块都需要依赖Annotation模块,同时需要通过annotationProcessor依赖Compiler模块。
app模块的gradle中依赖关系如下:
implementation project(':annotation')
annotationProcessor project(':factory-compiler')
APT项⽬的模块的结构图如下所⽰:
为什么要强调上述两个模块⼀定要是Java Library?如果创建Android Library模块你会发现不能找到Ab
stractProcessor这个类,这是因为Android平台是基于OpenJDK的,⽽OpenJDK中不包含APT的相关代码。因此,在使⽤APT时,必须在Java Library中进⾏。
⼆、从⼀个例⼦开始认识APT
在学习Java基础的时候想必⼤家都写过简单⼯⼚模式的例⼦,回想⼀下什么是简单⼯⼚模式。接下来引⼊⼀个⼯⼚模式的例⼦,⾸先定义⼀个形状的接⼝IShape,并为其添加 draw()⽅法:
public interface IShape {
void draw();
双鱼座女
}
接下来定义⼏个形状实现IShape接⼝,并重写draw()⽅法:龙生九子
public class Rectangle implements IShape {
@Override
public void draw(){
System.out.println("Draw a Rectangle");
}
}
public class Triangle implements IShape {
@Override
public void draw(){中国文脉读后感
System.out.println("Draw a Triangle");
}
}
public class Circle implements IShape {
@Override
public void draw(){
System.out.println("Draw a circle");
}
}
接下来我们需要⼀个⼯⼚类,这个类接收⼀个参数,根据我们传⼊的参数创建出对应的形状,代码如下:
public class ShapeFactory {
public Shape create(String id){
if(id ==null){
throw new IllegalArgumentException("id is null!");
}
if("Circle".equals(id)){
return new Circle();
}
if("Rectangle".equals(id)){
return new Rectangle();
}
if("Triangle".equals(id)){
return new Triangle();
}动物科学专业
throw new IllegalArgumentException("Unknown id = "+ id);
}
}
以上就是⼀个简单⼯⼚模式的⽰例代码,想必⼤家都能够理解。
那么,现在问题来了,在项⽬开发过程中,我们随时可能会添加⼀个新的形状。此时就不得不修改⼯⼚类来适配新添加的形状了。试想⼀下,每添加⼀个形状类都需要我们⼿动去更新Factory类,是不是影响了我们的开发效率?如果这个Factory类能够根据我们添加新的形状来同步更新Factory代码,岂不是就省了我们很多时间了吗?
应该怎么做才能满⾜上述需求呢?在第⼀节中已经提到了使⽤APT可以帮助我们⾃动⽣成代码。那么这个⼯⼚类是不是可以使⽤APT技术来⾃动⽣成呢?我们唯⼀要做的事情就是新添加的形状类上加上⼀个注解,注解处理器就会在编译时根据注解信息⾃动⽣成ShapeFactory类的代码了,美哉,美哉!理想很丰满,但是,现实很⾻感。虽然已经明确了要做什么,但是想要注解处理器帮我们⽣成代码,却还有很长的路要⾛。不过,不当紧,接下来我们将⼀步步实现注解处理器并让其⾃动⽣成Factory类。
三、使⽤APT处理注解
1.定义Factory注解
⾸先在annotation模块下添加⼀个Factory的注解,Factory注解的Target为ElementType,表⽰它可以
注解类、接⼝或者枚举。Retention指定为RetentionPolicy.CLASS,表⽰该在字节码中有效。Factory注解添加两个成员,⼀个Class类型的type,⽤来表⽰注解的类的类型,相同的类型表⽰属于同⼀个⼯⼚。令需⼀个String类型的id,⽤来表⽰注解的类的名称。Factory注解代码如下:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public@interface Factory {
Class type();
String id();
}
接下来我们⽤@Factory去注解形状类,如下:
@Factory(id ="Rectangle", type = IShape.class)
public class Rectangle implements IShape {
@Override
public void draw(){
System.out.println("Draw a Rectangle");
}
}
...其他形状类代码类似不再贴出
2.认识AbstractProcessor
接下来,就到了我们本篇⽂章所要讲的核⼼了。没错,就是AbstractProcessor!我们先在factory-compiler模块下创建⼀个FactoryProcessor类继承AbstractProcessor ,并重写相应的⽅法,代码如下:
@AutoService(Processor.class)
public class FactoryProcessor extends AbstractProcessor {
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment){
super.init(processingEnvironment);
}
@Override
public Set<String>getSupportedAnnotationTypes(){
SupportedAnnotationTypes();电视迷吧
}
@Override
public boolean process(Set<?extends TypeElement> t, RoundEnvironment roundEnvironment){
return fal;
}
@Override
public SourceVersion getSupportedSourceVersion(){
SupportedSourceVersion();
}
}
可以看到,在这个类上添加了@AutoService注解,它的作⽤是⽤来⽣成META-INF/rvices/javax.annotation.processing.Processor ⽂件的,也就是我们在使⽤注解处理器的时候需要⼿动添加META-INF/rvices/javax.annotation.processing.Processor,⽽有了
@AutoService后它会⾃动帮我们⽣成。是Google开发的⼀个库,使⽤时需要在factory-compiler中添加依赖,如下:
implementation 'le.auto.rvice:auto-rvice:1.0-rc4'
接下来我们将⽬光移到FactoryProcessor类内部,可以看到在这个类中重写了四个⽅法,我们由易到难依次来看:
(1) public SourceVersion getSupportedSourceVersion()
这个⽅法⾮常简单,只有⼀个返回值,⽤来指定当前正在使⽤的Java版本,通常return SourceVersion.latestSupported()即可。
(2) public Set<String> getSupportedAnnotationTypes()
这个⽅法的返回值是⼀个Set集合,集合中指要处理的注解类型的名称(这⾥必须是完整的包名+类名,例如
(3) public synchronized void init(ProcessingEnvironment processingEnvironment)
这个⽅法⽤于初始化处理器,⽅法中有⼀个ProcessingEnvironment类型的参数,ProcessingEnvironment是⼀个注解处理⼯具的集合。它包含了众多⼯具类。例如:乡下人家课文
Filer可以⽤来编写新⽂件;
Messager可以⽤来打印错误信息;
Elements是⼀个可以处理Element的⼯具类。
在这⾥我们有必要认识⼀下什么是Element。 在Java语⾔中,Element是⼀个接⼝,表⽰⼀个程序元素,它可以指代包、类、⽅法或者⼀个变量。Element已知的⼦接⼝有如下⼏种:
PackageElement 表⽰⼀个包程序元素。提供对有关包及其成员的信息的访问。
ExecutableElement 表⽰某个类或接⼝的⽅法、构造⽅法或初始化程序(静态或实例),包括注释类型元素。
TypeElement 表⽰⼀个类或接⼝程序元素。提供对有关类型及其成员的信息的访问。注意,枚举类型是⼀种类,⽽注解类型是⼀种接⼝。
VariableElement 表⽰⼀个字段、enum 常量、⽅法或构造⽅法参数、局部变量或异常参数。
接下来,我希望⼤家先来理解⼀个新的概念,即抛弃我们现有对Java类的理解,把Java类看作是⼀个
结构化的⽂件。什么意思?就是把Java类看作⼀个类似XML或者JSON⼀样的东西。有了这个概念之后我们就可以很容易的理解什么是Element了。带着这个概念来看下⾯的代码:
package;//    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> t, RoundEnvironment roundEnvironment)
终于,到了FactoryProcessor类中最后⼀个也是最重要的⼀个⽅法了。先看这个⽅法的返回值,是⼀个boolean类型,返回值表⽰注解是否由当前Processor 处理。如果返回 true,则这些注解由此注解来处理,后续其它的 Processor ⽆需再处理它们;如果返回 fal,则这些注解未在此Processor中处理并,那么后续 Processor 可以继续处理它们。
在这个⽅法的⽅法体中,我们可以校验被注解的对象是否合法、可以编写处理注解的代码,以及⾃动⽣成需要的java⽂件等。因此说这个⽅法是AbstractProcessor 中的最重要的⼀个⽅法。我们要处理的⼤部分逻辑都是在这个⽅法中完成。
了解上述四个⽅法之后我们便可以初步的来编写FactoryProcessor类的代码了,如下:
板蓝根冲剂

本文发布于:2023-06-20 23:21:35,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/82/1001755.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:注解   模块   处理   代码   类型   添加
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图