靜態(tài)代理和動態(tài)代理

一:概述

????Java的代理模式主要分為靜態(tài)代理和動態(tài)代理全陨,靜態(tài)代理非常簡單黍聂,簡單說明一下豆拨,重點是動態(tài)代理痹束。先來看下代理模式的類圖(盜圖):
代理模式.png

二:靜態(tài)代理

????理解靜態(tài)代理的經(jīng)典場景就是買火車票张症,火車票代售點和火車站之間的關(guān)系就是靜態(tài)代理的關(guān)系无畔。代售點具有賣出火車票的功能,火車站也有賣票的功能吠冤,但是代售點賣票是依賴于火車站實現(xiàn)的浑彰,而火車站自己就實現(xiàn)了賣票功能;既然代售點和火車站都有賣票功能拯辙,那么可以把這個功能提取出來郭变,封裝在一個接口里面,然后代售點和火車站都實現(xiàn)這個接口涯保,那么他們都有了賣票的功能:

public interface Ticket {
    void saleTicket();
}

????下面分別實現(xiàn)火車站類和代售點類:

//火車站類
public class TrainStation implements Ticket {

    @Override
    public void saleTicket() {
        System.out.println("我是火車站诉濒,1、2夕春、3未荒,賣票");
    }
}
//代售點類
public class ProxyStation implements Ticket {

    Ticket sale = new TrainStation();
    
    @Override
    public void saleTicket() {
        System.out.println("我是代售點,開始賣票");
        sale.saleTicket();
    }
}

????從上面的代碼可以看出及志,代售點類持有了一個火車站類的引用片排,代售點賣票其實就是調(diào)用了火車站類的賣票方法,實際的賣票動作是在火車票進行的速侈;代售點類就是個大騙子率寡,什么事都使喚火車站類去做。測試代碼如下:

public static void main(String[] args) {
    Ticket t1 = new ProxyStation();
    t1.saleTicket();
}

????輸出結(jié)果如下:

我是代售點倚搬,開始賣票
我是火車站冶共,1、2、3捅僵,賣票

????通過上面的代碼家卖,我們可以歸納出代理模式的三大要素:公共接口(Ticket)、真實對象(TrainStation)和代理對象(ProxyStation)庙楚。代理對象是供外部調(diào)用的對象篡九,真實對象是真正實現(xiàn)公共接口邏輯的對象,公共接口就是抽象出代理對象和真實對象的公共功能的地方醋奠,這都很好理解榛臼。

????也許有人會說,為什么不能直接創(chuàng)建真實對象直接調(diào)用方法窜司,而非要弄出一個中間人角色的代理對象出來沛善?要知道,代理對象雖然是狐假虎威塞祈,但是他對隱藏真實對象是很有用的金刁,外部不能直接與真實對象接觸,這對保護數(shù)據(jù)什么的有很大作用议薪;而且尤蛮,在代理類里面也可以對方法進行增強:比如在賣票之前,檢查身份證什么的斯议;這樣真實對象就可以專注于自己業(yè)務(賣票)的實現(xiàn)了产捞。

????靜態(tài)代理就是這么簡單,沒什么好說的哼御。下面通過例子來說明動態(tài)代理坯临。在上面的例子中,現(xiàn)在要求代售點在賣票之前恋昼,先檢查身份證的有效性看靠,這樣就需要添加一個校驗身份證的功能(方法)。根據(jù)開閉原則液肌,既然接口挟炬、TrainStation和ProxyStation已經(jīng)寫好了,穩(wěn)定了嗦哆,那么就不應該再去該他了谤祖,怎么辦?當然是創(chuàng)建一個新的代理類吝秕,在新的代理類里面實現(xiàn)校驗身份證的功能:

public class ProxyStation1 implements Ticket{
    Ticket sale = new TrainStation();
    
    @Override
    public void saleTicket() {
        System.out.println("我是代售點泊脐,開始賣票");
        
        //原來的邏輯不變,但是添加檢查身份證的功能
        boolean pass = checkID();
        if(pass) {
            sale.saleTicket();
        }
    }
    
