如何使用 java / kotlin 中的注释处理将方法附加到现有类?
Posted
技术标签:
【中文标题】如何使用 java / kotlin 中的注释处理将方法附加到现有类?【英文标题】:How to append a method to existing class using annotation processing in java / kotlin? 【发布时间】:2018-10-20 18:49:24 【问题描述】:我是注释处理和代码生成的新手。我想了解如何执行此类操作,例如将新方法附加到现有类。这是我想做的一个例子:
假设我们有一个带有像这样的自定义注释的类:
class SourceClass
@CustomAnnotation
fun annotatedFun1(vararg argument: Any)
//Do something
@CustomAnnotation
fun annotatedFun2(vararg argument: Any)
//Do something
fun someOtherFun()
//Do something
我想要得到的结果 - 该类的扩展副本:
class ResultClass
fun hasFunWithName(name: String): Boolean
return (name in arrayOf("annotatedFun1", "annotatedFun2"))
fun callFunByName(name: String, vararg arguments: Any)
when (name)
"annotatedFun1" -> annotatedFun1(*arguments)
"annotatedFun2" -> annotatedFun2(*arguments)
fun annotatedFun1(vararg argument: Any)
//Do something
fun annotatedFun2(vararg argument: Any)
//Do something
fun someOtherFun()
//Do something
我已经了解了如何创建注释处理器。我正在寻找一种方法来保存源类中的所有现有字段、属性和方法,并为其添加更多方法。
如果可以在不创建新类的情况下修改类 - 这将是完美的,但在所有教程中只创建新类,我没有找到任何将源类的所有内容复制到另一个类的示例。
请不要建议使用反射。我需要这个用于android,所以反射不是资源成本的选择原因。我正在寻找编译时解决方案。
在应用程序中实现的自定义脚本语言是必需的,应该用于简化包装类结构。当这项工作直接在代码中完成时——当这样的方法计数超过每个类 20 时,它看起来很糟糕。
【问题讨论】:
您找到解决方案了吗?就我而言,我需要在注释处理阶段向现有方法/归档添加注释。 @SashaShpota 不,我目前没有解决方案。写一个,如果你发现了什么。 【参考方案1】:使用 Kotlin,您可以使用扩展函数,这是向您无法控制的现有类添加新功能的推荐方法。 https://kotlinlang.org/docs/reference/extensions.html
【讨论】:
我要求的是不同的东西。这不是我问题的答案。【参考方案2】:您可能会遵循Project Lombok 使用的模式。详情请参阅How does lombok work? 或source code。
另一种选择是编写一个扩展源类的新类:
class ResultClass : SourceClass
fun hasFunWithName(name: String): Boolean
return (name in arrayOf("annotatedFun1", "annotatedFun2"))
fun callFunByName(name: String, vararg arguments: Any)
when (name)
"annotatedFun1" -> annotatedFun1(*arguments)
"annotatedFun2" -> annotatedFun2(*arguments)
或者也许使用组合代替并为SourceClass
中的所有公共方法实现覆盖方法。
如果您不依赖于使用注释处理来执行此操作,则可以在编译之前使用单独的一段自定义代码来处理源代码文件。也许使用像/@CustomAnnotation\s+.*fun (\w+)\s*\(([^)]*)\)/gm
(Test on Regex101) 这样的正则表达式来查找带注释的方法。
【讨论】:
谢谢 (+1)。您能否详细说明龙目岛是如何做到的?在链接的 SO 问题中提到,在 Lombok 中,他们将javax.lang.model.element.Element
转换为底层内部 API 以修改现有源,但我在代码库中找不到它。可能我需要很好地学习代码库,但如果你知道它是如何/在哪里完成的,请告诉我。
对不起,我不知道龙目岛项目的效果如何,无法解释。【参考方案3】:
这是我最近使用的 Java 注释处理的 good example。
这是an implementation 的@Immutable
注释。
查看 ByteBuddy 或 Kotlin Poet 以了解额外代码生成的工作原理。
对于 Kotlin,您的操作几乎相同,请查看 this manual 了解 Kotlin 特定的步骤。
【讨论】:
谢谢 (+1)。您能否详细说明如何实现这一目标?据我了解,Kotlin Poet 有助于生成新代码,而我需要更改已经现有类的语法树。我在源码中没有发现 ByteBuddy 使用注解处理。我很清楚如何创建注释处理器,不清楚的是如何修改现有文件以便将其编译为增强的字节码。 要添加新方法,您需要扩展类定义阶段或在您自己的AnnotationProcessor
中实现process
方法。然后你可以getElementsAnnotatedWith
一些东西并为这些元素应用代码生成。在此处查看第 5 节:baeldung.com/byte-buddy。您也可能需要重新定义整个类,请查看第 6 节了解更多详细信息
谢谢你,谢尔盖。我是否理解正确,我在注释处理阶段使用 ByteBuddy 引入的更改将保留在生成的类文件的字节码中?
您只会在字节码(.class 文件)中获得声明的方法
也许我遗漏了一些东西,但是在注释处理阶段,我无法访问我需要修改的类的Class
对象(它尚未编译)。同时,我需要它让 ByteBuddy 工作。我该如何处理?【参考方案4】:
如果我正确理解了要求,那么目标是实现如下所述的内容。
您有一个源文件C.java
,它定义了类C
,如下所示:
public final class C
@Getter
@Setter
private int m_IntValue;
@Getter
@Constructor
private final String m_Text;
现在您想知道如何编写一个注释处理器,该处理器在编译期间跳转并修改编译器看到的 C.java
的源代码,如下所示:
public final class C
private int m_IntValue;
public final int getIntValue() return m_IntValue;
public final void setIntValue( final int intValue ) m_IntValue = intValue;
private final String m_Text;
public final String getText() return m_Text;
public C( final String text ) m_Text = text;
坏消息是,这是不可能的……没有注释处理器,Java 15 也没有。
对于 Java 8,有一种方法,使用一些带有反射的内部类来说服 AP 以某种方式操作已经加载的源代码,并让编译器对其进行第二次编译。不幸的是,它失败的次数多于工作的次数……
目前,注释处理器只能创建一个新的(在附加的意义上)源文件。所以一种解决方案可能是扩展类(当然,这不适用于上面的示例类C
,因为类本身是最终的,所有属性都是私有的……
因此编写预处理器将是另一种解决方案;您的硬盘驱动器上没有文件C.java
,而是一个名为C.myjava
的文件,预处理器将使用该文件生成C.java
,然后编译器使用该文件。但这不是由注释处理器完成的,但可能会以这种方式滥用它。
您还可以使用编译器生成的字节码,并在其中添加缺少的(或附加的)功能。但这与注释处理相去甚远……
总结一下:今天(从 Java 15 开始),注解处理器不允许对现有源代码进行操作(您甚至不能排除某些源代码正在编译);您只能使用注释处理器生成额外的源文件。
【讨论】:
谢谢 (+1)。我猜Lombok 也用 Java 15 解决了这个问题。以上是关于如何使用 java / kotlin 中的注释处理将方法附加到现有类?的主要内容,如果未能解决你的问题,请参考以下文章