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

它的實(shí)現(xiàn)方法就是在兩個(gè)JVM中各開一個(gè)Stub和Skeleton,二者通過socket通信來實(shí)現(xiàn)參數(shù)和返回值的傳遞。
有關(guān)RMI的例子代碼網(wǎng)上可以找到不少,但絕大部分都是通過extend the interface java.rmi.Remote實(shí)現(xiàn),已經(jīng)封裝的很完善了,不免使人有霧里看花的感覺。下面的例子是我在《Enterprise JavaBeans》里看到的,雖然很粗糙,但很直觀,利于很快了解它的工作原理。
1. 定義一個(gè)Person的接口,其中有兩個(gè)business method, getAge() 和getName()
java代碼: |
public interface Person { public int getAge() throws Throwable; public String getName() throws Throwable; } |
2. Person的實(shí)現(xiàn)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. 好,我們現(xiàn)在要在Client機(jī)器上調(diào)用getAge()和getName()這兩個(gè)business method,那么就得編寫相應(yīng)的Stub(Client端)和Skeleton(Server端)程序。這是Stub的實(shí)現(xiàn):
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。它們都實(shí)現(xiàn)了getAge()和getName()兩個(gè)business method,不同的是PersonServer是真的實(shí)現(xiàn),Person_Stub是建立socket連接,并向Skeleton發(fā)請求,然后通過Skeleton調(diào)用PersonServer的方法,最后接收返回的結(jié)果。
4. Skeleton實(shí)現(xiàn)
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,它長駐在后臺(tái)運(yùn)行,隨時(shí)接收client發(fā)過來的request。并根據(jù)發(fā)送過來的key去調(diào)用相應(yīng)的business method。
5. 最后一個(gè),Client的實(shí)現(xiàn)
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的本質(zhì)是,它要知道Person接口的定義,并實(shí)例一個(gè)Person_Stub,通過Stub來調(diào)用business method,至于Stub怎么去和Server溝通,Client就不用管了。
注意它的寫法:
Person person = new Person_Stub();
而不是
Person_Stub person = new Person_Stub();
為什么?因?yàn)橐嫦蚪涌诰幊搪铮呛恰?
感謝您有耐心看到這里,關(guān)于RMI,我想說的就這么多了。但是好象還沒寫到EJB,本人就累了個(gè)半死,算了,我還是先去睡覺,明天再往下續(xù)吧。。。
本人沒有用過Weblogic,這里就結(jié)合WebSphere來講講各個(gè)類的調(diào)用關(guān)系吧。
假定我們要?jiǎng)?chuàng)建一個(gè)讀取User信息的SessionBean,需要我們寫的有3個(gè)文件:
1. UserServiceHome.java
Home接口
2. UserService.java
Remote接口
3. UserServiceBean.java
Bean實(shí)現(xiàn)
WSAD最終會(huì)生成10個(gè)class。其它7個(gè)是什么呢?我們一個(gè)一個(gè)數(shù)過來:
4. _UserServiceHome_Stub.java
這個(gè)當(dāng)然就是Home接口在Client端(動(dòng)態(tài)加載)的Stub類了,它implements UserServiceHome。
5. _EJSRemoteStatelessUserServiceHome_a940aa04_Tie.java
Home接口在Server端的Skeleton類,"a940aa04"應(yīng)該是隨機(jī)生成的,所有其他的相關(guān)class名里都會(huì)有這個(gè)標(biāo)志串,Tie是Corba對Skeleton的叫法。
6. EJSRemoteStatelessUserServiceHome_a940aa04.java
Home接口在Server端的實(shí)現(xiàn),當(dāng)然,它也implements UserServiceHome。
7. EJSStatelessUserServiceHomeBean_a940aa04.java
由#6調(diào)用,create _UserService_Stub。(為什么#6不能直接create _UserService_Stub呢?后面再講。)
8. _UserService_Stub.java
Remote接口在Client端(動(dòng)態(tài)加載)的Stub類。它implements UserService。
9. _EJSRemoteStatelessUserService_a940aa04_Tie.java
Remote接口在Server端的Skeleton類。
10. EJSRemoteStatelessUserService_a940aa04.java
Remote接口在Server端的實(shí)現(xiàn),當(dāng)然,它也implements UserService。并且,它負(fù)責(zé)調(diào)用UserServiceBean——也就是我們所寫的Bean實(shí)現(xiàn)類——里面的business method。
那么,各個(gè)類之間的調(diào)用關(guān)系到底是怎么樣的呢?簡單的說,就是兩次RMI循環(huán)。
先來看看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); } |
在第一步之后,我們得到了一個(gè)UserServiceHome(interface)定義的對象home,那么,home到底是哪個(gè)class的instance呢?用debug看一下,知道了home原來就是_UserServiceHome_Stub的實(shí)例。
從第二步開始,就是我們的關(guān)注所在,雖然只有簡單的一行代碼,
UserService object = home.create();
但是他背后的系統(tǒng)是怎么運(yùn)做的呢?我們進(jìn)入代碼來看吧:
1. 調(diào)用home.create()
java代碼: |
UserServiceHome home; UserService obj = home.create(); |
2. 實(shí)際是調(diào)用_UserServiceHome_Stub.create(),在這個(gè)方法里面,Stub向Skeleton發(fā)送了一個(gè)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發(fā)來的request,并調(diào)用相應(yīng)的方法:
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調(diào)用的是UserServiceHome的Server端實(shí)現(xiàn)類的create方法
java代碼: |
EJSRemoteStatelessUserServiceHome_a940aa04.create() { UserService _EJS_result; _EJS_result = EJSStatelessUserServiceHomeBean_a940aa04.create(); } |
5. #4又調(diào)用EJSStatelessUserServiceHomeBean_a940aa04.create()
java代碼: |
UserService result = super.createWrapper(new BeanId(this, null)); |
至此,我們終于結(jié)束了第一個(gè)RMI循環(huán),并得到了Remote接口UserService的Stub類_UserService_Stub,就是#5里面的result。
這里有一個(gè)問題,為什么#4不直接create _UserService_Stub,而又轉(zhuǎn)了一道#5的手呢?因?yàn)?4 extends from EJSWrapper,它沒有能力create Stub,因此必須借助#5,which extends from EJSHome,這樣才可以生成一個(gè)Stub。如果不是為了生成這個(gè)Stub,應(yīng)該可以不走#5這一步。
OK, now we got the object which is instanceOf _UserService_Stub, and implements UserService
現(xiàn)在我們的Client端走到第三步了:
UserInfo ui = object.getUserInfo(userId);
繼續(xù)看代碼,開始第二個(gè)RMI循環(huán):
1. 調(diào)用object.getUserInfo()
java代碼: |
UserService object; object.getUserInfo(userId); |
2. 實(shí)際是調(diào)用_UserService_Stub.getUserInfo(int arg0),在這個(gè)方法里面,Stub向Skeleton發(fā)送了一個(gè)getUserInfo的字串和arg0這個(gè)參數(shù):
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發(fā)來的request,并調(diào)用相應(yīng)的方法:
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調(diào)用的是UserService的Server端實(shí)現(xiàn)類的getUserInfo方法
java代碼: |
EJSRemoteStatelessUserService_a940aa04.getUserInfo() { UserServiceBean _EJS_beanRef = container.preInvoke(this, 0, _EJS_s); _EJS_result = _EJS_beanRef.getUserInfo(id); } |
最后的最后,#4終于調(diào)用了我們寫的UserServiceBean里的getUserInfo方法,這才是我們真正想要去做的事情。
至此,第二個(gè)RMI循環(huán)也終于結(jié)束了。
回顧一下上面的分析,可以很清晰的看到兩次RMI循環(huán)的過程,下圖(見鏈接)描述了整個(gè)流程:
http://www.pbase.com/image/27229257
http://mk23.image.pbase.com/u42/nobo123/upload/27229257.ejb2.jpg
黃色的1,6,10是程序員要寫的,其余是系統(tǒng)生成的。
#1是Home interface, #2和#4都implements 了它。
#6是Remote interface, #7和#9都implements 了它。
#10是Bean實(shí)現(xiàn)。
寫到這里,基本要說的就說完了。這實(shí)在是一項(xiàng)累死人的工作,希望您能稀飯。歡迎補(bǔ)充,歡迎摘錯(cuò)。謝謝,呵呵。
簡單講,就是為了適應(yīng)分布式開發(fā)的需要。
首先,回到我最后給出的流程圖。
Client端最原始的沖動(dòng),肯定是能直接調(diào)用#10.UserServiceBean就爽了。那么第一個(gè)問題來了,
Client和Server不在一個(gè)JVM里。
這好辦,我們不是有RMI嗎,好,這個(gè)問題就這么解決了:
1. UserServiceBeanInterface.getUserInfo()
2. UserServiceBeanStub
3. UserServiceBeanSkeleton
4. UserServiceBean
用著用著,第二個(gè)問題來了,
UserServiceBean只有人用,沒人管理,transaction logic, security logic, bean instance pooling logic這些不得不考慮的問題浮出水面了。
OK,我們想到用一個(gè)delegate,EJBObject,來進(jìn)行所有這些logic的管理。client和EJBObject打交道,EJBObject調(diào)用UserServiceBean。
注意,這個(gè)EJBObject也是一個(gè)Interface,#6.UserService這個(gè)interface正是從它extends而來。并且EJBObject所管理的這些logic,正是AppServer的一部分。
現(xiàn)在的流程變?yōu)榱耍?
EJBObject
1. UserService.getUserInfo()
2. UserServiceStub
3. UserServiceSkeleton
4. UserServiceImp
5. UserServiceBean
這已經(jīng)和整幅圖里的#6, #7, #8, #9, #10一一對應(yīng)了。
現(xiàn)在能滿足我們的需求了嗎?不,第三個(gè)問題又來了:
既然是分布式開發(fā),那么我當(dāng)然沒理由只用一個(gè)Specified Server,我可能需要用到好幾個(gè)不同的Server,而且EJBObject也需要管理呀
OK,為了適應(yīng)你的需要,我們還得加再一個(gè)HomeObject,首先它來決定用哪個(gè)Server(當(dāng)然,是由你用JNDI String設(shè)定的),其次,它來管理EJBObject。
注意,這個(gè)EJBHome也是一個(gè)Interface,#1.UserServiceHome這個(gè)interface正是從它extends而來。并且EJBHome管理EJBObject的logic,也是AppServer的一部分。
現(xiàn)在的調(diào)用次序是
1. EJBHome.create()
2. EJBHomeStub
3. EJBHomeSkeleton
4. EJBHomeImp(EJSWrapper)
5. EJSHome
得到EJBObject
6. UserService.getUserInfo()
7. UserServiceStub
8. UserServiceSkeleton
9. UserServiceImp
10. UserServiceBean
現(xiàn)在已經(jīng)完全和流程圖的調(diào)用順序一致了。
綜上所述,EJB的調(diào)用確實(shí)很麻煩,但是搞的這么麻煩,確實(shí)是有搞的麻煩的道理,實(shí)在是不得不為也。
哎喲,好累啊。希望我把這個(gè)問題說清楚了,您也沒給我繞迷糊。謝謝。
posted on 2005-05-04 03:04 Vikings 閱讀(307) 評論(0) 編輯 收藏 所屬分類: frame-work