歡迎您光臨本站 註冊首頁

Dynamic Proxy 在 Java RMI 中的應用

←手機掃碼閱讀     火星人 @ 2014-03-10 , reply:0

相對於其他的分散式對象模型 (CORBA,COM) ,RMI 顯得很輕,很簡單.但是有時候程序員想在RMI中加入各種服務(service)而不改變其interface,卻不是那麼簡單."Interceptor"作為一種重要的"Design Pattern"在現代軟體技術中非常流行,它通常用來實現service,因此是實現FrameWork的關鍵技術之一.

下面是一個非常簡單的RMI例子.我要在這個例子上加入Interceptor,用以展現神奇的Dynamic Proxy技術.

//: SmallWorld Interface    public interface SmallWorld extends java.rmi.Remote      {      public static final String NAME = "SmallWorld";      public String hello(int i) throws java.rmi.RemoteException;      }      SmallWorld 是一個簡單的RMI interface,其中只定義了一個方法hello().      //: SmallWorldImpl      import java.rmi.server.*;      import java.rmi.*;      public class SmallWorldImpl extends UnicastRemoteObject 
   implements SmallWorld      {      public SmallWorldImpl() throws RemoteException {      super();      }      public String hello(int i) throws java.rmi.RemoteException      {      System.out.println("In SmallWorldImpl i = "   i);      return ("Hello number="   i);      }      }

SmallWorldImpl是SmallWorld interface 的實現.這是一個再標準不過的RMI實現,你可以在任何RMI教科書中找到類似的例子.

Client的實現如下:

  //: Client    import java.rmi.*;      public class Test      {      public static void main(String[] args)      {      try {      SmallWorld he =(SmallWorld)Naming.lookup(SmallWorld.NAME);      System.out.println(he.hello(4));      System.out.println(he.hello(8));      } catch (Exception e) {      e.printStackTrace();      }      }      }   

Client首先用RMI Naming Service找到SmallWorld的Stub對象,然後調用它的遠程方法"hello()".

是一個簡單的Server,它實現一個SmallWorldImpl的實例,然後把它bind到RMI Naming Service上.

//: SimpleServer    import java.rmi.*;      import java.rmi.server.*;      import java.rmi.registry.*;      public class SimpleServer      {      public static void main(String[] args)      {      try {      // Create remote object      SmallWorld server = new SmallWorldImpl();      Registry reg = LocateRegistry.createRegistry(1099);      reg.bind(SmallWorld.NAME, server);      } catch (Exception e) {      e.printStackTrace();      }      }      }

大多數的RMI應用程序都是基於上面的模型實現的.可是這一模型很難加入security, log 等服務.一個正式的RMI應用系統中的遠程對象和遠程數量非常巨大,時常處於變動之中,給每個方法(Method)加上個數不定的服務(service)是不現實的.筆者曾參預的一個項目曾有很不錯的設想:系統欲實現session管理,因此引入了session key.問題是每次遠程調用都要傳遞session key,每一個method都要檢查session key,相信沒有程序員願意這麼做.結果當然可想而知啦.

實際上,每一個不錯的FrameWork都會採用類似下面的實現方案:

這裡Interceptors用來實現各種可插入的service,例如authentication, access controller, logging, performance metrics, transaction 等等.這種方案的關鍵在於各種service是可以配置的,它們和遠程方法相對獨立.因此寫遠程對象的程序員不必為代碼中時不時要插入各種service的調用而煩惱,這些service已經在進入遠程對象之前被執行了.

對於RMI,我們要求程序員的代碼不改變或者極少改變,而service provider能加入各種interceptor.這可是一個很美妙的夢,Rickard Oberg經過不懈的探索終於實現了這一夢想.如今該技術已經成為Jboss的兩大核心技術之一(另一核心技術是JMX) .要理解該技術你必須稍微深入了解一下RMI的實現,Java Dynamic Proxy,ClassLoader,以及Object Serialization.這裡我只是講這些技術

在RMI Stub中的應用,如果讀者覺得困惑或者想吃透這些技術,請參閱本文所列參考資料.Rickard Oberg幾乎每行代碼都值得仔細推敲,筆者剛開始幾乎什麼都看不明白,遇到不懂就查閱相關資料,這才弄明白了其中的道理.:-)

深入Java RMI : RemoteObject, RemoteStub, RemoteRef, exportObject

其實要想深入了解RMI是很困難的.只要看一看RMI的幾個package包含多少class, interface, exception,你就明白了.相比之下,EJB包含的東西要少的多,interface之間的關係也非常簡單明了.我現在感興趣的是:程序員寫的遠程對象(SmallWorldImpl,不同於RemoteObject class,下面我用"遠程對象"表示這一級別的class,而用"RemoteObject"表示java RMI中的class) 實現時包含哪幾部份?RMI是怎樣創建它們的?

