15_傳智播客Spring2.5視頻教程_使用JDK中的Proxy技術實現AOP功能 3

動態代理是很多框架和技術的基礎, spring 的AOP實現就是基於動態代理實現的。瞭解動態代理的機制對於理解AOP的底層實現是很有幫助的。

       查看doc文檔就可以知道,在java.lang.reflect包中有一個叫Proxy的類。下面是doc文檔對Proxy類的說明:

       "A dynamic proxy class (simply referred to as a proxy class below) is a class that implements a list of interfaces specified at runtime when the class is created, with behavior as described below. A proxy interface is such an interface that is implemented by a proxy class. A proxy instance is an instance of a proxy class. Each proxy instance has an associated invocation handler object, which implements the interface InvocationHandler."

        Proxy類的設計用到代理模式的設計思想,Proxy類對象實現了代理目標的所有接口,並代替目標對象進行實際的操作。但這種替代不是一種簡單的替代,這樣沒有任何意義,代理的目的是在目標對象方法的基礎上作增強,這種增強的本質通常就是對目標對象的方法進行攔截。所以,Proxy應該包括一個方法攔截器,來指示當攔截到方法調用時作何種處理。InvocationHandler就是攔截器的接口

      InvocationHandler接口也是在java.lang.reflec

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

     這個接口有三個參數,其中第二和第三個參數都比較好理解,一個是被攔截的方法,一個是該方法的參數列表。關鍵是第一個參數。按照doc文檔的解析,

      proxy - the proxy instance that the method was invoked on

      也就是說,proxy應該是一個代理實例,但爲什麼要傳入這個參數呢?

      帶着這個問題,自己編了個小程序作了一點試驗。

///////////////////////////////////////

      public interface IAnimal {
           void info();
      }

////////////////////////////////////

    public class Dog implements IAnimal

    {

          public void info() {
             System.out.println("I am a dog!");
          }
    }

///////////////////////////////////////
import java.lang.reflect.*;

public class ProxyTest {
public static void main(String[] args) throws InterruptedException {
  final IAnimal animal = new Dog();
  Object proxyObj =Proxy.newProxyInstance(
    animal.getClass().getClassLoader(),
    animal.getClass().getInterfaces(),
    new InvocationHandler()
    {
     public Object invoke(Object proxy, Method method, Object[] args)
     {
      try {
       System.out.println("被攔截的方法:" + method.getName());
       return method.invoke(animal, args);
      }
      catch (IllegalArgumentException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
       return null;
      } catch (IllegalAccessException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
       return null;
      } catch (InvocationTargetException e) {
       // TODO Auto-generated catch block
       e.printStackTrace();
       return null;
      }
     }
    });
  if(proxyObj instanceof IAnimal)
  {
   System.out.println("the proxyObj is an animal!");
  }
  else
  {
   System.out.println("the proxyObj isn't an animal!");
  }
 
  if(proxyObj instanceof Dog)
  {
   System.out.println("the proxyObj is a dog!");
  }
  else
  {
   System.out.println("the proxyObj isn't a dog!");
  }
 
  IAnimal animalProxy = (IAnimal)proxyObj;
  animalProxy.info();
  animalProxy.hashCode();
  System.out.println(animalProxy.getClass().getName().toString());
}
}

程序執行的結果如下:

the proxyObj is an animal!
the proxyObj isn't a dog!
被攔截的方法:info
I am a dog!
被攔截的方法:hashCode
$Proxy0

從結果可以看出以下幾點:

1. proxyObj 是一個實現了目標對象接口的對象,而不同於目標對象。也就是說,這種代理機制是面向接口,而不是面向類的。

2. info方法(在接口中)被成功攔截了,hashCode方法也成功被攔截了,但意外的是,getClass方法(繼承自Object 類的方法)並沒有被攔截!!

3. 應用調試還可以看出Invocation接口中invoke方法的傳入的proxy參數確實就是代理對象實例proxyObj

爲何getClass()沒有被攔截?proxy參數又有何用呢?

先不管,做一個試驗看看。既然這個proxy參數就是代理實例對象,它理所當然和proxyObj是一樣的,可以調用info等方法。於是我們可以在invoke方法中加上如下一條語句:

((IAnimal)proxy).info();

結果是:

the proxyObj is an animal!
the proxyObj isn't a dog!
被攔截的方法:info
被攔截的方法:info

.......

被攔截的方法:info
被攔截的方法:info

然後就是棧溢出

結果是很明顯的,在invoke方法中調用proxy中的方法會再一次引發invoke方法,這就陷入了死循環,最終結果當然是棧溢出的。

可以在invoke方法中調用proxy.getClass(), 程序可以正常運行。但如果調用hashCode()方法同樣會導致棧溢出。

       通過上面的試驗,可以得出一些初步結論,invoke 接口中的proxy參數不能用於調用所實現接口的方法。奇怪的是hashCode()和getClass()方法都是從Object中繼承下來的方法,爲什麼一個可以另一個不可以呢?帶首疑問到doc文檔看一下Object中這兩個方法,發現getClass()是定義爲final的,而hashCode()不是。難道是這個原因,於是找到一個非final方法,如equals試了一下,真的又會導致棧溢出;找另一個final方法如wait(),試了一下,invoke又不攔截了。final 難道就是關鍵之處?

      還有一個問題就是proxy有什麼用?既然proxy可以調用getClass()方法,我們就可以得到proxy的Class類象,從而可以獲得關於proxy代理實例的所有類信息,如方法列表,Annotation等,這就爲我們提供的一個分析proxy的有力工具,如通過分析Annotation分析方法的聲明式事務需求。我想傳入proxy參數應該是這樣一個用意吧。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章