    //檢查身份證
    public boolean checkID() {
        //假裝檢查通過
        return true;
    }
}

????測試代碼就不寫了烁峭。可以看到,這樣寫沒什么大問題约郁,僅僅是新增了一個類缩挑,增加了檢查身份證的方法,同時重寫了賣票的方法鬓梅。

????現(xiàn)在為了了解賣票過程的更具體的信息供置,需要計時功能,就是記錄買一張票要多長時間绽快,為鐵道部以后的服務政策提供數(shù)據(jù)支撐芥丧。還是根據(jù)開閉原則,原來的類不能動坊罢,只能再新增一個類:

public class ProxyStation1 implements Ticket{
    Ticket sale = new TrainStation();
    
    //開始賣票和結(jié)束賣票的時間戳
    long start;
    long end;
    
    @Override
    public void saleTicket() {
        //開始計時
        start = System.currentTimeMillis();
        
        System.out.println("我是代售點续担,開始賣票");
        
        //原來的邏輯不變,但是添加檢查身份證的功能
        boolean pass = checkID();
        if(pass) {
            sale.saleTicket();
            
            //結(jié)束計時
            end = System.currentTimeMillis();
            
            //計算賣一張票需要花費多少時間
            long duration = end - start;
        }
    }
    
    //檢查身份證
    public boolean checkID() {
        //假裝檢查通過
        return true;
    }
}

????為了增加一個功能活孩,又要實現(xiàn)一個類物遇;當然,上面的類可以通過基礎實現(xiàn)部分代碼的復用憾儒,這里僅僅是為了舉例询兴,沒去注意做。隨著功能的越來越多起趾,你就會發(fā)現(xiàn)新增的類就會越來越多诗舰,這樣做肯定是不合理的,況且維護起來也非常麻煩训裆。

????所以靜態(tài)代理就會有這樣的弊端始衅,靜態(tài)代理需要我們事先把代理類預先寫好,預先寫好有預先寫好的好處缭保,但是一旦需要擴展的時候汛闸,就會使得類的數(shù)量彭總;而且有些功能艺骂,比如檢查身份證的方法checkID诸老,在別的場景也能用得上,比如辦銀行卡钳恕;如果在一個辦卡的類里面也要實現(xiàn)檢查身份證功能别伏,怎么辦?還能咋地忧额,再寫一次唄厘肮,又不能把賣票的類引進來調(diào)用,畢竟賣票的類有很多功能和把銀行卡沒半毛錢關(guān)系睦番,引進來不合適类茂;計時功能可能在別的場景上遇得到耍属,比如去營業(yè)廳辦理業(yè)務,為了提高服務質(zhì)量巩检,首先要調(diào)查每次服務的時間厚骗,這個還是也要引入計時功能(假裝計時功能封裝在一個方法里面,而不是上面的獲取兩個時間戳然后相減)兢哭,怎么辦领舰?再寫一個與運營商有關(guān)的代理類唄,然后SB的把計時功能再寫一遍(或者Ctrl + c然后Ctrl + v)迟螺;可是這樣做累不累冲秽?所以靜態(tài)代理可能會導致代碼量的急劇碰撞,一些相同功能得不到復用矩父。

三:動態(tài)代理

????這個時候锉桑,動態(tài)代理就派上用場了。還是以賣票為例 浙垫,公共接口和真實對象不變刨仑,從增加身份證檢查的功能開始,先定義一個實現(xiàn)了InvocationHandler接口的類CheckIDHandler:

public class CheckIDHandler implements InvocationHandler {
    //真實對象類夹姥,在本例中就是火車站類
    private Object target;
    
    //檢查身份證
    public boolean checkID() {
        //假裝檢查通過
        System.out.println("身份證檢查通過");
        return true;
    }
    
    //InvocationHandler接口唯一的方法
    //要實現(xiàn)方法增強水醋,就在這個方法里面加自己的邏輯
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法增強  
        checkID();
        Object result = method.invoke(target, args);
        return result;
    }
}

