精彩推薦 |【Java技術(shù)專題】「重塑技術(shù)功底」攻破Java技術(shù)盲點(diǎn)之剖析動(dòng)態(tài)代理的實(shí)現(xiàn)原理和開發(fā)指南(上)

背景介紹

在Java編程中碌更,動(dòng)態(tài)代理的應(yīng)用非常廣泛。它被廣泛應(yīng)用于Spring AOP框架十减、Hibernate數(shù)據(jù)查詢告抄、測試框架的后端mock喊积、RPC以及Java注解對象獲取等領(lǐng)域。

靜態(tài)代理和動(dòng)態(tài)代理

與靜態(tài)代理不同玄妈,動(dòng)態(tài)代理的代理關(guān)系是在運(yùn)行時(shí)確定的,這使得它在靈活性上更勝一籌髓梅。相比之下拟蜻,靜態(tài)代理的代理關(guān)系在編譯時(shí)就確定了,實(shí)現(xiàn)起來相對簡單枯饿,適用于代理類較少且確定的情況酝锅。然而,動(dòng)態(tài)代理提供了更大的靈活性奢方,能夠更好地應(yīng)對復(fù)雜的編程需求搔扁。

動(dòng)態(tài)代理與靜態(tài)代理的區(qū)別

image.png

本篇文章主要來重塑和探討Java中兩種常見的動(dòng)態(tài)代理方式:JDK原生動(dòng)態(tài)代理和CGLIB動(dòng)態(tài)代理爸舒。

進(jìn)入正題

為了做一個(gè)參考,我們先試用一個(gè)靜態(tài)代理模式的案例為基礎(chǔ)稿蹲,從而襯托出動(dòng)態(tài)代理的優(yōu)勢和靈活扭勉,先來看看靜態(tài)代理模式,先從直觀的示例說起苛聘,假設(shè)我們有一個(gè)接口ProxyTest和一個(gè)簡單實(shí)現(xiàn)ProxyTestImp涂炎,這是Java中常見的模式,使用接口來定義協(xié)議设哗,然后通過不同的實(shí)現(xiàn)類來具體實(shí)現(xiàn)這些行為唱捣。

public interface ProxyTest{
    String test(String str);
}
// 實(shí)現(xiàn)
public class ProxyTestImp implements ProxyTest{
    @Override
    public String test(String str) {
        return "exec: " + str;
    }
}

通過日志記錄來追蹤test()方法的調(diào)用,你可以通過靜態(tài)代理來實(shí)現(xiàn)這一目標(biāo)网梢。

重溫:靜態(tài)代理

因?yàn)樾枰獙σ恍┖瘮?shù)進(jìn)行二次處理震缭,或是某些函數(shù)不讓外界知道時(shí),可以使用代理模式战虏,通過訪問第三方拣宰,間接訪問原函數(shù)的方式,達(dá)到以上目的活烙,來看一下代理模式的類圖:

image.png

實(shí)現(xiàn)靜態(tài)代理案例

通過創(chuàng)建一個(gè)實(shí)現(xiàn)了相同接口的代理類徐裸,并在代理類中調(diào)用目標(biāo)類的方法并記錄日志,來實(shí)現(xiàn)對test()調(diào)用的日志記錄啸盏。這樣重贺,你就可以在代理類中實(shí)現(xiàn)對test()方法的調(diào)用和日志記錄的統(tǒng)一管理。

image.png

靜態(tài)代理可以在編譯時(shí)確定代理關(guān)系回懦,實(shí)現(xiàn)起來相對簡單气笙。

class StaticProxiedTest implements ProxyTest{
    private ProxyTest proxyTest = new ProxyTestImp ();
    @Override
    public String test(String str) {
        logger.info("You said: " + str);
        return proxyTest .test(str);
    }
}

靜態(tài)代理的弊端

當(dāng)需要為多個(gè)類進(jìn)行代理時(shí),建立多個(gè)代理類會(huì)導(dǎo)致維護(hù)難度增加怯晕。

靜態(tài)代理之所以存在這些問題潜圃,是因?yàn)榇黻P(guān)系在編譯期就已經(jīng)確定。然而舟茶,如果在運(yùn)行期才確定代理哪個(gè)類谭期,那么解決這些問題會(huì)更加簡單。因此吧凉,動(dòng)態(tài)代理的存在變得非常必要隧出,它提供了更大的靈活性,能夠更好地應(yīng)對這類問題阀捅。

