【问题标题】:Kotlin: Possible to modify functions during compile time through metaprogramming?Kotlin:可以在编译期间通过元编程修改函数吗?
【发布时间】:2019-02-04 19:42:07
【问题描述】:

在 JavaScript/Python 等动态语言中,可以在运行时覆盖或“修改”函数。例如,为了修改 JS 中的alert 函数,可以这样做:

const _prev_alert = window.alert;
window.alert = function() {
  _prev_alert.apply(this, arguments);
  console.log("Alert function was called!");
}

这将输出“Alert function was called!”每次调用alert 函数时都会发送到控制台。

现在,由于 Kotlin-JVM 或 Kotlin-Native 的静态特性,显然这样的事情在运行时是不可能的。但是,对于那些相同的语言,是否有可能在编译期间修改非编译函数?我不是指来自库的预编译函数,而是我在我正在开发的同一个项目中编写的函数。

例如,假设我编写了一个名为get_number 的函数。我可以修改get_number 以返回不同的数字而不改变它在 main 中的调用方式并且不直接修改它的代码吗? (或者有没有办法我可以写出原始的get_number 以便以后可以进行修改?)

fun main(args: Array<String>) {
    println(get_number())
}

fun get_number(): Int {
    return 3
}

// Without modifying the code above, can I get main to print something besides 3?

我一直在阅读 Kotlin 的带有注释和反射的元编程,所以也许这些可以控制编译器的行为并覆盖 get_number 的代码?或者这是完全的疯狂,并且这种性质的事情唯一可能的方法是通过在 Kotlin 上开发我自己的、独立的元编程包装器?

另外,再次澄清一下,这个问题与 Kotlin-JS 无关,答案(如果存在)应该适用于 Kotlin-JVM 或 Native。

【问题讨论】:

  • 这里的最终目标有点不清楚。从应用程序设计的角度来看,使用适当的设计模式几乎总是比开始依赖 dynamic proxiesreflectionAOP 之类的东西更可取。那么,可以做到吗?是的。应该这样做吗?可能不会。
  • @RobbyCornelissen 它的目的是移植一个休闲、可扩展的教育游戏开发库,该库使用与 JavaScript 类似的元编程风格。修改大多是抽象的,只是为了清楚起见,它们只是用于不篡改源代码的快速和兼容的扩展开发。这不是关于如何避免类实现/扩展或一般结构的奇怪问题,而只是对 Kotlin 元编程潜力的好奇询问。既然您声称可以做到,也许您可​​以在答案中对此进行扩展?

标签: kotlin overloading kotlin-extension self-modifying


【解决方案1】:

正如我在评论中所说:在几乎所有情况下,使用适当的设计模式比开始依赖诸如 dynamic proxiesreflectionAOP 之类的东西来解决此类问题更可取。

话虽如此,问题是在编译时通过元编程修改 Kotlin 函数是否可能,答案是“是”。为了演示,下面是一个使用AspectJ的完整示例。


项目结构

我建立了一个基于Maven 的小型项目,其结构如下:

.
├── pom.xml
└── src
    └── main
        └── kotlin
            ├── Aop.kt
            └── Main.kt

我将重现以下部分中所有文件的内容。


应用代码

实际的应用程序代码在名为 Main.kt 的文件中,并且——除了我将你的函数重命名为符合 Kotlin naming rules 的事实——它与提供的代码相同你的问题。 getNumber() 方法旨在返回 3

fun main(args: Array<String>) {
    println(getNumber())
}

fun getNumber(): Int {
    return 3
}

AOP 代码

AOP相关的代码在Aop.kt,非常简单。它有一个@Around 通知,其切入点与getNumber() 函数的执行相匹配。该通知将拦截对getNumber() 方法的调用并返回42(而不是3)。

import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.annotation.Around
import org.aspectj.lang.annotation.Aspect

@Aspect
class Aop {
    @Around("execution(* MainKt.getNumber(..))")
    fun getRealNumber(joinPoint: ProceedingJoinPoint): Any {
        return 42
    }
}

(注意为Main.kt文件生成的类的名称是MainKt。)


POM 文件

POM 文件将所有内容放在一起。我正在使用 4 个插件:

这是完整的 POM 文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                             http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>x.y.z</groupId>
    <artifactId>kotlin-aop</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <java.version>1.8</java.version>
        <kotlin.version>1.2.61</kotlin.version>
        <aspectj.version>1.9.1</aspectj.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.jetbrains.kotlin</groupId>
            <artifactId>kotlin-stdlib</artifactId>
            <version>${kotlin.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>${aspectj.version}</version>
        </dependency>
    </dependencies>
    <build>
        <sourceDirectory>${project.basedir}/src/main/kotlin</sourceDirectory>
        <plugins>
            <plugin>
                <artifactId>kotlin-maven-plugin</artifactId>
                <groupId>org.jetbrains.kotlin</groupId>
                <version>${kotlin.version}</version>
                <executions>
                    <execution>
                        <id>kapt</id>
                        <goals>
                            <goal>kapt</goal>
                        </goals>
                    </execution>
                    <execution>
                        <id>compile</id>
                        <goals>
                            <goal>compile</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>com.jcabi</groupId>
                <artifactId>jcabi-maven-plugin</artifactId>
                <version>0.14.1</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>ajc</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>3.1.0</version>
                <configuration>
                    <archive>
                        <manifest>
                            <addClasspath>true</addClasspath>
                            <mainClass>MainKt</mainClass>
                        </manifest>
                    </archive>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>3.1.1</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>
</project>

构建和执行

要构建,与任何 Maven 项目一样,您只需运行:

mvn clean package

这将在target/kotlin-aop-1.0-SNAPSHOT.jar 位置构建一个胖 JAR。然后可以使用java 命令执行此 JAR:

java -jar target/kotlin-aop-1.0-SNAPSHOT.jar

执行然后给我们以下结果,证明一切都按预期工作:

42

(应用程序是在撰写本文时使用最新的 Oracle Java 8 JDK — 1.8.0_181 构建和执行的)


结论

如上例所示,重新定义 Kotlin 函数当然是可能的,但是——重申我的原点——在几乎所有情况下,都有更优雅的解决方案可以满足您的需求。

【讨论】:

  • 我完全不同意您的断言,即“使用适当的设计模式比开始依赖动态代理、反射或 AOP 之类的东西更可取”。事实上,我坚持认为正是这些东西构成了一个优雅的解决方案。请启发我对 AOP 解决的问题的其他“更优雅的解决方案”。例如,如果没有基于 AOP 的解决方案,您将如何实现诸如域对象安全之类的东西?您的解决方案示例将大大改变我的看法。
  • 另外,关于上面给出的示例项目,我要澄清一个。您已经证明可以编织二进制 .class 文件它们已被 Kotlin 编译器和 kapt 编译之后。这与使用 ajc 一次性编译全部内容不同,就像在 Java 中一样。我对OP问题的解释是指后者,而不是前者。也许 OP 的问题应该是“如何使用 ajc 之类的 AOP 编译器编译我的 Kotlin 文件,以便一步完成编译和编织?”或类似的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-12-13
  • 1970-01-01
  • 2022-08-15
  • 2023-01-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多