????動態(tài)代理的實現(xiàn)套路是這樣:1.創(chuàng)建一個實現(xiàn)了InvocationHandler接口的類寂嘉;2.增加自己的邏輯,用于方法增強;3.重寫invoke方法涌韩,在method.invoke方法的前面(或者后面)增加自己的邏輯衷旅,實現(xiàn)方法增強在扰。

????測試代碼:

public static void main(String[] args) {
    //創(chuàng)建一個剛才新建的類的對象
    CheckIDHandler handler = new CheckIDHandler();
        
    //真實類對象
    TrainStation ts = new TrainStation();
        
    //創(chuàng)建真實類對象的代理對象胚股,遙想當年,我們都
    //是先把代理類寫好士八,然后一個個創(chuàng)建他們的對象
    //的容燕;這里系統(tǒng)幫我們創(chuàng)建好代理對象并返回給我們
    Ticket p1 = (Ticket) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
                ts.getClass().getInterfaces(), handler);
        
    //拿到代理對象后,當然可以直接調(diào)用真實對象的方法了
    p1.saleTicket();
}

????動態(tài)代理的實現(xiàn)套路是這樣:1.創(chuàng)建一個增強類的對象婚度;2.創(chuàng)建一個真實類的對象蘸秘;3.調(diào)用Proxy的靜態(tài)方法newProxyInstance,傳入當前線程的上下文類加載器蝗茁,也可以是真實對象的類加載器醋虏,還有真實對象的接口(就是那個公共接口),最后一個就是增強類對象哮翘;4.經(jīng)過 前面3步颈嚼,就拿到了代理對象,然后就可以調(diào)用真實對象里面的方法了饭寺。

????輸出結(jié)果如下:

身份證檢查通過
我是火車站阻课,1叫挟、2、3柑肴,賣票

????可以看到霞揉,我們沒有定義代理類旬薯,更沒有手動創(chuàng)建代理類的對象晰骑,就這樣,在賣票之前做到了檢查身份證的功能绊序,實現(xiàn)了方法增強硕舆。

????也許你會說,臥槽骤公,上面明明拿到了真實對象的引用抚官,在真實對象的前面調(diào)用checkID()方法,不就是實現(xiàn)了檢查身份證的功能嗎阶捆?好吧凌节,那么檢查身份證的方法放在哪個類里面?調(diào)用者的類里面洒试?如果別的地方也要用到檢查身份證的功能呢倍奢?再寫一遍?也許你會說放在工具類里面垒棋,但是不是所有的類都可以放在工具類里面當做靜態(tài)方法來調(diào)的卒煞。
????把檢查身份證的方法放在CheckIDHandler里面是有好處的,比如如果營業(yè)廳在辦理業(yè)務時叼架,要增加檢查身份證的步驟畔裕,怎么辦?

//下面是偽代碼

//檢查身份證的類
CheckIDHandler handler = new CheckIDHandler();

//營業(yè)廳的真實類
BusinessHall bh = new BusinessHall();
        
//Business是營業(yè)廳的接口乖订,b1是返回的代理類
Business b1 = (Ticket) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
        ts.getClass().getInterfaces(), handler);
//調(diào)用辦理業(yè)務的接口
b1.doBusiness();

????可以看到扮饶,通過引入CheckIDHandler就可以在辦理業(yè)務之前,強行把檢查身份證的動作強行插入進去乍构,這也就是所謂的面向切面編程(AOP)甜无,同時可以看到的是,檢查身份證的邏輯得到了極大的復用蜡吧,另外上例中的計時功能也能封裝到一個類毫蚓,然后在買票創(chuàng)景和營業(yè)廳辦理業(yè)務場景中復用。

????通過上面的對比可以看到昔善,靜態(tài)代理在要進行方法增強的時候元潘,需要創(chuàng)建新的類,而且方法增強的越多君仆,新增的類也就越多翩概,同時這些增強的功能還沒法復用牲距,別的模塊用不了,耦合太高钥庇,也造成了大量的冗余代碼牍鞠;而動態(tài)代理,每增強一次评姨,也會創(chuàng)建一個類难述,但是這個類只關(guān)注增強的邏輯,與別的類耦合度較低吐句,同時此類的增強方法還能用于別的模塊胁后。

