Java的動態代理經常使用來包裝原始方法調用,用於加強或改寫現有方法的邏輯,它在Java技術領域被廣爲使用,在阿里的Sofa RPC框架序列化中你能看到它的身影,Hibernate的實體類功能加強也是以動態代理的方式解決的,還有Spring吹牛逼的AOP功能也是它搞定的。接下來咱們看一個例子,該例子用於對原有的方法調用先後各打印一句話,這也算是對原有類方法的一種加強。java
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
複製代碼
爲了便於理解,咱們用圖來表示上面的對象的關係。咱們調用Proxy.newProxyInstance
產生了一個匿名類實例,該實例一樣實現了IHello接口,它的做用就是用來替代原生的RealHello
實例。這個匿名實例持有HelloDelegate實例的引用,當你對這個匿名實例進行方法調用時,它會將調用邏輯委託給HelloDelegate實例的invoke方法。HelloDelegate實例內部又持有原生RealHello對象的引用,因此用戶就能夠在invoke方法裏實現任意附加邏輯,以及對原生RealHello對象的調用。bash
上面是jdk自帶的動態代理技術,它的缺點是必須定義接口才能實現目標對象的方法加強,甚至想使用abstract class來替代也不行。因此開源市場上冒出了好幾個動態代理的庫,用於替代原生的jdk動態代理技術,它們不單單功能更強大,並且內部使用了字節碼加強實現,在性能上還也要比原生jdk高出不少。框架
javaassist是使用最普遍的動態代理開源庫。下面咱們使用javaassist實現一個無需定義接口就能加強原始方法的例子。ide
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還提供了方法過濾器,它能夠選擇性地對特定方法進行加強。ui
Python是動態語言,對於上面複雜的動態代理技術,它一笑而過。this
下面咱們來看看Python若是實現所謂的動態代理功能spa
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更加簡單呢?歡迎你們一塊兒來評論區吵架。
閱讀更多精彩文章,關注公衆號「碼洞」