Java代理(Proxy)模式

代理模式(Proxy)是通過代理對象訪問目標對象刃滓,這樣可以在目標對象基礎(chǔ)上增強額外的功能畜疾,如添加權(quán)限,訪問控制和審計等功能握恳。


代理模式
代理(Proxy)模式結(jié)構(gòu)圖

Java代理分為靜態(tài)代理和動態(tài)代理和Cglib代理挖帘,下面進行逐個說明完丽。

1.靜態(tài)代理

接口類AdminService.java接口

package com.lance.proxy.demo.service;

public interface AdminService {
    void update();
    Object find();
}

實現(xiàn)類AdminServiceImpl.java

package com.lance.proxy.demo.service;

public class AdminServiceImpl implements AdminService{
    public void update() {
        System.out.println("修改管理系統(tǒng)數(shù)據(jù)");
    }

    public Object find() {
        System.out.println("查看管理系統(tǒng)數(shù)據(jù)");
        return new Object();
    }
}

代理類AdminServiceProxy.java

package com.lance.proxy.demo.service;

public class AdminServiceProxy implements AdminService {

    private AdminService adminService;

    public AdminServiceProxy(AdminService adminService) {
        this.adminService = adminService;
    }

    public void update() {
        System.out.println("判斷用戶是否有權(quán)限進行update操作");
        adminService.update();
        System.out.println("記錄用戶執(zhí)行update操作的用戶信息、更改內(nèi)容和時間等");
    }

    public Object find() {
        System.out.println("判斷用戶是否有權(quán)限進行find操作");
        System.out.println("記錄用戶執(zhí)行find操作的用戶信息拇舀、查看內(nèi)容和時間等");
        return adminService.find();
    }
}

測試類StaticProxyTest.java

package com.lance.proxy.demo.service;

public class StaticProxyTest {

    public static void main(String[] args) {
        AdminService adminService = new AdminServiceImpl();
        AdminServiceProxy proxy = new AdminServiceProxy(adminService);
        proxy.update();
        System.out.println("=============================");
        proxy.find();
    }
}

輸出:

判斷用戶是否有權(quán)限進行update操作
修改管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行update操作的用戶信息舰涌、更改內(nèi)容和時間等
=============================
判斷用戶是否有權(quán)限進行find操作
記錄用戶執(zhí)行find操作的用戶信息、查看內(nèi)容和時間等
查看管理系統(tǒng)數(shù)據(jù)

總結(jié):
靜態(tài)代理模式在不改變目標對象的前提下你稚,實現(xiàn)了對目標對象的功能擴展瓷耙。
不足:靜態(tài)代理實現(xiàn)了目標對象的所有方法,一旦目標接口增加方法刁赖,代理對象和目標對象都要進行相應(yīng)的修改搁痛,增加維護成本。

2.JDK動態(tài)代理

為解決靜態(tài)代理對象必須實現(xiàn)接口的所有方法的問題宇弛,Java給出了動態(tài)代理鸡典,動態(tài)代理具有如下特點:
1.Proxy對象不需要implements接口;
2.Proxy對象的生成利用JDK的Api枪芒,在JVM內(nèi)存中動態(tài)的構(gòu)建Proxy對象彻况。需要使用java.lang.reflect.Proxy類的

  /**
     * Returns an instance of a proxy class for the specified interfaces
     * that dispatches method invocations to the specified invocation
     * handler.
 
     * @param   loader the class loader to define the proxy class
     * @param   interfaces the list of interfaces for the proxy class
     *          to implement
     * @param   h the invocation handler to dispatch method invocations to
     * @return  a proxy instance with the specified invocation handler of a
     *          proxy class that is defined by the specified class loader
     *          and that implements the specified interfaces

     */
static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler invocationHandler );

方法谁尸,方法參數(shù)說明:
a.ClassLoader loader:指定當前target對象使用類加載器,獲取加載器的方法是固定的纽甘;
b.Class<?>[] interfaces:target對象實現(xiàn)的接口的類型良蛮,使用泛型方式確認類型
c.InvocationHandler invocationHandler:事件處理,執(zhí)行target對象的方法時,會觸發(fā)事件處理器的方法悍赢,會把當前執(zhí)行target對象的方法作為參數(shù)傳入决瞳。

2.1 實戰(zhàn)代碼:

AdminServiceImpl.java和AdminService.java和原來一樣,這里不再贅述左权。
AdminServiceInvocation.java

package com.lance.proxy.demo.service;

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

public class AdminServiceInvocation  implements InvocationHandler {

