3
votes

Comment créer un proxy dynamique dans le code commun de Kotlin?

Si je suis sur la JVM, je peux faire ceci:

object Playground {

    class DynamicInvocationHandler : InvocationHandler {

        @Throws(Throwable::class)
        override operator fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
            LOGGER.info("Invoked method: {}", method.name)

            return 42
        }

        companion object {

            private val LOGGER = LoggerFactory.getLogger(
                    DynamicInvocationHandler::class.java)
        }
    }

    @JvmStatic
    fun main(args: Array<String>) {
        val proxy = Proxy.newProxyInstance(
                Playground::class.java.classLoader,
                arrayOf<Class<*>>(MutableMap::class.java),
                DynamicInvocationHandler()) as MutableMap<String, String>

        proxy["foo"] = "bar"
    }
}

et l'exécuter affichera Méthode invoquée: put . Comment puis-je faire quelque chose comme ça dans un projet Kotlin commun ?

Modifier: je n'essaye pas d'utiliser quoi que ce soit de Java dans mon module commun . Je sais comment fonctionnent les projets communs. Ce qui m'intéresse à la place, c'est de savoir s'il existe ou non une solution basée sur Kotlin.

Édition 2: Je n'essaye pas de créer un proxy pour la carte classe. Je recherche quelque chose comme Proxy dans le JDK que je peux utiliser pour proxy n'importe quelle interface . Désolé pour la confusion.


2 commentaires

Vous ne pouvez pas utiliser Java Reflection dans le module commun. Parce qu'il s'agit d'une fonctionnalité Java et disponible uniquement dans le module Java.


J'ai édité ma réponse. Je sais que c'est une fonctionnalité Java, je comprends ce qu'est un projet commun. Je n'ai pas été en mesure de trouver une solution basée sur Kotlin, c'est pourquoi je pose la question.


3 Réponses :


1
votes

Probablement Expect / Actual Factory devrait résoudre le problème.

Code commun:

actual object Logger {

    private val instance = LoggerFactory.getLogger(
            DynamicInvocationHandler::class.java)

    actual fun info(message: String, vararg arguments: Any) {
        instance.info(message, *arguments)
    }
}

actual object ProxyFactory  {
    actual fun mutableMapWithProxy(handler: ProxyHandler): MutableMap<String, String> {
        return Proxy.newProxyInstance(
                Playground::class.java.classLoader,
                arrayOf<Class<*>>(MutableMap::class.java),
                ProxyHandlerAdapter(handler)) as MutableMap<String, String>
    }
}

class ProxyHandlerAdapter(private val handler: ProxyHandler) : InvocationHandler {

    @Throws(Throwable::class)
    override operator fun invoke(proxy: Any, method: Method, args: Array<Any>): Any {
        return handler.invoke(proxy, methodToProxyMethod(method), args)
    }

    fun methodToProxyMethod(method: Method): ProxyMethod {
        // convert Method to ProxyMethod
    }
}

@JvmStatic
fun main(args: Array<String>) {
    Common.doSomething()
}

Code Java:

interface ProxyMethod {
    val name: String
    // other properties
}

interface ProxyHandler {
    fun invoke(proxy: Any, method: ProxyMethod, args: Array<Any>): Any
}

expect object Logger {
    fun info(message: String, vararg arguments: Any)
}

expect object ProxyFactory {
    fun mutableMapWithProxy(handler: ProxyHandler): MutableMap<String, String>
}

private class ProxyHandlerImpl: ProxyHandler {
    override fun invoke(proxy: Any, method: ProxyMethod, args: Array<Any>): Any {
        Logger.info("Invoked method: {}", method.name)
        return 0
    }
}

object Common {
    fun doSomething() {
        val myMap = ProxyFactory.mutableMapWithProxy(ProxyHandlerImpl())
        myMap["foo"] = "bar"
    }
}

Malheureusement, je ne connais aucune bibliothèque qui simplifie ce travail, vous devriez donc le faire à la main pour chaque plate-forme et interface.


4 commentaires

Je n'essaye pas de créer un proxy pour une carte . Je recherche une solution proxy avec laquelle je peux proxy n'importe quelle interface .


Je comprends ce que tu veux. Mais maintenant, cela n'est pas possible sans des efforts excessifs. Peut-être pas, mais ça y ressemble. Je mets à jour ma réponse pour montrer comment créer un proxy pour n'importe quelle interface.


La génération de code simplifiera le travail. Mais quelqu'un devrait écrire une bibliothèque qui génère le code.


Attention avec java.lang.reflect.Proxy , il ne vous permet pas de lancer une exception de quelque type que ce soit (il les encapsulera pour suivre JLS) jonnyzzz.com/blog/2018/11/22/proxy



1
votes

Je pense que la réponse simple est que la réflexion Kotlin Multi Platform ne prend pas en charge les proxies. Vous pouvez utiliser la solution expect - réelle de @ KamiSempai lorsque vous utilisez le module commun dans une application java, mais vous devrez trouver des alternatives pour JS et les cibles natives.


0 commentaires

0
votes

Il n'y a pas d'équivalent pour cela dans les versions actuelles de Kotlin Native. En regardant les autres réponses, ils semblent avoir des types réels dans les expect / actuals, mais le but d'un proxy en direct est de fournir un type au moment de l'exécution et de générer une instance compatible qui peut être déléguée.

C'est ainsi que fonctionnent des choses comme la modernisation. Plutôt que la génération source, les définitions d'interface sont mandatées en interne.

Pour l'instant, vous devrez faire source-gen, pour autant que je sache. C'est pour natif. Je ne suis pas sûr de JS.


2 commentaires

Se mettre d'accord. Dans ce cas, la génération de code est le seul moyen de faire les astuces similaires aux astuces avec réflexion en Java.


Je ne prévois pas de prendre en charge le natif mais j'ai vu que JS a également une implémentation de Proxy !