EJB工作原理學習筆記!

EJB工作原理學習筆記

1 RMI工作原理
2 websphere實現
3 weblogic實現
4 理解體會

1:RMI工作原理
首先說說RMI的工作原理,因爲EJB畢竟是基於RMI的嘛。廢話就不多講了,RMI的本質就是實現在不同JVM之間的

調用
它的實現方法就是在兩個JVM中各開一個Stub和Skeleton,二者通過socket通信來實現參數和返回值的傳遞。

有關RMI的例子代碼網上可以找到不少,但絕大部分都是通過extend the interface java.rmi.Remote實現,已

經封裝的很完善了,不免使人有霧裏看花的感覺。下面的例子是我在《Enterprise JavaBeans》裏看到的,雖

然很粗糙,但很直觀,利於很快了解它的工作原理。

1. 定義一個Person的接口,其中有兩個business method, getAge() 和getName()

代碼:

public interface Person {
    public int getAge() throws Throwable;
    public String getName() throws Throwable;
}
 


2. Person的實現PersonServer類
代碼:

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的實現:
代碼:

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實現
代碼:

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的實現
代碼:

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();

爲什麼?因爲要面向接口編程嘛,呵呵。

2:websphere實現
EJB類一覽   
這裏結合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循環。
 
   
  
 
第一個RMI循環   
先來看看Client端的程序是怎麼寫的:

代碼:

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()
代碼:

UserServiceHome home;
UserService obj = home.create();
 


2. 實際是調用_UserServiceHome_Stub.create(),在這個方法裏面,Stub向Skeleton發送了一個create的字串


代碼:

org.omg.CORBA.portable.OutputStream out = _request("create", true);
in = (org.omg.CORBA_2_3.portable.InputStream)_invoke(out);
 


3. Server端的Skeleton接收Stub發來的request,並調用相應的方法:
代碼:

_EJSRemoteStatelessUserServiceHome_a940aa04_Tie._invoke() {
    ......
    switch (method.length()) {
        case 6:
            if (method.equals("create")) {
                return create(in, reply);
            }
        ......
    }
}
 


代碼:

_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方法
代碼:

EJSRemoteStatelessUserServiceHome_a940aa04.create() {
    UserService _EJS_result;
    _EJS_result = EJSStatelessUserServiceHomeBean_a940aa04.create();
}
 


5. #4又調用EJSStatelessUserServiceHomeBean_a940aa04.create()
代碼:

    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這一步。
  
   
 
第二個RMI循環   
OK, now we got the object which is instanceOf _UserService_Stub, and implements UserService

現在我們的Client端走到第三步了:
UserInfo ui = object.getUserInfo(userId);

繼續看代碼,開始第二個RMI循環:

1. 調用object.getUserInfo()
代碼:

UserService object;
object.getUserInfo(userId);
 


2. 實際是調用_UserService_Stub.getUserInfo(int

arg0),在這個方法裏面,Stub向Skeleton發送了一個getUserInfo的字串和arg0這個參數:

代碼:

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,並調用相應的方法:
代碼:

_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方法
代碼:

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

黃色的1,6,10是程序員要寫的,其餘是系統生成的。

#1是Home interface, #2和#4都implements 了它。
#6是Remote interface, #7和#9都implements 了它。
#10是Bean實現。
 
 
3:weblogic實現
一個遠程對象至少要包括4個class文件:遠程對象;遠程對象的接口;實現遠程接口的對象的stub;對象的ske

leton這4個class文件。

在EJB中則至少要包括10個class:

Bean類,特定App Server的Bean實現類

Bean的remote接口,特定App Server的remote接口實現類,特定App

Server的remote接口的實現類的stub類和skeleton類

Bean的home接口,特定App Server的home接口實現類,特定App

Server的home接口的實現類的stub類和skeleton類

和RMI不同的是,EJB中這10個class真正需要用戶編寫的只有3個,分別是Bean類和它的remote接口,home接口

,至於其它的7個class到底是怎麼生成,被打包在什麼地方,或者是否需要更多的類文件,會根據不同的App

Server表現出比較大的差異,不能一概而論。

拿Weblogic的來說吧,Weblogic的Bean實現類,以及兩個接口的Weblogic的實現類是在ejbc的時候被打包到EJB

的jar包裏面的,這3個class文件可以看到。而home接口和remote接口的Weblogic的實現類的stub類和skeleton

類是在EJB被部署到Weblogic的時候,由Weblogic動態生成stub類和Skeleton類的字節碼,因此看不到這4個類

文件。

對於一次客戶端遠程調用EJB,要經過兩個遠程對象的多次RMI循環。首先是通過JNDI查找Home接口,獲得Home