    private Object target;

    public AdminServiceInvocation(Object target) {
        this.target = target;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("判斷用戶是否有權(quán)限進行操作");
       Object obj = method.invoke(target);
        System.out.println("記錄用戶執(zhí)行操作的用戶信息皮胡、更改內(nèi)容和時間等");
        return obj;
    }
}

AdminServiceDynamicProxy.java

package com.lance.proxy.demo.service;

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

public class AdminServiceDynamicProxy {
    private Object target;
    private InvocationHandler invocationHandler;
    public AdminServiceDynamicProxy(Object target,InvocationHandler invocationHandler){
        this.target = target;
        this.invocationHandler = invocationHandler;
    }

    public Object getPersonProxy() {
        Object obj = Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocationHandler);
        return obj;
    }
}

DynamicProxyTest.java

package com.lance.proxy.demo.service;

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

public class DynamicProxyTest {
    public static void main(String[] args) {

        // 方法一
        System.out.println("============ 方法一 ==============");
        AdminService adminService = new AdminServiceImpl();
        System.out.println("代理的目標對象:" + adminService.getClass());

        AdminServiceInvocation adminServiceInvocation = new AdminServiceInvocation(adminService);

        AdminService proxy = (AdminService) new AdminServiceDynamicProxy(adminService, adminServiceInvocation).getPersonProxy();

        System.out.println("代理對象:" + proxy.getClass());

        Object obj = proxy.find();
        System.out.println("find 返回對象:" + obj.getClass());
        System.out.println("----------------------------------");
        proxy.update();

        //方法二
        System.out.println("============ 方法二 ==============");
        AdminService target = new AdminServiceImpl();
        AdminServiceInvocation invocation = new AdminServiceInvocation(adminService);
        AdminService proxy2 = (AdminService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), invocation);

        Object obj2 = proxy2.find();
        System.out.println("find 返回對象:" + obj2.getClass());
        System.out.println("----------------------------------");
        proxy2.update();

        //方法三
        System.out.println("============ 方法三 ==============");
        final AdminService target3 = new AdminServiceImpl();
        AdminService proxy3 = (AdminService) Proxy.newProxyInstance(target3.getClass().getClassLoader(), target3.getClass().getInterfaces(), new InvocationHandler() {

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("判斷用戶是否有權(quán)限進行操作");
                Object obj = method.invoke(target3, args);
                System.out.println("記錄用戶執(zhí)行操作的用戶信息、更改內(nèi)容和時間等");
                return obj;
            }
        });

        Object obj3 = proxy3.find();
        System.out.println("find 返回對象:" + obj3.getClass());
        System.out.println("----------------------------------");
        proxy3.update();


    }
}

輸出結(jié)果:

============ 方法一 ==============
代理的目標對象:class com.lance.proxy.demo.service.AdminServiceImpl
代理對象:class com.sun.proxy.$Proxy0
判斷用戶是否有權(quán)限進行操作
查看管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息赏迟、更改內(nèi)容和時間等
find 返回對象:class java.lang.Object
----------------------------------
判斷用戶是否有權(quán)限進行操作
修改管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息屡贺、更改內(nèi)容和時間等
============ 方法二 ==============
判斷用戶是否有權(quán)限進行操作
查看管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息、更改內(nèi)容和時間等
find 返回對象:class java.lang.Object
----------------------------------
判斷用戶是否有權(quán)限進行操作
修改管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息锌杀、更改內(nèi)容和時間等
============ 方法三 ==============
判斷用戶是否有權(quán)限進行操作
查看管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息甩栈、更改內(nèi)容和時間等
find 返回對象:class java.lang.Object
----------------------------------
判斷用戶是否有權(quán)限進行操作
修改管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息、更改內(nèi)容和時間等

3.Cglib代理

JDK動態(tài)代理要求target對象是一個接口的實現(xiàn)對象抛丽,假如target對象只是一個單獨的對象,并沒有實現(xiàn)任何接口饰豺,這時候就會用到Cglib代理(Code Generation Library)亿鲜,即通過構(gòu)建一個子類對象,從而實現(xiàn)對target對象的代理冤吨,因此目標對象不能是final類(報錯)蒿柳,且目標對象的方法不能是final或static(不執(zhí)行代理功能)。
Cglib依賴的jar包

  <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.10</version>
  </dependency>

3.1實戰(zhàn)

目標對象類AdminCglibService.java

package com.lance.proxy.demo.service;

public class AdminCglibService {
    public void update() {
        System.out.println("修改管理系統(tǒng)數(shù)據(jù)");
    }

