3

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?

2
  • Tip unrelated to question, instead of (0 until 5).forEach{ ... } You can use built in function repeat: repeat(5){ ... }. Commented Apr 6, 2019 at 22:23
  • @Pawel Thanks, I'll apply the suggesion to question for more idiomatic code. Commented Apr 7, 2019 at 11:57

2 Answers 2

6

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.

Sign up to request clarification or add additional context in comments.

Comments

1

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
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.