????我個人的理解是動態(tài)代理就是把要增強的功能封裝到一個類里面,然后在需要他的地方強行插入進入嗦枢,一來不破壞原有的代碼攀芯;二來增強功能能夠得到復用。

????再盜一張圖:
動態(tài)代理思維導圖.jpg

四:動態(tài)代理的原理

????從上面例子中可以看出文虏,動態(tài)代理的核心在于Proxy的newProxyInstance方法里面侣诺,我們?nèi)ヒ惶骄烤梗?/p>

......
    //注釋略,太長了氧秘。第一個參數(shù)是真實對象的類加載器年鸳;第二個
    //參數(shù)是真實對象實現(xiàn)的接口最后一個參數(shù)是我們實現(xiàn)的增強類
    @CallerSensitive
    public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
            throws IllegalArgumentException {
        //增強類不能非空(判空新思路)
        Objects.requireNonNull(h);

        //將真實對象實現(xiàn)的接口拷到新數(shù)組
        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.
         *
         * 查找或者生成指定的代理類敏储,動態(tài)生成哦
         */
        Class<?> cl = getProxyClass0(loader, intfs);

        /*
         * Invoke its constructor with the designated invocation handler.
         *
         * 使用指定的調(diào)用處理程序調(diào)用其構(gòu)造函數(shù)阻星。
         */
        try {
            //安全檢查,pass
            if (sm != null) {
                checkNewProxyPermission(Reflection.getCallerClass(), cl);
            }

            //獲取代理類的構(gòu)造器
            final Constructor<?> cons = cl.getConstructor(constructorParams);


            final InvocationHandler ih = h;

            //getModifiers是Class的方法已添,返回的是該類的修飾符妥箕,比如public
            //,final更舞,static畦幢,abstract,interface等,拿到這個修飾符后
            //缆蝉,調(diào)用Modifier的isPublic方法判斷該類是否是public類
            if (!Modifier.isPublic(cl.getModifiers())) {
                //如果該類不是public類宇葱,那么就要放開對這個類的訪問權(quán)限,不管
                AccessController.doPrivileged(new PrivilegedAction<Void>() {
                    public Void run() {
                        cons.setAccessible(true);
                        return null;
                    }
                });
            }

            //拿到Class對象后刊头,就可以創(chuàng)建相應的實例了
            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);
        }
    }
......

????newProxyInstance的實現(xiàn)思路是:1.首先調(diào)用getProxyClass0查找或者生成指定的代理類黍瞧;2.檢查該類是否是public的,如果不是原杂,放開權(quán)限印颤;3.調(diào)用Class的newInstance方法創(chuàng)建實例。最重要的是getProxyClass0方法:

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
        //調(diào)用proxyClassCache的get方法穿肄;proxyClassCache對象是WeakCache類型的年局,他
        //持有一個弱引用隊列际看,每次查找就去隊列里面找,如果沒找到就ProxyClassFactory的apply
        //方法去創(chuàng)建
        return proxyClassCache.get(loader, interfaces);
    }

