在 java.lang.reflect.Proxy 对象上调用扩展函数时的 Kotlin 奇怪行为
Kotlin strange behaviour when calling extension function on java.lang.reflect.Proxy object
今天我在 Kotlin 中玩了一些 java.lang.reflect.Proxy
,我对这种行为感到惊讶:
import java.lang.reflect.Proxy
interface Dog {
fun bark()
fun bark3Times()
}
class DogImpl : Dog {
override fun bark() = println("Bark!")
override fun bark3Times() = repeat(3) { bark() }
}
fun Dog.bark5Times() = repeat(5) { bark() }
fun main(args: Array<String>) {
val classLoader = Dog::class.java.classLoader
val realDog: Dog = DogImpl()
val proxyDog: Dog = Proxy.newProxyInstance(
classLoader,
arrayOf(Dog::class.java)
) { _, method, _ ->
println("Proxy invoked! Method = ${method.name}")
method.invoke(realDog)
} as Dog
println("--- Dog barking 3 times ---")
proxyDog.bark3Times()
println()
println("--- Dog barking 5 times ---")
proxyDog.bark5Times()
}
输出:
--- Dog barking 3 times ---
Proxy invoked! Method = bark3Times
Bark!
Bark!
Bark!
--- Dog barking 5 times ---
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
问题:
为什么在第一个示例中仅针对 bark3Times
调用而不是针对单独的 bark
调用调用代理,但在第二个示例中未针对 bark5Times
调用代理,但这次调用每 bark
个电话?
这就是所谓的 自调用 并且是基于代理的 AOP(例如 Spring 的 @Transactional
和 @Cacheable
).
您的 Proxy
Dog
正在充当底层 DogImpl
实例的装饰器。当您的主方法调用 proxyDog.bark5Times()
时,扩展方法在代理对象 上连续调用 bark()
五次 ,其中包含建议并因此打印您的 "Proxy invoked!" 消息。
但是,当您调用 bark3Times()
时,该调用会触发代理(打印日志消息!)...然后 DogImpl
实例调用 this.bark()
直接在自身上三次,不通过代理。
您的困惑来自对扩展函数的误解。它们在JVM中被编译成静态方法,并不是神奇地"injected"变成类。当您意识到它们看起来像 Java 代码时,问题就变得很明显:
// Kotlin
fun Dog.bark5Times() = (0 until 5).forEach { bark() }
// equivalent in Java
public static void dogBark5Times(Dog dog){
for(i = 0; i < 5; i++){ dog.bark(); } // in this case dog is proxy so log is shown
}
今天我在 Kotlin 中玩了一些 java.lang.reflect.Proxy
,我对这种行为感到惊讶:
import java.lang.reflect.Proxy
interface Dog {
fun bark()
fun bark3Times()
}
class DogImpl : Dog {
override fun bark() = println("Bark!")
override fun bark3Times() = repeat(3) { bark() }
}
fun Dog.bark5Times() = repeat(5) { bark() }
fun main(args: Array<String>) {
val classLoader = Dog::class.java.classLoader
val realDog: Dog = DogImpl()
val proxyDog: Dog = Proxy.newProxyInstance(
classLoader,
arrayOf(Dog::class.java)
) { _, method, _ ->
println("Proxy invoked! Method = ${method.name}")
method.invoke(realDog)
} as Dog
println("--- Dog barking 3 times ---")
proxyDog.bark3Times()
println()
println("--- Dog barking 5 times ---")
proxyDog.bark5Times()
}
输出:
--- Dog barking 3 times ---
Proxy invoked! Method = bark3Times
Bark!
Bark!
Bark!
--- Dog barking 5 times ---
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
Proxy invoked! Method = bark
Bark!
问题:
为什么在第一个示例中仅针对 bark3Times
调用而不是针对单独的 bark
调用调用代理,但在第二个示例中未针对 bark5Times
调用代理,但这次调用每 bark
个电话?
这就是所谓的 自调用 并且是基于代理的 AOP(例如 Spring 的 @Transactional
和 @Cacheable
).
您的 Proxy
Dog
正在充当底层 DogImpl
实例的装饰器。当您的主方法调用 proxyDog.bark5Times()
时,扩展方法在代理对象 上连续调用 bark()
五次 ,其中包含建议并因此打印您的 "Proxy invoked!" 消息。
但是,当您调用 bark3Times()
时,该调用会触发代理(打印日志消息!)...然后 DogImpl
实例调用 this.bark()
直接在自身上三次,不通过代理。
您的困惑来自对扩展函数的误解。它们在JVM中被编译成静态方法,并不是神奇地"injected"变成类。当您意识到它们看起来像 Java 代码时,问题就变得很明显:
// Kotlin
fun Dog.bark5Times() = (0 until 5).forEach { bark() }
// equivalent in Java
public static void dogBark5Times(Dog dog){
for(i = 0; i < 5; i++){ dog.bark(); } // in this case dog is proxy so log is shown
}