← На главную

Пример работы с динамическими прокси-классами в Scala

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

Ссылку на соответствующий пример исходного кода мне подсказал 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), а не какой попало класс.

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

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