重溫:動(dòng)態(tài)代理

動(dòng)態(tài)代理模式是Java中常見的一種設(shè)計(jì)模式胀瞪,它可以動(dòng)態(tài)地創(chuàng)建代理對象,對方法進(jìn)行攔截和處理饲鄙。動(dòng)態(tài)代理模式有兩種實(shí)現(xiàn)方式凄诞,一種是基于Java的內(nèi)置支持圆雁,稱為Java動(dòng)態(tài)代理;另一種是使用第三方庫帆谍,如cglib伪朽。

image.png

Java動(dòng)態(tài)代理

Java動(dòng)態(tài)代理是通過接口來實(shí)現(xiàn)的,它要求被代理的對象必須實(shí)現(xiàn)一個(gè)或多個(gè)接口既忆。在運(yùn)行時(shí)驱负,Java動(dòng)態(tài)代理會(huì)生成一個(gè)實(shí)現(xiàn)了這些接口的代理類,該代理類繼承了java.lang.reflect.Proxy類患雇,并使用InvocationHandler作為參數(shù)來設(shè)置對方法調(diào)用的處理邏輯跃脊。

InvocationHandler

Java動(dòng)態(tài)代理模式里面有個(gè)調(diào)用處理器的概念,在JDK中苛吱,實(shí)現(xiàn)了InvocationHandler這個(gè)接口的類就是一個(gè)調(diào)用處理器類酪术,其中使用了些反射的相關(guān)技術(shù)。

調(diào)用處理器的概念:請求到后臺(tái)服務(wù)翠储,會(huì)先經(jīng)過調(diào)用處理器绘雁,之后才會(huì)到后臺(tái)服務(wù)。然后繼續(xù)有之后的操作援所,就像一個(gè)過濾網(wǎng)庐舟,一層層的過濾,只要滿足一定條件住拭,才能繼續(xù)向后執(zhí)行挪略。

調(diào)用處理器的作用:控制目標(biāo)對象的目標(biāo)方法的執(zhí)行。

image.png

Java動(dòng)態(tài)代理的實(shí)現(xiàn)

開發(fā)調(diào)用處理器以實(shí)現(xiàn)動(dòng)態(tài)代理的具體操作步驟包括以下幾個(gè)關(guān)鍵環(huán)節(jié):

  1. 引入必要的類:在開發(fā)過程中滔岳,首先需要引入目標(biāo)類以及與擴(kuò)展方法相關(guān)的類庫杠娱。這些類庫將為后續(xù)的代理處理提供必要的支持。
  2. 對象賦值:在創(chuàng)建代理處理器時(shí)谱煤,通常需要通過調(diào)用目標(biāo)類的構(gòu)造函數(shù)來為其相關(guān)對象進(jìn)行賦值摊求。這些賦值操作對于確保代理處理的正確性和一致性至關(guān)重要。
  3. 邏輯合并:在實(shí)現(xiàn)動(dòng)態(tài)代理的過程中刘离,需要在invoke方法中巧妙地結(jié)合各種邏輯處理室叉。這個(gè)方法決定了目標(biāo)方法是否被調(diào)用,以及如何響應(yīng)和處理這些調(diào)用硫惕。通過合理地組織這些邏輯太惠,可以確保代理處理器能夠根據(jù)需求動(dòng)態(tài)地?cái)U(kuò)展和調(diào)整其行為。
下面看具體的代碼實(shí)例
目標(biāo)接口類和對應(yīng)實(shí)現(xiàn)類

先定義一個(gè)代理接口類

/** 
 * 目標(biāo)接口: 
 * 包含目標(biāo)方法的聲明 
 */  
public interface TargetInterface {  
   void exec();  
} 

先定義一個(gè)代理接口類的實(shí)現(xiàn)類疲憋,用于作為被代理的實(shí)際對象。

/** 
 * 被代理的類 
 * 目標(biāo)對象類 
 * 實(shí)現(xiàn)目標(biāo)接口. 
 * 繼而實(shí)現(xiàn)目標(biāo)方法梁只。 
 */  
