EJB 工作原理 (ZT) -相見恨晚
原帖: xanada ----http://www.hibernate.org.cn/viewtopic.php?t=3832&postdays=0&postorder=asc&start=0
前兩天在這個版塊的精華區里翻到了Robbin關于EJB的調用原理的分析,受益非淺,但感覺用純文字來表達效果似乎不夠直觀,而且對RMI的闡述也略嫌少了些。這里我根據自己的一點體會,在Robbin帖子的基礎上再來說說這個話題,供大家參考。
首先,我想先說說RMI的工作原理,因為EJB畢竟是基于RMI的嘛。廢話就不多講了,RMI的本質就是實現在不同JVM之間的調用,工作原理圖如下:

它的實現方法就是在兩個JVM中各開一個Stub和Skeleton,二者通過socket通信來實現參數和返回值的傳遞。
有關RMI的例子代碼網上可以找到不少,但絕大部分都是通過extend the interface java.rmi.Remote實現,已經封裝的很完善了,不免使人有霧里看花的感覺。下面的例子是我在《Enterprise JavaBeans》里看到的,雖然很粗糙,但很直觀,利于很快了解它的工作原理。
1. 定義一個Person的接口,其中有兩個business method, getAge() 和getName()
java代碼: |
public interface Person { public int getAge() throws Throwable; public String getName() throws Throwable; } |
2. Person的實現PersonServer類
java代碼: |
public class PersonServer implements Person { int age; String name; public PersonServer(String name, int age) { this.age = age; this.name = name; } public int getAge() { return age; } public String getName() { return name; } } |
3. 好,我們現在要在Client機器上調用getAge()和getName()這兩個business method,那么就得編寫相應的Stub(Client端)和Skeleton(Server端)程序。這是Stub的實現:
java代碼: |
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; public class Person_Stub implements Person { Socket socket; public Person_Stub() throws Throwable { // connect to skeleton socket = new Socket("computer_name", 9000); } public int getAge() throws Throwable { // pass method name to skeleton ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("age"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return inStream.readInt(); } public String getName() throws Throwable { // pass method name to skeleton ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); outStream.writeObject("name"); outStream.flush(); ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); return (String)inStream.readObject(); } } |
注意,Person_Stub和PersonServer一樣,都implements Person。它們都實現了getAge()和getName()兩個business method,不同的是PersonServer是真的實現,Person_Stub是建立socket連接,并向Skeleton發請求,然后通過Skeleton調用PersonServer的方法,最后接收返回的結果。
4. Skeleton實現
java代碼: |
import java.io.ObjectOutputStream; import java.io.ObjectInputStream; import java.net.Socket; import java.net.ServerSocket; public class Person_Skeleton extends Thread { PersonServer myServer; public Person_Skeleton(PersonServer server) { // get reference of object server this.myServer = server; } public void run() { try { // new socket at port 9000 ServerSocket serverSocket = new ServerSocket(9000); // accept stub's request Socket socket = serverSocket.accept(); while (socket != null) { // get stub's request ObjectInputStream inStream = new ObjectInputStream(socket.getInputStream()); String method = (String)inStream.readObject(); // check method name if (method.equals("age")) { // execute object server's business method int age = myServer.getAge(); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // return result to stub outStream.writeInt(age); outStream.flush(); } if(method.equals("name")) { // execute object server's business method String name = myServer.getName(); ObjectOutputStream outStream = new ObjectOutputStream(socket.getOutputStream()); // return result to stub outStream.writeObject(name); outStream.flush(); } } } catch(Throwable t) { t.printStackTrace(); System.exit(0); } } public static void main(String args []) { // new object server PersonServer person = new PersonServer("Richard", 34); Person_Skeleton skel = new Person_Skeleton(person); skel.start(); } } |
Skeleton類 extends from Thread,它長駐在后臺運行,隨時接收client發過來的request。并根據發送過來的key去調用相應的business method。
5. 最后一個,Client的實現
java代碼: |
public class PersonClient { public static void main(String [] args) { try { Person person = new Person_Stub(); int age = person.getAge(); String name = person.getName(); System.out.println(name + " is " + age + " years old"); } catch(Throwable t) { t.printStackTrace(); } } } |
Client的本質是,它要知道Person接口的定義,并實例一個Person_Stub,通過Stub來調用business method,至于Stub怎么去和Server溝通,Client就不用管了。
注意它的寫法:
Person person = new Person_Stub();
而不是
Person_Stub person = new Person_Stub();
為什么?因為要面向接口編程嘛,呵呵。
感謝您有耐心看到這里,關于RMI,我想說的就這么多了。但是好象還沒寫到EJB,本人就累了個半死,算了,我還是先去睡覺,明天再往下續吧。。。
本人沒有用過Weblogic,這里就結合WebSphere來講講各個類的調用關系吧。
假定我們要創建一個讀取User信息的SessionBean,需要我們寫的有3個文件:
1. UserServiceHome.java
Home接口
2. UserService.java
Remote接口
3. UserServiceBean.java
Bean實現
WSAD最終會生成10個class。其它7個是什么呢?我們一個一個數過來:
4. _UserServiceHome_Stub.java
這個當然就是Home接口在Client端(動態加載)的Stub類了,它implements UserServiceHome。
5. _EJSRemoteStatelessUserServiceHome_a940aa04_Tie.java
Home接口在Server端的Skeleton類,"a940aa04"應該是隨機生成的,所有其他的相關class名里都會有這個標志串,Tie是Corba對Skeleton的叫法。
6. EJSRemoteStatelessUserServiceHome_a940aa04.java
Home接口在Server端的實現,當然,它也implements UserServiceHome。
7. EJSStatelessUserServiceHomeBean_a940aa04.java
由#6調用,create _UserService_Stub。(為什么#6不能直接create _UserService_Stub呢?后面再講。)
8. _UserService_Stub.java
Remote接口在Client端(動態加載)的Stub類。它implements UserService。
9. _EJSRemoteStatelessUserService_a940aa04_Tie.java
Remote接口在Server端的Skeleton類。
10. EJSRemoteStatelessUserService_a940aa04.java
Remote接口在Server端的實現,當然,它也implements UserService。并且,它負責調用UserServiceBean——也就是我們所寫的Bean實現類——里面的business method。
那么,各個類之間的調用關系到底是怎么樣的呢?簡單的說,就是兩次RMI循環。
先來看看Client端的程序是怎么寫的:
java代碼: |
try { InitialContext ctx = new InitialContext(); //第一步 UserServiceHome home = (UserServiceHome) PortableRemoteObject.narrow( ctx.lookup(JNDIString), UserServiceHome.class); //home: _UserServiceHome_Stub System.out.println(home.toString()); //第二步 UserService object = home.create(); //ojbect: _UserService_Stub System.out.println(object.toString()); //第三步 int userId = 1; UserInfo ui = object.getUserInfo(userId); } |
在第一步之后,我們得到了一個UserServiceHome(interface)定義的對象home,那么,home到底是哪個class的instance呢?用debug看一下,知道了home原來就是_UserServiceHome_Stub的實例。
從第二步開始,就是我們的關注所在,雖然只有簡單的一行代碼,
UserService object = home.create();
但是他背后的系統是怎么運做的呢?我們進入代碼來看吧:
1. 調用home.create()
java代碼: |
UserServiceHome home; UserService obj = home.create(); |
2. 實際是調用_UserServiceHome_Stub.create(),在這個方法里面,Stub向Skeleton發送了一個create的字串:
java代碼: |
org.omg.CORBA.portable.OutputStream out = _request("create", true); in = (org.omg.CORBA_2_3.portable.InputStream)_invoke(out); |
3. Server端的Skeleton接收Stub發來的request,并調用相應的方法:
java代碼: |
_EJSRemoteStatelessUserServiceHome_a940aa04_Tie._invoke() { ...... switch (method.length()) { case 6: if (method.equals("create")) { return create(in, reply); } ...... } } |
java代碼: |
_EJSRemoteStatelessUserServiceHome_a940aa04_Tie.create() { EJSRemoteStatelessUserServiceHome_a940aa04 target = null; result = target.create(); org.omg.CORBA.portable.OutputStream out = reply.createReply(); Util.writeRemoteObject(out,result); return out; } |
4. Skeleton調用的是UserServiceHome的Server端實現類的create方法
java代碼: |
EJSRemoteStatelessUserServiceHome_a940aa04.create() { UserService _EJS_result; _EJS_result = EJSStatelessUserServiceHomeBean_a940aa04.create(); } |
5. #4又調用EJSStatelessUserServiceHomeBean_a940aa04.create()
java代碼: |
UserService result = super.createWrapper(new BeanId(this, null)); |
至此,我們終于結束了第一個RMI循環,并得到了Remote接口UserService的Stub類_UserService_Stub,就是#5里面的result。
這里有一個問題,為什么#4不直接create _UserService_Stub,而又轉了一道#5的手呢?因為#4 extends from EJSWrapper,它沒有能力create Stub,因此必須借助#5,which extends from EJSHome,這樣才可以生成一個Stub。如果不是為了生成這個Stub,應該可以不走#5這一步。
OK, now we got the object which is instanceOf _UserService_Stub, and implements UserService
現在我們的Client端走到第三步了:
UserInfo ui = object.getUserInfo(userId);
繼續看代碼,開始第二個RMI循環:
1. 調用object.getUserInfo()
java代碼: |
UserService object; object.getUserInfo(userId); |
2. 實際是調用_UserService_Stub.getUserInfo(int arg0),在這個方法里面,Stub向Skeleton發送了一個getUserInfo的字串和arg0這個參數:
java代碼: |
org.omg.CORBA.portable.OutputStream out = _request("getUserInfo", true); out.write_long(arg0); in = (org.omg.CORBA_2_3.portable.InputStream)_invoke(out); |
3. Server端的Skeleton接收Stub發來的request,并調用相應的方法:
java代碼: |
_EJSRemoteStatelessUserService_a940aa04_Tie._invoke() { switch (method.charAt(5)) { case 83: if (method.equals("getUserInfo")) { return getUserInfo(in, reply); } ...... } } _EJSRemoteStatelessUserService_a940aa04_Tie.getUserInfo() { EJSRemoteStatelessUserService_a940aa04 target = null; int arg0 = in.read_long(); UserDTO result = target.getUserInfo(arg0); org.omg.CORBA_2_3.portable.OutputStream out = reply.createReply(); out.write_value(result,UserDTO.class); return out; } |
4. Skeleton調用的是UserService的Server端實現類的getUserInfo方法
java代碼: |
EJSRemoteStatelessUserService_a940aa04.getUserInfo() { UserServiceBean _EJS_beanRef = container.preInvoke(this, 0, _EJS_s); _EJS_result = _EJS_beanRef.getUserInfo(id); } |
最后的最后,#4終于調用了我們寫的UserServiceBean里的getUserInfo方法,這才是我們真正想要去做的事情。
至此,第二個RMI循環也終于結束了。
回顧一下上面的分析,可以很清晰的看到兩次RMI循環的過程,下圖(見鏈接)描述了整個流程:
http://www.pbase.com/image/27229257
http://mk23.image.pbase.com/u42/nobo123/upload/27229257.ejb2.jpg
黃色的1,6,10是程序員要寫的,其余是系統生成的。
#1是Home interface, #2和#4都implements 了它。
#6是Remote interface, #7和#9都implements 了它。
#10是Bean實現。
寫到這里,基本要說的就說完了。這實在是一項累死人的工作,希望您能稀飯。歡迎補充,歡迎摘錯。謝謝,呵呵。
簡單講,就是為了適應分布式開發的需要。
首先,回到我最后給出的流程圖。
Client端最原始的沖動,肯定是能直接調用#10.UserServiceBean就爽了。那么第一個問題來了,
Client和Server不在一個JVM里。
這好辦,我們不是有RMI嗎,好,這個問題就這么解決了:
1. UserServiceBeanInterface.getUserInfo()
2. UserServiceBeanStub
3. UserServiceBeanSkeleton
4. UserServiceBean
用著用著,第二個問題來了,
UserServiceBean只有人用,沒人管理,transaction logic, security logic, bean instance pooling logic這些不得不考慮的問題浮出水面了。
OK,我們想到用一個delegate,EJBObject,來進行所有這些logic的管理。client和EJBObject打交道,EJBObject調用UserServiceBean。
注意,這個EJBObject也是一個Interface,#6.UserService這個interface正是從它extends而來。并且EJBObject所管理的這些logic,正是AppServer的一部分。
現在的流程變為了:
EJBObject
1. UserService.getUserInfo()
2. UserServiceStub
3. UserServiceSkeleton
4. UserServiceImp
5. UserServiceBean
這已經和整幅圖里的#6, #7, #8, #9, #10一一對應了。
現在能滿足我們的需求了嗎?不,第三個問題又來了:
既然是分布式開發,那么我當然沒理由只用一個Specified Server,我可能需要用到好幾個不同的Server,而且EJBObject也需要管理呀
OK,為了適應你的需要,我們還得加再一個HomeObject,首先它來決定用哪個Server(當然,是由你用JNDI String設定的),其次,它來管理EJBObject。
注意,這個EJBHome也是一個Interface,#1.UserServiceHome這個interface正是從它extends而來。并且EJBHome管理EJBObject的logic,也是AppServer的一部分。
現在的調用次序是
1. EJBHome.create()
2. EJBHomeStub
3. EJBHomeSkeleton
4. EJBHomeImp(EJSWrapper)
5. EJSHome
得到EJBObject
6. UserService.getUserInfo()
7. UserServiceStub
8. UserServiceSkeleton
9. UserServiceImp
10. UserServiceBean
現在已經完全和流程圖的調用順序一致了。
綜上所述,EJB的調用確實很麻煩,但是搞的這么麻煩,確實是有搞的麻煩的道理,實在是不得不為也。
哎喲,好累啊。希望我把這個問題說清楚了,您也沒給我繞迷糊。謝謝。
posted on 2005-05-04 03:04 Vikings 閱讀(316) 評論(0) 編輯 收藏 所屬分類: frame-work