1. 程式人生 > >【Java必會】一個保姆與兩隻寵物的“代理”故事(動態代理)

【Java必會】一個保姆與兩隻寵物的“代理”故事(動態代理)

文章導航:

文章目的: 1、介紹什麼是代理 2、如何理解“代理” 3、代理有哪些實現方式

什麼是代理?

在講解Java代理之前我們先要搞明白什麼是“代理”?

代理:法律上指以他人的名義,在授權範圍內進行對被代理人直接發生法律效力的法律行為。 1.短時間代人擔任職務 2.受委託代表當事人進行某種活動。如:訴訟、簽訂合同等 3.對為別人進行訴訟的人的認可 4.代理人的職務

如果對博主上面查詢到的結果有異議,一律駁回!除非你給我搬出《新華詞典》!

在《head first 設計模式》中也對Java的代理模式做出瞭解釋:

代理模式: 為另一個物件提供一個替身或佔位符以控制對這個物件的訪問。

所以總的來說: 代理就是控制一個物件的訪問,並替被代理者進行相應的行為。

代理模式在我們生活中也是非常常見的,博主忘記在哪個論壇上看過這樣一個例子,非常形象,印象很深,說的是三國時期一個非常有名的代理事件—— 曹操挾天子以令諸侯!這個故事想必大家都知道,在這個事件中,漢獻帝就是被代理者,曹操就是他的代理。曹操要想代替天子做某些事情,首先就必須把天子控制起來,天子想發一道詔書,必須經過曹丞相,下面文武百官想向天子提交奏摺也必須經過曹丞相,這樣一來曹操就控制了天子的訪問,並代替他進行了一些行為。

當然,上面那個例子只是形容了代理模式中控制物件的訪問,並不是說所有的代理都是像上面那個例子一樣被迫進行的。即使不是非常準確的比喻,但是我們還是可以從上面那個例子中可以提取出兩個要點:1)代理擁有被代理的控制權

2)被代理物件需要進行的操作都要經過代理物件

下面我會通過兩個案例進行相關講解:

Java代理分為靜態代理和動態代理,首先我們先說一下靜態代理。

靜態代理

博主呢養了兩隻寵物,一個小貓(Mark),一隻小狗(Lucky),現在我們就從他們入手,我先建立一個動物介面,描述兩隻寵物的一些行為

package com.promonkey.biz;

/**
 * Created by promonkey on 2017/2/21.
 */
public interface AnimalBase {

    String eat();//吃東西

    String exercise();//運動
}

接下來就開始寫兩隻小傢伙的實現類了 小貓: 

package com.promonkey.biz.impl;

