Динамический прокси (Dynamic Proxy) оформился еще в Java 1.3.
Он позволяет использовать базовые возможности аспектно-ориентированного программирования без задействования, собственно, AOP фреймворка. Многие приложения и фреймворки используют такие возможности. Примерами могут быть spring и hibernate.
В этой заметке я расскажу как использовать динамический прокси в Java и поясню, как достичь того же в Ruby.
Java
Вероятно, вы видели в стеках имена классов вроде $Proxy[XX] — это и есть следы присутствия динамического прокси. Для того, чтобы создать таковой, вам надо указать, что вы хотите создать прокси для определенного интерфейса, и прокси будет создан в run-time. Вы определяете класс InvocationHandler и он будет вызван в случае вызова одного из методов прокси. В InvocationHandler передаются имя метода и его параметры, и с этими данными вы можете делать всё, что захотите. Обычно происходит вызов реализованного метода и какие-то утилитарные действия: логгирование, обработка исключений, управление транзакциями — типичные кандидаты при использование AOP.
Целевым объектом будет RealFoo.
В java это делается так (большая часть действий выполняется в invocation handler) :
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; interface Foo { void bar(String s, int i); } class MyInvocationHandler implements InvocationHandler { private final Object targetImpl; public MyInvocationHandler(Object targetImpl) { this.targetImpl = targetImpl; } @Override public Object invoke(Object proxy, Method m, Object[] args) throws Throwable { // немного отладочной информации System.out.println("Handler: Starting work for " + m.getName()); // передаем управление классу, который действительно реализует интерфейс String name = m.getName(); Class[] types = m.getParameterTypes(); Method realMethod = targetImpl.getClass().getMethod(name, types); Object ret = realMethod.invoke(targetImpl, args); // отчитываемся, что отработал метод System.out.println("Handler: Finishing work for " + m.getName()); return ret; } public static Object proxyFor(Object target, Class[] ifcs) { // создание динамического прокси InvocationHandler hdlr = new MyInvocationHandler(target); ClassLoader clsLdr = target.getClass().getClassLoader(); return Proxy.newProxyInstance(clsLdr, ifcs, hdlr); } } class RealFoo implements Foo { @Override public void bar(String s, int i) { System.out.println("RealFoo: bar called, s = " + s + ", i = " + i); System.out.println("Printing stack trace for info"); // бросаем исключение просто для того, чтоб вывести стек Thread.dumpStack(); } } public class Main { public static void main(String[] args) { Foo proxy = (Foo) MyInvocationHandler.proxyFor(new RealFoo(), new Class[]{Foo.class}); proxy.bar("test string", 10); } } |
Результат работы:
Handler: Starting work for bar RealFoo: bar called, s = test string, i = 10 Printing stack trace for info Handler: Finishing work for bar java.lang.Exception: Stack trace at java.lang.Thread.dumpStack(Thread.java:1333) at ru.outofrange.test.RealFoo.bar(Main.java:53) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at ru.outofrange.test.MyInvocationHandler.invoke(Main.java:30) at ru.outofrange.test.service.$Proxy0.bar(Unknown Source) at ru.outofrange.test.Main.main(Main.java:60) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498) at com.intellij.rt.execution.application.AppMain.main(AppMain.java:147) |
Ruby
Ruby – это динамический язык программирования, который позволяет менять в run-time структуру классов и функций. В нем нет интерфейсов, он статически типизирован. Пример будет опираться на использование method_missing, который вызывается в runtime, если вызван несуществующий метод объекта. «Прокси» — это класс без методов (за исключением определенных в Object), который передает управление в целевой объект (target object).
Исходный код:
class Proxy def initialize(targetObj) @targetObject=targetObj end def method_missing(m, *args, &block) puts "intercepted call for #{m}" ret = @targetObject.send(m,*args, &block) puts "finished call for #{m}" end end class Foo def bar(s,i) puts "Foo: bar called, s=#{s}, i=#{i}" puts "Foo: printing backtrace for info" # print back trace puts caller end end # run the example foo = Foo.new proxy = Proxy.new(foo) proxy.bar("test string",100) |
Результат работы:
Foo: bar called, s=test string, i=100 Foo: printing backtrace for info C:/Users/<>/workspace/ruby_scratch/Proxy.rb:9:in `send' C:/Users/<>/workspace/ruby_scratch/Proxy.rb:9:in `method_missing' C:/Users/<>/workspace/ruby_scratch/Proxy.rb:28 Proxy: finished call for bar |
Вот, пожалуй, и всё. И в java, и в Ruby придется предпринимать дополнительные действия, если вы хотите обрабатывать исключения от целевых объектов. Пример на руби не показывает все возможные случаи использования (к примеру, замыкания) и может вызывать конфликты, если вы будете использовать такой подход в связке с библиотеками, которые меняют структуру класса. Эффект может быть достигнут и другими путями, например, переопределением метода целевого объекта. Мой пример приведен просто для ознакомления с прокси.