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

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

          public interface IHelloWorld{
          void sayHello();
          }


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


          這樣,我就可以創建一個HelloWorldImpl對象,來實現IHelloWorld中定義的服務。

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

           那怎么攔截呢?

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

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

          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;

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

          //利用反射,進行真正的調用
              result = m.invoke(oriObj, args);

          //在函數調用后執行
              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類型的對象裝配起來。重點的函數只有一個,那就是java.lang.reflect.Proxy類中的一個靜態工廠方法:
          public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
          這個方法返回一個對象,我們稱返回的對象為代理對象(proxy)。
          而后,我們就不把真正的原對象暴露給外接,而使用這個代理對象。這個代理對象接受對源對象的一切函數調用(也就是把所有調用都攔截了),然后根據我們寫的InvocationHandler,來對函數進行處理。

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

           相應的測試代碼如下:

          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編譯運行的結果:
          [java] ################################
          [java] method name : sayHello
          [java] Do Before
          [java] Hello, World
          [java] Do After
          [java] ################################


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

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

           仔細想想,有兩個問題。第一,產生一個代理對象,需要源對象么?第二,我能不能產生一個動態代理對象,來實現源對象沒有實現的接口?

           第一個問題和第二個問題其實是一致的。我們完全可以脫離源對象,而直接產生一個代理對象,也可以利用動態代理,讓源對象實現更多的接口,為源對象增強功能。

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

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

          //在函數調用前輸出一些信息
            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{
          //傳遞給源對象
          //利用反射,進行真正的調用
              result = m.invoke(oriObj, args);
          }

          //在函數調用后執行
            doAfter();
          System.out.println(
          "################################");
          return result;
          }

           然后,我們在裝配的過程中,改變一下參數,并強轉之后調用一下close方法:

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

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

          三、更多的代理~
          我們現在能夠讓sayHello()函數執行之前和之后,輸出一些內容了。那如果我還想在裝配一個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運行結果:
          [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 閱讀(514) | 評論 (0)編輯 收藏

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

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

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

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

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

           

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

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

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

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

          加載ClassA --> 調用ClassA的靜態初始化代碼塊 --> 調用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的代碼,也就無法檢查是否存在靜態初始化代碼塊循環問題。事實上,上述程序在java中是能夠編譯通過的。

          其次,運行時的結果。當程序運行到第3行時,JVM加載ClassA類,此時,會執行ClassA類中的靜態初始化代碼塊。當程序執行到第12行時,調用ClassB的print方法,此時,程序跳轉到18行。

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

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

          <2025年5月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          常用鏈接

          留言簿(1)

          隨筆分類

          隨筆檔案

          文章分類

          搜索

          •  

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 和龙市| 蓝田县| 清远市| 潮安县| 同心县| 安国市| 威远县| 南皮县| 稷山县| 潞城市| 海晏县| 泾阳县| 柘城县| 平度市| 张家口市| 嘉峪关市| 禹城市| 化德县| 娄底市| 正定县| 罗田县| 水城县| 乐清市| 昌邑市| 吉隆县| 迁西县| 长子县| 海阳市| 岑溪市| 青河县| 嘉义县| 正定县| 津南区| 平陆县| 关岭| 化德县| 呼图壁县| 枞阳县| 张北县| 凤翔县| 济阳县|