import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class Cat implements PetBase{

    private String name;
    private int age;
    private int health;

    private static Cat cat;

    //因為我只有一隻小貓,所以私有化構造器,寫成了單例模式
    private Cat(String name, int age, int health) {
        this.name = name;
        this.age = age;
        this.health = health;
    }

    //Mark吃魚
    public String eat() {
        return this.name + " eat fish!";
    }

    //Mark喜歡玩球類
    public String exercise() {
        return this.name + " paly ball!";
    }

    //獲得Mark物件
    public static Cat getCat() {
        if (cat == null) {
            cat = new Cat("Mark", 2, 90);
        }
        return cat;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getHealthValue() {
        return health;
    }

    /**
     * 為了方便顯示出動物的一些屬性值,重寫了toString方法
     */
    @Override
    public String toString() {
        return "-------\n" +
                "name:" + this.getName() +
                "\nage:" + this.getAge() +
                "\nhealth:" + this.getHealthValue() +
                "\n-------";
    }
}

---------------------

本文來自 AProMonkey 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/jdjdndhj/article/details/56291629?utm_source=copy 

小狗(寫法和小貓的類似):

package com.promonkey.biz.impl;

import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class Dog implements PetBase {

    private String name;
    private int age;
    private int health;

    private static Dog lucky;

    public Dog(String name, int age, int health) {
        this.name = name;
        this.age = age;
        this.health = health;
    }

    //Lucky 吃骨頭
    public String eat() {
        return getName() + " eat bone!";
    }

    //Lucky 奔跑
    public String exercise() {
        return getName() + " run!";
    }

    public static Dog getDog() {
        if (lucky == null) {
            lucky = new Dog("Lucky", 5, 95);
        }
        return lucky;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getHealthValue() {
        return health;
    }

    @Override
    public String toString() {
        return "-------\n" +
                "name:" + this.getName() +
                "\nage:" + this.getAge() +
                "\nhealth:" + this.getHealthValue() +
                "\n-------";
    }
}

現在就可以讓兩個小傢伙做一些動作了: 測試類:

 @Test
 public void testCat() {
    AnimalAbstract cat = Cat.getCat();//獲得Mark物件
    System.out.println(cat);//輸出Mark的一些屬性值
    System.out.println(cat.eat());//Mark吃魚
    System.out.println(cat.exercise());//Mark玩球

}

 @Test
public void testDog() {
    AnimalAbstract dog = Dog.getDog();//獲得Lucky物件
    System.out.println(dog);//輸出Lucky的一些屬性值
    System.out.println(dog.eat());//Lucky吃骨頭
    System.out.println(dog.exercise());//Lucky奔跑
}

新增需求:現在我需要記錄兩個小傢伙的一些日常行為,比如什麼時候吃東西了,什麼時候運動了……就相當於一個日誌記錄。

有一個很直接的方法——讓兩個小傢伙自己記錄。然而,你如果有這樣的寵物,請送我一打!即便是它們可以後天學會,但是我還得花大量時間去教會它們(重新編寫內部程式)!況且我可能只是這兩天心血來潮想知道他們的的行蹤而已,過兩天我可能就不想知道他們幾時幾分在幹嘛,而是想知道其他的內容,難道我又去教?顯然不會!像哥這麼機智的銀,肯定會去給它請一個管家,幫它記錄自己的一些行為以及時間!

假如管家要管理我的小寵物,首先要知道小寵物所有的行為(重寫eat(),exercise()),以及小寵物的屬性值,還需要擁有小寵物的控制權(this.petProxy = pet)

管家——petProxy :

package com.promonkey.proxy;


import com.promonkey.biz.PetBase;

/**
 * Created by promonkey on 2017/2/21.
 */
public class PetProxy implements PetBase {

    //小傢伙的管家——petProxy
    private PetBase petProxy;

    /**
     * 管家構造器
     *
     * @param pet 需要管家管理的寵物
     */
    public PetProxy(PetBase pet) {
        //讓管家獲得小傢伙的所有權
        this.petProxy = pet;
    }

    //小傢伙吃的行為;管家控制寵物吃東西需要的操作(控制訪問)
    public String eat() {
        //this就是管家物件,由他負責記錄(記錄功能是他自己的)
        this.logger();
        //這裡實際是小動物(Mark或lucky)的行為,前面已經把小傢伙的控制權給了管家petProxy
        return this.petProxy.eat();
    }

    //小傢伙運動的行為
    public String exercise() {
        logger();
        return this.petProxy.exercise();
    }

    public PetBase getAnimalProxy() {
        return petProxy;
    }

    public void logger() {
        System.out.println("當前時間:" + System.currentTimeMillis());
    }
}

現在兩個小傢伙的管理已經請過來了,現在測試一下它好不好用:

   @Test
    public void testCatProxy() {
        System.out.println("=====未請管理之前=====");
        //獲得Mark物件
        PetBase mark = Cat.getCat();
        //輸出Mark的一些屬性值
        System.out.println(mark);
        //Mark吃魚
        System.out.println(mark.eat());
        //Mark玩球
        System.out.println(mark.exercise());

        System.out.println("=====請管理後=====");
        //請了管理,現在把Mark交給管理員markPry
        PetBase markProxy = new PetProxy(mark);
        //輸出管理員獲得的小貓資訊
        System.out.println(markProxy);
        //管理員控制小貓吃並記錄
        System.out.println(markProxy.eat());
        //管理員控制小貓運動並記錄
        System.out.println(markProxy.exercise());
    }

測試結果:這裡寫圖片描述

看來這個管理員還是不錯的!小狗lucky的請自行測試,都一樣。

這就是靜態代理,之所以叫為“靜態”,是因為使用這種模式時我們需要為任何一個需要被代理的介面建立代理類。

動態代理

此前我只需要記錄我的小寵物的日常行為,所以只請一個管家。但是我現在還要記錄我家保姆的工作情況,如果做得好需要適當的加價薪對吧,哈哈!但是現在就有一個問題了,我之前請的寵物管家只知道我的小寵物的行為這裡寫圖片描述

並且他只能接管我的小寵物這裡寫圖片描述

除此之外他對其他情況一概不知,更加不知道我家保姆需要做哪些事情。雖然保姆的行為我可以教他(給他加上保姆的行為方法),但是重點是他沒有接管保姆的這一項功能(繼承的是寵物的抽象類)。這樣一來肯定就不能讓他去記錄了,我要想記錄保姆的工作情況就必須再去請一個管家了,也就是說需要為我的保姆類寫一個新的代理類。如果我還要記錄我家花花草草的成長記錄呢?也是需要重新請一個管家?於現實而言,我沒有那麼多money去請那麼多管家,錢燒得慌!於開發而言,我沒有那麼多精力和時間去為每一個需要被代理的介面建立新的代理類。

那麼有沒有什麼方法途徑可以把這些事情全部交給一個管家去做呢?即節省了money,又提高了效率,豈不快哉!

答案肯定是有的!那就是“動態代理”!下面我們來完成上面所說的,除了記錄小寵物,還要記錄保姆李嬸的工作情況。

保姆介面:

package com.promonkey.biz;

/**
 * Created by promonkey on 2017/2/22.
 */
public interface BabySitter {

    //照看小孩
    String childmind();

    //打掃衛生
    String sweep();

    //下廚
    String cook();

}
  • 保姆介面實現類 —— 李嬸
package com.promonkey.biz.impl;

import com.promonkey.biz.BabySitter;

/**
 * Created by promonkey on 2017/2/22.
 */
public class BabySitterImpl implements BabySitter {

    private String name;//保姆姓名

    //家裡只請一個保姆,沒打算請其他保姆
    private BabySitterImpl(String name) {
        this.name = name;
    }

    public String childmind() {
        return this.name + " 照看小孩!";
    }

    public String sweep() {
        return this.name + " 打掃衛生!";
    }

    public String cook() {
        return this.name + " 下廚做飯!";
    }

    //取得"李嬸"物件
    public static BabySitter getBabySitter(){
        return new BabySitterImpl("李嬸");
    }
}

先看下李嬸的正常工作情況:這裡寫圖片描述

接下來就要請一個”全能管家”了,既要記錄小寵物的日常行為,還要記錄李嬸的工作情況。現在我們有兩種途徑可以請到這樣的”全能管家”,一種是通過jdk,一種是通過cglib。

通過jdk請管家(jdk實現動態代理)

管家:

package com.promonkey.proxy;

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

/**
 * Created by promonkey on 2017/2/22.
 */
public class StewardInvocationHandler implements InvocationHandler {

    // 目標物件
    private Object object;

    /**
     * 構造方法
     *
     * @param object 需要被代理的物件(目標物件)
     */
    public StewardInvocationHandler(Object object) {
        super();
        this.object = object;
    }

    /**
     * 執行目標物件(被代理物件)的方法
     *
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 管家在被管理物件(寵物或保姆)有行為操作時記錄時間
        logger();
        // 執行被管理物件的操作
        Object result = method.invoke(object, args);
        return result;
    }

    /**
     * 獲得目標物件的代理物件(獲得管家物件)
     *
     * @return 返回代理物件(管家)
     */
    public Object getProxyObject() {
        return Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), object.getClass().getInterfaces(), this);
    }

    //時間記錄
    public void logger() {
        System.out.println(">>>>>Time: " + System.currentTimeMillis());
    }
}

