Котлин: Можно ли изменять функции во время компиляции через метапрограммирование? - PullRequest
0 голосов
/ 30 августа 2018

В динамических языках, таких как JavaScript / Python, можно перезаписывать или «модифицировать» функции во время выполнения. Например, чтобы изменить функцию alert в JS, можно сделать:

const _prev_alert = window.alert;
window.alert = function() {
  _prev_alert.apply(this, arguments);
  console.log("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?

Я читал о метапрограммировании Котлина с помощью аннотаций и отражений, так что, возможно, они могли бы контролировать поведение компилятора и перезаписывать код get_number? Или это полное безумие, и единственный способ сделать что-то подобное - это разработать мою собственную отдельную обертку для метапрограммирования над Kotlin?

Кроме того, просто для уточнения, этот вопрос не касается Kotlin-JS, и ответ (если он существует) должен быть применим к Kotlin-JVM или Native.

1 Ответ

0 голосов
/ 30 августа 2018

Как указано в моем комментарии: почти во всех случаях более желательно использовать соответствующий шаблон проектирования, чем начинать полагаться на такие вещи, как динамические прокси , отражение или AOP для решения такого рода проблем.

При этом вопрос задает вопрос: возможно ли 1010 * модифицировать функции Kotlin во время компиляции посредством метапрограммирования, и ответом является "Да" . Для демонстрации ниже приведен полный пример, который использует AspectJ .


Структура проекта

Я создал небольшой Maven проект со следующей структурой:

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

Я воспроизведу содержимое всех файлов в следующих разделах.


Код приложения

Фактический код приложения находится в файле с именем Main.kt, и - за исключением того факта, что я переименовал вашу функцию, чтобы она соответствовала Правилам именования Kotlin - это идентично к коду, указанному в вашем вопросе. Метод 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

Это создаст толстый JAR в target/kotlin-aop-1.0-SNAPSHOT.jar месте. Затем этот JAR-файл можно выполнить с помощью команды java:

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

Затем выполнение дает нам следующий результат, демонстрирующий, что все работает как ожидалось:

42

(Приложение было создано и выполнено с использованием самой последней версии Oracle Java 8 JDK на момент написания статьи - 1.8.0_181)


Заключение

Как показывает приведенный выше пример, безусловно, можно переопределить функции Kotlin, но, повторяя мою первоначальную точку зрения, почти во всех случаях существуют более элегантные решения для достижения того, что вам нужно.

...