你管這叫代理模式(Proxy Pattern)

代理模式

  代理模式即給一個真實類提供一個代理類,該代理類代替真實類完成其功能,一般還可在代理類上添加一些真實類不具有的附加功能,通俗來講代理模式就是我們生活中常見的中介,代理模式又可分為靜態代理動態代理

靜態代理

  靜態代理需要代理類和目標類實現一樣的介面,一般將被代理對象組合到代理類中,然後使用其完成對應功能,並可在代理類中添加額外功能。

租房靜態代理過程

第一步:創建服務介面

public interface Rent {
    // 出租房屋
    void rent();
}

第二步:實現類實現服務介面

public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房東要出租房子...");
    }
}

第三步:代理類實現服務介面

public class Proxy implements Rent{
	// 被代理的類,用於完成被代理類功能。
    private Host host;
	// 用於初始化被代理類
    public void setHost(Host host) {
        this.host = host;
    }
	// 完成被代理類的功能,同時可附加其他功能。
    @Override
    public void rent() {
        doSomething();
        host.rent();
        doSomething();
    }
    // 附加的功能
    private void doSomething() {
        System.out.println("doSomething...");
    }
}

第四步:編寫測試類

public class Test {
    public static void main(String[] args) {
        Host host = new Host();
        Proxy proxy = new Proxy();
        proxy.setHost(host);
        proxy.rent();
    }
}

靜態代理總結

優點

  • 高擴展性:可以在不修改目標類的前提下擴展目標類的功能。

缺點

  • 冗餘:一個目標類就會產生一個代理類,會產生過多的代理類。
  • 開發效率低:代理類程式碼量繁重。
  • 不易維護:一旦介面改變,目標類與代理類都要進行修改。

動態代理

  動態代理中我們不需要手動的創建代理類,只需要編寫一個動態處理器來為我們動態創建代理對象,代理對象由 JDK 在運行時動態創建,動態代理基於反射來實現

常用的動態代理方式

  • 基於介面的動態代理:由 JDK 提供,使用 JDK 官方的 Proxy 類創建代理對象。
  • 基於類的動態代理:由第三方 CGLib 提供,使用 CGLib 的 Enhancer 類創建代理對象
  • javassist
  • ……

基於介面的動態代理

  使用基於介面的動態代理首先需要了解 JDK 中的 Proxy 類和 InvocationHandler介面,然後用其編寫動態處理器用於完成動態代理。

Proxy 類

  Proxy 提供了創建動態代理類和實例的靜態方法,它也是由這些方法創建的所有動態代理類的超類。
  一般使用 Proxy 的 newProxyInstance 靜態方法創建代理類,需要提供一個類載入器、被代理類的服務介面,以及一個 InvocationHandler 的對象。

  • 類載入器:用於載入類和介面
  • 被代理類的介面:提供被代理類服務介面資訊
  • InvocationHandler 對象:用於執行被代理類的方法
public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h);

InvocationHandler 介面

  當在代理實例上調用方法時,方法調用被編碼並分派到其調用處理程式的調用方法,即在 Proxy 中調用方法時,該方法調用被分派給 InvocationHandler 對象中的 invoke 方法執行,由 invoke 方法調用執行被代理對象的方法。

Object invoke(Object proxy, Method method, Object[] args);

動態處理器工具類

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

public class ProxyInvocationHandler implements InvocationHandler {
    // 被代理的介面
    private Object target;
    
    public ProxyInvocationHandler() {
    }

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

    // 用於初始化被代理類
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成得到代理類
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 處理代理示例並返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

基於介面的租房動態代理過程

第一步:創建服務介面

public interface Rent {
    // 出租房屋
    void rent();
}

第二步:實現類實現服務介面

public class Host implements Rent{
    @Override
    public void rent() {
        System.out.println("房東要出租房子...");
    }
}

第三步:編寫動態處理器

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

public class ProxyInvocationHandler implements InvocationHandler {
    // 被代理的介面
    private Object target;

    // 用於初始化被代理類
    public void setTarget(Object target) {
        this.target = target;
    }

    // 生成得到代理類
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
    }

    // 處理代理示例並返回結果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        doSomething();
        Object invoke = method.invoke(target, args);
        doSomething();
        return invoke;
    }

    // 附加的功能
    private void doSomething() {
        System.out.println("doSomething...");
    }
}

第四步:編寫測試類

public class Test {
    public static void main(String[] args) {
        Host host = new Host();
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
        proxyInvocationHandler.setTarget(host);
        Rent proxy = (Rent)proxyInvocationHandler.getProxy();
        proxy.rent();
    }
}

動態代理總結

優點

  • 高擴展性:可以在不修改目標類的前提下擴展目標類的功能。
  • 低冗餘:會產生過多的代理類。
  • 低耦合:減少了對業務介面的依賴。
  • 高效開發:大大減少開發任務,一個動態處理器即可完成代理。

注意事項

  • 由於在客戶端和真實主題之間增加了代理對象,因此有些類型的代理模式可能會造成請求的處理速度變慢。
  • 實現代理模式需要額外的工作,有些代理模式的實現非常複雜。
  • 和適配器模式的區別:適配器模式主要改變所考慮對象的介面,而代理模式不能改變所代理類的介面。
  • 和裝飾器模式的區別:裝飾器模式為了增強功能,而代理模式是為了加以控制。