使用jdk(實現InvocationHandler 介面)請到了我們需要的管家,現在進行測試,看這個管家是否合格:

package com.promonkey.test;

import com.promonkey.biz.BabySitter;
import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.BabySitterImpl;
import com.promonkey.biz.impl.Cat;
import com.promonkey.biz.impl.Dog;
import com.promonkey.proxy.StewardInvocationHandler;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestSteward {

    public static void main(String[] args) {

        // 需要被管家管理的物件
        PetBase cat = Cat.getCat();
        PetBase dog = Dog.getDog();
        BabySitter babySitter = BabySitterImpl.getBabySitter();

        // 管家物件(全能管家可以根據不同的管理物件進行“變身”)
        StewardInvocationHandler steward ;

        // 管理貓的時候
        System.out.println("=====管理Mark=====");
        steward = new StewardInvocationHandler(cat);
        PetBase catProxy = (PetBase) steward.getProxyObject();
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());

        // 管理狗的時候
        System.out.println("=====管理Lucky=====");
        steward = new StewardInvocationHandler(dog);
        PetBase dogProxy = (PetBase) steward.getProxyObject();
        System.out.println(dogProxy.eat());
        System.out.println(dogProxy.exercise());

        // 管理保姆的時候
        System.out.println("=====管理李嬸=====");
        steward = new StewardInvocationHandler(babySitter);
        BabySitter sitterProxy = (BabySitter) steward.getProxyObject();
        System.out.println(sitterProxy.childmind());
        System.out.println(sitterProxy.sweep());
        System.out.println(sitterProxy.cook());
    }
}

測試結果:這裡寫圖片描述

nice!這不就是我們需要的“全能管家”嗎!管家在手,天下我有!哈哈!

現在已經證實在jdk平臺請我們需要的管家沒問題,但是本著“貨比三家”的原則,我們再看看其他途徑請的管家是不是會優惠一點,或者“質量”好點呢!

通過cglib請管家(cglib實現動態代理)

通過cglib請管家的話我們還需要下載cglib這個“APP”才能請。 maven:

  <!-- https://mvnrepository.com/artifact/cglib/cglib -->
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.2.2</version>
        </dependency>

導好jar包後,我們就開始“請”管家了。

管家:

package com.promonkey.proxy;

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

import java.lang.reflect.Method;

/**
 * Created by promonkey on 2017/2/22.
 */
public class CglibStewardProxy implements MethodInterceptor {

    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        // 管家在被管理物件(寵物或保姆)有行為操作時記錄時間
        logger();
        // 執行被管理物件的操作
        Object result = methodProxy.invokeSuper(o, objects);
        return result;
    }

    /**
     * 獲得目標物件的代理物件(獲得管家物件)
     *
     * @return 返回代理物件(管家)
     */
    public Object getProxyObject(Class superClass,Class[] dataType,Object[] args) {
        Enhancer enhancer = new Enhancer();
        enhancer.setCallback(this);
        enhancer.setSuperclass(superClass);
        return enhancer.create(dataType,args);
    }

    //時間記錄
    public void logger(){
        System.out.println(">>>>>Time: " + System.currentTimeMillis());
    }
}

同樣的,通過cglib請到了管家後,我們來測試一下這個管家是否合格! 測試:

package com.promonkey.test;

import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.Cat;
import com.promonkey.proxy.CglibStewardProxy;
import net.sf.cglib.proxy.Enhancer;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestCglibProxy {

    public static void main(String[] args) {
        CglibStewardProxy stewardProxy = new CglibStewardProxy();

        Enhancer enhancer = new Enhancer();

        // 管理貓的時候
        System.out.println("=====管理Mark=====");
        // Cat類中構造器引數型別
        Class[] cat_type = {String.class, int.class, int.class};
        // Cat類構造器引數
        Object[] cat_obj = {"Mark", 2, 90};
        // 獲得代理物件
        PetBase catProxy = (PetBase) stewardProxy.getProxyObject(Cat.class, cat_type, cat_obj);
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());
    }
}

執行結果:執行結果—報錯

咦?這個cglib平臺“不靠譜”呀,管家都沒請到!看看報錯資訊, No visible constructors in class com.promonkey.biz.impl.Cat;在Cat類中沒有可見的構造器!!!

是的,如果你還記得我們最開始是把Cat構造器私有化了,因為cglib需要使用構造器來獲得被代理的物件(cglib的內部實現原理以後有時間我們再來詳細研究一下),現在我們對之前的Cat,Dog,BabySitterImpl三個類進行稍微的改動,以便此次的測試。把三個類現有的構造器設定為public即可!

修改完後我們再次進行測試:

package com.promonkey.test;

import com.promonkey.biz.BabySitter;
import com.promonkey.biz.PetBase;
import com.promonkey.biz.impl.BabySitterImpl;
import com.promonkey.biz.impl.Cat;
import com.promonkey.biz.impl.Dog;
import com.promonkey.proxy.CglibStewardProxy;

/**
 * Created by promonkey on 2017/2/22.
 */
public class TestCglibProxy {

    public static void main(String[] args) {

        CglibStewardProxy stewardProxy = new CglibStewardProxy();

        // 管理貓的時候
        System.out.println("=====管理Mark=====");
        // Cat類中構造器引數型別
        Class[] cat_type = {String.class, int.class, int.class};
        // Cat類構造器引數
        Object[] cat_obj = {"Mark", 2, 90};
        // 獲得代理物件
        PetBase catProxy = (PetBase) stewardProxy.getProxyObject(Cat.class, cat_type, cat_obj);
        System.out.println(catProxy.eat());
        System.out.println(catProxy.exercise());

        // 管理狗的時候
        System.out.println("=====管理Lucky=====");
        Class[] dog_type = {String.class, int.class, int.class};
        Object[] dog_obj = {"Lucky", 5, 95};
        // 獲得代理物件
        PetBase dogProxy = (PetBase) stewardProxy.getProxyObject(Dog.class, dog_type, dog_obj);
        System.out.println(dogProxy.eat());
        System.out.println(dogProxy.exercise());

        // 管理保姆的時候
        System.out.println("=====管理李嬸=====");
        Class[] type = {String.class};
        Object[] obj = {"李嬸"};
        // 獲得代理物件
        BabySitter sitterProxy = (BabySitter) stewardProxy.getProxyObject(BabySitterImpl.class, type, obj);
        System.out.println(sitterProxy.childmind());
        System.out.println(sitterProxy.sweep());
        System.out.println(sitterProxy.cook());
    }
}

執行結果:這裡寫圖片描述

好了,這次終於通過cglib請來了管家!比較兩個不同平臺(jdk、cglib)“請”管家的方法,你感覺哪一個更順手一點呢?

其中一個比較明顯的比較:這裡寫圖片描述

這裡寫圖片描述

兩段程式碼可以看出,jdk實現動態代理,其被代理的物件必須要實現了某個介面,而cglib只是獲取被代理物件的超類,沒有要求被代理物件實現介面。

思考與總結

文章中我們介紹了靜態代理和動態代理兩種模式,你覺得他們的區別是什麼?

靜態代理和動態代理的區別: 靜態代理需要手動為每一個被代理物件介面建立代理類;而動態代理不需要手動建立代理類,在程式執行時可以動態生成。

看到這裡,現在你有沒有一點自己的理解,為什麼使用代理,以及使用代理的好處和不足呢?

什麼是代理:代理說白了就是控制一個物件的訪問。這也是代理模式和修飾模式的最大區別,修飾模式是對一個物件新增行為,而代理是控制一個物件的訪問。

為什麼使用代理:在某些情況下,一個客戶不想或者不能直接引用另一個物件,而代理物件可以在客戶端和目標物件之間起到中介的作用

代理模式的不足: 1、代理物件通過持有被代理的引用,控制記錄被代理方法的呼叫,但是,代理物件並不知道被代理物件內部實現的邏輯; 2、不清楚被代理物件在方法體中做了什麼,所以註定代理物件在其方法體中只能做一些與被代理物件中的邏輯無關的一些操作。例如,日誌記錄,許可權攔截,訪問控制,異常攔截等通用性功能;

這篇博文主要是想介紹一下什麼是代理,如何理解代理。至於其中一些具體的實現原理(比如cglib內部實現原理)我們之後再進行學習。文章中如若有不妥之處,歡迎大家指正交流!

--------------------- 本文來自 AProMonkey 的CSDN 部落格 ,全文地址請點選:https://blog.csdn.net/jdjdndhj/article/details/56291629?utm_source=copy    ---------------------