关于java:聊聊如何运用JAVA注解处理器APT

3次阅读

共计 4225 个字符,预计需要花费 11 分钟才能阅读完成。

什么是 APT

APT(Annotation Processing Tool)它是 Java 编译期注解处理器,它能够让开发人员在编译期对注解进行解决,通过 APT 能够获取到注解和被注解对象的相干信息,并依据这些信息在编译期按咱们的需要生成 java 代码模板或者配置文件(比方 SPI 文件或者 spring.fatories)等。APT 获取注解及生成代码都是在代码编译时候实现的,相比反射在运行时解决注解大大提高了程序性能

APT 的工作流程

什么是注解

:因为 APT = 注解 + 注解处理器 (AbstractProcessor)。因而须要理解什么是注解,不过对于 java 开发人员来说,注解应该是耳熟能详了,这边就不再阐述。如果不理解啥是注解的小伙伴,能够查看如下文章科普一下

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 具体内容能够查看如下链接

https://www.jianshu.com/p/899063e8452e

创立注解处理器步骤

  • 创立注解类
  • 创立一个继承自 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<>();

    @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) {
       // 注解解决实现,创立配置文件
        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 的简略版本

demo 链接

https://github.com/lyb-geek/springboot-learning/tree/master/springboot-apt

正文完
 0