上节中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-compiler-plugin插件最好使用比较新的版本,我用的版本是3.8.1版本,老的版本对Annotation Processor的支持不是很好,可能会出不少问题,pom.xml中的plugin配置如下:
maven-compiler-plugin
3.8.1
true
-Adebug
依赖配置如下所示,其中processor属于编译时行为,不需要实际编译进Jar包或程序中,所以使用provided:
com.dream.test
hello-annotation
${project.version}
com.dream.test
hello-processor
${project.version}
provided
如此即配置完毕,相对来说还是比较简单的,不过还有另一种配置方式,对于注解处理器,也可以在maven-compiler-plugin中设置,不需要在依赖中使用provided,如下所示:
maven-compiler-plugin
3.8.1
true
-Adebug
com.dream.test
hello-processor
${project.version}
个人比较倾向于上面这种插件中定义的方式,这样可以比较明确的区分出注解处理器,从而使得依赖管理中的各个依赖的意义更加明晰。
最后,生成的Java文件的默认位置在target/generated-sources/annotations目录下。需要注意的是,在IDEA中编译后,代码中的生成类是能够识别不会报错误的,但是利用idea运行时会出现类找不到的错误,此时在idea全局设置中,找到Annotation Processors并启用即可,如下所示:
如果设置了还是不行,可以试一下Files => Invalidate Caches / Restart ...选项。
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的使用。