java代理(靜態(tài)代理和動(dòng)態(tài)代理)

我們根據(jù)加載被代理類的時(shí)機(jī)不同,將代理分為靜態(tài)代理和動(dòng)態(tài)代理蜘腌。如果我們在代碼編譯時(shí)就確定了被代理的類是哪一個(gè)饵隙,那么就可以直接使用靜態(tài)代理金矛;如果不能確定,那么可以使用類的動(dòng)態(tài)加載機(jī)制娶耍,在代碼運(yùn)行期間加載被代理的類這就是動(dòng)態(tài)代理伺绽,比如RPC框架和Spring AOP機(jī)制嗜湃。

本文介紹如下三種代理:
JDK靜態(tài)代理购披、JDk動(dòng)態(tài)代理春瞬、CGLIB動(dòng)態(tài)代理

一狼忱、靜態(tài)代理


public interface Person {
    public void sayHello(String content, int age);
    public void sayGoodBye(boolean seeAgin, double time);
}


public class Student implements Person{
 
    @Override
    public void sayHello(String content, int age) {
        // TODO Auto-generated method stub
        System.out.println("student say hello" + content + " "+ age);
    }
 
    @Override
    public void sayGoodBye(boolean seeAgin, double time) {
        // TODO Auto-generated method stub
        System.out.println("student sayGoodBye " + time + " "+ seeAgin);
    }
 
}


public class ProxyTest implements Person{
    private Person o;
    
    public ProxyTest(Person o){
        this.o = o;
    }
    public void sayHello(String content, int age) {
        // TODO 自動(dòng)生成的方法存根
        System.out.println("ProxyTest sayHello begin");
        //在代理類的方法中 間接訪問被代理對象的方法
        o.sayHello(content, age);
        System.out.println("ProxyTest sayHello end");
    }

    public void sayGoodBye(boolean seeAgin, double time) {
        // TODO 自動(dòng)生成的方法存根
        System.out.println("ProxyTest sayHello begin");
        //在代理類的方法中 間接訪問被代理對象的方法
        o.sayGoodBye(seeAgin, time);
        System.out.println("ProxyTest sayHello end");
    }

}


public class Client {

    public static void main(String[] args) {
        // TODO 自動(dòng)生成的方法存根
        //s為被代理的對象太防,某些情況下 我們不希望修改已有的代碼氓皱,我們采用代理來間接訪問
        Student s = new Student();
        //創(chuàng)建代理類對象
        ProxyTest proxy = new ProxyTest(s);
        //調(diào)用代理類對象的方法
        proxy.sayHello("welcome to java", 20);
        System.out.println("******");
        //調(diào)用代理類對象的方法
        proxy.sayGoodBye(true, 100);
    }

}


運(yùn)行結(jié)果:
ProxyTest sayHello begin
student say hellowelcome to java 20
ProxyTest sayHello end
******
ProxyTest sayHello begin
student sayGoodBye 100.0 true
ProxyTest sayHello end

二波材、動(dòng)態(tài)代理

我們先直接上動(dòng)態(tài)代理的代碼廷区,之后再分析代碼的行為贾铝,上面的Person接口和Student被代理類保持不變
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class MyInvocationHandler implements InvocationHandler{
    private Object object;
    
    public MyInvocationHandler(Object object){
        this.object = object;
    }
    public Object invoke(Object proxy, Method method, Object[] args)
            throws Throwable {
        // TODO 自動(dòng)生成的方法存根
        System.out.println("MyInvocationHandler invoke begin");
        System.out.println("proxy: "+ proxy.getClass().getName());
        System.out.println("method: "+ method.getName());
        for(Object o : args){
            System.out.println("arg: "+ o);
        }
        //通過反射調(diào)用 被代理類的方法
        method.invoke(object, args);
        System.out.println("MyInvocationHandler invoke end");
        return null;
    }

}


import java.lang.reflect.Proxy;

public class Client2 {

