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

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 {@Overridepublic void say(String s) {System.out.println("hello " + s);}}// 增強器
class HelloDelegate implements InvocationHandler {private IHello target;  // 原始對象public HelloProxy(IHello target) {this.target = target;}@Overridepublic 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對象的調用。

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

javaassist

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

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;}@Overridepublic 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是動態語言,對于上面復雜的動態代理技術,它一笑而過。

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

class Proxy(object):def __init__(self, target):self.target = targetdef __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 resreturn newAttrclass RealHello(object):def prints(self, s):print 'hello', sif __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更加簡單呢?歡迎大家一起來評論區吵架。

精彩文章,關注公眾號「碼洞」

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/451622.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/451622.shtml
英文地址,請注明出處:http://en.pswp.cn/news/451622.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

shell實現從1加到100

#!/bin/bash # test"while do done"PATH/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:~/bin export PATHsum0 for i in $(seq 1 100); do let sumi done echo "The sum is > $sum "

面試常考的數據結構Java實現

1、線性表 2、線性鏈表 3、棧 4、隊列 5、串 6、數組 7、廣義表 8、樹和二叉樹 二叉樹&#xff1a;每個結點至多只有兩棵子樹&#xff08;即二叉樹中不存在度大于2的結點&#xff09;&#xff0c;并且&#xff0c;二叉樹的子樹有左右之分&#xff0c;其次序不能任意顛倒。 二叉…

Java5線程并發庫之LOCK(鎖)CONDITION(條件)實現線程同步通信

為什么80%的碼農都做不了架構師&#xff1f;>>> Lock&#xff08;鎖&#xff09;&Condition&#xff08;條件&#xff09;實現線程同步通信 接下來介紹&#xff0c;java5線程并發庫里面的鎖。跟鎖有關的類和接口主要是位于java.util.concurrent.locks包。 Lock…

互聯網,可預見的未來

我記憶中的1998年代&#xff0c;PC迅猛發展&#xff0c;CPU速度逐年翻番&#xff0c;持續了7年&#xff0c;但下一個7年到現在&#xff0c;基本上沒有太大提升&#xff1b;顯示器從14英寸CRT發展到2005的21英寸LED&#xff0c;后來也沒有繼續進化。為什么&#xff1f;當人對計算…

什么時候用GET?什么時候用POST?

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 一、 GET和POST兩種方法都是將數據送到服務器&#xff0c;但你該用哪一種呢&#xff1f; HTTP標準包含這兩種方法是為了達到不同的目的…

邏輯運算符與邏輯表達式

1 #include <stdio.h>2 3 int main()4 {5 int a0;int b0;6 if(a&&b)//a&&ba的邏輯值為0&#xff0c;則執行else7 {8 printf("a&&b is true\n");9 } 10 else 11 { 12 printf("a&&…

linux/shell相關知識點

阿里Linux Shell腳本面試25個經典問答 Linux運維工程師12道面試題整理 感謝作者分享&#xff01;

20180601]函數與標量子查詢2.txt

[20180601]函數與標量子查詢2.txt --//昨天看http://www.cnblogs.com/kerrycode/p/9099507.html鏈接,里面提到: 通俗來將&#xff0c;當使用標量子查詢的時候&#xff0c;ORACLE會將子查詢結果緩存在哈希表中&#xff0c; 如果后續的記錄出現同樣的值&#xff0c;優化器通過緩存…

ODP 使用 ArrayBind 時可能會遇到的巨坑 'System.IConvertible' 的解決方法

Unable to cast object of type System.Nullable1[System.Int16][] to type System.IConvertible 一段代碼99%不會出錯&#xff0c;0.1%會報上邊的錯&#xff0c;debug費了老鼻子時間&#xff0c;發現此坑很深。異常是 cmd.ExecuteNonQuery() 拋的&#xff0c;實際是 para.Valu…

eclipse快速定位到錯誤處

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程 以前都是按著滾動條往下拉&#xff0c;找到錯誤的地方&#xff0c;有時比較多的時候就很麻煩。 其實eclipse是可以直接快速定位的&#x…

C語言中的“”和“”

先說左移,左移就是把一個數的所有位都向左移動若干位,在C中用<<運算符.例如: int i 1; i i << 2; //把i里的值左移2位 也就是說,1的2進制是000...0001(這里1前面0的個數和int的位數有關,32位機器,gcc里有31個0),左移2位之后變成 000...0100,也就是10進制的4,所以…

網站性能優化的三重境界

這篇文章是關于網站性能優化體驗的&#xff0c;性能優化是一個復雜的話題&#xff0c;牽涉的東西非常多&#xff0c;我只是按照我的理解列出了性能優化整個過程中需要考慮的種種因素。點到為止&#xff0c;包含的內容以淺顯的介紹為主&#xff0c;如果你有見解能告知我那再好不…

Linux使用RSA實現免密登錄(原理)

參考文獻Linux密鑰rsa加密原理和ssh使用密鑰實現免密碼登錄 感謝作者分享&#xff01;

PYTHON 爬蟲筆記十一:Scrapy框架的基本使用

Scrapy框架詳解及其基本使用 scrapy框架原理 Scrapy是一個為了爬取網站數據&#xff0c;提取結構性數據而編寫的應用框架。 其可以應用在數據挖掘&#xff0c;信息處理或存儲歷史數據等一系列的程序中。其最初是為了頁面抓取 (更確切來說, 網絡抓取 )所設計的&#xff0c; 也可…

java設計把兩個字符串的值交換 而不使用中間變量

public class Test {public static void main(String[] args) {String s1 "aaa";String s2 "cccx";s1 s1 s2;s2 s1.substring(0, s1.length()-s2.length());s1 s1.substring(s2.length());System.out.println(s1" - "s2);}}

服務器返回值 解釋 ajax提交方式 后臺數據刷進前端

轉載于:https://www.cnblogs.com/liuliang389897172/p/9120715.html

no typehandler found for property XXXX 解決

前些天發現了一個巨牛的人工智能學習網站&#xff0c;通俗易懂&#xff0c;風趣幽默&#xff0c;忍不住分享一下給大家。點擊跳轉到教程。 1. ssm框架下 啟動服務報錯如題。 2. 原因&#xff1a; 我的情況是&#xff0c;代碼中實體屬性映射書寫和數據庫字段名字不一致。 數據…

C++主流預處理,編譯和鏈接過程

在C的程序的編寫過程中&#xff0c;基本上都碰到過LNK2005的錯誤吧&#xff0c;下面就針對這個問題詳細分析&#xff1a;首先&#xff0c;預處理階段&#xff1a;這一過程&#xff0c;主要針對#include和#define進行處理&#xff0c;具體過程如下&#xff1a;對于cpp文件中經常…

shell中sed -i特殊字符

可參考文獻&#xff1a; Linux生產環境上&#xff0c;最常用的一套“sed“技巧 看懂shell中的各種語句

Win10遠程桌面提示你的憑據不工作的處理方法

需要確保在組策略編輯器&#xff08;WinR 輸入 gpedit.msc &#xff09;中計算機配置->Windows設置->安全設置->本地策略->安全選項->右側的網絡訪問:本地帳戶的共享和安全模型。修改為使用經典模式即可&#xff01;