Пример работы с динамическими прокси-классами в Scala
28 января 2015
Рассмотрим такую практическую задачу. Есть некое множество классов. У этих классов есть методы, по которым нам хотелось бы, например, собирать метрики: количество вызовов, время выполнения, число ошибок. Наиболее простой и красивый способ решения этой задачи, не требующий написания тысяч строк кода в разных местах проекта, заключается в использовании динамических прокси-классов.
Ссылку на соответствующий пример исходного кода мне подсказал Alexander Semenov в ScalaChat. В упрощенном виде решение выглядит как-то так:
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)
}
При запуске программы мы получим следующий вывод:
someOtherMethod invoked
Future returned!
Как видите, мы перехватываем имя метода, его аргументы, возвращаемое значение. Таким образом, мы можем получить время перед вызовом оригинального метода и при возврате из него, повесить onComplete на возвращаемую футуру и так далее. Можно писать логи, собирать метрики, сериализовать аргументы и отправлять их в REST-сервис, и так далее.
Работает это примерно таким образом. При вызове Proxy.newProxyInstance генерируется обычный (то есть, как бы статический) прокси-класс, как если бы мы писали его вручную. Только делается это во время исполнения, потому прокси и динамический. Хорошо все-таки работать под виртуальной машиной, правда? Однако важно помнить, что при использовании динамических классов-прокси мы можем проксировать только интерфейс (в приведенном ранее примере — TestTrait), а не какой попало класс.
Ссылки по теме:
- https://docs.oracle.com/javase/8/docs/api/java/lang/reflect/Proxy.html;
- http://blog.leodev.ru/java-dynamic-proxy/;
- http://samolisov.blogspot.ru/2010/04/proxy-java.html;
А используете ли вы в своих проектах динамические прокси-классы?
Метки: Scala, Функциональное программирование.
Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.