    public static void main(String[] args) {
        // TODO 自動(dòng)生成的方法存根
        //創(chuàng)建需要被代理的類
        Student s = new Student();
        //這一句是生成代理類的class文件垢揩,前提是你需要在工程根目錄下創(chuàng)建com/sun/proxy目錄水孩,不然會(huì)報(bào)找不到路徑的io異常
        //System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
        //獲得加載被代理類的 類加載器
        ClassLoader loader = Thread.currentThread().getContextClassLoader();
        //指明被代理類實(shí)現(xiàn)的接口
        Class<?>[] interfaces = s.getClass().getInterfaces();
        // 創(chuàng)建被代理類的委托類,之后想要調(diào)用被代理類的方法時(shí)俘种,都會(huì)委托給這個(gè)類的invoke(Object proxy, Method method, Object[] args)方法
        MyInvocationHandler h = new MyInvocationHandler(s);
        //生成代理類
        Person proxy = (Person)Proxy.newProxyInstance(loader, interfaces, h);
        //通過代理類調(diào)用 被代理類的方法
        proxy.sayHello("yujie.wang", 20);
        proxy.sayGoodBye(true, 100);
        System.out.println("end");

              /*或者
                //創(chuàng)建需要被代理的類
                Student s = new Student();
                // 創(chuàng)建被代理類的委托類,之后想要調(diào)用被代理類的方法時(shí)宙刘,都會(huì)委托給這個(gè)類的invoke(Object proxy, Method method, Object[] args)方法
        InvocationHandler handler = new MyInvocationHandler(s);
                //生成代理類
        Person p = (Person) Proxy.newProxyInstance(handler.getClass().getClassLoader(), s.getClass().getInterfaces(), handler);
/*
         * 通過Proxy的newProxyInstance方法來創(chuàng)建我們的代理對象,我們來看看其三個(gè)參數(shù)
         * 第一個(gè)參數(shù) handler.getClass().getClassLoader() 衙猪,我們這里使用handler這個(gè)類的ClassLoader對象來加載我們的代理對象
         * 第二個(gè)參數(shù)realSubject.getClass().getInterfaces()垫释,我們這里為代理對象提供的接口是真實(shí)對象所實(shí)行的接口,表示我要代理的是該真實(shí)對象显蝌,這樣我就能調(diào)用這組接口中的方法了
         * 第三個(gè)參數(shù)handler曼尊, 我們這里將這個(gè)代理對象關(guān)聯(lián)到了上方的 InvocationHandler 這個(gè)對象上
         */
                //通過代理類調(diào)用 被代理類的方法
        p.sayHello("yujie.wang", 20);
        p.sayGoodBye(true, 100);*/
    }

}

運(yùn)行結(jié)果:
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayHello
arg: yujie.wang
arg: 20
student say helloyujie.wang 20
MyInvocationHandler invoke end
MyInvocationHandler invoke begin
proxy: com.sun.proxy.$Proxy0
method: sayGoodBye
arg: true
arg: 100.0
student sayGoodBye 100.0 true
MyInvocationHandler invoke end
end

對比上面兩種代理的實(shí)現(xiàn)骆撇,發(fā)現(xiàn)很明顯的一個(gè)區(qū)別父叙,就是靜態(tài)代理中,代理類中已經(jīng)指明了要被代理的類(也就是Student類屿岂,我們就實(shí)現(xiàn)了他的接口Person)鲸匿,這個(gè)邏輯是在寫代碼的時(shí)候就已經(jīng)知道带欢,但若寫代碼的時(shí)候不知道要代理哪個(gè)類乔煞,就只能通過動(dòng)態(tài)代理實(shí)現(xiàn)了,動(dòng)態(tài)代理中逗宜,代理類中指定的是Object 對象空骚,也就是可以代理任何的類囤屹,然后在運(yùn)行的時(shí)候,把Student賦予給代理類(InvocationHandler handler = new MyInvocationHandler(s);)乡括,這樣就完成了動(dòng)態(tài)代理的過程。

