天天看點

黑馬程式員-----Java代理機制的學習筆記

-------android教育訓練、java教育訓練、期待與您交流! ----------

内容源自 張孝祥老師的Java高新技術. 以下是我看視訊的筆記: --------------------------------------------------------------------------- 這些筆記對應張孝祥老師的Java高新技術的如下視訊:

49.分析代理類的作用與原理及AOP概念

50.建立動态類及檢視其方法清單資訊

51.建立動态類的執行個體對象及調用其方法

52.完成InvocationHandler對象的内部功能

53.分析InvocationHandler對象的運作原理

54.總結分析動态代理類的設計原理與結構

55.編寫可生成代理和插入通告的通用方法

56.實作類似spring的可配置的AOP架構

-------------------------------------------------------

49.分析代理類的作用與原理及AOP概念

比如:

    要為已存在的多個具有相同接口的目标類的各個方法

        增加一些系統功能:

    如: 異常處理, 日志, 計算方法的運作時間, 事務管理, 等等.

如何處理這種要求呢?

使用代理機制來處理這種問題.

如下

class X{

    void sayHello(){syso:helloworld;}

}

代理類:

class XProxy{

    void sayHello(){

        starttime

        X.sayHello() //這裡調用X的方法

        endtime

    }

}

即, 

做一個X的代理XProxy.

即, 其中的方法和X一樣.

    在X代理XProxy的方法裡面, 調用X的方法.

即, 在調用原來的方法的前後可以加上一些功能.

然後, 用戶端, 調用的是代理.

    代理和目标類 有着相同的 接口.

    即, 我們會把是先把目标類中的要用上代理的方法做成一個接口.

    然後, 代理隻需要去實作這個接口就可以了.

然後, 在調用時, 用戶端實際上是就是調用代理的這個接口的方法.

我們采用工廠模式 和 配置檔案 的方式進行管理, 

    則不需要修改用戶端程式.

在配置檔案中配置 是使用普通類, 還是使用代理類.

    這樣以後很容易切換.

如:

    想要日志功能時, 就配置代理類, 

    否則, 就配置為目标類.

這樣, 增加系統功能很容易.

在運作一段時間後, 想去掉系統功能也很容易.

AOP(aspect oriented program), 面向方面的程式設計.

系統中存在交叉業務

如多個類, 各處理各的事, 

    但不管處理什麼事, 都要有 安全, 事務, 日志 等功能.

即, 安全, 事務, 日志 等功能, 要貫穿在好多子產品裡面.

即, 安全, 事務, 日志就是這些類的就交叉業務.

                  安全   事務   日志

StudentService -----|------|------|-------

CourseService  -----|------|------|-------

MiscService    -----|------|------|-------

用具體的程式代碼描述交叉業務:

method1     method2      method3

{           {            {

------------------------------------切面

 ...          ...          ...

------------------------------------切面

}           }            }

AOP的目标就是要使交叉業務子產品化, 

    可以采用将切面代碼移動到原始方法的周圍, 

    這與直接在方法中編寫切面代碼的運作效果是一樣的.

如下所示:

------------------------------------切面

func1       func2        func3

{           {            {

 ...          ...          ...

}           }            }

------------------------------------切面

使用代理技術正好可以解決這種問題, 

    代理是實作AOP功能的核心和關鍵技術.

隻要涉及AOP, 就會涉及到代理技術.

要為系統中的各種接口的類, 增加代理功能, 

    那将需要太多的代理類, 

全部采用靜态代理方式, 将是一件非常麻煩的事情.

JVM可以在運作期動态生成出類的位元組碼, 

    這種動态生成的類 往往被用作代理類, 即動态代理類.

注意:

    動态生成的類  不一定是代理類. 

    隻是多用來生成代理類而已.

JVM生成的動态類, 必須實作 一個/多個接口.

    是以, JVM生成的動态類 

            隻能用作 具有相同接口 的目标類 的代理.

