Today I played with some java.lang.reflect.Proxy
in Kotlin, and I was surprised by this behaviour:
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()
}
Output:
--- 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!
The question:
Why in first example proxy is called only for bark3Times
calls and not for separate bark
calls, but in second example it's not called for bark5Times
, but this time is called for every bark
call?
This is what's known as a self-call and is a substantial source of bugs in proxy-based AOP (such as Spring's @Transactional
and @Cacheable
).
Your Proxy
Dog
is serving as a decorator to an underlying DogImpl
instance. When your main method calls proxyDog.bark5Times()
, the extension method calls bark()
five times in a row on the proxy object, which contains the advice and thus prints your "Proxy invoked!" message.
However, when you call bark3Times()
, that call hits the proxy (log message printed!)... and then the DogImpl
instance calls this.bark()
on itself directly three times, not going through the proxy.