Как создать динамический прокси в общем коде Kotlin?

Если я на JVM, я могу сделать это:

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"
    }
}

и запуск этого напечатает Invoked method: put. Как я могу сделать что-то подобное в проекте Kotlin common?

Изменить: я не пытаюсь использовать что-либо из Java в своем общем модуле. Я знаю, как работают обычные проекты. Вместо этого меня интересует, есть ли для этого решение на основе Kotlin или нет.

Редактировать 2: я не пытаюсь проксировать класс Map. Я ищу что-то вроде Proxy в JDK, которое я могу использовать для проксирования любого интерфейса. Извините за путаницу.


person Adam Arold    schedule 23.01.2019    source источник
comment
Вы не можете использовать Java Reflection в общем модуле. Потому что это функция Java и доступна только в модуле Java.   -  person Denis Shurygin    schedule 23.01.2019
comment
Я отредактировал свой ответ. Я знаю, что это фича Java, я понимаю, что такое общий проект. Мне не удалось найти решение на основе Kotlin, поэтому я и спрашиваю.   -  person Adam Arold    schedule 23.01.2019
comment
Найдено discuss.kotlinlang.org/t/dynamic-proxy/17802 при обсуждении этого тема.   -  person Michiel Leegwater    schedule 02.05.2021


Ответы (3)


Я думаю, что простой ответ заключается в том, что отражение Kotlin Multi Platform не поддерживает прокси. Вы можете использовать решение @KamiSempai expect - actual при использовании общего модуля в приложении Java, но вам нужно будет найти альтернативы для JS и собственных целей.

person Kirill Rakhman    schedule 23.01.2019

Вероятно, Expect/Actual Factory должен решить проблему.

Общий код:

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"
    }
}

Java-код:

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()
}

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

person Denis Shurygin    schedule 23.01.2019
comment
Я не пытаюсь проксировать Map. Я ищу прокси-решение, с помощью которого я могу проксировать любой интерфейс. - person Adam Arold; 23.01.2019
comment
Я понимаю, что вы хотите. Но сейчас это невозможно без чрезмерных усилий. Может и нет, но похоже. Я обновляю свой ответ, чтобы показать, как проксировать любой интерфейс. - person Denis Shurygin; 23.01.2019
comment
Генерация кода упростит работу. Но кто-то должен написать библиотеку, которая генерирует код. - person Denis Shurygin; 23.01.2019
comment
Будьте осторожны с java.lang.reflect.Proxy, он не позволяет вам генерировать исключения любого типа (он будет обертывать их, чтобы следовать JLS) jonnyzzz.com/blog/2018/11/22/proxy - person Eugene Petrenko; 23.01.2019

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

Вот как работают такие вещи, как Retrofit. Вместо генерации исходного кода определения интерфейса проксируются внутри.

На данный момент, насколько я знаю, вам нужно сделать source-gen. Это для родного. Насчет JS не уверен.

person Kevin Galligan    schedule 23.01.2019
comment
Соглашаться. В этом случае генерация кода — единственный способ проделать трюки, подобные трюкам с отражением в Java. - person Denis Shurygin; 23.01.2019
comment
Я не планирую поддерживать натив, но я видел, что JS также имеет реализацию Proxy! - person Adam Arold; 23.01.2019