Рассмотрим такую практическую задачу. Есть некое множество классов. У этих классов есть методы, по которым нам хотелось бы, например, собирать метрики: количество вызовов, время выполнения, число ошибок. Наиболее простой и красивый способ решения этой задачи, не требующий написания тысяч строк кода в разных местах проекта, заключается в использовании динамических прокси-классов.
Ссылку на соответствующий пример исходного кода мне подсказал 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 ;
А используете ли вы в своих проектах динамические прокси-классы?