Странное поведение Kotlin при вызове функции расширения для объекта java.lang.reflect.Proxy - PullRequest
3 голосов
/ 06 апреля 2019

Сегодня я играл с некоторыми 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?

Ответы [ 2 ]

5 голосов
/ 07 апреля 2019

Это то, что известно как самозвонок и является существенным источником ошибок в AOP на основе прокси (таких как Spring * @Transactional и @Cacheable).

ВашProxy Dog служит декоратором для базового DogImpl экземпляра.Когда ваш основной метод вызывает proxyDog.bark5Times(), метод расширения вызывает bark() пять раз подряд на прокси-объекте , который содержит рекомендацию и, таким образом, печатает ваш «Прокси вызван!»message.

Однако, когда вы вызываете bark3Times(), этот вызов попадает на прокси-сервер (распечатывается сообщение журнала!) ... и затем экземпляр DogImpl вызывает this.bark() непосредственно для себя три раза, не проходя через прокси.

1 голос
/ 07 апреля 2019

Ваша путаница возникает из-за неправильного понимания функций расширения.Они скомпилированы в статические методы в JVM и не магически «внедряются» в классы.Проблема становится очевидной, когда вы понимаете, как они выглядят как код 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
}
...