    public Object find() {
        System.out.println("查看管理系統(tǒng)數(shù)據(jù)");
        return new Object();
    }
}

代理類AdminServiceCglibProxy.java

package com.lance.proxy.demo.service;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class AdminServiceCglibProxy implements MethodInterceptor {

    private Object target;

    public AdminServiceCglibProxy(Object target) {
        this.target = target;
    }

    //給目標對象創(chuàng)建一個代理對象
    public Object getProxyInstance() {
        //工具類
        Enhancer en = new Enhancer();
        //設(shè)置父類
        en.setSuperclass(target.getClass());
        //設(shè)置回調(diào)函數(shù)
        en.setCallback(this);
        //創(chuàng)建子類代理對象
        return en.create();
    }

    public Object intercept(Object object, Method method, Object[] arg2, MethodProxy proxy) throws Throwable {

        System.out.println("判斷用戶是否有權(quán)限進行操作");
        Object obj = method.invoke(target);
        System.out.println("記錄用戶執(zhí)行操作的用戶信息漩蟆、更改內(nèi)容和時間等");
        return obj;
    }


}

Cglib代理測試類CglibProxyTest.java

package com.lance.proxy.demo.service;

public class CglibProxyTest {
    public static void main(String[] args) {

        AdminCglibService target = new AdminCglibService();
        AdminServiceCglibProxy proxyFactory = new AdminServiceCglibProxy(target);
        AdminCglibService proxy = (AdminCglibService)proxyFactory.getProxyInstance();

        System.out.println("代理對象:" + proxy.getClass());

        Object obj = proxy.find();
        System.out.println("find 返回對象:" + obj.getClass());
        System.out.println("----------------------------------");
        proxy.update();
    }
}

輸出結(jié)果:

代理對象:class com.lance.proxy.demo.service.AdminCglibService$$EnhancerByCGLIB$$41b156f9
判斷用戶是否有權(quán)限進行操作
查看管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息垒探、更改內(nèi)容和時間等
find 返回對象:class java.lang.Object
----------------------------------
判斷用戶是否有權(quán)限進行操作
修改管理系統(tǒng)數(shù)據(jù)
記錄用戶執(zhí)行操作的用戶信息、更改內(nèi)容和時間等

總結(jié)

理解上述Java代理后怠李,也就明白Spring AOP的代理實現(xiàn)模式圾叼,即加入Spring中的target是接口的實現(xiàn)時,就使用JDK動態(tài)代理捺癞,否是就使用Cglib代理夷蚊。Spring也可以通過<aop:config proxy-target-class="true">強制使用Cglib代理,使用Java字節(jié)碼編輯類庫ASM操作字節(jié)碼來實現(xiàn)髓介,直接以二進制形式動態(tài)地生成 stub 類或其他代理類惕鼓,性能比JDK更強。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末唐础,一起剝皮案震驚了整個濱河市箱歧,隨后出現(xiàn)的幾起案子矾飞,更是在濱河造成了極大的恐慌,老刑警劉巖呀邢,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件洒沦,死亡現(xiàn)場離奇詭異,居然都是意外死亡驼鹅,警方通過查閱死者的電腦和手機微谓,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來输钩,“玉大人豺型,你說我怎么就攤上這事÷蚰耍” “怎么了姻氨?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長剪验。 經(jīng)常有香客問我肴焊,道長,這世上最難降的妖魔是什么功戚? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任娶眷,我火速辦了婚禮,結(jié)果婚禮上啸臀,老公的妹妹穿的比我還像新娘届宠。我一直安慰自己,他們只是感情好乘粒,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布豌注。 她就那樣靜靜地躺著,像睡著了一般灯萍。 火紅的嫁衣襯著肌膚如雪轧铁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天旦棉,我揣著相機與錄音齿风,去河邊找鬼。 笑死绑洛,一個胖子當著我的面吹牛聂宾,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播诊笤,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼系谐,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起纪他,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤鄙煤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后茶袒,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梯刚,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年薪寓,在試婚紗的時候發(fā)現(xiàn)自己被綠了亡资。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡向叉,死狀恐怖锥腻,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情母谎,我是刑警寧澤瘦黑,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站奇唤,受9級特大地震影響幸斥,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜咬扇,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一甲葬、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧懈贺,春花似錦经窖、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽葛虐。三九已至胎源,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間屿脐,已是汗流浹背涕蚤。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留的诵,地道東北人万栅。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像西疤,于是被迫代替她去往敵國和親烦粒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355