public class TargetObject implements TargetInterface {  
    @Override  
    public void exec() {  
        System.out.println("exec");  
    }  
}
定義和實(shí)現(xiàn)調(diào)用處理器

首先缚柳,實(shí)現(xiàn)一個(gè)InvocationHandler埃脏,方法調(diào)用會(huì)被轉(zhuǎn)發(fā)到該類的invoke()方法。然后在需要使用TargetObject 的時(shí)候秋忙,通過JDK動(dòng)態(tài)代理獲取TargetObject的代理對象彩掐。

import java.lang.reflect.InvocationHandler;  
import java.lang.reflect.Method;  
  
/** 
 * 動(dòng)態(tài)代理-攔截器 
 */  
public class MyInvocationHandler implements InvocationHandler {  
    private Object target;//目標(biāo)類    
    public MyInterceptor(Object target) {  
        this.target = target;  
    }  
  
    /** 
     * args 目標(biāo)方法的參數(shù) 
     * method 目標(biāo)方法 
     */  
    @Override  
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
        System.out.println("aaaaa");//切面方法a();  
        method.invoke(this.target, args);//調(diào)用目標(biāo)類的目標(biāo)方法  
        System.out.println("bbbbb");//切面方法f();  
        return null;  
    }  
}  
目標(biāo)方法的具體測試效果

具體通過調(diào)用代理對象,來調(diào)用目標(biāo)對象的目標(biāo)方法的具體測試

import java.lang.reflect.Proxy;    
public class MainTest {  
    public static void main(String[] args) {  
        //目標(biāo)對象  
        TargetObject target = new TargetObject();  
        //攔截器  
        MyInvocationHandler myInterceptor = new MyInvocationHandler (target);  
        /* 
         *  Proxy.newProxyInstance參數(shù): 
         *  1灰追、目標(biāo)類的類加載器 
         *  2堵幽、目標(biāo)類的所有的接口 
         *  3、攔截器 
         */  
        //代理對象弹澎,調(diào)用系統(tǒng)方法自動(dòng)生成  
        TargetInterface proxyObj = (TargetInterface)    
        Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), myInterceptor);  
        proxyObj.exec();  
    }  
}  

上述代碼的關(guān)鍵是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法朴下,該方法會(huì)根據(jù)指定的參數(shù)動(dòng)態(tài)創(chuàng)建代理對象。

三個(gè)參數(shù)的意義如下
image.png

newProxyInstance()會(huì)返回一個(gè)實(shí)現(xiàn)了指定接口的代理對象苦蒿,對該對象的所有方法調(diào)用都會(huì)轉(zhuǎn)發(fā)給InvocationHandler.invoke()方法殴胧。

動(dòng)態(tài)代理神奇的地方
image.png
代理調(diào)用和目標(biāo)調(diào)用
  • 代理調(diào)用:在invoke()方法中,你可以自由地加入各種邏輯佩迟,比如修改方法參數(shù)团滥、添加日志功能或安全檢查功能等。通過這種方式报强,你可以靈活地控制代理對象的操作行為灸姊,實(shí)現(xiàn)更加復(fù)雜的邏輯功能。

  • 目標(biāo)調(diào)用:之后我們通過某種方式執(zhí)行真正的方法體秉溉,示例中通過反射調(diào)用了TargetObject對象的相應(yīng)方法力惯,還可以通過RPC調(diào)用遠(yuǎn)程方法。

注意:對于從Object中繼承的方法坚嗜,JDK Proxy會(huì)把hashCode()夯膀、equals()、toString()這三個(gè)非接口方法轉(zhuǎn)發(fā)給InvocationHandler苍蔬,其余的Object方法則不會(huì)轉(zhuǎn)發(fā)诱建。

Java動(dòng)態(tài)代理的好處

  • 省去了編寫代理類的工作量】:通過動(dòng)態(tài)代理可以很明顯的看到它的好處,在使用靜態(tài)代理時(shí)碟绑,如果不同接口的某些類想使用代理模式來實(shí)現(xiàn)相同的功能俺猿,將要實(shí)現(xiàn)多個(gè)代理類,但在動(dòng)態(tài)代理中格仲,只需要一個(gè)代理類就好了押袍。

  • 靈活地重用于不同的應(yīng)用場景】:動(dòng)態(tài)代理實(shí)現(xiàn)了可以在原始類和接口還未知的時(shí)候,就確定代理類的代理行為凯肋,當(dāng)代理類與原始類脫離直接聯(lián)系后谊惭,就可以很靈活地重用于不同的應(yīng)用場景中。