????下面直接看ProxyClassFactory的apply方法:

        //第一個參數(shù)是真實對象的類加載器矢否;第二個參數(shù)是真實對象實現(xiàn)的接口
        @Override
        public Class<?> apply(ClassLoader loader, Class<?>[] interfaces) {

            //創(chuàng)建一個Map容器仲闽,用于裝載真實對象實現(xiàn)的接口
            Map<Class<?>, Boolean> interfaceSet = new IdentityHashMap<>(interfaces.length);

            //遍歷這些接口,創(chuàng)建這些接口的Class對象僵朗,主要用于校驗這些接口的合法性
            for (Class<?> intf : interfaces) {
                /*
                 * Verify that the class loader resolves the name of this
                 * interface to the same Class object.
                 */
                Class<?> interfaceClass = null;
                try {
                    //使用指定的類加載器創(chuàng)建一個與給定的名字關(guān)聯(lián)的類或者接口的Class對象
                    interfaceClass = Class.forName(intf.getName(), false, loader);
                } catch (ClassNotFoundException e) {
                }

                //如果創(chuàng)建失敗赖欣,說明類加載器不對
                if (interfaceClass != intf) {
                    throw new IllegalArgumentException(
                            intf + " is not visible from class loader");
                }
                /*
                 * Verify that the Class object actually represents an
                 * interface.
                 */
                //如果創(chuàng)建出來的Class不是不是接口類型的,那么死給你看
                if (!interfaceClass.isInterface()) {
                    throw new IllegalArgumentException(
                            interfaceClass.getName() + " is not an interface");
                }
                /*
                 * Verify that this interface is not a duplicate.
                 */
                //如果interfaceClass已經(jīng)存在于容器中衣迷,說明重復創(chuàng)建了畏鼓,那么也死給你看
                if (interfaceSet.put(interfaceClass, Boolean.TRUE) != null) {
                    throw new IllegalArgumentException(
                            "repeated interface: " + interfaceClass.getName());
                }
            }

            //代理類的包名
            String proxyPkg = null;     // package to define proxy class in

            //將該類設置成public final類型的類酱酬,網(wǎng)上有方法可以拿到這個代理類壶谒,從
            //他反編譯的結(jié)果來看,確實是public final類型的類膳沽,有興趣的自己研究下
            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) {

                //獲取該接口的Class對象的訪問修飾符
                int flags = intf.getModifiers();

                //如果不是public類型的
                if (!Modifier.isPublic(flags)) {
                    //那么去掉上面的設置成public final類型的操作
                    accessFlags = Modifier.FINAL;

                    //返回該接口的名字(包括包名)
                    String name = intf.getName();

                    //獲取全限定名的最后的.號
                    int n = name.lastIndexOf('.');

                    //生成的代理類的包名,包名就是接口的全限定名 - 接口類的名字挑社,類似于"java.lang."
                    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");
                    }
                }
            }

            //如果木有非公共代理接口陨界,那么使用默認的包名com.sun.proxy
            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.
             */
            //生成一個數(shù)字,過程不管
            long num = nextUniqueNumber.getAndIncrement();

            //拼接代理類的全限定名
            String proxyName = proxyPkg + proxyClassNamePrefix + num;

            /*
             * Generate the specified proxy class.
             */
            //生成代理類的byte數(shù)組痛阻,沒看到源碼菌瘪,不管了
            byte[] proxyClassFile = ProxyGenerator.generateProxyClass(
                    proxyName, interfaces, accessFlags);
            try {
                //將byte數(shù)組轉(zhuǎn)換成Class對象并返回
                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());
            }
        }

???? apply方法比較長,但是邏輯并不復雜阱当,不過出現(xiàn)了一些我們很少用到的接口俏扩,查看API就可以了,不過最后還是沒看到生成byte數(shù)組的generateProxyClass方法弊添,從網(wǎng)上摘抄了一部分資料:

public static byte[] generateProxyClass(final String name,  
                                           Class[] interfaces)  
   {  
       ProxyGenerator gen = new ProxyGenerator(name, interfaces);  
       // 這里動態(tài)生成代理類的字節(jié)碼
       final byte[] classFile = gen.generateClassFile();  
 
       // 如果saveGeneratedFiles的值為true录淡,則會把所生成的代理類的字節(jié)碼保存到硬盤上  
       if (saveGeneratedFiles) {  
           java.security.AccessController.doPrivileged(  
           new java.security.PrivilegedAction<Void>() {  
               public Void run() {  
                   try {  
                       FileOutputStream file =  
                           new FileOutputStream(dotToSlash(name) + ".class");  
                       file.write(classFile);  
                       file.close();  
                       return null;  
                   } catch (IOException e) {  
                       throw new InternalError(  
                           "I/O exception saving generated file: " + e);  
                   }  
               }  
           });  
       }  
  
       // 返回代理類的字節(jié)碼  
       return classFile;  
   }  