多數遠程對象實現時都繼承了UnicastRemoteObject,通過javac編譯生成class文件后,再用rmic編譯該class文件生成相應的Stub和Skeleton class.Stub class是該遠程對象的遠程代理,用於發出遠程調用;Skeleton和遠程對象處於同一地址,Skeleton用於接收,轉發"調用"到該對象.

但是Skeleton並不是必需的.如果你在用rmic時加上"-v1.2" 的選項,生成的是Java2標準的Stub class ,這時沒有Skeleton class.

RemoteRef是RemoteObject的遠程handle,它的作用體現在RemoteStub中.Client端的遠程調用都是通過RemoteRef實行的.你可以用下面的命令生成SmallWorldImpl_Stub的源代碼:

rmic -keep -v1.2 SmallWorldImpl

其中hello()方法是這樣實現的:

 // implementation of hello(int)    public java.lang.String hello(int $param_int_1) throws 
java.rmi.RemoteException      {      try {      Object $result = ref.invoke(this, $method_hello_0,      new java.lang.Object[] {new java.lang.Integer($param_int_1)},      -7781462318672951424L);      return ((java.lang.String) $result);      } catch (java.lang.RuntimeException e) {      throw e;      } catch (java.rmi.RemoteException e) {      throw e;      } catch (java.lang.Exception e) {      throw new java.rmi.UnexpectedException
("undeclared checked exception", e);      }      }

而RemoteRef的prototype是:

public Object invoke(Remote obj, Method method, Object[] params, long opnum) throws Exception

其中opnum是表示method的hash值,用來作authentication,這裡我們不關心.我們將會看到正是該方法的反射性(reflection),使實現client端的interceptor成為可能.

下面我們來看一下Stub在server端的生成過程,以及client發現它的過程.

SmallWorldImpl的構造方法調用了super()方法,該方法實際調用到UnicastRemoteObject的構造方法,而UnicastRemoteObject的構造方法將調用exportObject()方法.exportObject試圖用SmallWorldImpl的ClassLoader載入SmallWorldImpl_Stub class並生成它的實例,也就是:

  // assume obj = SmallWorldImpl    String name = obj.getClass().getName();      Class c = Class.forName(name   「_Stub」, false, 
obj.getClass().getClassLoader());      RemoteStub stub = (RemoteStub)c.newInstance();

然後把它發布到指定的socket上,這時候該遠程對象就可以使用了.為了client端查找方便,server應該用Naming Service把這個Stub實例公布出來,這就是Registry.bind()起的作用.

java.rmi.registry.Registry

public void bind(String name, Remote object);

該方法把SmallWorldImpl_Stub實例序列化(Serialization) ,再把序列化的object發送到Naming Server上去.

Server也可以直接調用UnicastRemoteObject的exportObject()方法公布遠程對象.

Client端調用Registry.lookup()方法找到SmallWorldImpl_Stub的實例,然後就可以調用其中的遠程方法了.

因此,我們必須實現自己的Stub,讓它實現遠程對象的所有遠程方法,在調用RemoteRef的invoke()方法之前先通過client端的一系列interceptors;在server端進入實際的被調用方法之前也要通過一系列interceptors,所有的interceptor都說"可以"才進入被調用方法.

JDK1.3里出現的Dynamic Proxy是的這一設想能夠簡單地實現.

Dynamic Proxy 工作原理

Dynamic Proxy是由兩個class實現的:java.lang.reflect.Proxy 和 java.lang.reflect.InvocationHandler,後者是一個介面.

所謂Dynamic Proxy是這樣一種class:它是在運行時生成的class,在生成它時你必須提供一組interface給它,然後該class就宣稱它實現了這些interface.你當然可以把該class的實例當作這些interface中的任何一個來用.當然啦,這個Dynamic Proxy其實就是一個Proxy,它不會替你作實質性的工作,在生成它的實例時你必須提供一個handler,由它接管實際的工作.:-)

java.lang.reflect.Proxy中的重要方法如下:

public static Class getProxyClass(ClassLoader loader, Class[] interfaces);

該方法返回一個Dynamic Proxy Class給你,但你必須提供一個ClassLoader和一組interface定義給它.記住:每一個Class在JVM中都有一個對應的ClassLoader,這個Proxy就說這個loader是它的ClassLoader,它還說它實現了這些interface.

protected Proxy(InvocationHandler handler);

返回給你的Dynamic Proxy有這樣一個構造方法,你只能用它生成實例.你對那些interface中所有方法的調用(基於該實例) 都會傳到這個handler中.

更簡單的方法是一步生成Dynamic Proxy的實例:

public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h) throws IllegalArgumentException;

這個方法包含了上面兩個方法的所有參數,意義相同.

java.lang.reflect.InvocationHandler中只有一個方法:

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

