如何使用 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 中的注释处理将方法附加到现有类?的主要内容,如果未能解决你的问题,请参考以下文章

在Kotlin中使用注释处理Android框架 kapt

Kotlin 多平台注释处理

Kotlin 中的“宁愿在该类上运行匕首处理器”

文件类的注释

将代码从Java更改为Kotlin后,注释未正确转换

在Java类中使用Kotlin注释类