private byte[] generateClassFile() {
        //第一步, 將所有的方法組裝成ProxyMethod對象
  3     //首先為代理類生成toString, hashCode, equals等代理方法
  4     addProxyMethod(hashCodeMethod, Object.class);
  5     addProxyMethod(equalsMethod, Object.class);
  6     addProxyMethod(toStringMethod, Object.class);
  7     //遍歷每一個接口的每一個方法, 并且為其生成ProxyMethod對象
  8     for (int i = 0; i < interfaces.length; i++) {
  9         Method[] methods = interfaces[i].getMethods();
 10         for (int j = 0; j < methods.length; j++) {
 11             addProxyMethod(methods[j], interfaces[i]);
 12         }
 13     }
 14     //對于具有相同簽名的代理方法, 檢驗方法的返回值是否兼容
 15     for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
 16         checkReturnTypes(sigmethods);
 17     }
 18     
 19     //第二步, 組裝要生成的class文件的所有的字段信息和方法信息
 20     try {
 21         //添加構(gòu)造器方法
 22         methods.add(generateConstructor());
 23         //遍歷緩存中的代理方法
 24         for (List<ProxyMethod> sigmethods : proxyMethods.values()) {
 25             for (ProxyMethod pm : sigmethods) {
 26                 //添加代理類的靜態(tài)字段, 例如:private static Method m1;
 27                 fields.add(new FieldInfo(pm.methodFieldName,
 28                         "Ljava/lang/reflect/Method;", ACC_PRIVATE | ACC_STATIC));
 29                 //添加代理類的代理方法
 30                 methods.add(pm.generateMethod());
 31             }
 32         }
 33         //添加代理類的靜態(tài)字段初始化方法
 34         methods.add(generateStaticInitializer());
 35     } catch (IOException e) {
 36         throw new InternalError("unexpected I/O Exception");
 37     }
 38     
 39     //驗證方法和字段集合不能大于65535
 40     if (methods.size() > 65535) {
 41         throw new IllegalArgumentException("method limit exceeded");
 42     }
 43     if (fields.size() > 65535) {
 44         throw new IllegalArgumentException("field limit exceeded");
 45     }
 46 
 47     //第三步, 寫入最終的class文件
 48     //驗證常量池中存在代理類的全限定名
 49     cp.getClass(dotToSlash(className));
 50     //驗證常量池中存在代理類父類的全限定名, 父類名為:"java/lang/reflect/Proxy"
 51     cp.getClass(superclassName);
 52     //驗證常量池存在代理類接口的全限定名
 53     for (int i = 0; i < interfaces.length; i++) {
 54         cp.getClass(dotToSlash(interfaces[i].getName()));
 55     }
 56     //接下來要開始寫入文件了,設置常量池只讀
 57     cp.setReadOnly();
 58     
 59     ByteArrayOutputStream bout = new ByteArrayOutputStream();
 60     DataOutputStream dout = new DataOutputStream(bout);
 61     try {
 62         //1.寫入魔數(shù)
 63         dout.writeInt(0xCAFEBABE);
 64         //2.寫入次版本號
 65         dout.writeShort(CLASSFILE_MINOR_VERSION);
 66         //3.寫入主版本號
 67         dout.writeShort(CLASSFILE_MAJOR_VERSION);
 68         //4.寫入常量池
 69         cp.write(dout);
 70         //5.寫入訪問修飾符
 71         dout.writeShort(ACC_PUBLIC | ACC_FINAL | ACC_SUPER);
 72         //6.寫入類索引
 73         dout.writeShort(cp.getClass(dotToSlash(className)));
 74         //7.寫入父類索引, 生成的代理類都繼承自Proxy
 75         dout.writeShort(cp.getClass(superclassName));
 76         //8.寫入接口計數(shù)值
 77         dout.writeShort(interfaces.length);
 78         //9.寫入接口集合
 79         for (int i = 0; i < interfaces.length; i++) {
 80             dout.writeShort(cp.getClass(dotToSlash(interfaces[i].getName())));
 81         }
 82         //10.寫入字段計數(shù)值
 83         dout.writeShort(fields.size());
 84         //11.寫入字段集合 
 85         for (FieldInfo f : fields) {
 86             f.write(dout);
 87         }
 88         //12.寫入方法計數(shù)值
 89         dout.writeShort(methods.size());
 90         //13.寫入方法集合
 91         for (MethodInfo m : methods) {
 92             m.write(dout);
 93         }
 94         //14.寫入屬性計數(shù)值, 代理類class文件沒有屬性所以為0
 95         dout.writeShort(0);
 96     } catch (IOException e) {
 97         throw new InternalError("unexpected I/O Exception");
 98     }
 99     //轉(zhuǎn)換成二進制數(shù)組輸出
