設計模式(1-1)-代理模式

幾個月沒寫部落格了。前些日子換了工作,把事情調整好了,又可以繼續寫部落格了~

學習了下代理模式,本篇文章講動態代理與靜態代理模式怎麼寫,後續文章會講動態代理(JDK動態代理)原理,講講怎麼使用CGLIB實現沒有介面的類的動態代理

一、代理在生活中的例子

代理, 代表授權方處理事務(wiki 的解釋)。 舉個例,顧客(client)想買一個u盤; 顧客想去西部數據工廠買一個u盤, 保安都直接把他攔住叫他去京東買; 沒辦法只能去被授權了的京東那去買,被可惡的中間商賺了差價。
雖然京東和西部數據都是賣u盤的,但是京東只是個代理商,最終需要的u盤還是要去西部數據進貨,京東但是可以發優惠券在顧客買u盤前。
生活中的代理例子很多, 找中介租房子。自己不想做的事,可以交給代理方做,但是代理方也能力有限,最終的事還是需要交給本人去做。

代理模式有啥用?

  • 方法增強, 代理方不僅僅是賣u盤了,還可以發放優惠券,賺取差價
  • 控制訪問,顧客去找西部數據工廠買個u盤,保安直接把他攔住了

二、 靜態代理模式實現

下面我們先看看靜態代理在Java中的實現

以買賣u盤為例子,

步驟一,定義一個介面(serviceInterface), 定義廠家與代理商的共同目標(賣u盤)

public interface UpanSell {

    float sell(int amount);
}

步驟二,創建廠家類(serive),實現步驟一定義的介面

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------西部數據賣出" + amount + "個u盤, 價格: " + price);

        return price;
    }
}

步驟三,創建一個商家類(proxy),實現相同的介面

public class jd implements UpanSell {

    private final UpanSell upanSell = new WesternDigitalUpanFactory();

    @Override
    public float sell(int amount) {

        float price = upanSell.sell(amount);

        // -------增強功能 ↓↓↓↓↓↓↓↓↓↓

        // 中間商賺差價
        price += 25.f * amount;

        System.out.println("------京東賣出" + amount + "個u盤, 價格: " + price);
        System.out.println("------給你一個優惠券, 0.01分");

        return price;
    }
}

步驟四, 創建一個客戶端(client)調用商家買U盤

public class Shop {
    public static void main(String[] args) {
        UpanSell upanSell = new jd();

        upanSell.sell(2);
    }
}

輸出結果

------西部數據賣出2個u盤, 價格: 170.0
------京東賣出2個u盤, 價格: 220.0
------給你一個優惠券, 0.01分

上面的代理類(jd), 做了兩件事,1. 中間商加了價格 2. 給了我們一張優惠券

我們能看到靜態代理的實現還是挺簡單的,也是多態的一個運用,我們是由商家類調用到的廠家類。
簡單是簡單,但是

  1. 如果多一個商家,比如淘寶,也要賣了,是不是需要多一個代理類。
  2. 如果serviceInterface,是不是廠家類與商家類都要修改。

那麼,我們來看看動態代理是怎麼把這些缺點規避掉的

運用JDK實現動態代理👇

三、動態代理模式的實現

動態代理,其實就是不用我們手動實現代理類,而是根據我們傳入的參數在程式運行時,動態生成代理類

3.1 JDK動態代理

我們在看JDK動態代理的程式碼前,不要問為什麼要這麼寫,因為我們可以把它理解成一個模板,規定就是要這麼寫(不用背,寫多了,就自然記住了)。

步驟一、二都跟上面一樣

步驟一,定義一個介面(serviceInterface), 定義廠家與代理商的共同目標(賣u盤)

public interface UpanSell {

    float sell(int amount);
}

步驟二,創建廠家類(serive),實現步驟一定義的介面

public class WesternDigitalUpanFactory implements UpanSell {

    @Override
    public float sell(int amount) {

        float price = 85.0f * amount;

        System.out.println("------西部數據賣出" + amount + "個u盤, 價格: " + price);

        return price;
    }
}

步驟三,實現InvocationHandler介面中的invoke() (定義代理類要乾的事情) !!!

public class MySellHandler implements InvocationHandler {

    /**
     * 目標對象
     */
    private final Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        // 執行目標方法
        Object res = method.invoke(target, args);

        int amount = (int) args[0];

        if(res != null)
        {
            float price = (Float) res;
            price += 25.f * amount;
            System.out.println("------京東賣出" + amount + "個u盤, 價格: " + price);
            System.out.println("------給你一個優惠券, 0.01分");
            res = price;
        }

        return res;
    }
}

步驟四,獲取代理實例,購買U盤 !!!

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

        // 1. 創建目標對象
        UpanSell factory = new WesternDigitalUpanFactory();

        // 2. 創建InvocationHandler對象
        InvocationHandler handler = new MySellHandler(factory);

        // 3. 生成一個代理實體類
        UpanSell proxy = (UpanSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler
        );

        float price = proxy.sell(2);
    }
}

輸出結果

------西部數據賣出2個u盤, 價格: 170.0
------京東賣出2個u盤, 價格: 220.0
------給你一個優惠券, 0.01分

輸出結果當然是一樣的。步驟三、步驟四是最重要的,可以依葫蘆畫瓢,寫一個新的JDK動態代理(中介租房…),不用在意細節!

我們能看見,如果增加或減少serviceInterface的方法,修改的地方沒有靜態代理的多;
用起來更加靈活,代理類與service是解耦的。唯一惱火,可能就是不太容易理解

我們可以看到,我們並沒有寫代理的類,卻實現了代理功能,如何看到這個生成的代理類?

在main方法的第一行加下面的屬性

...
   // 加一個這個玩意兒,就可以了
   System.getProperties().setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");

...

生成的代理class文件

package com.sun.proxy;

import com.ukyu.dynamicproxy.service.UpanSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class $Proxy0 extends Proxy implements UpanSell {
    private static Method m1;
    private static Method m2;
    private static Method m0;
    private static Method m3;

    public $Proxy0(InvocationHandler var1) throws  {
        super(var1);
    }

    public final boolean equals(Object var1) throws  {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    public final String toString() throws  {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final int hashCode() throws  {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final float sell(int var1) throws  {
        try {
            return (Float)super.h.invoke(this, m3, new Object[]{var1});
        } catch (RuntimeException | Error var3) {
            throw var3;
        } catch (Throwable var4) {
            throw new UndeclaredThrowableException(var4);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m3 = Class.forName("com.ukyu.dynamicproxy.service.UpanSell").getMethod("sell", Integer.TYPE);
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

先對這個class文件混個眼熟吧,下一篇文章開始去了解JDK動態代理的原理。