Java動(dòng)態(tài)代理的總結(jié)歸納

類比靜態(tài)代理,可以發(fā)現(xiàn)代理類不需要實(shí)現(xiàn)原接口了圈盔,而是實(shí)現(xiàn)InvocationHandler豹芯。通過Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj .getClass().getInterfaces(), this);來動(dòng)態(tài)生成一個(gè)代理類,該類的類加載器與被代理類相同驱敲,實(shí)現(xiàn)的接口與被代理類相同铁蹈,通過上述方法生成的代理類相當(dāng)于靜態(tài)代理中的代理類。

Java動(dòng)態(tài)代理在運(yùn)行期決定代理對象是怎么樣的众眨,解決了靜態(tài)代理的弊端握牧。當(dāng)動(dòng)態(tài)生成的代理類調(diào)用方法時(shí),會(huì)觸發(fā)invoke方法娩梨,在invoke方法中可以對被代理類的方法進(jìn)行增強(qiáng)沿腰。

Java動(dòng)態(tài)代理的原理剖析

JDK的動(dòng)態(tài)代理的類看不見摸不著,雖然可以看到效果姚建,但是底層到底是怎么做的矫俺,為什么要求實(shí)現(xiàn)接口呢?

代理調(diào)用的實(shí)現(xiàn)i原理

上文說了,當(dāng)動(dòng)態(tài)生成的代理類調(diào)用方法時(shí)掸冤,會(huì)觸發(fā)invoke方法厘托。很顯然invoke方法并不是顯示調(diào)用的,它是一個(gè)回調(diào)機(jī)制稿湿,那么回調(diào)機(jī)制是怎么被調(diào)用的呢铅匹?

上述動(dòng)態(tài)代理的代碼中,唯一不清晰的地方只有Proxy創(chuàng)建代理對象饺藤,如下所示:

Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), this);
Proxy.newProxyInstance

我們先來分析一下對應(yīng)的JDK的源碼:

    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
        throws IllegalArgumentException{
        // 判空包斑,判斷 h 對象是否為空,為空就拋出 NullPointerException
        Objects.requireNonNull(h);
        final Class<?>[] intfs = interfaces.clone();
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
         // 進(jìn)行包訪問權(quán)限涕俗、類加載器等權(quán)限檢查
           checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
        }
        /*
         * Look up or generate the designated proxy class.
         * 查找或生成指定的代理類
         */
        Class<?> cl = getProxyClass0(loader, intfs);
        // 省略若干代碼
    }

第一步罗丰,嘗試獲取代理類,該代理類可能會(huì)被緩存再姑,如果沒有緩存萌抵,那么進(jìn)行生成邏輯.

java.lang.reflect.Proxy#getProxyClass0
    private static Class<?> getProxyClass0(ClassLoader loader,
                                           Class<?>... interfaces) {
        // 數(shù)量超過 65535 就拋出異常,665535 這個(gè)就不用說了吧
        if (interfaces.length > 65535) {
            throw new IllegalArgumentException("interface limit exceeded");
        }
        // 如果代理類已經(jīng)通過類加載器對給定的接口進(jìn)行實(shí)現(xiàn)了元镀,那么從緩存中返回其副本
        // 否則绍填,它將通過ProxyClassFactory創(chuàng)建代理類
        return proxyClassCache.get(loader, interfaces);
    }

最后發(fā)現(xiàn)會(huì)對生成的代理類進(jìn)行緩存,有了栖疑,就不直接返回讨永,沒有的,還得生成代理類遇革,我們繼續(xù)往下走:

proxyClassCache = new WeakCache<>(new KeyFactory(), new ProxyClassFactory());

java.lang.reflect.Proxy.ProxyClassFactory#apply

關(guān)鍵點(diǎn)在于 ProxyClassFactory 這個(gè)類卿闹,從名字也可以猜出來這個(gè)類的作用揭糕。看看代碼:

/**
     * A factory function that generates, defines and returns the proxy class given
     * the ClassLoader and array of interfaces.
     */
    private static final class ProxyClassFactory
        implements BiFunction<ClassLoader, Class<?>[], Class<?>>
    {
        // prefix for all proxy class names 定義前綴
        private static final String proxyClassNamePrefix = "$Proxy";

        // next number to use for generation of unique proxy class names  原子操作比原,適用于多線程
        private static final AtomicLong nextUniqueNumber = new AtomicLong();
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {
                    Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
            // 通過反射獲取到接口類
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }
         // 所得到的接口類與傳進(jìn)來的不相等插佛,說明不是同一個(gè)類
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                        intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                        interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate. 
                 */
                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;

            /*
             * Record the package of a non-public proxy interface so that the
             * proxy class will be defined in the same package.  Verify that
             * all non-public proxy interfaces are in the same package.
             */
            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 + ".";
            }
            /*
             * Choose a name for the proxy class to generate.
             */
            long num = nextUniqueNumber.getAndIncrement();
       // 生產(chǎn)代理類的名字
            String proxyName = proxyPkg + proxyClassNamePrefix + num;
            // 一些驗(yàn)證、緩存量窘、同步的操作,不是我們研究的重點(diǎn)
            /*
             * Generate the specified proxy class.
             * 生成特殊的代理類
             */
            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(proxyName, interfaces, accessFlags);``,這段代碼即為生成動(dòng)態(tài)代理類的關(guān)鍵,執(zhí)行完后會(huì)返回該描述該代理類的字節(jié)碼數(shù)組.隨后程序讀取該字節(jié)碼數(shù)組氢拥,將其轉(zhuǎn)化為運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)-Class對象,作為一個(gè)常規(guī)類使用.

    public static byte[] generateProxyClass(final String var0, Class<?>[] var1, int var2) {
        ProxyGenerator var3 = new ProxyGenerator(var0, var1, var2);
        final byte[] var4 = var3.generateClassFile();
        // 如果聲明了需要持久化代理類蚌铜,則進(jìn)行磁盤寫入.
        if (saveGeneratedFiles) {
            AccessController.doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    try {
                        int var1 = var0.lastIndexOf(46);
                        Path var2;
                        if (var1 > 0) {
                            Path var3 = Paths.get(var0.substring(0, var1).replace('.', File.separatorChar));
                            Files.createDirectories(var3);
                            var2 = var3.resolve(var0.substring(var1 + 1, var0.length()) + ".class");
                        } else {
                            var2 = Paths.get(var0 + ".class");
                        }
                        Files.write(var2, var4, new OpenOption[0]);
                        return null;
                    } catch (IOException var4x) {
                        throw new InternalError("I/O exception saving generated file: " + var4x);
                    }
                }
            });
        }
        return var4;
    }

這里我們找到了一個(gè)關(guān)鍵的判斷條件-saveGeneratedFiles,即是否需要將代理類進(jìn)行持久化.

ProxyGenerator.generateProxyClass