對於Dynamic Proxy實例中方法的調用都將調到這個方法.其中proxy正是那個Dynamic Proxy實例,而method,args是被調用方法和參數.

比較java.lang.reflect.Method中的這個方法:

public Object invoke(Object obj, Object[] args);

它說的是你現在調用obj實例中的Method.getName()方法,傳遞的參數是args.

實際上,所有的InvocationHandler都會調用Method中的這一方法,它是到達真正方法的唯一途徑.

來看一個實例:

 public class Interceptor implements InvocationHander    {      private Object obj;      public InvocationHandler(Object o)      {      obj = o; //保存實例,以備後用      }      public Object invoke(Object proxy, Method method, Object[] args)      {      system.out.println(「Enter interceptor」);      method.invoke(obj, args); //調用真正的方法      }      }      //假設obj是一個SmallWorldImpl的實例      SmallWorld sw =Proxy.newInstance(obj.getClass().getClassLoader(),

 obj.getClass().getDeclaredClasses(), new Interceptor(obj));      sw.hello(4);

對於hello()的調用首先通過Interceptor的invoke()方法.

java.lang.reflect package是如此重要,以至於任何稍具規模的應用都離不開它.它給java帶來巨大的靈活性,但是誰測試過它對系統性能的影響呢?

好了,我們該看看Rickard Oberg怎麼實現他的smart stub了. DynamicClassLoader我們已經知道Stub class是通過遠程對象(SmallWorldImpl)的ClassLoader載入到JVM中的,而RMI是在遠程對象名字之後加 "_Stub" 字元串形成Stub的class名字的.這裡DynamicClassLoader用來載入遠程對象的class和Stub的class:

 //: DynamicClassLoader    import java.net.URLClassLoader;      import java.net.URL;      public class DynamicClassLoader extends URLClassLoader      { // Static      static ThreadLocal currentClass = new ThreadLocal();      public static Class getCurrentClass()      {      return (Class)currentClass.get();      }      // Constructors --------------------------------------------------      public DynamicClassLoader(URL[] urls)      {      super(urls);      }      // Public implementation -----------------------------------------      protected Class findClass(String name)
    throws ClassNotFoundException      {      if (name.endsWith("_Stub")) {      name = name.substring(0,name.length()-5);      // Get impl      Class cl = loadClass(name);      // Assume that it only implements one remote interface      currentClass.set(cl.getInterfaces()[0]);      return DynamicRemoteStub.class;      } else {      return super.findClass(name);      }      }      }   

這個class有兩點值得注意:

DynamicClassLoader繼承了URLClassLoader,大多數用戶定製(Customize) 的ClassLoader都繼承這個ClassLoader.DynamicClassLoader只需重載(Overload) findClass()這一個方法.如果欲載入class名字以"_Stub" 結束,返回DynamicRemoteStub的class;否則讓URLClassLoader載入這個class.

如果欲載入class 是Stub class,findClass()要找到對應的遠程對象實現的所有remote interface,並且記住它們,以後的DynamicRemoteStub要代理這些interface.對於我們的例子,Stub class 是SmallWorldImpl_Stub,遠程對象是SmallWorldImpl,Remote interface只有一個:SmallWorld.為了簡化問題,代碼假設遠程對象只實現一個interface它就是Remote interface.實際實現時要做一些檢查,並有些技巧.

記住這些Remote interface是一個問題.這時DynamicRemoteStub的實例還沒有生成,我們沒辦法傳到它裡面.這裡使用了ThreadLocal class.ThreadLocal是一個和Thread相關聯的變數,它屬於一個特定的Thread.DynamicRemoteStub的實例初始化方法仍用這個Thread,它能讀出這些interface.

DynamicRemoteStub和DynamicStubHandler

Rickard的DynamicRemoteStub如下:

  import java.lang.reflect.Proxy;    import java.io.*;      import java.rmi.server.*;      public class DynamicRemoteStub extends RemoteStub      {      // Constructors --------------------------------------------------      Class cl;      public DynamicRemoteStub(RemoteRef ref)      {      super(ref);      cl = DynamicClassLoader.getCurrentClass();      }      // Serializable implementation -----------------------------------      Object readResolve() throws ObjectStreamException      {      if (cl == null) return this;      DynamicStubHandler stubHandler = new DynamicStubHandler();      Object proxy = Proxy.newProxyInstance(cl.getClassLoader(),      new Class[] { cl },      stubHandler);      stubHandler.setProxy(this);      cl = null;      return proxy;      }      }

這裡的關鍵代碼在於readResolve()方法.java.io.Serializable API 文檔說:

Classes that need to designate a replacement when an instance of it is read from the stream should implement this special method with the exact signature.

ANY-ACCESS-MODIFIER Object readResolve() throws ObjectStreamException;

就是說一個class如果實現了java.io.Serializable interface 並且實現了這個readResolve方法,那麼這個對象在序列化(Serialization) 之後,被讀出還原時,系統要執行這個方法,執行的結果是序列化的對象變成了另一個對象!

對應的方法是writeReplace(),它是在序列化寫入流(stream) 時替換成新的對象.

,這裡DynamicRemoteStub被RMI序列化並傳到client端時變成了一個新的對象,它是一個Dynamic Proxy.這個Proxy實現了RemoteObject的所有Remote方法,而它的handler是一個DynamicStubHandler實例.

在client端的所有遠程調用都會由這個Proxy傳遞到DynamicStubHandler中去.

DynamicStubHandler主要部分是那個invoke()方法:

 public class DynamicStubHandler    implements InvocationHandler, java.io.Serializable      {      RemoteStub stub;      // Public --------------------------------------------------------      public void setProxy(RemoteStub stub)      {      this.stub = stub;      }      // InvocationHandler implementation ------------------------------      public Object invoke(Object proxy,      Method method,      Object[] args)      throws Throwable      {      System.out.println("DynamicStubHandler!");      return stub.getRef().invoke(stub, method, args, getHash(method));      }      }   

我們已經知道client端的遠程調用最終要繞到RemoteRef里,而RemoteRef是DynamicRemoteStub的成員,DynamicRemoteStub在生成這個DynamicStubHandler實例后要調用它的setProxy()方法把它自己傳入,以便後者能找到這個RemoteRef.

這個invoke()方法主要是調用RemoteRef的invoke()方法,計算Hash值的方法很繁瑣,但它和本文沒太大關係.

我們終於可以在client端插入各種interceptor了.這裡只列印了一句話驗證這一點.

那麼server端呢? 如果我們不作修改,它將直接調到SmallWorldImpl的方法里.我們還要在server端加入interceptor.

新Server的實現

新Server的簡單實現如下:

import java.lang.reflect.*;    import java.      io.*;      import java.net.*;      import java.rmi.*;      import java.rmi.server.*;      import java.rmi.registry.*;      public class SimpleServer      {      public static void main(String[] args)      {      try {      // Create remote object      URLClassLoader cl = new DynamicClassLoader(new URL[] { new      File("demo").toURL() });      Remote server = (Remote)cl.loadClass("SmallWorldImpl").newInstance();      // it should be DynamicRemoteStub      System.out.println("StubObject is: "        RemoteObject.toStub((RemoteObject)server).getClass());      // add log proxy to the remote object      server = (Remote)Proxy.newProxyInstance
(server.getClass().getClassLoader(),      new Class[] { SmallWorld.class },      new LogProxy(server));      // add performance proxy to the remote object      server = (Remote)Proxy.newProxyInstance
(server.getClass().getClassLoader(),      new Class[] { SmallWorld.class },      new PerformanceProxy(server));      UnicastRemoteObject.exportObject(server);      Registry reg = LocateRegistry.createRegistry(1099);      reg.bind(SmallWorld.NAME, server);      } catch (Exception e) {      e.printStackTrace();      }      }      }   

Server首先創建一個DynamicClassLoader的實例,然後用它載入SmallWorldImpl class 並創建它的實例,雖然這時一個SmallWorldImpl的Stub對象會被RMI創立,我們卻不會去用它.

然後Server創建了一個針對SmallWorldImpl的Dynamic Proxy,其handler是LogProxy實例,用以模仿Log interceptor.再其後創建針對Dynamic Proxy的Dynamic Proxy,其handler是PerformanceProxy實例.

LogProxy的代碼如下:

 import java.lang.reflect.*;    public class LogProxy implements InvocationHandler      {      Object obj;      public LogProxy(Object o)      {      obj = o;      }      // InvocationHandler implementation ------------------------------      public Object invoke(Object proxy,      Method method,      Object[] args)      throws Throwable      {      System.out.println("Invoke LogProxy");      return method.invoke(obj, args);      }      }   

注意它是怎麼向下一個interceptor或RemoteObject傳遞調用的.

這樣,從client端來調用要經過如下途徑到達RemoteObje

ct:

PerformanceProxy --> LogProxy --> SmallWorldImpl

Server 把這個Dynamic Proxy 公布(用UnicastRemoteObject.exportObject) ,然後用Registry.bind() bind到Naming Server上,client 就可以找到它並用它了.而client端的代碼根本不用改變.

注意Server的代碼完全可以做到與SmallWorld無關.它的信息完全可以從配置文件中讀出.想一下Application Server是不是這樣實現的呢?

結束語

這樣我們就明白了Dynamic Proxy 在RMI Stub中的應用.我們可以用這一技術往系統中加入許多有趣的interceptor,甚至loadbalance也用它實現.


[火星人 ] Dynamic Proxy 在 Java RMI 中的應用已經有842次圍觀

http://coctec.com/docs/java/show-post-61938.html