Java靜態代理&動態代理筆記

java代理

閱讀原文請訪問我的博客BrightLoong's Blog

最近在學習Java反射的一些知識,看到了一些有關代理的例子,好記性不如爛筆頭,所以這里將它記錄下來。接下來話不多說,直接進入主題。

代理:為其他對象提供一個代理以控制對某個對象的訪問。

靜態代理

  • 接口
public interface IDoSomething {
    public int doSometing(int num);
}
  • 被代理類的實現
public class Sing implements IDoSomething {

    @Override
    public int doSometing(int num) {
        System.out.println("Sing a song");
        return num;
    }
}
  • 代理類的實現
 public class SingProxy implements IDoSomething{
    
    private IDoSomething sing = new Sing();

    @Override
    public int doSometing(int num) {
        System.out.println("Befor singing ");
        int result = sing.doSometing(num);
        System.out.println("After singing");
        return result;
    }

}
  • 測試類
public class ProxyDemo {
     public static int sing(IDoSomething sing, int num) {
         return sing.doSometing(num);
     }
     public static void main(String[] args) {
         System.out.println(ProxyDemo.sing(new SingProxy(), 5));
     }
 }
  • 輸出結果
Befor singing 
Sing a song
After singing
5

以上就是簡單的靜態代理,不在過多的介紹,下面是動態代理。

動態代理

Java的動態代理可以動態的創建代理并動態的處理對所代理方法的調用。動態代理有兩種實現方法,一種是使用JDK自帶的,一種是使用Cglib實現。

實現JDK自帶的動態代理

實現JDK自帶的動態代理,關鍵是實現InvocationHandler,同時它要求被代理對象必須有接口。下面是實現的代碼,我加上了必要的注釋。

  • 接口
public interface IProxyClass {
    public int doSomething(int i);
}
  • 被代理類的實現
public class ProxyClassImpl implements IProxyClass {
    @Override
    public int doSomething(int num) {
        System.out.println("方法執行中.....");
        return num;
    }
}
  • 實現InvocationHandler接口
    ??這里我實現了InvocationHandler接口,并手動生成了代理類,保存到了電腦F盤上
public class DynamicProxyHandler implements InvocationHandler {
    
    private Object proxied;
    
    /**
     * @param proxied 被代理對象
     */
    public DynamicProxyHandler(Object proxied) {
        this.proxied = proxied;
    }
    
    /**
     * 返回代理對象
     * @return
     */
    public Object newProxyInstance() {
        return Proxy.newProxyInstance(proxied.getClass().getClassLoader(), proxied.getClass().getInterfaces(), this);
    }

    /**
     * 
     * @param proxy 代理對象
     * @param method 代理方法
     * @param args 方法參數
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 
        //將代理對象生成字節碼到F盤上,方便反編譯出java文件查看,實際動態代理是不需要自己生成的
        addClassToDisk(proxy.getClass().getName(), ProxyClassImpl.class,"F:/$Proxy0.class");
        System.out.println("method:"+method.getName());  
        System.out.println("args:"+args[0].getClass().getName());  
        System.out.println("Before invoke method...");  
        Object object=method.invoke(proxied, args);
        System.out.println("After invoke method...");  
        return object;  
    }
    
    /**
     * 用于生產代理對象的字節碼,并將其保存到硬盤上
     * @param className
     * @param cl
     * @param path
     */
    private void addClassToDisk(String className, Class<?> cl, String path) {
        //用于生產代理對象的字節碼
        byte[] classFile = ProxyGenerator.generateProxyClass(className, cl.getInterfaces());
        FileOutputStream out = null;  
        try {  
            out = new FileOutputStream(path);  
            //將代理對象的class字節碼寫到硬盤上
            out.write(classFile);  
            out.flush();  
        } catch (Exception e) {  
            e.printStackTrace();  
        } finally {  
            try {  
                out.close();  
            } catch (IOException e) {  
                e.printStackTrace();  
            }  
        }  
    }
    
}
  • 測試類
public class SimpleProxyDemo {
      public static void main(String[] args) throws SecurityException, NoSuchMethodException {
          ProxyClassImpl c = new ProxyClassImpl();
          DynamicProxyHandler proxyHandler = new DynamicProxyHandler(c);
          IProxyClass proxyClass = (IProxyClass)proxyHandler.newProxyInstance();
          System.out.println(proxyClass.getClass().getName());
          System.out.println(proxyClass.doSomething(5));
      }
}
  • 輸出結果
com.sun.proxy.$Proxy0
method:doSomething
args:java.lang.Integer
Before invoke method...
方法執行中.....
After invoke method...
5

從結果我們可以看到(IProxyClass)proxyHandler.newProxyInstance();實際返回的是com.sun.proxy.$Proxy0,我們把生成的$Proxy0.class文件,使用jad.exe進行反編譯,使用命令(要求文件和jad.exe在同一個目錄下,或者你可以吧jad加到環境變量中去):

jad -p java $Proxy0.class

得到的$Proxy0.java如下:

public final class $Proxy0 extends Proxy
    implements IProxyClass
{

    public $Proxy0(InvocationHandler invocationhandler)
    {
        super(invocationhandler);
    }

    public final boolean equals(Object obj)
    {
        try
        {
            return ((Boolean)super.h.invoke(this, m1, new Object[] {
                obj
            })).booleanValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final void doSomething(int i)
    {
        try
        {
            super.h.invoke(this, m3, new Object[] {
                Integer.valueOf(i)
            });
            return;
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final int hashCode()
    {
        try
        {
            return ((Integer)super.h.invoke(this, m0, null)).intValue();
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    public final String toString()
    {
        try
        {
            return (String)super.h.invoke(this, m2, null);
        }
        catch(Error _ex) { }
        catch(Throwable throwable)
        {
            throw new UndeclaredThrowableException(throwable);
        }
    }

    private static Method m1;
    private static Method m3;
    private static Method m0;
    private static Method m2;

    static 
    {
        try
        {
            m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] {
                Class.forName("java.lang.Object")
            });
            m3 = Class.forName("io.github.brightloong.proxy.IProxyClass").getMethod("doSomething", new Class[] {
                Integer.TYPE
            });
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
        }
        catch(NoSuchMethodException nosuchmethodexception)
        {
            throw new NoSuchMethodError(nosuchmethodexception.getMessage());
        }
        catch(ClassNotFoundException classnotfoundexception)
        {
            throw new NoClassDefFoundError(classnotfoundexception.getMessage());
        }
    }
}

可以看到實際調用的是25行的doSometing()方法。如果你想了解更加具體的JDK動態代理的實現原理可以訪問Rejoy的博文JDK動態代理實現原理

使用Cglib實現動態代理

Cglib不是jdk自帶的jar包,需要下載并加入到項目中。個人覺得Cglib比使用jdk自帶的實現動態代理更為先進,畢竟它不再需要接口,而且它還有其他強大的功能,大家可以自行研究。

  • 實現MethodInterceptor接口
public class CglibProxy implements MethodInterceptor{
    private Enhancer enhancer = new Enhancer();
    @Override
    /**
     * 
     * @param o 是被代理對象
     * @param method 調用方法的Method對象
     * @param args 方法參數
     * @param methodProxy
     * @return cglib生成用來代替Method對象的一個對象,使用MethodProxy比調用JDK自身的Method直接執行方法效率會有提升
     * @throws Throwable
     */
    public Object intercept(Object o, Method method, Object[] args,
            MethodProxy methodProxy) throws Throwable {
        System.out.println("before " + methodProxy.getSuperName());  
        System.out.println(method.getName());  
        Object o1 = methodProxy.invokeSuper(o, args);  
        //Object o2 = method.invoke(o, args); 使用這種方式會發生死循環,因為方法會被攔截
        System.out.println("after " + methodProxy.getSuperName());  
        return o1;  
    }
    
    public  Object newProxyInstance(Class<?> c) {
        //設置產生的代理對象的父類。
        enhancer.setSuperclass(c); 
        //設置CallBack接口的實例
        enhancer.setCallback(this);  
        //使用默認無參數的構造函數創建目標對象 
        return enhancer.create();  
    }
}
  • 被代理對象和測試類
public class CglibDemo {
    public static void main(String[] args) {
        CglibProxy cglibProxy = new CglibProxy();  
        Do o = (Do)cglibProxy.newProxyInstance(Do.class);  
        System.out.println(o.doSomething(5));
    }
}
class Do{
    public int doSomething(int num){
        System.out.println("方法執行中。。。。。。");
        return num;
    }
}
  • 輸出結果
before CGLIB$doSomething$0
doSomething
方法執行中。。。。。。
after CGLIB$doSomething$0
5

本篇筆記參考于:

http://www.cnblogs.com/shijiaqi1066/p/3429691.html

http://rejoy.iteye.com/blog/1627405

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,505評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,556評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,463評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,009評論 1 312
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,778評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,218評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,281評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,436評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,969評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,795評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,993評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,537評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,229評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,659評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,917評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,687評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,990評論 2 374

推薦閱讀更多精彩內容

  • 從三月份找實習到現在,面了一些公司,掛了不少,但最終還是拿到小米、百度、阿里、京東、新浪、CVTE、樂視家的研發崗...
    時芥藍閱讀 42,320評論 11 349
  • 轉自:http://xiezhaodong.me/2017/03/31/Java-JDK%E4%BB%A3%E7%...
    王帥199207閱讀 1,293評論 1 17
  • 隨著一年辛勞的結束,2017新年的鐘聲也敲響了。 年的味道越來越濃。不知是自己敏感還是怎么,今年這年過得真冷清。 ...
    莫念zyj閱讀 285評論 0 0
  • 文 /淺漸深 ‘ 與你相遇,好幸運! ’ 楚源曾說: 在愛情里, 我就是個拿的起放不下的人 ,誰遇...
    淺漸深閱讀 481評論 0 0
  • 以前是只想著每天都寫點什么東西,但是每次,手機打上幾個字是,打了刪刪了打,其實到底我也不知道該說些什么。直到用了這...
    wangaq閱讀 234評論 0 0