100     return bout.toByteArray();
101 }

????至此,我們基本上了解了代理對象是怎么生成的油坝。
????還有個問題是嫉戚,那個invoke是什么時候調(diào)用的呢?要想解開這個謎題澈圈,只能查看動態(tài)生成的代理對象的源碼彬檀,同樣從網(wǎng)上摘抄了一個代理對象的源碼:

{
  private static Method m1;
  private static Method m2;
  private static Method m3;
  private static Method m0;
  
  /**
  *注意這里是生成代理類的構(gòu)造方法,方法參數(shù)為InvocationHandler類型瞬女,看到這窍帝,是不是就有點明白
  *為何代理對象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
  *被代理對象的實例拆魏,不禁會想難道是....盯桦? 沒錯慈俯,就是你想的那樣。
  *
  *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);
  }
  
  //這個靜態(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.Person").getMethod("giveMoney", 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)用代理對象的giveMoney方法突梦,直接就調(diào)用了InvocationHandler中的invoke方法,并把m3傳了進去羽利。
  *this.h.invoke(this, m3, null);這里簡單宫患,明了。
  *來这弧,再想想娃闲,代理對象持有一個InvocationHandler對象,InvocationHandler對象持有一個被代理的對象匾浪,
  *再聯(lián)系到InvacationHandler中的invoke方法皇帮。嗯,就是這樣蛋辈。
  */
  public final void giveMoney()
    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方法一毛一樣挂洛。
}

????上面代碼的giveMoney就相當于上面例子中的checkID()礼预。可以看到虏劲,當我們調(diào)用代理對象的checkID()方法時托酸,代理對象內(nèi)部調(diào)用了增強類的invoke方法,正式在這個invoke方法里面柒巫,我們才能實現(xiàn)方法的增強励堡,當然在調(diào)用invoke的時候,我們還把最終要調(diào)用的買票的方法saleTicket傳進去了堡掏,這個方法最終通過反射的機制得到調(diào)用应结。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子鹅龄,更是在濱河造成了極大的恐慌揩慕,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扮休,死亡現(xiàn)場離奇詭異迎卤,居然都是意外死亡,警方通過查閱死者的電腦和手機玷坠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門蜗搔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人八堡,你說我怎么就攤上這事樟凄。” “怎么了兄渺?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵缝龄,是天一觀的道長。 經(jīng)常有香客問我溶耘,道長二拐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任凳兵,我火速辦了婚禮,結(jié)果婚禮上企软,老公的妹妹穿的比我還像新娘庐扫。我一直安慰自己,他們只是感情好仗哨,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布形庭。 她就那樣靜靜地躺著,像睡著了一般厌漂。 火紅的嫁衣襯著肌膚如雪萨醒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天苇倡,我揣著相機與錄音富纸,去河邊找鬼。 笑死旨椒,一個胖子當著我的面吹牛晓褪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播综慎,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼涣仿,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起好港,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤愉镰,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后钧汹,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體岛杀,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年崭孤,在試婚紗的時候發(fā)現(xiàn)自己被綠了类嗤。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡辨宠,死狀恐怖遗锣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嗤形,我是刑警寧澤精偿,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站赋兵,受9級特大地震影響笔咽,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜霹期,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一叶组、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧历造,春花似錦甩十、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至臣淤,卻和暖如春橄霉,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背邑蒋。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工姓蜂, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人寺董。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓覆糟,卻偏偏與公主長得像,于是被迫代替她去往敵國和親遮咖。 傳聞我的和親對象是個殘疾皇子滩字,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

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