什么是APT
APT(Annotation Processing Tool)它是Java编译期注解处理器,它能够让开发人员在编译期对注解进行解决,通过APT能够获取到注解和被注解对象的相干信息,并依据这些信息在编译期按咱们的需要生成java代码模板或者配置文件(比方SPI文件或者spring.fatories)等。APT获取注解及生成代码都是在代码编译时候实现的,相比反射在运行时解决注解大大提高了程序性能
APT的工作流程
什么是注解
注:因为APT = 注解+ 注解处理器(AbstractProcessor)。因而须要理解什么是注解,不过对于java开发人员来说,注解应该是耳熟能详了,这边就不再阐述。如果不理解啥是注解的小伙伴,能够查看如下文章科普一下
baike.baidu.com/item/%E6%B3…
这边得特地说下元注解@Retention
因为APT是在java编译器应用,因而@Retention的value通常指定为source或者class,这样能够进步一点性能。就我集体而言,我偏向指定为source
APT之Element罕用元素以及Element元素罕用变量
1、罕用元素
这些元素映射到java,我通过一个例子大家应该就能够理解这些元素是指什么
2、Element元素罕用变量
更多element具体内容能够查看如下链接
www.jianshu.com/p/899063e84…
创立注解处理器步骤
创立注解类
创立一个继承自 AbstractProcessor 的类,这就是 APT 的外围类
注册处理器
创立注解处理器示例
注: 示例要实现的性能,通过一个自定义注解AutoComponent,通过注解处理器扫描解析AutoComponent注解,并生成lybgeek.components,spring通过解析lybgeek.components,实现bean注册
1、创立注解类
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoComponent {
}
复制代码
2、创立一个继承自 AbstractProcessor 的类
这边需介绍这个类外面几个外围的办法
public synchronized void init(ProcessingEnvironment processingEnv)
复制代码
init办法能够让咱们处理器的初始化阶段,通过ProcessingEnvironment来获取一些帮忙咱们来解决注解的工具类
// Element操作类,用来解决Element的工具
Elements elementUtils = processingEnv.getElementUtils();
// 类信息工具类,用来解决TypeMirror的工具
Types typeUtils = processingEnv.getTypeUtils();
// 日志工具类,因为在process()中不能抛出一个异样,那会使运行注解处理器的JVM解体。所以Messager提供给注解处理器一个报告谬误、正告以及提示信息的路径,用来写一些信息给应用此注解器的第三方开发者看
Messager messager = processingEnv.getMessager();
// 文件工具类,罕用来读取或者写资源文件
Filer filer = environment.getFiler();
复制代码
public Set<String> getSupportedAnnotationTypes()
复制代码
getSupportedAnnotationTypes办法用来指定须要解决的注解汇合,返回的汇合元素须要是注解全门路(包名+类名)
public SourceVersion getSupportedSourceVersion()
复制代码
getSupportedSourceVersion办法用来指定以后正在应用的Java版本,个别返回SourceVersion.latestSupported()示意最新的java版本即可
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)
复制代码
process是注解处理器外围办法,注解的解决和生成代码或者配置资源都是在这个办法中实现。
Java官网文档给出的注解处理过程的定义:注解处理过程是一个有序的循环过程。在每次循环中,一个处理器可能被要求去解决那些在上一次循环中产生的源文件和类文件中的注解。
每次循环都会调用process办法,process办法提供了两个参数,第一个是咱们申请解决注解类型的汇合(也就是咱们通过重写getSupportedAnnotationTypes办法所指定的注解类型),第二个是无关以后和上一次循环的信息的环境。返回值示意这些注解是否由此 Processor 申明,如果返回 true,则这些注解已申明并且不要求后续 Processor 解决它们;如果返回 false,则这些注解未声明并且可能要求后续 Processor 解决它们。
外围办法介绍完后,咱们通过示例来自定义一个注解处理器
@AutoService(Processor.class)
@SupportedOptions("debug")
public class AutoComponentProcessor extends AbstractComponentProcessor {
/** * 元素辅助类 */private Elements elementUtils;private Set<String> componentClassNames = new ConcurrentSkipListSet<>();@Overridepublic synchronized void init(ProcessingEnvironment processingEnv) { super.init(processingEnv); elementUtils = processingEnv.getElementUtils();}@Overridepublic Set<String> getSupportedAnnotationTypes() { return Collections.singleton(AutoComponent.class.getName());}@Overrideprotected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { // 注解解决实现,创立配置文件 if (roundEnv.processingOver()) { generateConfigFiles(); } else { processAnnotations(annotations, roundEnv); } return false;}
复制代码
3、注册处理器
因为处理器是通过SPI机制实现,因而它的注册,其实就是在META-INF/services底下创立javax.annotation.processing.Processor文件,文件内容为自定义的处理器类
com.github.lybgeek.apt.process.AutoComponentProcessor
复制代码
不过咱们能够在我的项目的POM中引入GAV
<dependency>
<groupId>com.google.auto.service</groupId> <artifactId>auto-service</artifactId> <version>1.0.1</version> <scope>provided</scope> </dependency>
复制代码
或者
<dependency>
<groupId>net.dreamlu</groupId> <artifactId>mica-auto</artifactId> <version>2.3.0</version> <scope>provided</scope> </dependency>
复制代码
在process的处理器上,加上注解
@AutoService(Processor.class)
复制代码
就会在编译期主动生成spi配置文件,它实现机制也是采纳APT
4、当咱们制作好处理器后,咱们能够将处理器打成jar,提供给我的项目用
示例
<dependency>
<groupId>${project.groupId}</groupId> <artifactId>springboot-apt-framework</artifactId> <version>${project.version}</version> </dependency>
复制代码
在我的项目编译后,就会在target的MATA-INF底下看到lybgeek.components文件
文件内容如下
Generated by LYB-GEEK AT TIME : 2023-01-12T17:14:24.982
com.github.lybgeek.test.service.EchoService
com.github.lybgeek.test.service.HelloService
复制代码
接下来就是解析lybgeek.components,并通过spring提供的扩大点和API进行bean注册,因为这块内容不属于APT的内容,本文就不再阐述,对这部分感兴趣的敌人,能够通过文末提供的demo链接查看
总结
在未接触APT之前,咱们可能会通过反射去解析注解并实现性能,接触APT之后,咱们又多了额定一种比反射更能晋升性能的实现实现。不过任何货色都有其实用场景,APT次要还是用在编译期帮咱们生成代码或者配置等,如果咱们我的项目要应用APT生成的代码,有可能还是须要通过反射解决。
咱们耳熟能详的lombok、mapstruct、包含spring5.0之后提供的@Index都是通过APT来实现,文中的示例其实就是仿造spring index来实现,能够看成是spring index的简略版本