posts - 13,  comments - 3,  trackbacks - 0
          最近在學(xué)習(xí)Spring。某大人跟我說,Spring的AOP其實就是Java反射中的動態(tài)代理。OK,那我就從動態(tài)代理開始看起。

          一、基本概念
          所謂動態(tài)代理,基本上是如下場景:假設(shè)我有個接口IHelloWorld

          public interface IHelloWorld{
          void sayHello();
          }


          我再有一個實現(xiàn)類HelloWorldImpl實現(xiàn)了IHelloWorld接口
          public class HelloWorldImpl implements IHelloWorld{
          public void sayHello(){
          System.out.println(
          "Hello, World");
          }
          }


          這樣,我就可以創(chuàng)建一個HelloWorldImpl對象,來實現(xiàn)IHelloWorld中定義的服務(wù)。

           問題是,現(xiàn)在,我打算為HelloWorldImpl增強(qiáng)功能,需要在調(diào)用sayHello方法前后各執(zhí)行一些操作。在有些情況下,你無法修改HelloWorldImpl的源代碼,那怎么辦呢?
          從道理上來說,我們可以攔截對HelloWorldImpl對象里sayHello()函數(shù)的調(diào)用。也就是說,每當(dāng)有代碼調(diào)用sayHello函數(shù)時,我們都把這種調(diào)用請求攔截下來之后,做自己想做的事情。

           那怎么攔截呢?

           首先,需要開發(fā)一個InvocationHandler。這個東東表示的是,你攔截下函數(shù)調(diào)用之后,究竟想干什么。InvocationHandler是一個接口,里面的聲明的函數(shù)只有一個:
          Object invoke(Object proxy, Method method, Object[] args) throws Throwable
          這個函數(shù)表示一次被攔截的函數(shù)調(diào)用。因此,proxy表示這個被攔截的調(diào)用,原本是對哪個對象調(diào)用的;method表示這個被攔截的調(diào)用,究竟是調(diào)用什么方法;args表示這個被攔截的調(diào)用里,參數(shù)分別是什么。

           我們下面寫一個攔截器,讓他在函數(shù)調(diào)用之前和之后分別輸出一句話。

          import java.lang.reflect.*;

          public class HelloHandler implements InvocationHandler{
          Object oriObj;

          public HelloProxy(Object obj){
          oriObj 
          = obj;
          }

          public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
          Object result 
          = null;

          //在函數(shù)調(diào)用前輸出一些信息
              System.out.println("################################");
          String methodName 
          = m.getName();
          System.out.println(
          "method name : " + methodName);
          doBefore();

          //利用反射,進(jìn)行真正的調(diào)用
              result = m.invoke(oriObj, args);

          //在函數(shù)調(diào)用后執(zhí)行
              doAfter();
          System.out.println(
          "################################");
          return result;
          }

          public void doBefore(){
          System.out.println(
          "Do Before");
          }
          public void doAfter(){
          System.out.println(
          "Do After");
          }
          }

           有了這個Handler之后,下面要做的,就是把這個Handler和一個IHelloWorld類型的對象裝配起來。重點的函數(shù)只有一個,那就是java.lang.reflect.Proxy類中的一個靜態(tài)工廠方法:
          public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
          這個方法返回一個對象,我們稱返回的對象為代理對象(proxy)。
          而后,我們就不把真正的原對象暴露給外接,而使用這個代理對象。這個代理對象接受對源對象的一切函數(shù)調(diào)用(也就是把所有調(diào)用都攔截了),然后根據(jù)我們寫的InvocationHandler,來對函數(shù)進(jìn)行處理。

           產(chǎn)生代理對象的過程,我把它理解成一個裝配的過程:由源對象、源對象實現(xiàn)的接口、InvocationHandler裝配產(chǎn)生一個代理對象。

           相應(yīng)的測試代碼如下:

          public class TestHello{
          public static void main(String args[])throws Exception{
          HelloWorldImpl h 
          = new HelloWorldImpl();

          Object proxy 
          = Proxy.newProxyInstance(
          h.getClass().getClassLoader(),
          new Class[]{IHelloWorld.class},
          new HelloProxy(h)
          );
          ((IHelloWorld)proxy).sayHello();
          }
          }

           利用ant編譯運行的結(jié)果:
          [java] ################################
          [java] method name : sayHello
          [java] Do Before
          [java] Hello, World
          [java] Do After
          [java] ################################


          二、更多理解
          我們看產(chǎn)生代理對象的newProxyInstance函數(shù)的聲明:
          public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException

           這個函數(shù)的第一個參數(shù)是ClassLoader,第三個參數(shù)是InvocationHandler,基本都沒什么問題。
          第二個參數(shù)是一個Class類型的數(shù)組,名字叫interfaces,表示的是產(chǎn)生的動態(tài)代理對象實現(xiàn)的接口。

           仔細(xì)想想,有兩個問題。第一,產(chǎn)生一個代理對象,需要源對象么?第二,我能不能產(chǎn)生一個動態(tài)代理對象,來實現(xiàn)源對象沒有實現(xiàn)的接口?

           第一個問題和第二個問題其實是一致的。我們完全可以脫離源對象,而直接產(chǎn)生一個代理對象,也可以利用動態(tài)代理,讓源對象實現(xiàn)更多的接口,為源對象增強(qiáng)功能。

           例如,假設(shè)我們希望讓源對象實現(xiàn)java.io.Closeable接口,則首先修改一下我們的Handler的invoke方法,讓他在獲取colse方法時,不要傳遞給源對象(因為源對象沒有實現(xiàn)該方法):

          public Object invoke(Object proxy, Method m, Object[] args) throws Throwable{
          Object result 
          = null;

          //在函數(shù)調(diào)用前輸出一些信息
            System.out.println("################################");
          String methodName 
          = m.getName();
          System.out.println(
          "method name : " + methodName);
          doBefore();

          //判斷是否是Closeabled的方法
            if (m.getDeclaringClass().isAssignableFrom(java.io.Closeable.class)){
          System.out.println(
          "I got the close() method!");
          }
          else{
          //傳遞給源對象
          //利用反射,進(jìn)行真正的調(diào)用
              result = m.invoke(oriObj, args);
          }

          //在函數(shù)調(diào)用后執(zhí)行
            doAfter();
          System.out.println(
          "################################");
          return result;
          }

           然后,我們在裝配的過程中,改變一下參數(shù),并強(qiáng)轉(zhuǎn)之后調(diào)用一下close方法:

          Object proxy = Proxy.newProxyInstance(
          h.getClass().getClassLoader(),
          new Class[]{IHelloWorld.class,java.io.Closeable.class},
          new HelloProxy(h)
          );
          ((Closeable)proxy).close();

           ant運行結(jié)果:
          [java] ################################
          [java] method name : close
          [java] Do Before
          [java] I got the close() method!
          [java] Do After
          [java] ################################

          三、更多的代理~
          我們現(xiàn)在能夠讓sayHello()函數(shù)執(zhí)行之前和之后,輸出一些內(nèi)容了。那如果我還想在裝配一個Handler呢?
          最簡單的方法:

          Object proxy = Proxy.newProxyInstance(
          h.getClass().getClassLoader(),
          new Class[]{IHelloWorld.class, java.io.Closeable.class},
          new HelloProxy(h)
          );
          Object proxy2 
          = Proxy.newProxyInstance(
          h.getClass().getClassLoader(),
          new Class[]{IHelloWorld.class, java.io.Closeable.class},
          new HelloProxy(proxy)
          );
          ((IHelloWorld)proxy2).sayHello();

          ant運行結(jié)果:
          [java] ################################
          [java] method name : sayHello
          [java] Do Before
          [java] ################################
          [java] method name : sayHello
          [java] Do Before
          [java] Hello, World
          [java] Do After
          [java] ################################
          [java] Do After
          [java] ################################

          不用我解釋了吧!

          posted @ 2009-03-02 22:28 Antony Lee 閱讀(519) | 評論 (0)編輯 收藏

          和人討論設(shè)計模式的時候,看到這樣一句話:

                   
          大阿亮<yighter@qq.com> 22:24:40
          java擴(kuò)展功能就是繼承和組合。肯定結(jié)構(gòu)都很相似。模式思想都是從解決問題背景和目的來區(qū)分的。

          恍然大悟。原來,很多情況下,所謂設(shè)計模式,是對同一種技術(shù)、實現(xiàn)的不同角度的理解。

          所以,設(shè)計無所謂好壞,只要能解決問題的,就是好設(shè)計。至于所謂“強(qiáng)耦合”,“Bad Smell”,本質(zhì)上是因為采用這些設(shè)計無法解決問題(就是無法快速應(yīng)對需求變化)。

          “不管黑貓白貓,只要能抓住耗子,就是好貓”,這句話蘊(yùn)含著深刻的設(shè)計思想。

           

          posted @ 2009-02-28 23:31 Antony Lee 閱讀(136) | 評論 (0)編輯 收藏

          在啃《The Java Programming Language 4th Edition》時看到的一個小知識點。先描述一下問題。

          一個類中,靜態(tài)初始代碼塊中的代碼會在類加載時自動運行。考慮下面這種情況:

          ClassA定義了靜態(tài)初始代碼塊,其中調(diào)用了ClassB的一個方法m(靜態(tài)非靜態(tài)均可)。而在ClassB的m方法中,又使用了ClassA類的信息。則,當(dāng)虛擬機(jī)在沒有ClassB類的情況下,加載ClassA類時,會遇到這樣一條線索:

          加載ClassA --> 調(diào)用ClassA的靜態(tài)初始化代碼塊 --> 調(diào)用ClassB的m方法 --> 加載ClassB --> 使用ClassA的信息

          注意這條線索的一頭一尾,我們要在對ClassA還沒完成加載時,使用ClassA的信息!

          示例代碼:

           1public class TestStaticInit{
           2 public static void main(String args[]){
           3  ClassA a= new ClassA();
           4 }

           5}

           6
           7class ClassA{
           8 static int a1;
           9 static int a2;
          10 static{
          11  a1 = 10;
          12  ClassB.print();
          13  a2 = 30;
          14 }

          15}

          16
          17class ClassB{
          18 public static void print(){
          19  System.out.println(ClassA.a1);
          20  System.out.println(ClassA.a2);
          21 }

          22}

          23
          24

           

          首先,編譯器無法解決這個問題,因為在編譯ClassA類時,無法找到ClassB的代碼,也就無法檢查是否存在靜態(tài)初始化代碼塊循環(huán)問題。事實上,上述程序在java中是能夠編譯通過的。

          其次,運行時的結(jié)果。當(dāng)程序運行到第3行時,JVM加載ClassA類,此時,會執(zhí)行ClassA類中的靜態(tài)初始化代碼塊。當(dāng)程序執(zhí)行到第12行時,調(diào)用ClassB的print方法,此時,程序跳轉(zhuǎn)到18行。

          關(guān)鍵在這兒:此時的print方法需要調(diào)用ClassA的信息,并打印其靜態(tài)屬性。而ClassA的信息正在加載過程中。此時,JVM采用的策略是:在print方法中使用ClassA不完整的信息。在print方法中ClassA的信息,是在第12行對ClassB.print方法之前的信息。此時ClassA.a1已經(jīng)被賦值為10,而ClassA.a2還未被賦值,它的值為默認(rèn)值。因此,最后打印出的是10、0。

          posted @ 2009-01-05 23:48 Antony Lee 閱讀(424) | 評論 (0)編輯 收藏
          終于要在Blogjava安家了。寫下第一篇日志,紀(jì)念一下
          posted @ 2008-12-28 00:27 Antony Lee| 編輯 收藏
          僅列出標(biāo)題
          共2頁: 上一頁 1 2 

          <2025年7月>
          293012345
          6789101112
          13141516171819
          20212223242526
          272829303112
          3456789

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 林州市| 柯坪县| 环江| 永安市| 壤塘县| 怀宁县| 台东县| 成武县| 北辰区| 阿鲁科尔沁旗| 龙井市| 右玉县| 通许县| 台山市| 长沙县| 余干县| 广东省| 云南省| 龙门县| 胶州市| 祁门县| 博野县| 清原| 昭平县| 梨树县| 安塞县| 驻马店市| 枣庄市| 麦盖提县| 兴国县| 三江| 安达市| 靖西县| 体育| 晋江市| 潼南县| 长宁县| 葵青区| 辽阳县| 兴安盟| 华安县|