Kotlin:是否可以在编译期间通过元编程来修改函数? [英] Kotlin: Possible to modify functions during compile time through metaprogramming?
问题描述
在像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:
-
kotlin-maven-plugin
负责编译Kotline文件.该配置包括执行 kapt 插件以处理AspectJ注释. - >
jcabi-maven-plugin
执行maven-jar-plugin
使用清单文件构建JAR文件引用主类. -
maven-shade-plugin
构建
- 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屋!