Динамический прокси на java и Ruby

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

You can leave a response, or trackback from your own site.

Leave a Reply