Kotlin:是否可以在编译期间通过元编程来修改函数? [英] Kotlin: Possible to modify functions during compile time through metaprogramming?

查看:90
本文介绍了Kotlin:是否可以在编译期间通过元编程来修改函数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在像JavaScript/Python这样的动态语言中,有可能在运行时覆盖或修改"函数.例如,为了修改JS中的alert函数,可以执行以下操作:

In dynamic languages like JavaScript/Python, it's possible to overwrite or "modify" functions during run-time. For example, in order to modify the alert function in JS, one could do:

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

这将输出已调用警报功能!"每次调用alert函数时,都会将其移至控制台.

This would output "Alert function was called!" to the console every time the alert function is called.

现在,由于它们的静态特性,因此在Kotlin-JVM或Kotlin-Native中运行时显然不可能实现这样的事情.但是,对于那些相同的语言,是否有可能在编译时修改未编译的函数?我并不是说要从库中预编译函数,而是要在我正在开发的同一项目中编写的函数.

Now, obviously something like this would be impossible during runtime in Kotlin-JVM or Kotlin-Native due to their static nature. However, in regards to those same languages, is it possible to perhaps modify a non-compiled function during compile time? I don't mean pre-compiled functions from libraries, but instead functions I have written in the same project I'm developing on.

例如,假设我有一个写的名为get_number的函数.我可以修改get_number以返回不同的数字,而无需更改其在main中的调用方式,也可以不直接修改其代码吗? (或者有没有办法我可以写原始的get_number以便可以进行修改?)

For example, let's say I have a function I wrote called get_number. Could I modify get_number to return a different number without changing how its called in main and without modifying its code directly? (Or is there a way I COULD write the original get_number so modification IS possible down the line?)

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上开发自己的,独立的元编程包装器?

I've been reading into Kotlin's metaprogramming with Annotations and Reflections, so perhaps those could control the compiler's behavior and overwrite get_number's code? Or is this complete lunacy and the only way something of this nature would be possible is through developing my own, separate, metaprogramming wrapper over Kotlin?

也要澄清一下,这个问题不是关于Kotlin-JS的,答案(如果存在)应该适用于Kotlin-JVM或Native.

推荐答案

正如我的评论所述:在几乎所有情况下,与开始依赖

As stated in my comment: in almost all cases, it's more desirable to use an appropriate design pattern than to start relying on things like dynamic proxies, reflection, or AOP to address this kind of problem.

话虽如此,该问题询问是否可能在元编译时通过元编程来修改Kotlin函数,并且答案为是".为了演示,下面是使用 AspectJ 的完整示例.

That being said, the question asks whether it's possible to modify Kotlin functions at compile time through meta-programming, and the answer is "Yes". To demonstrate, below is a complete example that uses AspectJ.

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

I set up a small Maven-based project with the following structure:

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

我将在下面的部分中复制所有文件的内容.

I'll reproduce the contents of all files in the sections below.

实际的应用程序代码在名为 Main.kt 的文件中,并且-除了我将您的函数重命名为符合

The actual application code is in the file named Main.kt, and—except for the fact that I renamed your function to be in line with Kotlin naming rules—it's identical to the code provided in your question. The getNumber() method is designed to return 3.

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

fun getNumber(): Int {
    return 3
}


AOP代码

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


AOP code

The AOP-related code is in Aop.kt, and is very simple. It has an @Around advice with a point cut that matches the execution of the getNumber() function. The advice will intercept the call to the getNumber() method and return 42 (instead of 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.)

(Note how the name of the generated class for the Main.kt file is MainKt.)

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

The POM file puts everything together. I'm using 4 plugins:

  • The kotlin-maven-plugin takes care of compiling the Kotline files. The configuration includes the execution of the kapt plugin to process the AspectJ annotations.
  • The jcabi-maven-plugin executes the AspectJ compiler/weaver to weave the aspects into the binary classes.
  • The maven-jar-plugin builds the JAR file with a manifest that references the main class.
  • The maven-shade-plugin builds a fat JAR that includes all library dependencies.

这是完整的POM文件:

This is the complete POM file:

<?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项目一样进行构建,只需运行:


Building and executing

To build, as with any Maven project, you just need to run:

mvn clean package

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

This will build a fat JAR at the target/kotlin-aop-1.0-SNAPSHOT.jar location. This JAR can then be executed using the java command:

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

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

Execution then gives us the following result, demonstrating that everything worked as expected:

42

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

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

As the example above demonstrates, it's certainly possible to redefine Kotlin functions, but—to reiterate my original point—in almost all cases, there are more elegant solutions available to achieve what you need.

这篇关于Kotlin:是否可以在编译期间通过元编程来修改函数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