接口的實現類,這個過程其實相當複雜,首先是找到Home接口的Weblogic實現類,然後創建一個Home接口的Web

logic實現類的stub類的對象實例,將它序列化傳送給客戶端(注意stub類的實例是在第1次RMI循環中,由服務

器動態發送給客戶端的,因此不需要客戶端保存Home接口的Weblogic實現類的stub類),最後客戶端獲得該stu

b類的對象實例(普通的RMI需要在客戶端保存stub類,而EJB不需要,因爲服務器會把stub類的對象實例發送給

客戶端)。

客戶端拿到服務器給它的Home接口的Weblogic實現類的stub類對象實例以後,調用stub類的create方法,(在代

碼上就是home.create(),但是後臺要做很多事情),於是經過第2次RMI循環,在服務器端,Home接口的Weblogic

實現類的skeleton類收到stub類的調用信息後,由它再去調用Home接口的Weblogic實現類的create方法。

在服務端,Home接口的Weblogic實現類的create方法再去調用Bean類的Weblogic實現類的ejbCreate方法,在服

務端創建或者分配一個EJB實例,然後將這個EJB實例的遠程接口的Weblogic實現類的stub類對象實例序列化發

送給客戶端。

客戶端收到remote接口的Weblogic實現類的stub類的對象實例,對該對象實例的方法調用(在客戶端代碼中實

際上就是對remote接口的調用),將傳送給服務器端remote接口的Weblogic實現類的skeleton類對象,而skele

ton類對象再調用相應的remote接口的Weblogic實現類,然後remote接口的Weblogic實現類再去調用Bean類的We

blogic實現類,如此就完成一次EJB對象的遠程調用。

看了一遍帖子,感覺還是沒有說太清楚,既然寫了帖子,就想徹底把它說清楚。

先拿普通RMI來說,有4個class,分別是遠程對象,對象的接口,對象的stub類和skeleton類。而對象本身和對

象的stub類同時都實現了接口類。而我們在客戶端代碼調用遠程對象的時候,雖然在代碼中操縱接口,實質上

是在操縱stub類,例如:

接口類:Hello

遠程對象:Hello_Server

stub類:Hello_Stub

skeleton類:Hello_Skeleton

客戶端代碼要這樣寫:

Hello h = new Hello_Stub();
h.getString();

我們不會這樣寫:

Hello_Stub h = new Hello_Stub();
h.getString();

因爲使用接口適用性更廣,就算更換了接口實現類,也不需要更改代碼。因此客戶端需要Hello.class和Hello_

Stub.class這兩個文件。但是對於EJB來說,就不需要Hello_Stub.class,因爲服務器會發送給它,但是Hello.

class文件客戶端是省不了的,必須有。表面上我們的客戶端代碼在操縱Hello,但別忘記了Hello只是一個接口

,抽象的,實質上是在操縱Hello_Stub。

拿Weblogic上的EJB舉例子,10個class分別是:

Bean類:HelloBean (用戶編寫)
Bean類的Weblogic實現類:HelloBean_Impl (EJBC生成)
Home接口:HelloHome (用戶編寫)
Home接口的Weblogic實現類 ((Hello Bean))_HomeImpl(EJBC生成)
Home接口的Weblogic實現類的stub類 ((Hello Bean))_HomeImpl_WLStub(部署的時候動態生成字節碼)
Home接口的Weblogic實現類的skeleton類 ((Hello Bean))_HomeImpl_WLSkeleton(部署的時候動態生成字節碼


Remote接口: Hello (用戶編寫)
Remote接口的Weblogic實現類 ((Hello Bean))_EOImpl(EJBC生成)
Remote接口的Weblogic實現類的stub類 ((Hello Bean))_EOImpl_WLStub(部署的時候動態生成字節碼)
Remote接口的Weblogic實現類的skeleton類 ((Hello Bean))_EOImpl_WLSkeleton(部署的時候動態生成字節碼

客戶端只需要Hello.class和HelloHome.class這兩個文件。

((Hello Home)) home = (Home) ((Portable Remote Object)).narrow(ctx.lookup("Hello"), ((Hello

Home)).class);

這一行代碼是從JNDI獲得Home接口,但是請記住!接口是抽象的,那麼home這個對象到底是什麼類的對象實例

呢?很簡單,用toString()輸出看一下就明白了,下面一行是輸出結果:

((Hello Bean))_HomeImpl_WLStub@18c458

這表明home這個通過從服務器的JNDI樹上查找獲得的對象實際上是HelloBean_HomeImpl_WLStub類的一個實例。

接下來客戶端代碼:

Hello h = home.create()

同樣Hello只是一個抽象的接口,那麼h對象是什麼東西呢?打印一下:

((Hello Bean))_EOImpl_WLStub@8fa0d1

原來是HelloBean_EOImpl_WLStub的一個對象實例。

用這個例子來簡述一遍EJB調用過程:

首先客戶端JNDI查詢,服務端JNDI樹上Hello這個名字實際上綁定的對象是HelloBean_HomeImpl_WLStub,所以

服務端將創建HelloBean_HomeImpl_WLStub的一個對象實例,序列化返回給客戶端。

於是客戶端得到home對象,表面上是得到HelloHome接口的實例,實際上是進行了一次遠程調用得到了HelloBea

n_HomeImpl_WLStub類的對象實例,別忘記了HelloBean_HomeImpl_WLStub也實現了HelloHome接口。

然後home.create()實質上就是HelloBean_HomeImpl_WLStub.create(),該方法將發送信息給HelloBean_HomeIm

pl_WLSkeleton,而HelloBean_HomeImpl_WLSkeleton接受到信息後,再去調用HelloBean_HomeImpl的create方

法,至此完成第1次完整的RMI循環。

注意在這次RMI循環過程中,遠程對象是HelloBean_HomeImpl,遠程對象的接口是HelloHome,對象的stub是Hel

loBean_HomeImpl_WLStub,對象的skeleton是HelloBean_HomeImpl_WLSkeleton。

然後HelloBean_HomeImpl再去調用HelloBean_Impl的ejbCreate方法,而HelloBean_Impl的ejbCreate方法將負

責創建或者分配一個Bean實例,並且創建一個HelloBean_EOImpl_WLStub的對象實例。

這一步比較有趣的是,在前一步RMI循環中,遠程對象HelloBean_HomeImpl在客戶端有一個代理類HelloBean_Ho

meImpl_WLStub,但在這一步,HelloBean_HomeImpl自己卻充當了HelloBean_Impl的代理類,只不過HelloBean_

HomeImpl不在客戶端,而是在服務端,因此不進行RMI。

然後HelloBean_EOImpl_WLStub的對象實例序列化返回給客戶端,這一步也很有趣,上次RMI過程,主角是Hello

Bean_HomeImpl和它的代理類HelloBean_HomeImpl_WLStub,但這這一次換成了HelloBean_EOImpl和它的代理類H

elloBean_EOImpl_WLStub來玩了。

Hello h = home.create();h.helloWorld();

假設Hello接口有一個helloWorld遠程方法,那麼表面上是在調用Hello接口的helloWorld方法,實際上是在調

用HelloBean_EOImpl_WLStub的helloWorld方法。

然後HelloBean_EOImpl_WLStub的helloWorld方法將發送信息給服務器上的HelloBean_EOImpl_WLSkeleton,而H

elloBean_EOImpl_WLSkeleton收到信息以後,再去調用HelloBean_EOImpl的helloWorld方法。至此,完成第2次

完整的RMI循環過程。

在剛纔HelloBean_EOImpl是作爲遠程對象被調用的,它的代理類是HelloBean_EOImpl_WLStub,但現在HelloBea

n_EOImpl要作爲HelloBean_Impl的代理類了。現在HelloBean_EOImpl去調用HelloBean_Impl的helloWorld方法

。注意!HelloBean_Impl繼承了HelloBean,而HelloBean中的helloWorld方法是我們親自編寫的代碼,現在終

於調用到了我們編寫的代碼了!

至此,一次EJB調用過程終於完成。在整個過程中,服務端主要要調用的類是HelloBean_Impl, Hello

Bean?_HomeImpl,HelloBean_HomeImpl_WLSkeleton,HelloBean_EOImpl,HelloBean_EOImpl_WLSkeleton。客

戶端主要調用的類是HelloBean_HomeImpl_WLStub,HelloBean_EOImpl_WLStub,這兩個類在客戶端代碼中並不

會直接出現,出現在代碼中的類是他們的接口HelloHome和Hello,因此客戶端需要這兩個接口文件,而Stub是

服務器傳送給他們的。

4 理解體會
簡單講,就是爲了適應分佈式開發的需要。

首先,回到我最後給出的流程圖。
http://www.pbase.com/nobo123/image/27229257

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打交道,EJBObj

ect調用UserServiceBean。

注意,這個EJBObject也是一個Interface,#6.UserService這個interface正是從它extends而來。並且EJBObje

ct所管理的這些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而來。並且EJBHo

me管理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的調用確實很麻煩,但是搞的這麼麻煩,確實是有搞的麻煩的道理,實在是不得不爲也。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章