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

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

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

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

admin

Share
Published by
admin

Recent Posts

Apple: история логотипа

Как менялся логотип Apple на протяжении многих лет. Логотип Apple — это не просто символ,…

6 дней ago

Security Boot Fail при загрузке Acer — решение проблемы

Security Boot Fail при загрузке Acer — решение проблемы При загрузке ноутбука Acer с флешки,…

3 недели ago

Ноутбук не включается — варианты решения

Ноутбук не включается — варианты решения Если при попытке включить ноутбук вы обнаруживаете, что он…

3 недели ago

The AC power adapter wattage and type cannot be determined — причины и решение

The AC power adapter wattage and type cannot be determined — причины и решение При…

3 недели ago

Свистит или звенит блок питания компьютера — причины и решения

Свистит или звенит блок питания компьютера — причины и решения Некоторые владельцы ПК могут обратить…

3 недели ago

Мигает Caps Lock на ноутбуке HP — почему и что делать?

Мигает Caps Lock на ноутбуке HP — почему и что делать? При включении ноутбука HP…

3 недели ago