Динамический прокси (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);
}
} |
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) |
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) |
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 |
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 придется предпринимать дополнительные действия, если вы хотите обрабатывать исключения от целевых объектов. Пример на руби не показывает все возможные случаи использования (к примеру, замыкания) и может вызывать конфликты, если вы будете использовать такой подход в связке с библиотеками, которые меняют структуру класса. Эффект может быть достигнут и другими путями, например, переопределением метода целевого объекта. Мой пример приведен просто для ознакомления с прокси.