因為對CORBA分析的需要,這里寫一個簡單的CORBA例子。從JDK1.2開始,JDK中集成了ORB的實現,本例子使用了JDK1.7,對於JDK1.2+應該都沒有問題。這個例子實現一個簡單的加減乘除的功能的計算器,客戶端將參數和請求的方法名傳送到服務端,服務端處理這個請求並將結果返回給客戶端。
我們知道不同編程語言中的類型的表達,內存模型是不一樣的,為此CORBA發明了一套中間描述語言IDL,不同語言平台的ORB實現負責將IDL中的類型映射到本地類型中。因此IDL是我們編寫CORBA程序的出發點,首先,我們用IDL來描述我們的接口/對象:
1 module com{ 2 module bes{ 3 module corba{ 4 module test{ 5 interface Calc{ 6 void add(in long a,in long b,out long c); 7 void sub(in long a,in long b,out long c); 8 void multi(in long a,in long b,out long c); 9 void div(in long a,in long b,out long c); 11 }; 12 }; 13 }; 14 }; 15 };
當然接口Calc中的方法的返回值不一定為void,這里將返回值放到了out類型的參數c中,方法可以帶有多個out類型的參數。然后我們用idlj工具(jdk自帶)將Calc.idl轉換為對應java的描述,並生成Stub和POA等類:
idlj給我們生成很多文件,首先我們來看一下UML圖:
上面的圖不涉及工具類CalcHelper和CalcHolder,這兩個類的作用在后而闡述。
package com.bes.corba.test; /** * com/bes/corba/test/_CalcStub.java . * 由IDL-to-Java 編譯器 (可移植), 版本 "3.2"生成 * 從Hello.idl * 2016年2月15日 星期一 下午09時08分34秒 CST */ public class _CalcStub extends org.omg.CORBA.portable.ObjectImpl implements com.bes.corba.test.Calc { public void add (int a, int b, org.omg.CORBA.IntHolder c) { org.omg.CORBA.portable.InputStream $in = null; try { org.omg.CORBA.portable.OutputStream $out = _request ("add", true); $out.write_long (a); $out.write_long (b); $in = _invoke ($out); c.value = $in.read_long (); return; } catch (org.omg.CORBA.portable.ApplicationException $ex) { $in = $ex.getInputStream (); String _id = $ex.getId (); throw new org.omg.CORBA.MARSHAL (_id); } catch (org.omg.CORBA.portable.RemarshalException $rm) { add (a, b, c ); } finally { _releaseReply ($in); } } // add public void sub (int a, int b, org.omg.CORBA.IntHolder c) { org.omg.CORBA.portable.InputStream $in = null; try { org.omg.CORBA.portable.OutputStream $out = _request ("sub", true); $out.write_long (a); $out.write_long (b); $in = _invoke ($out); c.value = $in.read_long (); return; } catch (org.omg.CORBA.portable.ApplicationException $ex) { $in = $ex.getInputStream (); String _id = $ex.getId (); throw new org.omg.CORBA.MARSHAL (_id); } catch (org.omg.CORBA.portable.RemarshalException $rm) { sub (a, b, c ); } finally { _releaseReply ($in); } } // sub public void multi (int a, int b, org.omg.CORBA.IntHolder c) { org.omg.CORBA.portable.InputStream $in = null; try { org.omg.CORBA.portable.OutputStream $out = _request ("multi", true); $out.write_long (a); $out.write_long (b); $in = _invoke ($out); c.value = $in.read_long (); return; } catch (org.omg.CORBA.portable.ApplicationException $ex) { $in = $ex.getInputStream (); String _id = $ex.getId (); throw new org.omg.CORBA.MARSHAL (_id); } catch (org.omg.CORBA.portable.RemarshalException $rm) { multi (a, b, c ); } finally { _releaseReply ($in); } } // multi public void div (int a, int b, org.omg.CORBA.IntHolder c) { org.omg.CORBA.portable.InputStream $in = null; try { org.omg.CORBA.portable.OutputStream $out = _request ("div", true); $out.write_long (a); $out.write_long (b); $in = _invoke ($out); c.value = $in.read_long (); return; } catch (org.omg.CORBA.portable.ApplicationException $ex) { $in = $ex.getInputStream (); String _id = $ex.getId (); throw new org.omg.CORBA.MARSHAL (_id); } catch (org.omg.CORBA.portable.RemarshalException $rm) { div (a, b, c ); } finally { _releaseReply ($in); } } // div // Type-specific CORBA::Object operations private static String[] __ids = { "IDL:com/bes/corba/test/Calc:1.0"}; public String[] _ids () { return (String[])__ids.clone (); } private void readObject (java.io.ObjectInputStream s) throws java.io.IOException { String str = s.readUTF (); String[] args = null; java.util.Properties props = null; org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init (args, props); try { org.omg.CORBA.Object obj = orb.string_to_object (str); org.omg.CORBA.portable.Delegate delegate = ((org.omg.CORBA.portable.ObjectImpl) obj)._get_delegate (); _set_delegate (delegate); } finally { orb.destroy() ; } } private void writeObject (java.io.ObjectOutputStream s) throws java.io.IOException { String[] args = null; java.util.Properties props = null; org.omg.CORBA.ORB orb = org.omg.CORBA.ORB.init (args, props); try { String str = orb.object_to_string (this); s.writeUTF (str); } finally { orb.destroy() ; } } } // class _CalcStub
_CalcStub是存根類,有過遠程調用編程經驗的讀者應該對這個詞比較熟悉,它是遠程對象在本地的一個代理(Proxy)。從業務划分角度來說,CORBA這些底層的東西不應該太多地污染到我們的應用,比如這個_CalcStub是我們不希望在業務代碼中出現的,我們只需要看到我們需要的Calc。客戶端從ORB中拿到的Calc接口實現其實是一個_CalcStub,客戶程序對Calc接口中的方法進行調用時,_CalcStub將相應方法的調用轉發到服務端,然后將服務器的響應返回給客戶端,從而成功從欺騙客戶端程序。
_CalcStub繼承了ObjectImpl類,這使得_CalcStub能夠關聯到ORB環境中,從而完成遠程調用。
idlj工具並沒有直接在Calc.java中定義idlj方法的java語言描述,而是在CalcOperation.java中。Calc接口繼承了IDLEntity,org.omg.CORBA.Object和CalcOperation三個接口:
IDLEntity是一個標記接口,表明Calc接口是一種IDL接口,這個與org.omg.CORBA.Object有點相類似,但實現了org.omg.CORBA.Object接口的對象不一定是IDL描述的,因此這里單獨把IDLEntity拎出來。
org.omg.CORBA.Object接口定義了一些CORBA相關的方法,因為客戶端所使用的是Calc,參數傳遞到ORB中語義上也是 Calc類型,當然我們不能將非IDL的對象傳遞到ORB中,ORB無法完成那樣子的操作;Calc接口繼承 org.omg.CORBA.Object(ORB層面使用的是org.omg.CORBA.Object),那就意味了通過編譯器來保證類型安全(避免 強制轉換,ClassCastException之類的異常)。
package com.bes.corba.test; /** * com/bes/corba/test/CalcOperations.java . * 由IDL-to-Java 編譯器 (可移植), 版本 "3.2"生成 * 從Hello.idl * 2016年2月15日 星期一 下午09時08分34秒 CST */ public interface CalcOperations { void add (int a, int b, org.omg.CORBA.IntHolder c); void sub (int a, int b, org.omg.CORBA.IntHolder c); void multi (int a, int b, org.omg.CORBA.IntHolder c); void div (int a, int b, org.omg.CORBA.IntHolder c); } // interface CalcOperations
CalcOperation是相對應的IDL映射。
package com.bes.corba.test; /** * com/bes/corba/test/CalcPOA.java . * 由IDL-to-Java 編譯器 (可移植), 版本 "3.2"生成 * 從Hello.idl * 2016年2月15日 星期一 下午09時08分34秒 CST */ public abstract class CalcPOA extends org.omg.PortableServer.Servant implements com.bes.corba.test.CalcOperations, org.omg.CORBA.portable.InvokeHandler { // Constructors private static java.util.Hashtable _methods = new java.util.Hashtable (); static { _methods.put ("add", new java.lang.Integer (0)); _methods.put ("sub", new java.lang.Integer (1)); _methods.put ("multi", new java.lang.Integer (2)); _methods.put ("div", new java.lang.Integer (3)); } public org.omg.CORBA.portable.OutputStream _invoke (String $method, org.omg.CORBA.portable.InputStream in, org.omg.CORBA.portable.ResponseHandler $rh) { org.omg.CORBA.portable.OutputStream out = null; java.lang.Integer __method = (java.lang.Integer)_methods.get ($method); if (__method == null) throw new org.omg.CORBA.BAD_OPERATION (0, org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE); switch (__method.intValue ()) { case 0: // com/bes/corba/test/Calc/add { int a = in.read_long (); int b = in.read_long (); org.omg.CORBA.IntHolder c = new org.omg.CORBA.IntHolder (); this.add (a, b, c); out = $rh.createReply(); out.write_long (c.value); break; } case 1: // com/bes/corba/test/Calc/sub { int a = in.read_long (); int b = in.read_long (); org.omg.CORBA.IntHolder c = new org.omg.CORBA.IntHolder (); this.sub (a, b, c); out = $rh.createReply(); out.write_long (c.value); break; } case 2: // com/bes/corba/test/Calc/multi { int a = in.read_long (); int b = in.read_long (); org.omg.CORBA.IntHolder c = new org.omg.CORBA.IntHolder (); this.multi (a, b, c); out = $rh.createReply(); out.write_long (c.value); break; } case 3: // com/bes/corba/test/Calc/div { int a = in.read_long (); int b = in.read_long (); org.omg.CORBA.IntHolder c = new org.omg.CORBA.IntHolder (); this.div (a, b, c); out = $rh.createReply(); out.write_long (c.value); break; } default: throw new org.omg.CORBA.BAD_OPERATION (0, org.omg.CORBA.CompletionStatus.COMPLETED_MAYBE); } return out; } // _invoke // Type-specific CORBA::Object operations private static String[] __ids = { "IDL:com/bes/corba/test/Calc:1.0"}; public String[] _all_interfaces (org.omg.PortableServer.POA poa, byte[] objectId) { return (String[])__ids.clone (); } public Calc _this() { return CalcHelper.narrow( super._this_object()); } public Calc _this(org.omg.CORBA.ORB orb) { return CalcHelper.narrow( super._this_object(orb)); } } // class CalcPOA
CalcPOA:它工作在服務端,POA是Portable Object Adapter的縮寫,這里Adapter(適配器)的語義是指適配相應的編程語言中的對象(比如在java中那意思就是指 Java對象的適配器),適配器的作用有三點:
1:接受客戶端發過來的調用請求,反序列化(Unmarshalling)參數,方法名等,然后將請求分發給對應的Servant。POA和Servant之間的關系如下圖。
2:將對象引用(Object Reference)和相應的Servant起來(可以看到Servant._object_id方法),比如我們在EJB中的有狀態會話Bean。
3:負責Servant的生命周期管理(如創建,鈍化,銷毀等),這里又讓我聯想到了EJB的生命周期。到這里這里我們可以清楚Home接口存在的理由。
好吧,我們要實現的例子確實很簡單,在這個例子中,讀者只需要了解到第一點即可(CORBA水很深,很容易死里面去的)。idlj為我們生成的POA中,集Servant,CalcOperation和InvocationHandler於一身,有越殂代皰的嫌疑,當然這並不影響程序的正常執行,當然如果服務端比較關注2,3兩點的話,自己實現POA還是很有必要的,但這已經超出了本文的范圍。
同樣地ORB的東西不應該玷污到我們服務端的業務邏輯 ,Servant和InvocationHandler將POA關聯到ORB中去。注意Servant和InvocationHandler是兩接口是分開的,這一點還不是太清楚(也許是為了特性的划分吧)。
package com.bes.corba.test; /** * com/bes/corba/test/CalcHelper.java . * 由IDL-to-Java 編譯器 (可移植), 版本 "3.2"生成 * 從Hello.idl * 2016年2月15日 星期一 下午09時08分34秒 CST */ abstract public class CalcHelper { private static String _id = "IDL:com/bes/corba/test/Calc:1.0"; public static void insert (org.omg.CORBA.Any a, com.bes.corba.test.Calc that) { org.omg.CORBA.portable.OutputStream out = a.create_output_stream (); a.type (type ()); write (out, that); a.read_value (out.create_input_stream (), type ()); } public static com.bes.corba.test.Calc extract (org.omg.CORBA.Any a) { return read (a.create_input_stream ()); } private static org.omg.CORBA.TypeCode __typeCode = null; synchronized public static org.omg.CORBA.TypeCode type () { if (__typeCode == null) { __typeCode = org.omg.CORBA.ORB.init ().create_interface_tc (com.bes.corba.test.CalcHelper.id (), "Calc"); } return __typeCode; } public static String id () { return _id; } public static com.bes.corba.test.Calc read (org.omg.CORBA.portable.InputStream istream) { return narrow (istream.read_Object (_CalcStub.class)); } public static void write (org.omg.CORBA.portable.OutputStream ostream, com.bes.corba.test.Calc value) { ostream.write_Object ((org.omg.CORBA.Object) value); } public static com.bes.corba.test.Calc narrow (org.omg.CORBA.Object obj) { if (obj == null) return null; else if (obj instanceof com.bes.corba.test.Calc) return (com.bes.corba.test.Calc)obj; else if (!obj._is_a (id ())) throw new org.omg.CORBA.BAD_PARAM (); else { org.omg.CORBA.portable.Delegate delegate = ((org.omg.CORBA.portable.ObjectImpl)obj)._get_delegate (); com.bes.corba.test._CalcStub stub = new com.bes.corba.test._CalcStub (); stub._set_delegate(delegate); return stub; } } public static com.bes.corba.test.Calc unchecked_narrow (org.omg.CORBA.Object obj) { if (obj == null) return null; else if (obj instanceof com.bes.corba.test.Calc) return (com.bes.corba.test.Calc)obj; else { org.omg.CORBA.portable.Delegate delegate = ((org.omg.CORBA.portable.ObjectImpl)obj)._get_delegate (); com.bes.corba.test._CalcStub stub = new com.bes.corba.test._CalcStub (); stub._set_delegate(delegate); return stub; } } }
CalcHelper:從名字上可以看出,它是一個工具類,它的職責在有:
1、通過提供narrow方法和RepositoryId等信息來實現類型安全。
2、與Any類型之間的進行轉換。
3、從InputStream中讀出Calc(對象引用),將Calc寫出到OutputStream中。
我們的例子中將只用到第一點。
package com.bes.corba.test; /** * com/bes/corba/test/CalcHolder.java . * 由IDL-to-Java 編譯器 (可移植), 版本 "3.2"生成 * 從Hello.idl * 2016年2月15日 星期一 下午09時08分34秒 CST */ public final class CalcHolder implements org.omg.CORBA.portable.Streamable { public com.bes.corba.test.Calc value = null; public CalcHolder () { } public CalcHolder (com.bes.corba.test.Calc initialValue) { value = initialValue; } public void _read (org.omg.CORBA.portable.InputStream i) { value = com.bes.corba.test.CalcHelper.read (i); } public void _write (org.omg.CORBA.portable.OutputStream o) { com.bes.corba.test.CalcHelper.write (o, value); } public org.omg.CORBA.TypeCode _type () { return com.bes.corba.test.CalcHelper.type (); } }
CalcHolder:這個類在Calc被作為out或者inout類型的參數傳遞時候被使用,如果我們在另一個IDL方法中使用Calc作為參數,那么生成的代碼將會是這樣子的:
void test(int a, int b, CalcHolder calc)
CalcHolder負責從InputStream或者OuputStream分別讀出和寫入Calc,從CalcHolder生成的代碼中我們可以看出,CalcHolder的_read和_write方法將相應的操作委托給了CalcHelper。
哆嗦了這么多,是時候拿出我們的客戶端和服務端了。
服務端代碼:
CalculatorImpl是Servant的實現,它繼承了CalcPOA類。
package com.bes.corba.impl; import org.omg.CORBA.IntHolder; import com.bes.corba.test.CalcPOA; public class CalculatorImpl extends CalcPOA{ @Override public void add(int a, int b, IntHolder c) { c.value=a+b; } @Override public void sub(int a, int b, IntHolder c) { c.value=a-b; } @Override public void multi(int a, int b, IntHolder c) { c.value=a*b; } @Override public void div(int a, int b, IntHolder c) { c.value=a/b; } }
Server類:
package com.bes.corba.test; import org.omg.CORBA.ORB; import org.omg.CosNaming.NameComponent; import org.omg.CosNaming.NamingContextExt; import org.omg.CosNaming.NamingContextExtHelper; import org.omg.PortableServer.POA; import org.omg.PortableServer.POAHelper; import com.bes.corba.test.Calc; import com.bes.corba.test.CalcHelper; import com.bes.corba.impl.CalculatorImpl; public class Server { public static void main(String[] args) throws Exception{ /* * ORB 初始化。 */ ORB orb=ORB.init(args,null); /* * 獲取根POA並初始化。 */ POA rootPoa=POAHelper.narrow(orb.resolve_initial_references("RootPOA") ); rootPoa.the_POAManager().activate(); /* * 構建一個CalculatorImpl。 */ CalculatorImpl calculatorImpl=new CalculatorImpl(); /* * 將Servant注冊到RootPOA中,建立Servant到Object Reference的相互映射, * 注意這里具體行為跟RootPOA的POA Policy有關。 */ org.omg.CORBA.Object ref=rootPoa.servant_to_reference(calculatorImpl); Calc iref=CalcHelper.narrow(ref); /* * 獲取命名服務。 */ org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService"); NamingContextExt ncRef= NamingContextExtHelper.narrow(objRef); /* * 將對象引用以相應的名字發布到命名服務中。 */ String name="Calc"; NameComponent path[] = ncRef.to_name(name); ncRef.rebind(path,iref); System.out.println("Calculator server ready..."); /* * 阻塞直到ORB關閉。 */ orb.run(); } }
客戶端代碼Client
package com.bes.corba.test; import org.omg.CORBA.IntHolder; import org.omg.CORBA.ORB; import org.omg.CosNaming.NamingContextExt; import org.omg.CosNaming.NamingContextExtHelper; import com.bes.corba.test.Calc; import com.bes.corba.test.CalcHelper; public class Client { public static void main(String[] args) throws Exception{ /* * ORB 初始化。 */ ORB orb=ORB.init(args,null); /* * 獲取命名服務。 */ org.omg.CORBA.Object objRef=orb.resolve_initial_references("NameService"); NamingContextExt ncRef=NamingContextExtHelper.narrow(objRef); /* * 從命名服務中查找相應的對象引用,並進行類型轉型。 */ String name="Calc"; Calc calc=CalcHelper.narrow(ncRef.resolve_str(name)); /* * 調用對象的方法。 */ IntHolder result=new IntHolder(); calc.add(1,2,result); System.out.printf("1+2=%d\n",result.value); } }
啟動命名服務,命名服務器不一定運行在對象服務器的進程中,尤其是在一個分布式的環境中,命名服務器與對象服務器通常是一對多的關系。
orbd -ORBInitialPort 1050 -ORBInitialHost localhost&
啟動服務端:
java HelloServer -ORBInitialPort 1050 -ORBInitialHost localhost
ORBInitialPort和ORBInitialHost參數指定了命名服務器的主機名和端口號。
啟動客戶端:
java com.bes.corba.test.Client -ORBInitialPort 1050 -ORBInitialHost localhost