Hoy he jugado con un poco java.lang.reflect.Proxy
de Kotlin, y me sorprendió por este comportamiento:
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()
}
Salida:
--- 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!
La pregunta:
¿Por qué en primer ejemplo proxy se llama sólo para bark3Times
llamadas y no para separar bark
las llamadas, pero en segundo ejemplo no se llama para bark5Times
, pero esta vez se llama para cada bark
llamada?
Esto es lo que se conoce como auto-llamada y es una fuente importante de errores en AOP basado en proxy (como la primavera de @Transactional
y @Cacheable
).
Su Proxy
Dog
está sirviendo como decorador a un subyacente DogImpl
ejemplo. Cuando sus principales llamadas de método proxyDog.bark5Times()
, el método de extensión llama bark()
cinco veces seguidas en el objeto proxy , que contiene el consejo y por lo tanto se imprimen su "proxy invoca!" mensaje.
Sin embargo, cuando se llama bark3Times()
, esa llamada golpea el proxy (mensaje de registro impreso!) ... y luego las DogImpl
llamadas de instancia this.bark()
sobre sí directamente tres veces, no pasa por el proxy.