javakotlinreflectiondynamic-proxyextension-function

Kotlin strange behaviour when calling extension function on java.lang.reflect.Proxy object


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?


Solution

  • 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.