天天看點

設計模式之代理模式(文末贈書)

靜态代理

在開始代理模式定義之前我們先看一段常見的業務邏輯,假設你有個接口ISubject,接口有個operator方法,然後有個具體的實作類來實作此方法:

  • 接口類
public interface ISubject {
    void operator();
}
      
  • 具體實作類
public class RealSubject implements ISubject{
    @Override
    public void operator() {
        System.out.println("do something");
    }
}      

那現在你有個新的需求,就是在執行operator方法之前需要列印一段日志,但是不能修改原RealSubject的業務邏輯,那該怎麼實作呢?

這時候大家夥肯定會想到建一個新的實作類來實作ISubject,并将RealSubject組合進來,真正的業務邏輯還是調用RealSubject的operator方法來實作,在執行operator之前實作我們需要的業務邏輯,比如日志列印。

public class SubjectProxy implements ISubject{

    private RealSubject subject;

    public SubjectProxy(RealSubject subject) {
        this.subject = subject;
    }

    @Override
    public void operator() {
        System.out.println("this is log");
        subject.operator();
    }
}      

用戶端調用的時候我們直接使用SubjectProxy來實作業務邏輯即可:

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

        RealSubject realSubject = new RealSubject();

        ISubject proxy = new SubjectProxy(realSubject);

        proxy.operator();
    }
}      

執行結果如下:

設計模式之代理模式(文末贈書)

看到這裡你可能會想,這就是代理模式?就這?

是的,這就是代理模式,了解了這個例子你就差不多掌握了代理模式,隻不過目前還是靜态代理,等會我們再講講如何實作動态代理。

讓我們先來看看代理模式的定義。

代理模式定義

代理模式的類圖結構如下:

設計模式之代理模式(文末贈書)

圖中的 Subject 是程式中的業務邏輯接口,RealSubject 是實作了 Subject 接口的真正業務類,Proxy 是實作了 Subject 接口的代理類,封裝了一個 RealSubject 引用。在程式中不會直接調用 RealSubject 對象的方法,而是使用 Proxy 對象實作相關功能。

Proxy.operator()

方法的實作會調用其中封裝的 RealSubject 對象的 operator() 方法,執行真正的業務邏輯。

這就是 “代理模式”。

通過上面的例子我們可以看出,使用代理模式可以在不修改被代理對象的基礎上,通過擴充代理類來進行一些功能的附加與增強,值得注意的是,代理類和被代理類應該共同實作一個接口,或者共同繼承某個類。

動态代理

上面例子中我們展示的是代理模式中的 “靜态代理模式”,這是因為我們需要預先定義好代理類SubjectProxy,當需要代理的類很多時,就會出現很多的Proxy類。

在這種場景下我們就需要使用動态代理了,動态代理的實作方式又有很多種,本章我們來看看JDK原生的動态代理。

JDK動态代理的代碼實作:

  • 動态代理類
public class SubjectInvokerHandler implements InvocationHandler {
    //真正的業務對象
    private Object target;

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

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("執行前置業務邏輯");
        //真正的業務邏輯
        method.invoke(target, args);
        System.out.println("執行後置業務邏輯");
        return null;
    }

    public Object getProxy() {
        //建立代理對象
        return Proxy.newProxyInstance(
                Thread.currentThread().getContextClassLoader(),
                target.getClass().getInterfaces(),
                this
        );
    }
}      

動态代理的核心就是InvocationHandler 接口,它隻有一個invoke()方法,這個方法決定了如何處理傳遞過來的方法調用。

  • 用戶端:
public class Client {
    public static void main(String[] args) {
        
        ISubject realSubject = new RealSubject();

        SubjectInvokerHandler invokerHandler = new SubjectInvokerHandler(realSubject);
        //擷取代理對象
        ISubject proxy = (ISubject) invokerHandler.getProxy();

        proxy.operator();
    }
}      

對于需要相同代理邏輯的業務類,隻需要提供一個 InvocationHandler 接口實作類即可。在 Java 運作的過程中,JDK會為每個 RealSubject 類動态生成相應的代理類并加載到 JVM 中,然後建立對應的代理執行個體對象,傳回給上層調用者。

  • 執行效果:
設計模式之代理模式(文末贈書)

可以看到我們這裡并沒有像靜态代理一樣實作具體的代理類,但是最終卻實作了同樣的效果。

JDK動态代理實作原理

了解了動态代理的基本使用後我們來看看動态代理的實作原理。

建立動态代理的入口類是

Proxy.newProxyInstance()

這個靜态方法,它的三個參數分别是加載動态生成的代理類的類加載器、業務類實作的接口和InvocationHandler對象。(代碼有點長,我們截取一下):

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        final Class<?>[] intfs = interfaces.clone();             
        //擷取代理類
        Class<?> cl = getProxyClass0(loader, intfs);

        try {
            ...
            //擷取代理類的構造方法
            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            return cons.newInstance(new Object[]{h});
            ...
        } 
        ...
    }      

newProxyInstance()

最終傳回一個執行個體,它是通過 cl 這個 Class 檔案的構造方法反射生成。cl 則由

getProxyClass0()

方法擷取,代碼如下:

private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
    ...
    // 如果指定的類加載器中已經建立了實作指定接口的代理類,則查找緩存;
    // 否則通過ProxyClassFactory建立實作指定接口的代理類
    return proxyClassCache.get(loader, interfaces);
}      

proxyClassCache 是定義在 Proxy 類中的靜态字段,主要用于緩存已經建立過的代理類,定義如下:

private static final WeakCache<ClassLoader, Class<?>[], Class<?>>
        proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());      

WeakCache.get() 方法會首先嘗試從緩存中查找代理類,如果查找不到,則會建立 Factory 對象并調用其 get() 方法擷取代理類。Factory 是 WeakCache 中的内部類,Factory.get() 方法會調用 ProxyClassFactory.apply() 方法建立并加載代理類。

ProxyClassFactory.apply() 方法首先會檢測代理類需要實作的接口集合,然後确定代理類的名稱,之後建立代理類并将其寫入檔案中,最後加載代理類,傳回對應的 Class 對象用于後續的執行個體化代理類對象。該類的核心代碼如下:

private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>{
    // 代理類的字首是 $Proxy
    private static final String proxyClassNamePrefix = "$Proxy";
    private static final AtomicLong nextUniqueNumber = new AtomicLong();

    @Override
    public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

       ...
        long num = nextUniqueNumber.getAndIncrement();
        //代理類的名稱是通過包名、代理類名稱字首以及編号這三項組成的
        String proxyName = proxyPkg + proxyClassNamePrefix + num;

        /*
         * 生成代理類,并寫入檔案
         */
        byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
            proxyName, interfaces, accessFlags);
        try {
            return defineClass0(loader, proxyName,
                                proxyClassFile, 0, proxyClassFile.length);
        } catch (ClassFormatError e) {
          ...
        }
    }
}      

小結

JDK動态代理的實作原理是動态建立代理類并通過指定類加載器進行加載,在建立代理對象時将InvocationHandler對象作為構造參數傳入。當調用代理對象時,會調用 InvocationHandler.invoke() 方法,進而執行代理邏輯,并最終調用真正業務對象的相應方法。