我正在尝试根据AST(抽象语法树)重写在构建期间重命名Java接口中的方法和Kotlin接口中的函数 . 对于这个问题,我们忽略了重命名方法/函数给调用带来的影响 . 要找到要重命名的方法/函数,我正在使用自定义注释和注释处理器 . 我按照这些说明操作Java接口 .
我用三个模块创建了一个新项目 . app模块,注释模块和注释处理器模块 .
app模块是一个Android应用程序,包含两个独立的Java和Kotlin接口文件,每个文件都带有一个带注释的方法/函数 .
RenameJava.java
package nl.peperzaken.renametest;
import nl.peperzaken.renameannotation.Rename;
public interface RenameJava {
@Rename
void methodToRename();
}
RenameKotlin.kt
package nl.peperzaken.renametest
import nl.peperzaken.renameannotation.Rename
interface RenameKotlin {
@Rename
fun functionToRename()
}
注释模块是一个只包含 @Rename
注释的Java库,我们指定只在函数上允许它,我们说它可能只在源代码中可见 .
Rename.kt
package nl.peperzaken.renameannotation
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.SOURCE)
annotation class Rename
注释处理器模块是一个Java库,它只包含迭代具有注释的元素并对其进行转换的处理器 .
RenameProcessor.kt
package nl.peperzaken.renameprocessor
import com.google.auto.service.AutoService
import com.sun.source.util.Trees
import com.sun.tools.javac.processing.JavacProcessingEnvironment
import com.sun.tools.javac.tree.JCTree
import com.sun.tools.javac.tree.TreeTranslator
import com.sun.tools.javac.util.Names
import nl.peperzaken.renameannotation.Rename
import javax.annotation.processing.*
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import javax.tools.Diagnostic
@AutoService(Processor::class)
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes("nl.peperzaken.renameannotation.Rename")
class RenameProcessor : AbstractProcessor() {
private lateinit var trees: Trees
private lateinit var names: Names
private val visitor = object : TreeTranslator() {
override fun visitMethodDef(jcMethodDecl: JCTree.JCMethodDecl) {
super.visitMethodDef(jcMethodDecl)
// print original declaration
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
jcMethodDecl.toString()
)
// Rename declaration
jcMethodDecl.name = names.fromString("renamed")
// print renamed declaration
processingEnv.messager.printMessage(
Diagnostic.Kind.NOTE,
jcMethodDecl.toString()
)
// commit changes
result = jcMethodDecl
}
}
@Synchronized
override fun init(processingEnvironment: ProcessingEnvironment) {
super.init(processingEnvironment)
trees = Trees.instance(processingEnvironment)
val context = (processingEnvironment as JavacProcessingEnvironment).context
names = Names.instance(context)
}
override fun process(set: Set<TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
// Find elements that are annotated with @Rename
for (element in roundEnvironment.getElementsAnnotatedWith(Rename::class.java)) {
val tree = trees.getTree(element) as JCTree
tree.accept(visitor)
}
return true
}
}
Gradle files
我将以下内容添加到注释处理器 build.gradle
:
// Add annotation dependency
implementation project(':rename-annotation')
// Used to generate META-INF so the processor can run
compile 'com.google.auto.service:auto-service:1.0-rc4'
kapt "com.google.auto.service:auto-service:1.0-rc4"
// To prevent unresolved references during building. You might not need this dependency.
implementation files("${System.getProperty('java.home')}/../lib/tools.jar")
我在应用 build.gradle
添加了以下内容:
compileOnly project(':rename-annotation')
annotationProcessor project(':rename-processor')
注释 build.gradle
除了默认生成的注释外没有依赖项 .
我们有不同模块的原因是我们可以阻止注释和处理器构建到最终APK,因为我们只需要在构建期间 .
Output
日志显示Java界面中的方法已重命名:
Note:
@Rename()
void methodToRename();
Note:
@Rename()
void renamed();
没有为Kotlin界面生成日志 . 指示注释处理器未运行 .
当您查看生成的APK的 classes.dex
时,您将看到以下内容:
您可以看到Java接口的方法已正确重命名 . 虽然Kotlin界面的功能还没有 . 即使它出现在日志中 .
您还会在日志中注意到此警告:
app:'annotationProcessor'依赖项不会被识别为kapt注释处理器 . 请将配置名称更改为'kapt'以获取这些工件:'RenameTest:rename-processor:unspecified'并应用kapt插件:“apply plugin:'kotlin-kapt'” .
所以,让我们做警告的建议 . 将 apply plugin: 'kotlin-kapt'
添加到app build.gradle
并将 annotationProcessor
更改为 kapt
. 同步并重建后输出为:
Note:
@Rename()
void methodToRename();
Note:
@Rename()
void renamed();
Note:
@nl.peperzaken.renameannotation.Rename()
public abstract void functionToRename();
Note:
@nl.peperzaken.renameannotation.Rename()
public abstract void renamed();
Java和Kotlin文件的日志都出现了 . 你认为成功吗?查看新生成的APK的 classes.dex
将会让您反思,因为两者都是原始形式:
Question
有没有办法在最终的APK中获得所需的输出?输出结果是Java接口中的方法和Kotlin接口中的函数都被重命名 .
链接到示例项目:https://github.com/peperzaken/kotlin-annotation-rename-test
1 回答
Kapt不直接处理Kotlin文件 - 它会在Java文件存根上运行注释处理 . 因此,Kotlin文件的AST树中的更改仅对其他注释处理器可见,并且不会影响编译 .
请注意,Java AST API不是注释处理API(JSR 269)的一部分 - 它实际上是一个内部Javac API,显然,Kotlinc不是Javac .
解决问题的更可靠的方法是类文件后处理(或Kotlin编译器插件,但它不适用于Java) .
此外,在Kotlin中,您有
@JvmName()
注释,用于更改JVM声明名称 .