如果目标類沒有接口的話, 怎麼辦?

    有一個第三方的開源類庫:CGLIB

        其可以動态生成一個類的子類, 

            一個類的子類也可以用作該類的代理

                ----這個就是項妙的地方了.

                ---- 在子類中重寫父類的方法,

                    并在重寫的方法中調用父類的方法.

代理類中的方法, 所做的功能可以添加的位置有:

    1 在調用目标方法 之前

    2 在調用目标方法 之後

    3 在調用目标方法 之前 和 之後 都加

    4 在調用目标方法 異常的catch塊中

-------------------------------------------------------

50.建立動态類及檢視其方法清單資訊

我們現在, 用JVM的接口來生成動态代理類

    ----要求目标類要用接口.

Proxy

全稱: java.lang.reflect.Proxy

    ----即, 其用的是反射的機制.

裡面全是 static方法

    ----即, 這是一個工具類.

其有一個方法:

    Class<?> getProxyClass(ClassLoader loader, Class<?>... interfaces) 

        即, 動态得到一個代理類.

        ClassLoader loader: 指定類加載器來加載這個所生成的代理類.

        Class<?>... interfaces: 指定這個代理類所實作的接口的Class對象.

        如:

        我們要生成一個動态代理類來實作Collection接口, 

            其所用的類加載器, 通常是Collection類的加載器.

            -----即, 我們一般是這樣做的.

應用:

    public static void main(String[] args) {

        // 生成實作了Collection接口的動态代理類

        // 并列印其構造方法

        //   列印其所有方法

        //1 生成一個動态代理類

        Class<?> clazzProxy = 

                Proxy.getProxyClass(Collection.class.getClassLoader(), 

                        Collection.class);

        //2 擷取這個動态代理類的名稱: com.sun.proxy.$Proxy0

        System.out.println(clazzProxy.getName());

        //3 擷取這個動态代理類的構造方法, 并列印出來

        //com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)

        Constructor[] constructors = clazzProxy.getConstructors();

        System.out.println("---------------構造方法-------------");

        for (Constructor constructor : constructors){

            StringBuilder str = new StringBuilder();

            str.append(constructor.getName());

            str.append("(");

            Class[] clazzParas = constructor.getParameterTypes();

            for (Class clazzPara : clazzParas){

                str.append(clazzPara.getName() + ",");

            }

            //注意, 這一步是必須的, 因為我們不能保證構造方法有參數

            //注意, 下面這兩個條件, 我隻是為了保險起見才這麼做的.

            if (clazzParas!=null && clazzParas.length!=0){

                str.deleteCharAt(str.length()-1);

            }

            str.append(")");

            System.out.println(str);

        }

        //4 擷取這個動态代理類的方法, 并列印出來

        Method[] methods = clazzProxy.getMethods();

        System.out.println("---------------一般方法-------------");

        for (Method method : methods){

            StringBuilder str = new StringBuilder();

            str.append(method.getName());

            str.append("(");

            Class[] clazzParas = method.getParameterTypes();

            for (Class clazzPara : clazzParas){

                str.append(clazzPara.getName() + ",");

            }

            //注意, 這一步是必須的, 因為我們不能保證構造方法有參數

            //注意, 下面這兩個條件, 我隻是為了保險起見才這麼做的.

            if (clazzParas!=null && clazzParas.length!=0){

                str.deleteCharAt(str.length()-1);

            }

            str.append(")");

            str.append(":" + method.getReturnType().getName());

            System.out.println(str);

        }

    }

得到結果:

com.sun.proxy.$Proxy0  -----動态代理類放在com.sun.proxy包中---名稱帶$表示複合類, 0表示序号

---------------構造方法-------------

com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)

---------------一般方法-------------

add(java.lang.Object):boolean

remove(java.lang.Object):boolean

equals(java.lang.Object):boolean ------- Collection裡已有

toString():java.lang.String

hashCode():int ------- Collection裡已有

clear():void

contains(java.lang.Object):boolean