public class ProxyGeneratorUtils {
    /**
     * 把代理類的字節(jié)碼寫到硬盤上 
     * @param path 保存路徑 
     */
    public static void writeProxyClassToHardDisk(String path) {
// 獲取代理類的字節(jié)碼  
        byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy11", Student.class.getInterfaces());
 
        FileOutputStream out = null;
 
        try {
            out = new FileOutputStream(path);
            out.write(classFile);
            out.flush();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

跟蹤這個(gè)方法的源碼,可以看到程序進(jìn)行了驗(yàn)證嫩海、優(yōu)化冬殃、緩存、同步叁怪、生成字節(jié)碼审葬、顯示類加載等操作,前面的步驟并不是我們關(guān)注的重點(diǎn)奕谭,而最后它調(diào)用了

byte[] proxyClassFile = ProxyGenerator.generateProxyClass(proxyName, interfaces);

該方法用來完成生成字節(jié)碼的動(dòng)作涣觉,這個(gè)方法可以在運(yùn)行時(shí)產(chǎn)生一個(gè)描述代理類的字節(jié)碼byte[]數(shù)組

輸出對應(yīng)的生產(chǎn)proxy的class代碼

在main函數(shù)中加入System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");,會(huì)在根目錄下生成了一個(gè) $Proxy0.class 文件血柳,把Class文件反編譯后可以看見如下代碼:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.TargetObject;

public final class $Proxy0 extends Proxy implements TargetObject
{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  * 注意這里是生成代理類的構(gòu)造方法官册,方法參數(shù)為InvocationHandler類型,看到這难捌,是不是就有點(diǎn)明白
  * 為何代理對象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法膝宁,而InvocationHandler又持有一個(gè)
  * 被代理對象的實(shí)例
  *
  * super(paramInvocationHandler),是調(diào)用父類Proxy的構(gòu)造方法根吁。
  * 父類持有:protected InvocationHandler h;
  * Proxy構(gòu)造方法:
  *    protected Proxy(InvocationHandler h) {
  *         Objects.requireNonNull(h);
  *         this.h = h;
  *    }
  *
  */
  public $Proxy0(InvocationHandler paramInvocationHandler)
    throws 
  {
    super(paramInvocationHandler);
  }
  
  //這個(gè)靜態(tài)塊本來是在最后的员淫,我把它拿到前面來,方便描述
   static
  {
    try
    {
      //看看這兒靜態(tài)塊兒里面有什么击敌,是不是找到了giveMoney方法介返。請記住giveMoney通過反射得到的名字m3,其他的先不管
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m3 = Class.forName("proxy.TargetObject").getMethod("exec", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
 
  /**
  * 
  *這里調(diào)用代理對象的exec方法愚争,直接就調(diào)用了InvocationHandler中的invoke方法映皆,并把m3傳了進(jìn)去。
  *this.h.invoke(this, m3, null);這里簡單轰枝,明了捅彻。
  *來,再想想鞍陨,代理對象持有一個(gè)InvocationHandler對象步淹,InvocationHandler對象持有一個(gè)被代理的對象从隆,
  *再聯(lián)系到InvacationHandler中的invoke方法。嗯缭裆,就是這樣键闺。
  */
  public final void exec() throws {
    try
    {
      this.h.invoke(this, m3, null);
      return;
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  //注意,這里為了節(jié)省篇幅澈驼,省去了toString辛燥,hashCode、equals方法的內(nèi)容缝其。原理和giveMoney方法一毛一樣挎塌。

}

動(dòng)態(tài)代理類不僅代理了顯示定義的接口中的方法,而且還代理了java的根類Object中的繼承而來的equals()内边、hashcode()榴都、toString()這三個(gè)方法,并且僅此三個(gè)方法漠其∽旄撸可以在上述代碼中看到,無論調(diào)用哪個(gè)方法和屎,都會(huì)調(diào)用到InvocationHandler的invoke方法拴驮,只是參數(shù)不同。

Proxy代理源碼流程總結(jié)

Java動(dòng)態(tài)代理的弊端

代理類和委托類需要實(shí)現(xiàn)同一個(gè)接口眶俩,這意味著只有實(shí)現(xiàn)了某個(gè)接口的類才能使用Java動(dòng)態(tài)代理機(jī)制莹汤。然而,在實(shí)際情況中颠印,并非所有類都會(huì)實(shí)現(xiàn)接口纲岭。因此,對于沒有實(shí)現(xiàn)接口的類线罕,Java動(dòng)態(tài)代理機(jī)制無法使用止潮。而CGLIB則可以實(shí)現(xiàn)對類的動(dòng)態(tài)代理,彌補(bǔ)了Java動(dòng)態(tài)代理的不足之處钞楼。


Cglib動(dòng)態(tài)代理

cglib是針對類來實(shí)現(xiàn)代理的喇闸,他的原理是對指定的目標(biāo)類生成一個(gè)子類,并覆蓋其中方法實(shí)現(xiàn)增強(qiáng)询件,但因?yàn)椴捎玫氖抢^承,所以不能對final修飾的類進(jìn)行代理。

而cglib是基于字節(jié)碼的庫舆瘪,可以在運(yùn)行時(shí)動(dòng)態(tài)地創(chuàng)建子類并覆蓋方法片效。相比Java動(dòng)態(tài)代理,cglib的使用更加靈活膨桥,因?yàn)樗恍枰淮淼膶ο髮?shí)現(xiàn)接口。同時(shí)壮虫,cglib還支持對私有方法的攔截和處理。

區(qū)別在于Java代理

image.png

未完待續(xù)

由于篇幅過長,會(huì)引起視覺疲勞和大腦疲勞办素,故此雷滚,作者會(huì)將cglib的的原理和實(shí)現(xiàn)放到了下一章:精彩推薦 |【Java技術(shù)專題】「重塑技術(shù)功底」攻破Java技術(shù)盲點(diǎn)之剖析動(dòng)態(tài)代理的實(shí)現(xiàn)原理和開發(fā)指南(下)呆万,希望大家多多消化本章節(jié)內(nèi)容躬充,等待下一章的到來霸褒。

總結(jié)分析

本文介紹了Java兩種常見動(dòng)態(tài)代理機(jī)制的用法和原理,JDK原生動(dòng)態(tài)代理是Java原生支持的,不需要任何外部依賴,但是它只能基于接口進(jìn)行代理孽文;CGLIB通過繼承的方式進(jìn)行代理减牺,無論目標(biāo)對象有沒有實(shí)現(xiàn)接口都可以代理愕贡,但是無法處理final的情況。

動(dòng)態(tài)代理是Spring AOP(Aspect Orient Programming, 面向切面編程)的實(shí)現(xiàn)方式,了解動(dòng)態(tài)代理原理,對理解Spring AOP大有幫助骤坐。

參考資料

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纽绍,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子拌夏,更是在濱河造成了極大的恐慌履因,老刑警劉巖世蔗,帶你破解...
    沈念sama閱讀 212,332評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異仅醇,居然都是意外死亡冗美,警方通過查閱死者的電腦和手機(jī)粉洼,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,508評論 3 385
  • 文/潘曉璐 我一進(jìn)店門叶摄,熙熙樓的掌柜王于貴愁眉苦臉地迎上來属韧,“玉大人,你說我怎么就攤上這事蛤吓∠梗” “怎么了?”我有些...
    開封第一講書人閱讀 157,812評論 0 348
  • 文/不壞的土叔 我叫張陵会傲,是天一觀的道長锅棕。 經(jīng)常有香客問我拙泽,道長,這世上最難降的妖魔是什么裸燎? 我笑而不...
    開封第一講書人閱讀 56,607評論 1 284
  • 正文 為了忘掉前任顾瞻,我火速辦了婚禮,結(jié)果婚禮上顺少,老公的妹妹穿的比我還像新娘朋其。我一直安慰自己,他們只是感情好脆炎,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,728評論 6 386
  • 文/花漫 我一把揭開白布梅猿。 她就那樣靜靜地躺著,像睡著了一般秒裕。 火紅的嫁衣襯著肌膚如雪袱蚓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,919評論 1 290
  • 那天几蜻,我揣著相機(jī)與錄音喇潘,去河邊找鬼。 笑死梭稚,一個(gè)胖子當(dāng)著我的面吹牛颖低,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播弧烤,決...
    沈念sama閱讀 39,071評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼忱屑,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了暇昂?” 一聲冷哼從身側(cè)響起莺戒,我...
    開封第一講書人閱讀 37,802評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎急波,沒想到半個(gè)月后从铲,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,256評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡澄暮,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,576評論 2 327
  • 正文 我和宋清朗相戀三年名段,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片泣懊。...
    茶點(diǎn)故事閱讀 38,712評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡吉嫩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出嗅定,到底是詐尸還是另有隱情自娩,我是刑警寧澤,帶...
    沈念sama閱讀 34,389評論 4 332
  • 正文 年R本政府宣布,位于F島的核電站忙迁,受9級(jí)特大地震影響脐彩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜姊扔,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,032評論 3 316
  • 文/蒙蒙 一惠奸、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧恰梢,春花似錦翔怎、人聲如沸厅克。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽塑猖。三九已至崔兴,卻和暖如春叹谁,著一層夾襖步出監(jiān)牢的瞬間稀蟋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,026評論 1 266
  • 我被黑心中介騙來泰國打工苛白, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留娃豹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,473評論 2 360
  • 正文 我出身青樓购裙,卻偏偏與公主長得像懂版,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子躏率,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,606評論 2 350

推薦閱讀更多精彩內(nèi)容