Java注解处理(Annotation Processor):(三) 代码生成

     上节中AutoService利用注解处理生成的Service文件,这次我们主要进行代码生成,分析注解处理过程中代码生成的过程以及Gradle和Maven应如何配置Annotation Processor的支持。

@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.TYPE)
public @interface Hello {
}

       本次示例注解如上所示,该注解作为一个单独的库模块提供(hello-annotation),待实现的功能很简单,就是在对应类或接口上打上该@Hello注解后,生成一个[被注解类名]Hello的Java类,该类提供一个静态方法sayHello

       接下来是对应的注解处理器的实现,如下所示,该类也是一样作为一个独立的库模块提供(hello-processor):

@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("com.dream.test.annotation.Hello")
@SupportedOptions("debug")
@AutoService(Processor.class)
public class HelloProcessor extends AbstractProcessor {
    private static final String HELLO_TEMPLATE =
            "package %1$s;\n\npublic class %2$sHello {\n  public static void sayHello() {\n    System.out.println(\"Hello %3$s\");\n  }\n}\n";

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        for (Element element : roundEnvironment.getElementsAnnotatedWith(Hello.class)) {
            TypeElement typeElem = (TypeElement) element;
            String typeName = typeElem.getQualifiedName().toString();
            Filer filer = processingEnv.getFiler();
            try (Writer sw = filer.createSourceFile(typeName + "Hello").openWriter()) {
                log("Generating " + typeName + "Hello source code");
                int lastIndex = typeName.lastIndexOf('.');
                sw.write(String.format(HELLO_TEMPLATE, typeName.substring(0, lastIndex), typeName.substring(lastIndex + 1), typeName));
            } catch (IOException e) {
                processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
            }
        }
        return true;
    }

    private void log(String msg) {
        if (processingEnv.getOptions().containsKey("debug")) {
            processingEnv.getMessager().printMessage(Diagnostic.Kind.NOTE, msg);
        }
    }
}

         功能和代码实现很简单,主要是通过Filer进行源码生成,在程序中使用也很简单,示例如下所示:

@Hello
public class Main {
    public static void main(String[] args) {
        MainHello.sayHello();
    }
}

         在Main上打上Hello标签后,会在编译时生成MainHello源文件并编译,从而后续在运行时可调用sayHello方法。除了上面的源码之外,接下来看一下在Maven和Gradle下如何配置注解处理器相关参数。


Maven Annotation Processor配置

       首先maven-compiler-plugin插件最好使用比较新的版本,我用的版本是3.8.1版本,老的版本对Annotation Processor的支持不是很好,可能会出不少问题,pom.xml中的plugin配置如下:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
         <showWarnings>true</showWarnings>  <!--输出Annotation Processor中所有级别的日志 -->
         <compilerArgument>-Adebug</compilerArgument> <!--传入debug参数 -->
    </configuration>
</plugin>

        依赖配置如下所示,其中processor属于编译时行为,不需要实际编译进Jar包或程序中,所以使用provided:

<dependency>
    <groupId>com.dream.test</groupId>
    <artifactId>hello-annotation</artifactId>
    <version>${project.version}</version>
</dependency>
<dependency>
    <groupId>com.dream.test</groupId>
    <artifactId>hello-processor</artifactId>
    <version>${project.version}</version>
    <scope>provided</scope>
</dependency>

          如此即配置完毕,相对来说还是比较简单的,不过还有另一种配置方式,对于注解处理器,也可以在maven-compiler-plugin中设置,不需要在依赖中使用provided,如下所示:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.8.1</version>
    <configuration>
        <showWarnings>true</showWarnings>
        <compilerArgument>-Adebug</compilerArgument>
        <annotationProcessorPaths>
            <path>
                <groupId>com.dream.test</groupId>
                <artifactId>hello-processor</artifactId>
                <version>${project.version}</version>
            </path>
         </annotationProcessorPaths>
    </configuration>
</plugin>

       个人比较倾向于上面这种插件中定义的方式,这样可以比较明确的区分出注解处理器,从而使得依赖管理中的各个依赖的意义更加明晰。

       最后,生成的Java文件的默认位置在target/generated-sources/annotations目录下。需要注意的是,在IDEA中编译后,代码中的生成类是能够识别不会报错误的,但是利用idea运行时会出现类找不到的错误,此时在idea全局设置中,找到Annotation Processors并启用即可,如下所示:

扫描二维码关注公众号,回复: 9272076 查看本文章

如果设置了还是不行,可以试一下Files => Invalidate Caches / Restart ...选项。


Gradle Annotation Processor配置

        Gradle的配置如下所示,和Maven相类似,对于Annotation Processor对应库,需要用annotationProcessor来进行依赖引用,保证注解处理器只用于编译阶段:

plugins {
    id 'java'
    id 'net.ltgt.apt-idea' version "0.21"
}

compileJava {
    options.compilerArgs << '-Adebug'
}

dependencies {
    compile project(':hello-annotation')
    annotationProcessor project(':hello-processor')
}

        特别需要注意的是apt-idea的插件,该插件会将生成的Java文件放置在build/generated/sources/annotationProcesssor/java/main下并会被idea所识别,据说在idea 2018.3之后可以不依赖该插件直接支持,具体还没试过,但是加个插件也没啥坏处;反而不加该插件的话,生成的源文件会放置在classes下并且会打包进行Jar中,不大合理(也可能是Gradle版本的问题)。


       总的说来,注解处理越来越多的应用使得其对应的编译工具以及IDE的支持也越来越全面,目前配置已经十分简单了。本节中的代码生成还是使用的原始字符串的方式直接生成的,后续我们将聊一聊poet的使用。

发布了42 篇原创文章 · 获赞 9 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/jjxojm/article/details/90349756