代理模式(靜態代理、JDK動態代理原理分析、CGLIB動態代理)

代理模式java

    代理模式是設計模式之一,爲一個對象提供一個替身或者佔位符以控制對這個對象的訪問,它給目標對象提供一個代理對象,由代理對象控制對目標對象的訪問。程序員

那麼爲何要使用代理模式呢?編程

一、隔離,客戶端類不能或者不想直接訪問目標對象,代理類能夠在遠程客戶端類和目標類之間充當中介。設計模式

2.代理類能夠對業務或者一些消息進行預處理,作一些過濾,而後再將消息轉給目標類,主要處理邏輯仍是在目標類,符合開閉原則。緩存

    在咱們生活中有不少體現代理模式的例子,如中介、媒婆、經紀人等等,好比說某個球隊要簽約一個球星,就須要和經紀人進行溝通,在一些編程框架中,也有不少地方使用代理模式,如Spring的AOP,java的RMI遠程調用框架等。app

   代理模式分爲靜態代理和動態代理,動態代理又分爲jdk動態代理和cglib動態代理,下面分別來闡述下這幾種代理模式的區別。框架

靜態代理ide

    靜態代理在使用的時候,須要定義一個接口或者父類,代理類和目標類(被代理類)都須要實現這個接口,代理類持有目標類的引用。咱們以球員簽約爲例,湖人想要簽下安東尼戴維斯,安東尼說,先和個人經紀人商討簽約狀況,商談成功以後再來找我簽約。咱們定義一個會談的接口,這個接口提供一個簽約的方法,再定義一個經紀人類和球員類,分別實現會談接口的簽約方法。經紀人和湖人說,想要簽下戴維斯也能夠,不過咱們須要交易否決權,且最後一年是球員選項。湖人的魔術師想了想,詹姆斯巔峯期的尾巴也沒幾年了,反正這幾年垃圾合同也簽了很多,不在意這一個,並且戴維斯正值巔峯,聯盟前十球員,不算太虧,就贊成了,畢竟仍是總冠軍重要。因此,經紀人在正式簽約前,談妥了戴維斯的球員選項和薪水,剩下的就須要戴維斯本身親自簽約了。而後經紀人就拉着戴維斯來簽約了。(寫博客的期間,魔術師辭職了,我........,算了,懶得改了)函數

/**
 * 會談接口,有一個簽約的方法
 */
public interface Talk {

    public void sign();
}

/**
 * 球員安東尼戴維斯,實現了簽約的方法,須要本人親自簽約
 */
public class Davis implements Talk {
    @Override
    public void sign() {
        System.out.println("簽約了,5年2.25億美圓");
    }
}

/**
 * 經紀人,也實現了簽約的方法,持有球員的引用,可是具體簽約流程仍是必須由球員完成
 */
public class Broker implements Talk {
    private Davis davis;
    public Broker(Davis davis){
        this.davis = davis;
    }
    @Override
    public void sign() {
        System.out.println("咱們擁有最後一年的球員選項");
        davis.sign();
        System.out.println("簽約成功,交易否決權開始生效");


    }
}

//**
 * 測試類,只須要調用經紀人的簽約方法就能夠了
 */
public class StaticProxyTest {

    public static void main(String[] args) {

        Davis davis = new Davis();
        Broker broker = new Broker(davis);
        broker.sign();

    }
}
控制檯打印:
咱們擁有最後一年的球員選項
簽約了,5年2.25億美圓
簽約成功,交易否決權開始生效

   經過以上代碼,咱們發現,代理對象須要與目標對象實現同樣的接口,當須要代理的對象不少的時候,就須要增長不少的類,假如代理接口須要新增一個方法,那麼代理類和目標類都須要修改維護,那麼有沒有更好的解決方式呢?測試

動態代理

1、jdk動態代理

  靜態代理的代理類是咱們在編譯期就已經建立好了,而動態代理是是指代理類是程序在運行過程當中建立的。jdk的動態代理是是基於接口的代理。

第一步:仍是定義一個會談接口:

/**
 * 會談接口,有一個簽約的方法
 */
public interface Talk {

    public void sign();
}

第二步:定義一個球員,須要實現真正的簽約方法

/**
 * 球員安東尼戴維斯,實現了簽約的方法,須要本人親自簽約
 */
public class Davis implements Talk {
    @Override
    public void sign() {
        System.out.println("簽約了,5年2.25億美圓");
    }
}

第三步:定義一個實現了InvocationHandler接口的實現類,該類須要綁定目標類。

public class MyInvocationHandler implements InvocationHandler {

    //目標類(被代理的類)
    private Object target;
    
