简体   繁体   中英

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?

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.

Your confusion comes from misunderstanding extension functions. They are compiled into static methods in JVM, and are not magically "injected" into classes. Issue becomes apparent when You realize how they look as Java code:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM