天天看點

07 結構型模式之代理模式(Proxy)

文章目錄

    • 1 結構型模式
    • 2 代理模式介紹
      • 2.1 概述
      • 2.1 角色
    • 3 靜态代理
    • 4 動态代理
      • 4.1 JDK動态代理
        • 4.1.1 API介紹
        • 4.1.2 示範
        • 4.1.3 jdk實作代理分析
      • 4.2 CGLIB動态代理
    • 5 三種代理的對比
      • 5.1 優缺點
      • 5.2 使用場景

1 結構型模式

結構型模式描述如何将類或對象按某種布局組成更大的結構。它分為類結構型模式和對象結構型模式,前者采用繼承機制來組織接口和類,後者釆用組合或聚合來組合對象。

由于組合關系或聚合關系比繼承關系耦合度低,滿足“合成複用原則”,是以對象結構型模式比類結構型模式具有更大的靈活性。

結構型模式分為以下 7 種:

  • 代理模式
  • 擴充卡模式
  • 裝飾者模式
  • 橋接模式
  • 外觀模式
  • 組合模式
  • 享元模式

2 代理模式介紹

2.1 概述

由于某些原因需要給某對象

提供一個代理以控制對該對象的通路。

這時,通路對象不适合或者不能直接引用目标對象,

代理對象作為通路對象和目标對象之間的中介

Java中的代理按照代理類生成時機不同又分為

靜态代理

動态代理

。靜态代理代理類在編譯期就生成,而動态代理代理類則是在Java運作時動态生成。

動态代理又有JDK代理和CGLib代理兩種。

2.1 角色

代理(Proxy)模式分為三種角色:

  • 抽象主題(Subject)類: 通過

    接口

    抽象類

    聲明真實主題和代理對象實作的業務方法。
  • 真實主題(Real Subject)類:

    實作了抽象主題中的具體業務

    ,是

    代理對象所代表的真實對象

    ,是

    最終要引用的對象

    。也叫目标類
  • 代理(Proxy)類 : 實作了與真實主題相同的接口,其内部含有對真實主題的引用,它可以通路、控制或擴充真實主題的功能。

比如:

如果要買火車票的話,需要去火車站買票,坐車到火車站,排隊等一系列的操作,顯然比較麻煩。而火車站在多個地方都有代售點,我們去代售點買票就友善很多了。這個例子其實就是典型的代理模式,火車站是目标對象,代售點是代理對象。

07 結構型模式之代理模式(Proxy)

3 靜态代理

07 結構型模式之代理模式(Proxy)
  1. 定義售票接口: 抽象主題(Subject)類
package study.wyy.design.proxy.staticproxy;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 3:03 下午
 */
public interface SellTicket {

    /**
     *  @author: wyaoyao
     *  @Date: 2020/12/11 3:04 下午
     *  @Description: 售票
     */
    void sellTicket(String IDCard);
}

           
  1. 定義火車站:目标類
package study.wyy.design.proxy.staticproxy;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 3:04 下午
 * 火車站,火車站具有賣票功能,是以需要實作SellTickets接口
 */
public class TrainStation implements SellTicket{

    @Override
    public void sellTicket(String IDCard) {
        System.out.println("火車站售票");
    }
}
           
  1. 代售點:代理類
package study.wyy.design.proxy.staticproxy;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 3:08 下午
 * 代售點
 */
public class ProxySellPoint implements SellTicket{

    private TrainStation trainStation;

    public ProxySellPoint(TrainStation trainStation) {
        this.trainStation = trainStation;
    }

    @Override
    public void sellTicket(String IDCard) {
        System.out.println("代售點收取服務費");
        trainStation.sellTicket(IDCard);
    }
}
           
  1. 測試
public static void main(String[] args) {
       TrainStation trainStation = new TrainStation();
       ProxySellPoint proxySellPoint = new ProxySellPoint(trainStation);
       proxySellPoint.sellTicket("12312312");
   }
           

從上面代碼中可以看出測試類直接通路的是ProxyPoint類對象,也就是說ProxyPoint作為通路對象和目标對象的中介。同時也對sell方法進行了增強(代理點收取一些服務費用)。

4 動态代理

4.1 JDK動态代理

接下來我們使用動态代理實作上面案例,先說說JDK提供的動态代理。Java中提供了一個動态代理類Proxy,

Proxy并不是我們上述所說的代理對象的類

,而是提供了一個建立代理對象的靜态方法(newProxyInstance方法)來擷取代理對象。

4.1.1 API介紹

@CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
           

參數介紹:

  • ClassLoader loader

    : 類加載器,用于加載代理類,使用真實對象的類加載器即可
  • Class<?>[] interfaces

    : 真實對象所實作的接口,代理模式真實對象和代理對象實作相同的接口,比如我們這個場景就是售票接口SellTicket
  • InvocationHandler h : 代理對象的調用處理程式

傳回值:傳回的就是代理對象

InvocationHandler接口:

package java.lang.reflect;

/**
 * @author      Peter Jones
 * @see         Proxy
 * @since       1.3
 */
public interface InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args)
        throws Throwable;
}
           
  • Object proxy

    : 代理對象
  • Method method

    : 對應于在代理對象上調用的接口方法的 Method 執行個體
  • Object[] args

    : 代理對象調用接口方法時傳遞的實際參數
  • 傳回值:傳回目标對象目标方法(這裡就是sellTicket方法)的傳回值

4.1.2 示範

  1. 定義售票接口: 抽象主題(Subject)類
package study.wyy.design.proxy.jdkproxy;

import lombok.Builder;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 5:15 下午
 * 簡單定義一個票model
 */
@Builder
@ToString
public class Ticket {

    // 所有者
    private final String owner;
    // 目的地
    private final String targetAddress;
    // 出發地
    private final String sourceAddress;
    // 票錢
    private final Long price;
    // 座位号
    private final String seat;


    public Ticket(String owner, String targetAddress, String sourceAddress, Long price, String seat) {
        this.owner = owner;
        this.targetAddress = targetAddress;
        this.sourceAddress = sourceAddress;
        this.price = price;
        this.seat = seat;
    }
}

           
package study.wyy.design.proxy.jdkproxy;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 3:03 下午
 */
public interface SellTicket {


    /**
    * @Description
    * @Author  wyaoyao
    * @Date   2020/12/11 5:17 下午
    * @Param    targetAddress: 目的地; sourceAddress:出發地, name姓名
    * @Return
    * @Exception
    */
    Ticket sellTicket(String name,String targetAddress,String sourceAddress);
}
           
  1. 定義火車站:目标類
package study.wyy.design.proxy.jdkproxy;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 5:19 下午
 */
public class TrainStation implements SellTicket{


    @Override
    public Ticket sellTicket(String name, String targetAddress, String sourceAddress) {
        Ticket ticket = Ticket.builder()
                .owner(name)
                .targetAddress(targetAddress)
                .sourceAddress(sourceAddress)
                .seat(generateSeat())
                .price(calPrice(targetAddress, sourceAddress))
                .build();
       System.out.println("生成車票");
        return ticket;
    }

    /****
     * 模拟生成一個座位号
     * @return
     */
    protected String generateSeat(){
        // 直接傳回一個
        return "8排6号";
    }

    /****
     * 計算價錢
     * @returns
     */
    protected Long calPrice(String targetAddress, String sourceAddress){
        // 直接傳回一個
        return 100L;
    }
}
           
  1. 生成一個代理類
package study.wyy.design.proxy.jdkproxy;

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

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 5:49 下午
 * 代理工廠
 */
public class ProxyFactory {

    // 目标對象
    private TrainStation station = new TrainStation();


    public SellTicket getProxyObject(){
        /**
         *  ClassLoader loader: 類加載器
         *  Class<?>[] interfaces: 真實對象所實作的接口,代理模式真實對象和代理對象實作相同的接口,比如我們這個場景就是售票接口SellTicket
         *  InvocationHandler h: 代理對象的調用處理程式
         *  傳回值:傳回的就是代理對象
         */
        SellTicket proxyInstance = (SellTicket) Proxy.newProxyInstance(this.getClass().getClassLoader(),
                new Class[]{SellTicket.class}, new InvocationHandler() {
                    /**
                     * `Object proxy`:  代理對象
                     * ` Method method`:  對應于在代理對象上調用的接口方法的 Method 執行個體
                     * `Object[] args` : 代理對象調用接口方法時傳遞的實際參數
                     * 傳回值:傳回目标對象目标方法(sellTicket)的傳回值
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("=================列印形參====================");
                        System.out.println("proxy類型:" + proxy.getClass().getName());
                        System.out.println("method:" + method.getName());
                        for (Object o :args) {
                            System.out.println("args:" + o);
                        }
                        System.out.println("=================列印形參====================");

                        // 執行目标對象目标方法:火車站的售票方法
                        // 這裡可以增加前置處理
                        System.out.println("前置處理。。。。。。");
                        Object invoke = method.invoke(station, args);
                        // 這裡可以增加後置處理
                        System.out.println("後置處理。。。。。。");
                        // 傳回目标方法的傳回值
                        return invoke;
                    }
                });
        return proxyInstance;
    }
}
           
  1. 測試
public static void main(String[] args) {
    ProxyFactory proxyFactory = new ProxyFactory();
    SellTicket proxyObject = proxyFactory.getProxyObject();
    Ticket ticket = proxyObject.sellTicket("john", "杭州", "北京");
    System.out.println(ticket);
}
           
=================列印形參====================
proxy類型:com.sun.proxy.$Proxy0
method:sellTicket
args:john
args:杭州
args:北京
=================列印形參====================
前置處理。。。。。。
生成車票
後置處理。。。。。。
Ticket(owner=john, targetAddress=杭州, sourceAddress=北京, price=100, seat=8排6号)
           

4.1.3 jdk實作代理分析

ProxyFactory不是代理模式中所說的代理類,而代理類是程式在運作過程中動态的在記憶體中生成的類(也就是所謂的動态代理)。通過阿裡巴巴開源的 Java 診斷工具(Arthas【阿爾薩斯】)檢視代理類的結構:

package com.sun.proxy;

import com.itheima.proxy.dynamic.jdk.SellTickets;
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 SellTicket {
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

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

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
            m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
            m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
            return;
        }
        catch (NoSuchMethodException noSuchMethodException) {
            throw new NoSuchMethodError(noSuchMethodException.getMessage());
        }
        catch (ClassNotFoundException classNotFoundException) {
            throw new NoClassDefFoundError(classNotFoundException.getMessage());
        }
    }

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

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

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

    public final void sell() {
        try {
            this.h.invoke(this, m3, null);
            return;
        }
        catch (Error | RuntimeException throwable) {
            throw throwable;
        }
        catch (Throwable throwable) {
            throw new UndeclaredThrowableException(throwable);
        }
    }
}
           

從上面的類中,我們可以看到以下幾個資訊:

  • 代理類($Proxy0)實作了SellTickets。這也就印證了我們之前說的真實類和代理類實作同樣的接口。
  • 代理類($Proxy0)将我們提供了的匿名内部類對象傳遞給了父類。
動态代理的執行流程是什麼樣?

下面是摘取的重點代碼:

public final class $Proxy0 extends Proxy implements SellTickets {
    private static Method m3;

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

    static {
        m3 = Class.forName("com.itheima.proxy.dynamic.jdk.SellTickets").getMethod("sell", new Class[0]);
    }

    public final void sell() {
        this.h.invoke(this, m3, null);
    }
}

//Java提供的動态代理相關類
public class Proxy implements java.io.Serializable {
	protected InvocationHandler h;
	 
	protected Proxy(InvocationHandler h) {
        this.h = h;
    }
}
           

執行流程如下:

1. 在測試類中通過代理對象調用sell()方法
2. 根據多态的特性,執行的是代理類($Proxy0)中的sell()方法
3. 代理類($Proxy0)中的sell()方法中又調用了InvocationHandler接口的子實作類對象的invoke方法
4. invoke方法通過反射執行了真實對象所屬類(TrainStation)中的sell()方法
           

4.2 CGLIB動态代理

同樣是上面的案例,我們再次使用CGLIB代理實作。

CGLIB代理是不需要接口的,是通過繼承實作的,jdk代理是通過接口實作的

如果沒有定義SellTickets接口,隻定義了TrainStation(火車站類)。很顯然JDK代理是無法使用了,因為

JDK動态代理要求必須定義接口,對接口進行代理

CGLIB是一個功能強大,高性能的代碼生成包。它為沒有實作接口的類提供代理

,為JDK的動态代理提供了很好的補充。

CGLIB是第三方提供的包,是以需要引入jar包的坐标:

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>2.2.2</version>
</dependency>
           
  1. 定義火車站:目标類
package study.wyy.design.proxy.cglib;



/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 5:19 下午
 * 這裡火車站隻有一個方法,cglig會把增強這個類的的所有public方法
 * 這裡為了測試,計算價錢這個方法改為public
 */
public class TrainStation {
   public Ticket sellTicket(String name, String targetAddress, String sourceAddress) {
       Ticket ticket = Ticket.builder()
                .owner(name)
                .targetAddress(targetAddress)
                .sourceAddress(sourceAddress)
                .seat(generateSeat())
                .price(calPrice(targetAddress, sourceAddress))
                .build();
        System.out.println("生成車票");
        return ticket;
    }

    /****
     * 模拟生成一個座位号
     * @return
     */
    protected String generateSeat(){
        // 直接傳回一個
        return "8排6号";
    }

    /****
     * 計算價錢這個方法改為public
     * @returns
     */
    public Long calPrice(String targetAddress, String sourceAddress){
        // 直接傳回一個
        return 100L;
    }

}

           
  1. 建立代理
package study.wyy.design.proxy.cglib;


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

import java.lang.reflect.Method;

/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 5:49 下午
 * 代理工廠
 */
public class ProxyFactory {

    // 目标對象
    private TrainStation station = new TrainStation();

    public TrainStation getProxyObject(){
        //1 建立Enhancer對象,類似于JDK動态代理的Proxy類,下一步就是設定幾個參數
        Enhancer enhancer =new Enhancer();
        //2 設定父類的位元組碼對象
        enhancer.setSuperclass(TrainStation.class);
        //3 設定回調函數
        enhancer.setCallback(new MethodInterceptor(){
            /*
        intercept方法參數說明:
            o : 代理對象
            method : 真實對象中的方法的Method執行個體
            args : 實際參數
            methodProxy :代理對象中的方法的method執行個體
            */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                System.out.println("=================列印形參====================");
                System.out.println("proxy類型:" + proxy.getClass().getName());
                System.out.println("method:" + method.getName());
                for (Object o :args) {
                    System.out.println("args:" + o);
                }
                System.out.println("=================列印形參====================");
                // 執行目标對象目标方法:火車站的售票方法
                // 這裡可以增加前置處理
                System.out.println("前置處理。。。。。。");
                Object result = methodProxy.invoke(station, args);
                // 這裡可以增加後置處理
                System.out.println("後置處理。。。。。。");
                return result;
            }
        });
        //4 建立代理對象
        TrainStation obj = (TrainStation) enhancer.create();
        return obj;
    }
}
           
  1. 測試
package study.wyy.design.proxy.cglib;



/**
 * @author by wyaoyao
 * @Description
 * @Date 2020/12/11 7:37 下午
 */
public class Client {
    public static void main(String[] args) {
        //建立代理工廠對象
        ProxyFactory factory = new ProxyFactory();
        //擷取代理對象
        TrainStation proxyObject = factory.getProxyObject();
		System.out.println("<測試sellTicket方法>");
        Ticket ticket = proxyObject.sellTicket("john", "杭州", "北京");
        System.out.println(ticket);

        System.out.println("<測試calPrice方法>");
        proxyObject.calPrice("青海", "北京");

    }
}
           

輸出

<測試sellTicket方法>
=================列印形參====================
proxy類型:study.wyy.design.proxy.cglib.TrainStation$$EnhancerByCGLIB$$41a7aa5e
method:sellTicket
args:john
args:杭州
args:北京
=================列印形參====================
前置處理。。。。。。
生成車票
後置處理。。。。。。
Ticket(owner=john, targetAddress=杭州, sourceAddress=北京, price=100, seat=8排6号)
<測試calPrice方法>
=================列印形參====================
proxy類型:study.wyy.design.proxy.cglib.TrainStation$$EnhancerByCGLIB$$41a7aa5e
method:calPrice
args:青海
args:北京
=================列印形參====================
前置處理。。。。。。
後置處理。。。。。。
           

5 三種代理的對比

  • jdk代理和CGLIB代理

    使用CGLib實作動态代理,CGLib底層采用ASM位元組碼生成架構,使用位元組碼技術生成代理類,在JDK1.6之前比使用Java反射效率要高。唯一需要注意的是,

    CGLib不能對聲明為final的類或者方法進行代理

    ,因為final不能繼承,CGLib原理是動态生成被代理類的子類。

    在JDK1.6、JDK1.7、JDK1.8逐漸對JDK動态代理優化之後,在調用次數較少的情況下,JDK代理效率高于CGLib代理效率,隻有當進行大量調用的時候,JDK1.6和JDK1.7比CGLib代理效率低一點,但是到JDK1.8的時候,JDK代理效率高于CGLib代理。是以如果有接口使用JDK動态代理,如果沒有接口使用CGLIB代理。

  • 動态代理和靜态代理

    動态代理與靜态代理相比較,最大的好處是接口中聲明的所有方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,我們可以進行靈活處理,而不需要像靜态代理那樣每一個方法進行中轉。

    如果接口增加一個方法,靜态代理模式除了所有實作類需要實作這個方法外,所有代理類也需要實作此方法。增加了代碼維護的複雜度。而動态代理不會出現該問題

5.1 優缺點

優點:

  • 代理模式在用戶端與目标對象之間起到一個中介作用和保護目标對象的作用;
  • 代理對象可以擴充目标對象的功能;
  • 代理模式能将用戶端與目标對象分離,在一定程度上降低了系統的耦合度;

缺點:

  • 增加了系統的複雜度;

5.2 使用場景

  • 遠端(Remote)代理

    本地服務通過網絡請求遠端服務。為了實作本地到遠端的通信,我們需要實作網絡通信,處理其中可能的異常。為良好的代碼設計和可維護性,我們将網絡通信部分隐藏起來,隻暴露給本地服務一個接口,通過該接口即可通路遠端服務提供的功能,而不必過多關心通信部分的細節。

  • 防火牆(Firewall)代理

    當你将浏覽器配置成使用代理功能時,防火牆就将你的浏覽器的請求轉給網際網路;當網際網路傳回響應時,代理伺服器再把它轉給你的浏覽器。

  • 保護(Protect or Access)代理

    控制對一個對象的通路,如果需要,可以給不同的使用者提供不同級别的使用權限。