Vikings

          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

          主站蜘蛛池模板: 佛学| 稷山县| 湘潭县| 晋州市| 安宁市| 灵璧县| 博罗县| 德令哈市| 新绛县| 延津县| 故城县| 广元市| 府谷县| 喜德县| 榆林市| 邵阳市| 松溪县| 健康| 若尔盖县| 武陟县| 高淳县| 金昌市| 广东省| 白玉县| 双鸭山市| 淳化县| 宁武县| 三门峡市| 昌黎县| 江山市| 武宣县| 连云港市| 盐亭县| 富平县| 科技| 铜山县| 宝清县| 集贤县| 高清| 北辰区| 长治市|