什么是APT
APT(Annotation Processing Tool)它是JAVA編譯期注解處理器,它可以讓開發(fā)人員在編譯期對注解進行處理,通過APT可以獲取到注解和被注解對象的相關信息,并根據(jù)這些信息在編譯期按我們的需求生成java代碼模板或者配置文件(比如SPI文件或者spring.fatories)等。APT獲取注解及生成代碼都是在代碼編譯時候完成的,相比反射在運行時處理注解大大提高了程序性能
APT的工作流程
什么是注解
注:因為APT = 注解+ 注解處理器(AbstractProcessor)。因此需要了解什么是注解,不過對于java開發(fā)人員來說,注解應該是耳熟能詳了,這邊就不再論述。如果不了解啥是注解的小伙伴,可以查看如下文章科普一下
https://baike.baidu.com/item/%E6%B3%A8%E8%A7%A3/22344968
這邊得特別說下元注解@Retention
因為APT是在java編譯器使用,因此@Retention的value通常指定為source或者class,這樣可以提高一點性能。就我個人而言,我傾向指定為source
APT之Element常用元素以及Element元素常用變量
1、常用元素
這些元素映射到java,我通過一個例子大家應該就可以了解這些元素是指什么
2、Element元素常用變量
更多element詳細內(nèi)容可以查看如下鏈接
https://www.jianshu.com/p/899063e8452e
創(chuàng)建注解處理器步驟
- 創(chuàng)建注解類
- 創(chuàng)建一個繼承自 AbstractProcessor 的類,這就是 APT 的核心類
- 注冊處理器
創(chuàng)建注解處理器示例
注: 示例要實現(xiàn)的功能,通過一個自定義注解AutoComponent,通過注解處理器掃描解析AutoComponent注解,并生成lybgeek.components,spring通過解析lybgeek.components,實現(xiàn)bean注冊
1、創(chuàng)建注解類
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface AutoComponent {
}
2、創(chuàng)建一個繼承自 AbstractProcessor 的類
這邊需介紹這個類里面幾個核心的方法
public synchronized void init(ProcessingEnvironment processingEnv)
init方法可以讓我們處理器的初始化階段,通過ProcessingEnvironment來獲取一些幫助我們來處理注解的工具類
// Element操作類,用來處理Element的工具
Elements elementUtils = processingEnv.getElementUtils();
// 類信息工具類,用來處理TypeMirror的工具
Types typeUtils = processingEnv.getTypeUtils();
// 日志工具類,因為在process()中不能拋出一個異常,那會使運行注解處理器的JVM崩潰。所以Messager提供給注解處理器一個報告錯誤、警告以及提示信息的途徑,用來寫一些信息給使用此注解器的第三方開發(fā)者看
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官方文檔給出的注解處理過程的定義:注解處理過程是一個有序的循環(huán)過程。在每次循環(huán)中,一個處理器可能被要求去處理那些在上一次循環(huán)中產(chǎn)生的源文件和類文件中的注解。
每次循環(huán)都會調(diào)用process方法,process方法提供了兩個參數(shù),第一個是我們請求處理注解類型的集合(也就是我們通過重寫
getSupportedAnnotationTypes方法所指定的注解類型),第二個是有關當前和上一次循環(huán)的信息的環(huán)境。返回值表示這些注解是否由此 Processor 聲明,如果返回 true,則這些注解已聲明并且不要求后續(xù) Processor 處理它們;如果返回 false,則這些注解未聲明并且可能要求后續(xù) Processor 處理它們。
核心方法介紹完后,我們通過示例來自定義一個注解處理器
@AutoService(Processor.class)
@SupportedOptions("debug")
public class AutoComponentProcessor extends AbstractComponentProcessor {
/**
* 元素輔助類
*/
private Elements elementUtils;
private Set<String> componentClassNames = new ConcurrentSkipListSet<>();
@Override
public synchronized void init(ProcessingEnvironment processingEnv) {
super.init(processingEnv);
elementUtils = processingEnv.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
return Collections.singleton(AutoComponent.class.getName());
}
@Override
protected boolean processImpl(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// 注解處理完成,創(chuàng)建配置文件
if (roundEnv.processingOver()) {
generateConfigFiles();
} else {
processAnnotations(annotations, roundEnv);
}
return false;
}
3、注冊處理器
因為處理器是通過SPI機制實現(xiàn),因此它的注冊,其實就是在META-INF/services底下創(chuàng)建
javax.annotation.processing.Processor文件,文件內(nèi)容為自定義的處理器類
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配置文件,它實現(xiàn)機制也是采用APT
4、當我們制作好處理器后,我們可以將處理器打成jar,提供給項目用
示例
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>springboot-apt-framework</artifactId>
<version>${project.version}</version>
</dependency>
在項目編譯后,就會在target的MATA-INF底下看到lybgeek.components文件
文件內(nèi)容如下
# 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注冊,因為這塊內(nèi)容不屬于APT的內(nèi)容,本文就不再論述,對這部分感興趣的朋友,可以通過文末提供的demo鏈接查看
總結(jié)
在未接觸APT之前,我們可能會通過反射去解析注解并實現(xiàn)功能,接觸APT之后,我們又多了額外一種比反射更能提升性能的實現(xiàn)實現(xiàn)。不過任何東西都有其適用場景,APT主要還是用在編譯期幫我們生成代碼或者配置等,如果我們項目要使用APT生成的代碼,有可能還是需要通過反射處理。
我們耳熟能詳?shù)膌ombok、mapstruct、包括spring5.0之后提供的@Index都是通過APT來實現(xiàn),文中的示例其實就是仿造spring index來實現(xiàn),可以看成是spring index的簡單版本
demo鏈接
https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apt