EJB 工作原理

前两天在这个版块的精华区里翻到了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()

代码
 

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

为什么?因为要面向接口编程嘛,呵呵。

感谢您有耐心看到这里,关于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端的程序是怎么写的:

代码
 

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这一步。

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实现。

写到这里,基本要说的就说完了。这实在是一项累死人的工作,希望您能稀饭。欢迎补充,欢迎摘错。谢谢,呵呵。

------------------------------------------------

了不起的工作!
不辞辛劳,详尽明白。

一定会好好学习的!

另外,有没有可能分析一下,这样的工作方式的“所以然”。或者说“不得不然”。再或者有没有“其他的实现方式”。

------------------------------------------------

简单讲,就是为了适应分布式开发的需要。

首先,回到我最后给出的流程图。

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的调用确实很麻烦,但是搞的这么麻烦,确实是有搞的麻烦的道理,实在是不得不为也。

哎哟,好累啊。希望我把这个问题说清楚了,您也没给我绕迷糊。谢谢。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值