看完Java的動態代理技術——Pythoner笑了

老錢發表於2018-05-31

Java的動態代理常用來包裝原始方法呼叫,用於增強或改寫現有方法的邏輯,它在Java技術領域被廣為使用,在阿里的Sofa RPC框架序列化中你能看到它的身影,Hibernate的實體類功能增強也是以動態代理的方式解決的,還有Spring吹牛逼的AOP功能也是它搞定的。接下來我們看一個例子,該例子用於對原有的方法呼叫前後各列印一句話,這也算是對原有類方法的一種增強。

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

interface IHello {

	void say(String s);

}

// 待加強的目標類
class RealHello implements IHello {

	@Override
	public void say(String s) {
		System.out.println("hello " + s);
	}

}

// 增強器
class HelloDelegate implements InvocationHandler {

	private IHello target;  // 原始物件

	public HelloProxy(IHello target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
		System.out.println("before print");
		method.invoke(target, args);  // 呼叫原始物件的方法
		System.out.println("after print");
		return null;
	}

}

public class DynamicProxy {

	public static void main(String[] args) {
		IHello hello = enhanceHello(new RealHello());  # 增強原始方法
		hello.say("world");
	}

	public static IHello enhanceHello(IHello target) {
		return (IHello) Proxy.newProxyInstance(DynamicProxy.class.getClassLoader(), new Class<?>[] { IHello.class },
				new HelloDelegate(target));
	}

}
複製程式碼

輸出

before print
hello world
after print
複製程式碼

看完Java的動態代理技術——Pythoner笑了

為了便於理解,我們用圖來表示上面的物件的關係。我們呼叫Proxy.newProxyInstance產生了一個匿名類例項,該例項同樣實現了IHello介面,它的作用就是用來替代原生的RealHello例項。這個匿名例項持有HelloDelegate例項的引用,當你對這個匿名例項進行方法呼叫時,它會將呼叫邏輯委託給HelloDelegate例項的invoke方法。HelloDelegate例項內部又持有原生RealHello物件的引用,所以使用者就可以在invoke方法裡實現任意附加邏輯,以及對原生RealHello物件的呼叫。

上面是jdk自帶的動態代理技術,它的缺點是必須定義介面才能實現目標物件的方法增強,甚至想使用abstract class來替代也不行。所以開源市場上冒出了好幾個動態代理的庫,用於替代原生的jdk動態代理技術,它們不僅僅功能更強大,而且內部使用了位元組碼增強實現,在效能上還也要比原生jdk高出很多。

javaassist

javaassist是使用最廣泛的動態代理開源庫。下面我們使用javaassist實現一個無需定義介面就能增強原始方法的例子。

看完Java的動態代理技術——Pythoner笑了

import java.lang.reflect.Method;

import javassist.util.proxy.MethodHandler;
import javassist.util.proxy.ProxyFactory;

class RealHello {

	public void say(String s) {
		System.out.println("hello " + s);
	}

}

class HelloDelegate<T> implements MethodHandler {

	private T target;

	public HelloDelegate(T target) {
		this.target = target;
	}

	@Override
	public Object invoke(Object self, Method method, Method proceed, Object[] args) throws Throwable {
		System.out.println("before print");
		method.invoke(target, args);
		System.out.println("after print");
		return null;
	}

}

public class DynamicProxy {

	public static void main(String[] args) {
		RealHello hello = enhanceHello(new RealHello());
		hello.say("world");
	}

	@SuppressWarnings("unchecked")
	public static <T> T enhanceHello(T target) {
		ProxyFactory proxy = new ProxyFactory();
		proxy.setSuperclass(RealHello.class);
		try {
			HelloDelegate<T> delegate = new HelloDelegate<T>(target);
			// create方法傳遞了兩個空陣列
			// 分別代表構造器的引數型別陣列和構造器的引數例項陣列
			return (T) proxy.create(new Class<?>[0], new Object[0], delegate);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return null;
	}

}
複製程式碼

輸出

before print
hello world
after print
複製程式碼

看起來和原生jdk提供的動態代理區別並不大,達到的效果是一樣的。只不過這裡要簡單了很多,省去了介面類的定義。javaassist的ProxyFactory還提供了方法過濾器,它可以選擇性地對特定方法進行增強。

Python

Python是動態語言,對於上面複雜的動態代理技術,它一笑而過。

看完Java的動態代理技術——Pythoner笑了

下面我們來看看Python如果實現所謂的動態代理功能

class Proxy(object):

    def __init__(self, target):
        self.target = target

    def __getattribute__(self, name):
        target = object.__getattribute__(self, "target")
        attr = object.__getattribute__(target, name)

        def newAttr(*args, **kwargs):  # 包裝
            print "before print"
            res = attr(*args, **kwargs)
            print "after print"
            return res
        return newAttr


class RealHello(object):

    def prints(self, s):
        print 'hello', s


if __name__ == '__main__':
    t = RealHello()
    p = Proxy(t)
    p.prints("world")
複製程式碼

輸出

before print
hello world
after print
複製程式碼

我們使用了神奇的__getattribute__方法。在Python裡面類的屬性(方法)都是一個物件,我們先拿到這個類方法物件attr,然後對這個類方法物件進行包裝,再返回包裝後的新方法物件newAttr。 注意在獲取target物件時,不能直接使用self.target,因為self.target會再次呼叫__getattribute__方法,這樣就會導致死迴圈致堆疊過深曝出異常。取而代之應該使用object.__getattribute__方法來獲取物件的屬性值。

以上就是Python實現動態代理的方案,讀者們,你們是否覺得Python更加簡單呢?歡迎大家一起來評論區吵架。

看完Java的動態代理技術——Pythoner笑了

閱讀更多精彩文章,關注公眾號「碼洞」

相關文章