Рассмотрим такую практическую задачу. Есть некое множество классов. У этих классов есть методы, по которым нам хотелось бы, например, собирать метрики: количество вызовов, время выполнения, число ошибок. Наиболее простой и красивый способ решения этой задачи, не требующий написания тысяч строк кода в разных местах проекта, заключается в использовании динамических прокси-классов.

Ссылку на соответствующий пример исходного кода мне подсказал Alexander Semenov в ScalaChat. В упрощенном виде решение выглядит как-то так:

import scala. concurrent . _
import java. lang . reflect . { Method, InvocationHandler, Proxy }
import scala. concurrent . ExecutionContext . Implicits . global

package object util {
def createProxy [ I ] ( proxee : I,
interfaceClass : Class [ I ] ,
handler : InvocationHandler ) : I = {
Proxy. newProxyInstance (
interfaceClass. getClassLoader ,
Array ( interfaceClass ) ,
handler ) . asInstanceOf [ I ]
}
}

import util. _

abstract class AspectBase ( proxee : AnyRef ) extends InvocationHandler {
protected def invokeProxee ( method : Method,
args : Array [ Object ] ) : AnyRef =
method. invoke ( proxee, args : _* )
}

class ProxyClass
( proxee : AnyRef )
( implicit protected val executionContext : ExecutionContext )
extends AspectBase ( proxee ) {

override def invoke ( proxy : Object,
method : Method,
args : Array [ Object ] ) : AnyRef = {

println ( s «${method.getName} invoked» )

val result = invokeProxee ( method, args )
result match {
case f : Future [ _ ] =>
println ( «Future returned!» )
case _ => ;
}
result
}
}

trait TestTrait {
def someMethod ( x : Long ) : Long
def someOtherMethod ( x : Long ) : Future [ Long ]
}

class TestTraitImpl extends TestTrait {
def someMethod ( x : Long ) = x * 2
def someOtherMethod ( x : Long ) = Future { x * 3 }
}

object Debug extends App {
val t = {
val proxee = new TestTraitImpl ( )
createProxy ( proxee, classOf [ TestTrait ] , new ProxyClass ( proxee ) )
}

t. someMethod ( 1 )
t. someOtherMethod ( 2 )
}

При запуске программы мы получим следующий вывод:

someMethod invoked
someOtherMethod invoked
Future returned!

Как видите, мы перехватываем имя метода, его аргументы, возвращаемое значение. Таким образом, мы можем получить время перед вызовом оригинального метода и при возврате из него, повесить onComplete на возвращаемую футуру и так далее. Можно писать логи, собирать метрики, сериализовать аргументы и отправлять их в REST-сервис, и так далее.

Работает это примерно таким образом. При вызове Proxy.newProxyInstance генерируется обычный (то есть, как бы статический) прокси-класс, как если бы мы писали его вручную . Только делается это во время исполнения, потому прокси и динамический. Хорошо все-таки работать под виртуальной машиной, правда? Однако важно помнить, что при использовании динамических классов-прокси мы можем проксировать только интерфейс (в приведенном ранее примере — TestTrait), а не какой попало класс.

Ссылки по теме:

А используете ли вы в своих проектах динамические прокси-классы?

EnglishRussianUkrainian