    public MyInvocationHandler(Object target){
        this.target = target;
    }
    /**
     *
     * @param proxy  生成的代理類的實例
     * @param method 被調用的方法對象
     * @param args   調用method方法時傳的參數
     * @return       method方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object object = null;
        System.out.println("咱們擁有最後一年的球員選項");
        //執行方法,至關於執行了Davis中的sign()方法,
        //當代理對象調用其方法時,其會自動的跳轉到代理對象關聯的handler對象的invoke方法來進行調用
        object = method.invoke(target,args);
        System.out.println("簽約成功,交易否決權開始生效");
        return object;
    }
}

第四步:編寫測試類:

public class DynamicProxyTest {
    public static void main(String[] args) {
        //被代理的對象
        Talk talk = new Davis();
        MyInvocationHandler myInvocationHandler = new MyInvocationHandler(talk);
        //傳入代理對象的字節碼文件和接口類型,讓Proxy來生成代理類
        Talk davisProxy = (Talk)Proxy.newProxyInstance(talk.getClass().getClassLoader(),
                talk.getClass().getInterfaces(),myInvocationHandler);
        //調用簽約方法
        davisProxy.sign();
    }
}
控制檯打印:

咱們擁有最後一年的球員選項
簽約了,5年2.25億美圓
簽約成功,交易否決權開始生效

  能夠看到控制檯和靜態代理打印的如出一轍。下面來分析一下jdk動態代理的原理:

    咱們看到,客戶端經過Proxy的靜態方法newProxyInstance生成了代理類,該方法有三個參數,分別是代理類的類加載器,這個代理類須要實現的接口以及一個處理器InvocationHandler,當咱們使用代理類調用sign()方法的時候,是怎麼執行這個方法的前置操做和後置操做,從代碼來看,咱們並無顯示的調用MyInvocationHandler的invoke方法,可是這個方法確實被執行了,到底是哪裏調用的呢?咱們來看一下newProxyInstance的源碼:

public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException
    {
        Objects.requireNonNull(h);

        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }

        /*
         * Look up or generate the designated proxy class.
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         */
        try {
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            final Constructor<?> cons = cl.getConstructor(constructorParams);
            final InvocationHandler ih = h;
            if (!Modifier.isPublic(cl.getModifiers())) {
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }
            return cons.newInstance(new Object[]{h});
        } catch (IllegalAccessException|InstantiationException e) {
            throw new InternalError(e.toString(), e);
        } catch (InvocationTargetException e) {
            Throwable t = e.getCause();
            if (t instanceof RuntimeException) {
                throw (RuntimeException) t;
            } else {
                throw new InternalError(t.toString(), t);
            }
        } catch (NoSuchMethodException e) {
            throw new InternalError(e.toString(), e);
        }
    }

源碼有一些檢驗判斷,咱們暫且忽略,重點是看怎麼建立代理類以及代理類調用方法的時候如何調用的invoke方法的

重點剖析這行代碼:

Class<?> cl = getProxyClass0(loader, intfs);

這個方法根據傳入的類加載器和接口類型生成了一個類,這個類便是代理類。點進去:

 private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }

        // If the proxy class defined by the given loader implementing
        // the given interfaces exists, this will simply return the cached copy;
        // otherwise, it will create the proxy class via the ProxyClassFactory
        return proxyClassCache.get(loader, interfaces);
    }

英文註釋寫到:若是緩存中有代理類了直接返回,不然將由ProxyClassFactory建立代理類。咱們再來看一下ProxyClassFactory:

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) {

            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 
                 */
                Class<?> interfaceClass = null;
                try {
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 *驗證該Class對象是否是接口
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 *驗證此接口不是重複的
                 */
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                        "repeated interface: " + interfaceClass.getName());
                }
            }

            String proxyPkg = null;     // package to define proxy class in
            int accessFlags = Modifier.PUBLIC | Modifier.FINAL;

            /*
             * 記錄非公共代理接口的包,以便代理類將在同一個包中定義
             * 驗證全部非公共代理接口都在同一個包中
             */
            for (Class<?> intf : interfaces) {
                int flags = intf.getModifiers();
                if (!Modifier.isPublic(flags)) {
                    accessFlags = Modifier.FINAL;
                    String name = intf.getName();
                    int n = name.lastIndexOf('.');
                    String pkg = ((n == -1) ? "" : name.substring(0, n + 1));
                    if (proxyPkg == null) {
                        proxyPkg = pkg;
                    } else if (!pkg.equals(proxyPkg)) {
                        throw new IllegalArgumentException(
                            "non-public interfaces from different packages");
                    }
                }
            }

            if (proxyPkg == null) {
                // if no non-public proxy interfaces, use com.sun.proxy package
                proxyPkg = ReflectUtil.PROXY_PACKAGE + ".";
            }

            /*
             * 爲代理類選擇一個全限定類名
             */
            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) {
                /*
                 * A ClassFormatError here means that (barring bugs in the
                 * proxy class generation code) there was some other
                 * invalid aspect of the arguments supplied to the proxy
                 * class creation (such as virtual machine limitations
                 * exceeded).
                 */
                throw new IllegalArgumentException(e.toString());
            }
        }
    }

咱們看到,最終調用了ProxyGenerator的generateProxyClass方法生成字節碼文件。回到newProxyInstance方法中,咱們看看這幾行代碼:

    //代理類構造函數的參數類型
一、private static final Class<?>[] constructorParams =
        { InvocationHandler.class };

二、final Constructor<?> cons = cl.getConstructor(constructorParams);

三、 cons.newInstance(new Object[]{h});

    代理類實例化的代碼在第三行,經過反射調用代理類對象的構造方法,選擇了這個InvocationHandler爲參數的構造方法,這個h就是咱們傳遞過來的實現了InvocationHandler的實例。因此,咱們猜想是生成的代理類持有咱們前文定義的MyInvocationHandler實例,並調用裏面的invoke方法。因此,咱們經過反編譯來看下生成的代理類的源碼。
使用IDEA,在VM options一欄中輸入:-Dsun.misc.ProxyGenerator.saveGeneratedFiles=true,就會在項目中生成一個代理類:

 

package com.sun.proxy;

import chenhuan.designpattern.proxy.staticproxy.Talk;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

//該類繼承了Proxy類,實現了Talk接口
public final class $Proxy0 extends Proxy implements Talk {

//聲明瞭一些Method變量,後面會用到
    private static Method m1;
    private static Method m2;
    private static Method m3;
    private static Method m0;

//代理類的構造方法調用父類的構造方法
    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);
        }
    }
  //實現sign()方法,注意傳入的是m3,
    public final void sign() throws  {
        try {
            super.h.invoke(this, m3, (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);
        }
    }

    static {
        try {
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            //加載Talk接口,並獲取其sign方法
            m3 = Class.forName("chenhuan.designpattern.proxy.staticproxy.Talk").getMethod("sign");
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }
}

至此,咱們發現,代理類經過調super.h.invoke(this, m2, (Object[])null);執行咱們實現的InvocationHandler接口的invoke方法。覆盤下jdk動態代理的實現原理的幾大步驟:

一、新建一個接口

二、爲接口實現一個代理類

三、建立一個實現了InvocationHandler接口的處理器

四、經過Proxy的靜態方法,根據類加載器,實現的接口,以及InvocationHandler處理器生成一個代理類

  ①爲接口建立代理類的字節碼文件

  ②使用ClassLoader將字節碼文件加載到JVM

  ③建立代理類實例對象,執行對象的目標方法

咱們看到,使用JDK動態代理,目標類必須實現的某個接口,若是某個類沒有實現接口則不能生成代理對象,那麼,有沒有不須要目標類實現接口的動態代理呢?讓咱們來看下cglib動態代理。

2、cglib動態代理

使用cglib須要引入cglib jar包,本篇案例使用的是cglib2.2.jar

直接先來看下代碼實現:

一、先定義一個目標類

package chenhuan.designpattern.proxy;

public class Davis  {
    
    public void sign() {
        System.out.println("簽約了,5年2.25億美圓");
    }
}

二、定義一個攔截器,

public class MyMethodInterceptor implements MethodInterceptor {
    /**
     *
     * @param o 表示加強的對象,即實現這個接口類的一個對象
     * @param method 表示要被攔截的方法
     * @param objects 表示要被攔截方法的參數
     * @param methodProxy
     * @return 表示要觸發父類的方法對象
     * @throws Throwable
     */
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("咱們擁有最後一年的球員選項");
      //調用代理類實例上的proxy方法的父類方法 Object object
= methodProxy.invokeSuper(o,objects); System.out.println("簽約成功,交易否決權開始生效"); return object; } }

三、使用字節碼加強器來生成代理類:

public class CglibProxyTest {
    public static void main(String[] args) {
        Enhancer enhancer = new Enhancer();
        //設置enhancer的父類對象
        enhancer.setSuperclass(Davis.class);
        //設置enhancer的回調對象,就是咱們定義的攔截器
        enhancer.setCallback(new MyMethodInterceptor());
        //生成代理類
        Davis davis = (Davis)enhancer.create();
        davis.sign();
    }
}

cglib底層採用ASM字節碼生成框架,使用字節碼技術生成代理類,比使用Java反射效率要高。須要注意的是,cglib不能對聲明爲final的方法進行代理,由於CGLib原理是動態生成被代理類的子類。

本文感受篇幅過長,就不分析cglib動態代理的源碼了 。

 

總結:靜態代理由程序員建立代理類,在程序運行前代理類就已經存在了,而且代理類和目標類都要實現相同的接口。當須要代理的對象不少的時候,就須要增長不少的類,假如代理接口須要新增一個方法,那麼代理類和目標類都須要修改維護,不易維護。jdk動態代理須要目標類實現接口,也就是說jdk動態代理只能對該類中實現了目標接口的方法進行代理,這個在實際編程中可能存在侷限性,cglib動態代理徹底不受代理類必須實現接口的限制,其生成的代理類是目標類的子類。

相關文章
相關標籤/搜索