總結(jié)一下:
jdk的代理讓我們在不直接訪問某些對象的情況下诲泌,通過代理機(jī)制也可以訪問被代理對象的方法盲赊,這種技術(shù)可以應(yīng)用在很多地方比如RPC框架,Spring AOP機(jī)制档礁,但是我們看到j(luò)dk的代理機(jī)制必須要求被代理類實(shí)現(xiàn)某個(gè)方法,這樣在生成代理類的時(shí)候才能知道重新那些方法吝沫。這樣一個(gè)沒有實(shí)現(xiàn)任何接口的類就無法通過jdk的代理機(jī)制進(jìn)行代理呻澜,當(dāng)然解決方法是使用cglib的代理機(jī)制進(jìn)行代理。

三惨险、cglib動(dòng)態(tài)代理

public class Person {
    public void sayHello(String name, int age){
        System.out.println(name+"的年齡為:"+age);
    };
}

import java.lang.reflect.Method;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

public class PersonMethodInterceptor implements MethodInterceptor{

    public Object intercept(Object o, Method method, Object[] objects,
            MethodProxy methodProxy) throws Throwable {
        // TODO 自動(dòng)生成的方法存根
        System.out.println("before:"+method.getName());
        Object object = methodProxy.invokeSuper(o, objects);
        System.out.println("after:"+method.getName());
        return object;
    }

}

import net.sf.cglib.proxy.Enhancer;

public class Client {

    public static void main(String[] args) {
        // TODO 自動(dòng)生成的方法存根
        Enhancer enchancer = new Enhancer();
        enchancer.setSuperclass(Person.class);//繼承被代理的類
        enchancer.setCallback(new PersonMethodInterceptor());//設(shè)置回調(diào)
        Person p = (Person) enchancer.create();//生成代理對象
        p.sayHello("caililiang", 20);//在調(diào)用代理類中的方法時(shí)會(huì)被我們實(shí)現(xiàn)的方法攔截器進(jìn)行攔截
    }

}

運(yùn)行結(jié)果:
before:sayHello
caililiang的年齡為:20
after:sayHello

總結(jié):


image.png
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市辫愉,隨后出現(xiàn)的幾起案子栅受,更是在濱河造成了極大的恐慌,老刑警劉巖恭朗,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件屏镊,死亡現(xiàn)場離奇詭異,居然都是意外死亡痰腮,警方通過查閱死者的電腦和手機(jī)而芥,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來膀值,“玉大人棍丐,你說我怎么就攤上這事〔滋ぃ” “怎么了歌逢?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長翘狱。 經(jīng)常有香客問我秘案,道長,這世上最難降的妖魔是什么潦匈? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任踏烙,我火速辦了婚禮,結(jié)果婚禮上历等,老公的妹妹穿的比我還像新娘讨惩。我一直安慰自己,他們只是感情好寒屯,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布荐捻。 她就那樣靜靜地躺著黍少,像睡著了一般。 火紅的嫁衣襯著肌膚如雪处面。 梳的紋絲不亂的頭發(fā)上厂置,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天,我揣著相機(jī)與錄音魂角,去河邊找鬼昵济。 笑死,一個(gè)胖子當(dāng)著我的面吹牛野揪,可吹牛的內(nèi)容都是我干的访忿。 我是一名探鬼主播,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼斯稳,長吁一口氣:“原來是場噩夢啊……” “哼海铆!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起挣惰,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤卧斟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后憎茂,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體珍语,經(jīng)...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年竖幔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了廊酣。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,617評論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赏枚,死狀恐怖亡驰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情饿幅,我是刑警寧澤凡辱,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站栗恩,受9級特大地震影響透乾,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜磕秤,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一乳乌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧市咆,春花似錦汉操、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽芒篷。三九已至,卻和暖如春采缚,著一層夾襖步出監(jiān)牢的瞬間针炉,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工扳抽, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留篡帕,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓贸呢,卻偏偏與公主長得像镰烧,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子贮尉,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,486評論 2 348