isEmpty():boolean

iterator():java.util.Iterator

size():int

toArray([Ljava.lang.Object;):[Ljava.lang.Object;

toArray():[Ljava.lang.Object;

spliterator():java.util.Spliterator

addAll(java.util.Collection):boolean

stream():java.util.stream.Stream

forEach(java.util.function.Consumer):void

containsAll(java.util.Collection):boolean

removeAll(java.util.Collection):boolean

removeIf(java.util.function.Predicate):boolean

retainAll(java.util.Collection):boolean

parallelStream():java.util.stream.Stream ------- 代理類自身的

isProxyClass(java.lang.Class):boolean ------- 代理類自身的

getInvocationHandler(java.lang.Object):java.lang.reflect.InvocationHandler ------- 代理類自身的

newProxyInstance(java.lang.ClassLoader,Ljava.lang.Class;,java.lang.reflect.InvocationHandler):java.lang.Object ------- 代理類自身的

getProxyClass(java.lang.ClassLoader,[Ljava.lang.Class;):java.lang.Class ------- 代理類自身的

wait():void

wait(long,int):void

wait(long):void

getClass():java.lang.Class

notify():void

notifyAll():void

一個是:

    我們看到生成的動态代理類的名稱為:

        com.sun.proxy.$Proxy0   ---而Proxy是放在java.lang.reflect包中的.

        -----動态代理類放在com.sun.proxy包中

        -----名稱帶$表示複合類, 

        -----Proxy 表示這是一個代理類

        -----0表示序号

一個是:

    我們在命名一個位元組碼變量時, 常常用 clazz來命名.

        這是一個習慣.

一個是:

    其構造方法:

        com.sun.proxy.$Proxy0(java.lang.reflect.InvocationHandler)

    隻有一個.

    參數為: InvocationHandler類型.

        後面會說到.

一個是:

    這個類中的方法, 不僅僅有Collection的方法, 

        還有一些代理類自己的方法:

        parallelStream():java.util.stream.Stream

        isProxyClass(Class):boolean

        getInvocationHandler(Object):InvocationHandler

        newProxyInstance(ClassLoader,[Ljava.lang.Class;,InvocationHandler):Object

        getProxyClass(ClassLoader,[Ljava.lang.Class;):Class

        其中 [Ljava.lang.Class 相當于 Class[].

一個是:

    InvocationHandler 是一個接口, 其隻有一個方法:

        public Object invoke(Object proxy, //動态代理類對象

                                           //invoke内部可能會用到動态代理類對象

                            Method method, //所調用的方法的Method對象

                            Object[] args) //方法的參數

        -----在這個方法裡, 我們可 實作這些系統的功能.

    具體的内容下面就要講到了.

-------------------------------------------------------

51.建立動态類的執行個體對象及調用其方法

通過上面, 我們僅僅得到了動态代理類的Class對象.

現在,

    我們利用 動态代理類 來建立其執行個體對象.

如何來建立 動态代理類的對象呢?

    當然 隻有兩個方法:

    一種: Class::newInstance()

            ---用無參構造方法

            ---但我們知道, 動态代理類沒有無參構造方法.

            ---是以不能采用這種方法.

    一種: $Proxy0 的構造方法.

            然後調用它來建立對象.

            ---下面我們說的就是這一種.

後面還會介紹一種:

    Proxy.newProxyInstance(ClassLoader, Class<?>[], InvocationHandler):Object ---後面基本隻用這種

        這是一個 static 方法.

        此方法是綜合以下兩個步驟:

            Proxy.getProxyClass(ClassLoader, Class<?>... interfaces):Class<?> 

          和

            $Proxy0(InvocationHandler): Object

動态代理類的構造方法隻有一種:

    $Proxy0(InvocationHandler)

        即, 要傳向一個 InvocationHandler 對象.

        但這個是一個 接口, 不能 new InvocationHandler()!

是以, 我們常常是用匿名類的方法來實作:

    Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){

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

                throws Throwable {

            return null;

        }

    });

完整代碼如下:

    //建立 動态代理類的 執行個體對象

    try {

        //1 獲得構造方法

        //  這個clazzProxy就是剛才我們上面:

        //       用Proxy.getProxyClass(_,_)得到的動态代理類Class對象

        Constructor constructor = clazzProxy.getConstructor(InvocationHandler.class);

        //2 調用這個構造方法

        //  一個是: 獲得的對象類型, 我們要用那個接口Collection來接收.

        //  一個是: 這裡有類型強制轉換

        //  一個是: 現在暫時将invoke方法傳回null, 即沒有實作其中功能.

        Collection<?> col = (Collection<?>) constructor.newInstance(new InvocationHandler(){

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

                    throws Throwable {

                return null;

            }

        });

        //3 使用這個 動态代理類對象col

        System.out.println(col);  //null

        System.out.println(col.toString()); //null

        System.out.println(col.size()); //異常 java.lang.NullPointerException

    } catch (Exception e){

        e.printStackTrace();

    }

注意:

    System.out.println(col);  //null

        得到null, 并不代表着col就為null, 有可能其toString為null.

    System.out.println(col.toString()); //null

        我們看到, 确實是toString為"null".

        如果是 col為null, 其會指空指針異常

    System.out.println(col.size()); //異常 java.lang.NullPointerException

        這是因為, 這是一個接口啊, 這個Collection沒有指向任何實際集合啊.

        是以, 不能得到集合的大小, 是以報異常.

-------------------------------------------------------

52.完成InvocationHandler對象的内部功能

總結下:

    我們上面要建立一個動态代理類, 需要三個資訊:

    一個是: 該代理類的加載器     ----我們要指定

    一個是: 該代理類實作的接口   ----我們要指定, 如Collection接口

    一個是: 用該代理類建立執行個體對象---我們要指定InvocationHandler對象

                                        ---在這裡, 我們可以做一些系統的功能.

上面, 将這三個資訊, 分成幾個部分來完成.

而實際上, 

    Proxy中有一個static方法newProxyInstance可以一步到位:

        Object newProxyInstance(ClassLoader loader, 

                                Class<?>[] interfaces, 

                                InvocationHandler h) 

    其參數就是我們上面所說的那三個:

    ClassLoader loader:    該代理類的加載器     ----我們要指定

    Class<?>[] interfaces: 該代理類實作的接口   ----我們要指定, 如Collection接口

    InvocationHandler h:   用該代理類建立執行個體對象---我們要指定InvocationHandler對象

                                        ---在這裡, 我們可以做一些系統的功能.

        因為實作的接口可能不止一個, 是以是一個數組.

        因為最後要還傳入一個InvocationHandler對象, 

            是以, 那麼得用數組, 而不能用可變參數.

現在, 我們用這個方式來寫一個:

public static void main(String[] args) {

    //注意, 這裡将類型改為final

    final ArrayList<String> list = new ArrayList<String>();

    //建立 動态代理類的執行個體----其已經和這個list對象挂鈎了

    Collection<String> proxy = 

            (Collection<String>) Proxy.newProxyInstance(Collection.class.getClassLoader(), 

                new Class[]{Collection.class}, 

                new InvocationHandler(){

                    //目錄标對象記錄到這裡來

                    private Object target = list; //要求這個list是final的.

                    //這個功能, 是用于計算目标類方法的運作時間

                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        long startTime = System.currentTimeMillis();

                        Object retVal = method.invoke(target, args);

                        long endTime = System.currentTimeMillis();

                        System.out.println("方法"+method.getName()+"運作時間為:"+(endTime-startTime));

                        return retVal;

                    }

                }

            );

    //調用動态代理類對象方法 來 調用list的方法

    proxy.add("hello");

    proxy.add("world");

    proxy.add("nihao");

    System.out.println(proxy.size());;

}

這裡幾點注意處:

一個是:

    InvocationHandler對象, 同樣也是用匿名内部類實作的, 如下:

        new InvocationHandler(){

            //目錄标對象記錄到這裡來

            private Object target = list; //要求這個list是final的.

            //這個功能, 是用于計算目标類方法的運作時間

            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                long startTime = System.currentTimeMillis();

                Object retVal = method.invoke(target, args);

                long endTime = System.currentTimeMillis();

                System.out.println("方法"+method.getName()+"運作時間為:"+(endTime-startTime));

                return retVal;

            }

        }

    其中, 我們得設一個域: private Object target = list; //要求這個list是final的.

        用于記錄 這個動态代理類對象 所要 代理的 那個目标對象 是哪一個.

    因為, 我們設立 代理的目标就是, 對目标類進行AOP程式設計的.

        代理類對象, 要和 目标類對象 一一對應.

    注意, 這裡, 我們是直接将list傳給這個target資料域的.

    因為匿名内部類不能有構造方法.

        而且, 剛好在匿名内部類内部是可以直接通路其外的方法的局部變量的.

    是以就這麼做了.

一個是:

    見下面講解 動态代理類 的原理部分吧.

-------------------------------------------------------

53.分析InvocationHandler對象的運作原理

JVM所自動生成的動态代理類的内部實質是:

class $Proxy0{

    //一個資料域

    private InvocationHandler handler;

    //構造方法

    public $Proxy0(InvocationHandler handler)

    {

        this.handler = handler;

    }

    //一般方法:

    int size(){

        return handler.invoke(this,   //動态代理類對象

                            this.getClass().getMethods("size"),  //size這個Method對象

                            null);  //參數

    }

    ...

}

注意:

一個是: 

    内部一個資料域: 

        private InvocationHandler handler;

    用于記錄, 這個代理類對象對應的 InvocationHandler 是哪一個.

    這個 資料域 , 我們得在構造方法中對它進行指派.

一個是:

    我們調用:

        proxy.add("haha");

            轉為三個要素:

            一個是 col---動态代理類的對象

            一個是 add ---所調用的方法

            一個是 "haha" ---參數.

    是以, 

        InvocationHandler的invoke方法的參數, 也對應這三個.

            handler.invoke(proxy,   //動态代理類對象

                           this.getClass().getMethods("add"),  //size這個Method對象

                           "haha");  //參數

        還把這個結果傳回.

    proxy.size()的機制也一樣.

一個是:

    如果 我們在invoke内部這麼調用:

        return method.invoke(proxy, args);

    結果呢, 會是一個死循環.

    要注意:

        動态代理類 和 目标類, 都實作了 共同的接口.

        是以, 如果 method.invoke(proxy, args)

            那麼, 就是代理類對象不斷調用自身了, 是以會死循環.

一個是:

    在invoke方法中, 

        我們可以加之前, 之後, 前後, 異常處理, 

        我們也可以對這個參數args進行修改.

    我們 在invoke中, 可以用到代理類對象, 

            為什麼 要傳入這個代理類對象呢?

            因為 我們在invoke中, 會進行一些操作, 

                    可能會用到動态代理類對象

            是以, 我們invoke的參數中, 保留了Object proxy, 以備用.

    而, 其内部, 必然會 用到 目标類對象.

        而目标類對象, 我們通常是設一個資料域來處理.

一個是:

    在我們最上面的例子中:

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

                throws Throwable {

            return null;

        }

    invoke直接傳回null.

        也正是這個原因, 導緻當時調用 col.size()會抛出異常.

        因為 size應傳回int型的, 

        而它, 卻傳回了null了.

        是以 抛異常.

    如果col有指向具體的集合的話, size會傳回0,1,等對應的包裝器對象, 

        是以不會傳回null的, 是以不會異常的的.

一個是:

    我們調用 動态代理類對象的 proxy.getClass(),

                我們會傳回$Proxy0啊, 

        而不是 目标的 Class對象: java.util.Arraylist.

    為什麼呢?

        因為 動态代理類 隻對 其所實作的接口的那些方法去調用invoke方法

    而對于Object的幾個方法, 

        隻有 hashCode, equals, toString才派發給handler去invoke目标類方法去處理, 

        其它的 方法, 如getClass, wait之類, 保留動态代理類自身的内容的.

            即, 不會去調用invoke實用目标的方法的.

    比如, 上面我們調用col.toString, 列印的null , 

        即, 對toString, 其會去目标那裡去實作.

    -----注意一點: Object 不是 接口.

        那為什麼會對 toString去調用invoke呢?----我已查證, Collection中沒有toString.

總結下, 動态代理類的機制:

像GUI中的注冊監聽器一樣, 

    當外界有 事件發生時, 兩件事:

        控制内部會得到一個事件對象, 

        觸發注冊到控制内部的事件監聽器.

    然後, 事件監聽器調用自己内部的方法來完成對事件的影應.

----不過, 這個說法勉強.

    因為GUI的事件監聽器, 其中不需要我們直接傳一個target進去的.

        而是, 而是通過它的響應方法的參數來傳進去的.

----而這個代理類的 InvoketionHandler的target對象, 需要我們手動傳進去. 麻煩.

動态代理類是這樣的:

    proxy調用接口方法時, 

        實際上是 注冊到自身的handler上的invoke來響應.

    在handler中, 其包含的内容有:

        一個是: 目标對象要用---否則怎麼調得動方法呢

        一個是: invoke方法要有.

總之:

    最終是要調用 InvocationHandler對象的invoke方法的.

    是以 invoke要知道幾個要素:

        一個是 target, 即, 要知道其調用的是哪一個目标類

        一個是 proxy, 即, 要知道其代理類對象是哪一個.

                    invoke内部可能會用到動态代理類對象--如取一些資訊之類

        一個是 method, 得知道自己要調接口的哪一個方法

        一個是 agrs, 得知道要往這個方法傳哪些參數.

    下面三個元素, 以invoke的參數形式傳進去, 

    而第一個元素, 通常是以 InvocationHandler的資料成員形式存在.

-------------------------------------------------------

54.總結分析動态代理類的設計原理與結構

55.編寫可生成代理和插入通告的通用方法

其工作原理圖為:

黑馬程式員-----Java代理機制的學習筆記

中間那個圈 要注意.

我們上面是将這個圈的代碼直接寫死了.---這叫寫死.

    這樣的話, 這個代理類隻能處理這種 代碼.

    不靈活.

是以, 我們可以把這個圈裡的東西封裝成一個方法.

    ----提高了代碼的複用性.

即, 下面, 我們就要 讓這種系統功能的代碼 以參數的形式傳給它.

    通常, 我們是傳一個對象給 InvocationHandler中的一個資料域.

    然後 invoke方法中, 去調用 這個資料域的方法就可以了.

通常, 我們會把這個東西設為一個接口/抽象類.

    名稱稱為Advice

在這個接口中, 設幾個方法:

    前面執行的操作, 

    後面執行的操作,

    異常進行中執行的操作.

其實有多個方法的話, 那麼改為抽象類更好一些.

如下:

    public abstract class Advice {  //視訊是将其做interface處理.

                        //這個會不會有影響? ---至少, 在我這裡, 代碼是可以運作的.

                        //這個是不是抽象類沒關系, 這隻是AOP所執行的方法而已. 

                        //而代理類那些則必須是接口!

        public void front(Object proxy, Method method, Object[] args){}

        public void behind(Object proxy, Method method, Object[] args){}

        public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception){}

    }

為什麼這裡的方法參數要這些?

因為, 在我們要添加的系統功能的這些方法, 可能會用到這些資訊.---當然, 可以不用, 需要時再寫.

因為, 我們這裡要做一個架構, 即要有廣泛的适應性, 是以建議把invoke方法的參數都寫上去.

即, 要在 InvocationHandler中設定兩個資料域.

    一個是 Object target; 域, 以放目标對象.

    一個是 Advice advice; 域, 以放一些處理的方法.

而invoke應當寫成這樣:

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

            throws Throwable {

        Object retValue = null;

        try{

            advice.front(proxy, method, args);

            retValue = method.invoke(target, args);

            advice.behind(proxy, method, args);

        } catch (Exception e){

            advice.exceptionProcess(proxy, method, args, e);

        }

        return retValue;

    }

即, 也要用一些異常處理的操作.---這個純粹是我寫的.

好了, 看下我的代碼:

先寫一個MyAdvice:

    ----即, 我們使用代理的最關鍵部分---AOP的核心.

public class MyAdvice extends Advice{

    private long startTime;

    private long endTime;

    @Override

    public void front(Object proxy, Method method, Object[] args) {

        startTime = System.currentTimeMillis();

        System.out.println("開始執行"+method.getName());

    }

    @Override

    public void behind(Object proxy, Method method, Object[] args) {

        endTime = System.currentTimeMillis();

        System.out.println("執行完成"+method.getName()+", 執行時間:"+(endTime-startTime)+"毫秒.");

    }

    @Override

    public void exceptionProcess(Object proxy, Method method, Object[] args, Throwable exception) {

        //還沒有想好要怎麼處理它

    }

}

    --------當然, 我們也可以用 匿名内部類的形式來寫這一個類.

    呵呵.

然後是寫一個 獲得動态代理對象的方法:

    ------這是我們這一單元的核心部分了.

    ------這個代碼是可以重用的.

    ------我把很對類型都改為Object類型了.---這樣, 可以目标對象的類型可以随意.

    我們可以看到:

        參數兩個:

            一個是 目标;----要在誰的方法這前做

            一個是 建議;----即要做什麼事

    public static Object getProxy(final Object obj, final Advice adv) {

        return Proxy.newProxyInstance(

                obj.getClass().getClassLoader(), 

                obj.getClass().getInterfaces(),  //擷取其所有接口.

                new InvocationHandler(){

                    //目錄标對象記錄到這裡來

                    private Object target = obj;

                    //記錄這個Advice對象

                    private Advice advice = adv;

                    //實作invoke方法

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

                            throws Throwable {

                        Object retVal = null;

                        try{

                            advice.beforeMethod(method);

                            retVal = method.invoke(target, args);

                            advice.afterMethod(method);

                        } catch (Throwable t){

                             advice.exceptionMethod(method, t);

                        }

                        return retVal;

                    }

                }

        );

    }

然後是, 進行一個調用:

    public static void main(String[] args) {

        //注意, 這裡将類型改為final

        final ArrayList<String> list = new ArrayList<String>();

        //建立 動态代理類的執行個體----其已經和這個list對象挂鈎了

        Collection<String> proxy = 

                (Collection<String>) getProxy(list, new MyAdvice());

        //調用動态代理類對象方法 來 調用list的方法

        proxy.add("hello");

        proxy.add("world");

        proxy.add("nihao");

        System.out.println(proxy.size());;

    }

以後寫Spring, 其實就是寫MyAdvice.

即, 我們傳入目标, 還有這個Advice對象 就行了.

這就是一個小小的架構.

即, 給你目标, 給你系統功能, 你得給我一個代理.

-------------------------------------------------------

56.實作類似spring的可配置的AOP架構

如果, 我們要做一個類似于Spring框的架的AOP, 

    其大概的實作思路是怎麼樣的?

Object obj = BeanFacktory.getBean("XXX");

        "XXX"---是指Bean的名字, 如java.util.Array

        這個"XXX"是由一個配置檔案來提供的.

這個BeanFacktory.getBean(__)内部怎麼寫?

兩種可能:

一種是 如果是 cn.itcast.ProxyFactoryBean這個特殊的類的話, 

    那麼 調用其方法來建立代理.

一種是: 不是這種特殊的類.

    則用 BeanFacktory.getBean(__)來建立Bean對象.

比如配置檔案如下:

#xxx=java.util.ArrayList

xxx=cn.itcast.ProxyFactoryBean

xxx.target=java.util.ArrayList

xxx.advic=cn.itcast.MyAdvice

如果是 ArrayList的話, 

    BeanFacktory.getBean("xxx")傳回ArrayList的javabean對象

如果是 ProxyFactoryBean的話, 

    那麼做調用 ProxyFactoryBean

    則 還會取下其target和advice是誰, 

    然後生成 一個其對應的動态代理類對象 傳回.

那麼:

obj就是建立出的javaBean對象.

        或者是 一個代理.

BeanFactory構造方法 接受一個配置檔案.

    即有一個資料域: Properties型的資料域.

對javabean而言, 一個重要的特性, 其必須有一個不帶參數的構造方法.

public class BeanFactory {

    //因為BeanFactory是根據配置檔案來産生javabean的,

    //是以要有一個域來保留這個配置檔案的資訊

    private Properties properties;

    //構造方法

    public BeanFactory(InputStream inputStream){

        properties = new Properties();

        try {

            properties.load(inputStream);

        } catch (IOException e) {

            e.printStackTrace();

        } 

    }

    //非靜态方法---因為每一個配置檔案對應一個對象

    public Object getBean(){

        String clazzName = properties.getProperty("className");

        Class<?> clazz = null;

        Object obj = null;

        try {

            clazz = Class.forName(clazzName);

            obj = clazz.newInstance();

            if (obj instanceof ProxyBeanFactory){

                Advice advice = (Advice)(Class.forName(properties.getProperty("advice")).newInstance());

                Object target = Class.forName(properties.getProperty("target")).newInstance();

                ProxyBeanFactory proxyFactory = (ProxyBeanFactory)obj;

                proxyFactory.setAdvice(advice);

                proxyFactory.setTarget(target);

                obj = proxyFactory.getProxy();

                return obj;

            }

            return obj;

        } catch (Exception e) {

            e.printStackTrace();

            throw new RuntimeException();

        } 

    }

    public static void main(String[] args) throws Exception{

        InputStream config = 

                BeanFactory.class.getResourceAsStream("config.properties");

        BeanFactory beanFactory = new BeanFactory(config);

        config.close();  //用完, 就要關掉.

        Object obj = beanFactory.getBean();

        System.out.println(obj.getClass());

        System.out.println(obj);

    }

}

public class ProxyBeanFactory {

    private Advice advice;

    private Object target;

    public Advice getAdvice() {

        return advice;

    }

    public void setAdvice(Advice advice) {

        this.advice = advice;

    }

    public Object getTarget() {

        return target;

    }

    public void setTarget(Object target) {

        this.target = target;

    }

    //與之前寫的那個getProxy不一樣了

    //這裡沒有參數, 而是把advice和target做為資料成員處理.

    public Object getProxy() {

        return Proxy.newProxyInstance(

                target.getClass().getClassLoader(), 

                target.getClass().getInterfaces(),

                new InvocationHandler(){

                    //目錄标對象記錄到這裡來

                    private Object target = ProxyBeanFactory.this.target;

                    //記錄這個Advice對象

                    private Advice advice = ProxyBeanFactory.this.advice;

                    //實作invoke方法

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

                            throws Throwable {

                        Object retVal = null;

                        try{

                            advice.beforeMethod(method);

                            retVal = method.invoke(target, args);

                            advice.afterMethod(method);

                        } catch (Throwable t){

                             advice.exceptionMethod(method, t);

                        }

                        return retVal;

                    }

                }

        );

    }

}

這個就是 Spring的BeanFactory的原理了.

這上面講了 Spring 的 兩個架構:

    一個是 getProxy 方法

    一個是 BeanFactory和ProxyBeanFactory兩個架構了.

道理其實就是這麼簡單.