JAVA AbstractProcessor javac代码编译期SPI接口

背景引入

在使用spring-boot开发过程中,自定义属性映射yml中自定义配置时有个有趣的现象:在yml文件里点住配置项(ctrl)就会跳转到对应的java类属性,很好奇这是什么黑科技;

下来研究一番终于大概知道了其一点内在乾坤,首先我们需要引入该依赖

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
        </dependency>

然后我们看看该依赖引入的jar包结构,在jar包路径下有个META-INF下有个services目录,这个目录很熟悉即java SPI服务发现规范目录;

于是查阅 javax.annotation.processing.Processor 相关的资料,该SPI实现类会在编辑期间即javac期间被编译器调用【参考】;这下子幡然醒悟,有点类型编译期间动态代理生成的模式(其原理是 spring-boot-configuration-processor相关组件在工程编译期生成了一个文件:spring-configuration-metadata.json,然后开发工具根据这个文件来做对应的代码跳转);

手写一个注解处理器,注解处理器的开发模式一般是处理器提供方在一个工程,能够独立编译和打包;然后再在其他工程里引入这打包好的依赖,因为提供方我们在编译和打包的时候需要设置屏蔽javac注解处理器,但是在使用方工程里我们要开启javac注解处理器;

注解处理器工程

工程大致结构如下

自定义注解(ApiAnnotation)

package com.xx.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Retention(RetentionPolicy.SOURCE)
@Target( {ElementType.TYPE, ElementType.METHOD})
public @interface ApiAnnotation {
    String author() default "XX";
}

这里需要注意的是注解里的元注解 @Retention 要标记在为源码生效即:@Retention(RetentionPolicy.SOURCE)

注解处理器处理类(DemoProcessor)

这个类写法一般要集成  AbstractProcessor,然后覆盖他的一些方法(摘自其他文档)

  • init(ProcessingEnvironment processingEnvironment):
    每一个注解处理器类都必须有一个空的构造函数。然而,这里有一个特殊的init()方法,它会被注解处理工具调用,并输入ProcessingEnviroment参数。ProcessingEnviroment提供很多有用的工具类Elements, Types和Filer。后面我们将看到详细的内容。
  • process(Set<? extends TypeElement> annotations, RoundEnvironment env):
    这相当于每个处理器的主函数main()。扫描、评估和处理注解的代码,以及生成Java文件。输入参数RoundEnviroment,可以让你查询出包含特定注解的被注解元素。后面我们将看到详细的内容。
  • getSupportedAnnotationTypes():
  • 这里你必须指定,这个注解处理器是注册给哪个注解的。注意,它的返回值是一个字符串的集合,包含本处理器想要处理的注解类型的合法全称。换句话说,你在这里定义你的注解处理器注册到哪些注解上。
  • getSupportedSourceVersion():
  • 用来指定你使用的Java版本。通常这里返回SourceVersion.latestSupported()。然而,如果你有足够的理由只支持Java 6的话,你也可以返回SourceVersion.RELEASE_6。我推荐你使用前者。
    在Java 7中,你也可以使用注解来代替getSupportedAnnotationTypes()和getSupportedSourceVersion(),像这样:
    @SupportedAnnotationTypes(“com.starcor.annotation.RouterAnnotation”),其中括号内内容表示要处理的注解名称,要写全名。

我们自定义一个如下,简单打印一下注解名称:

package com.xx;

import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.TypeElement;

@SupportedAnnotationTypes( {"com.xx.annotation.ApiAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class DemoProcessor extends AbstractProcessor {
    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        for(TypeElement typeElement : annotations){
            System.out.println("getQualifiedName--" + typeElement.getQualifiedName());
        }
        return false;
    }
}

其中支持的源码版本和注解类型也可以通过注解指定,如本例的

@SupportedAnnotationTypes( {"com.xx.annotation.ApiAnnotation"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)

SPI配置文件

我们需要在规范的SPI路径下新增我们的处理器类名,即MATA-INF/services下新增了文件名为 javax.annotation.processing.Processor 的文件,内容是我们的真实处理类(Processor的子类)

pom编译配置

你会发现在不修改pom配置文件直接执行compile以上的goal你会发现工程编译不过,并会提示:java: java.util.ServiceConfigurationError: javax.annotation.processing.Processor: Provider com.xx.DemoProcessor not found

java: 编译器 (1.8.0_261) 中出现异常错误。如果在 Bug Database (http://bugs.java.com) 中没有找到该错误, 请通过 Java Bug 报告页 (http://bugreport.java.com) 建立该 Java 编译器 Bug。请在报告中附上您的程序和以下诊断信息。谢谢。
java: java.util.ServiceConfigurationError: javax.annotation.processing.Processor: Provider com.xx.DemoProcessor not found
java: 	at java.util.ServiceLoader.fail(ServiceLoader.java:239)
java: 	at java.util.ServiceLoader.access$300(ServiceLoader.java:185)
java: 	at java.util.ServiceLoader$LazyIterator.nextService(ServiceLoader.java:372)
java: 	at java.util.ServiceLoader$LazyIterator.next(ServiceLoader.java:404)
java: 	at java.util.ServiceLoader$1.next(ServiceLoader.java:480)
java: 	at org.jetbrains.jps.javac.Iterators$9.next(Iterators.java:165)

这是因为默认情况下javac是开启了注解处理器,发现SPI目录下有指定的处理器就回去找,但是我们执行的目的就是要把自定义的处理器编译出来,这就形成了一个类似“死锁”的场景,最终找不到类;

所以我们要在编译的时候关闭掉编译处理器

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.0</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                    <!--Disable annotation processing for ourselves,关闭注解处理器-->
                    <compilerArgument>-proc:none</compilerArgument>
                </configuration>
            </plugin>
        </plugins>
    </build>

这样就可以编译成功了,然后我们install到本地并deploy到远程仓库供其他工厂引入使用;

新建一个工程:refdemo

 在refdemo工程里引入依赖

    <dependencies>
        <dependency>
            <groupId>com.uu</groupId>
            <artifactId>processor</artifactId>
            <version>1.0.0</version>
        </dependency>
    </dependencies>

java代码用注解标记上 

package com.uu;

import com.xx.annotation.ApiAnnotation;

@ApiAnnotation
public class Demo {
}

执行compile

打印出了:"getQualifiedName--com.xx.annotation.ApiAnnotation"

[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ refdemo ---
[WARNING] Using platform encoding (UTF-8 actually) to copy filtered resources, i.e. build is platform dependent!
[INFO] Copying 0 resource
[INFO] 
[INFO] --- maven-compiler-plugin:3.8.0:compile (default-compile) @ refdemo ---
[INFO] Changes detected - recompiling the module!
[WARNING] File encoding has not been set, using platform encoding UTF-8, i.e. build is platform dependent!
[INFO] Compiling 1 source file to E:\code-opensource\code-demoparent\annotationprocessor\refdemo\target\classes
getQualifiedName--com.xx.annotation.ApiAnnotation
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  1.687 s
[INFO] Finished at: 2021-02-08T11:10:24+08:00
[INFO] ------------------------------------------------------------------------
[WARNING] The requested profile "nexus" could not be activated because it does not exist.

源代码可以参考:https://gitee.com/aqu415/code-demoparent/tree/develop/annotationprocessor

我们用到的lombok里面就是用这个特性

おすすめ

転載: blog.csdn.net/Aqu415/article/details/113719362