1 RMI工作原理
2 websphere实现
3 weblogic实现
4 理解体会
1QRMI工作原理
RMI的本质就是实现在不同JVM之间的调?它的实现Ҏ是在两个JVM中各开一个Stub和SkeletonQ二者通过socket通信来实现参数和q回值的传递?
有关RMI的例子代码网上可以找C,但绝大部分都是通过extend the interface java.rmi.Remote实现Q已l封装的很完善了Q不免人有N看花的感觉。下面的例子是我在《Enterprise JavaBeans》里看到的,虽然很粗p,但很直观Q利于很快了解它的工作原理?
1. 定义一个Person的接口,其中有两个business method, getAge() 和getName()
代码:
public interface Person {
public int getAge() throws Throwable;
public String getName() throws Throwable;
}
2. Person的实现PersonServerc?
代码:
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()q两个business
methodQ那么就得编写相应的Stub(Client?和Skeleton(Server?E序。这是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();
}
}
注意QPerson_Stub和PersonServer一P都implements
Person。它们都实现了getAge()和getName()两个business
methodQ不同的是PersonServer是真的实玎ͼPerson_Stub是徏立socketq接Qƈ向Skeleton发请求,然后通过
Skeleton调用PersonServer的方法,最后接收返回的l果?
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();
}
}
Skeletonc?extends from ThreadQ它镉K在后台运行,随时接收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的本质是Q它要知道Person接口的定义,q实例一个Person_StubQ通过Stub来调用business methodQ?/p>
至于Stub怎么dServer沟通,Client׃用管了?
注意它的写法Q?
Person person = new Person_Stub();
而不?
Person_Stub person = new Person_Stub();
Z么?因ؓ要面向接口编E嘛Q呵c?/p>
//RMI实质上就是生?个类stubQskeleton来进行参数和q回值的传递,采用g递方?/font>
//cM于以前写的聊天室E序Q被传递的对象应实现java.io.Serializable接口
2Qwebsphere实现
EJBcM?nbsp;
q里l合WebSphere来讲讲各个类的调用关pd?
假定我们要创Z个读取User信息的SessionBeanQ需要我们写的有3个文Ӟ
1. UserServiceHome.java
Home接口
2. UserService.java
Remote接口
3. UserServiceBean.java
Bean实现
WSAD最l会生成10个class。其?个是什么呢Q我们一个一个数q来Q?
4. _UserServiceHome_Stub.java
q个当然是Home接口在Client?动态加?的StubcMQ它implements UserServiceHome?
5. _EJSRemoteStatelessUserServiceHome_a940aa04_Tie.java
Home接口在Server端的Skeletonc,"a940aa04"应该是随机生成的Q所有其他的相关class名里都会有这个标?/p>
ԌTie是Corba对Skeleton的叫法?
6. EJSRemoteStatelessUserServiceHome_a940aa04.java
Home接口在Server端的实现Q当Ӟ它也implements UserServiceHome?
7. EJSStatelessUserServiceHomeBean_a940aa04.java
?6调用Qcreate _UserService_Stub?Z?6不能直接create _UserService_Stub呢?后面再讲?
8. _UserService_Stub.java
Remote接口在Client?动态加?的StubcR它implements UserService?
9. _EJSRemoteStatelessUserService_a940aa04_Tie.java
Remote接口在Server端的SkeletoncR?
10. EJSRemoteStatelessUserService_a940aa04.java
Remote接口在Server端的实现Q当Ӟ它也implements UserService。ƈ且,它负责调用UserServiceBean—?/p>
也就是我们所写的Bean实现cZ—里面的business method?
那么Q各个类之间的调用关pd底是怎么L呢?单的_是两次RMI循环?
W一个RMI循环
先来看看Client端的E序是怎么写的Q?
代码:
try {
InitialContext ctx = new InitialContext();
//W一?
UserServiceHome home =
(UserServiceHome) PortableRemoteObject.narrow(
ctx.lookup(JNDIString),
UserServiceHome.class);
//home: _UserServiceHome_Stub
System.out.println(home.toString());
//W二?
UserService object = home.create();
//ojbect: _UserService_Stub
System.out.println(object.toString());
//W三?
int userId = 1;
UserInfo ui = object.getUserInfo(userId);
}
在第一步之后,我们得到了一个UserServiceHome(interface)定义的对象homeQ那么,home到底是哪个class?/p>
instance呢?用debug看一下,知道了home原来是_UserServiceHome_Stub的实例?
从第二步开始,是我们的关注所在,虽然只有单的一行代码,
UserService object = home.create();
但是他背后的pȝ是怎么q做的呢Q我们进入代码来看吧Q?
1. 调用home.create()
代码:
UserServiceHome home;
UserService obj = home.create();
2. 实际是调用_UserServiceHome_Stub.create()Q在q个Ҏ里面QStub向Skeleton发送了一个create的字?/p>
Q?
代码:
org.omg.CORBA.portable.OutputStream out = _request("create", true);
in = (org.omg.CORBA_2_3.portable.InputStream)_invoke(out);
3. Server端的Skeleton接收Stub发来的requestQƈ调用相应的方法:
代码:
_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));
xQ我们终于结束了W一个RMI循环Qƈ得到了Remote接口UserService的Stubc_UserService_StubQ就?5
里面的result?
q里有一个问题,Z?4不直接create _UserService_StubQ而又转了一?5的手呢?因ؓ#4 extends from
EJSWrapperQ它没有能力create StubQ因此必d助#5Qwhich extends from EJSHomeQ这h可以生成一?/p>
Stub。如果不是ؓ了生成这个StubQ应该可以不?5q一步?
W二个RMI循环
OK, now we got the object which is instanceOf _UserService_Stub, and implements UserService
现在我们的Client端走到第三步了:
UserInfo ui = object.getUserInfo(userId);
l箋看代码,开始第二个RMI循环Q?
1. 调用object.getUserInfo()
代码:
UserService object;
object.getUserInfo(userId);
2. 实际是调用_UserService_Stub.getUserInfo(int
arg0)Q在q个Ҏ里面QStub向Skeleton发送了一个getUserInfo的字串和arg0q个参数Q?
代码:
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发来的requestQƈ调用相应的方法:
代码:
_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);
}
最后的最后,#4l于调用了我们写的UserServiceBean里的getUserInfoҎQ这才是我们真正惌d的事?/p>
?
xQ第二个RMI循环也终于结束了?
调用程?nbsp;
回顾一下上面的分析Q可以很清晰的看CơRMI循环的过E,下图(见链?描述了整个流E:
http://www.pbase.com/image/27229257
黄色?Q?Q?0是程序员要写的,其余是系l生成的?
#1是Home interface, #2?4都implements 了它?
#6是Remote interface, #7?9都implements 了它?
#10是Bean实现?
3Qweblogic实现
一个远E对象至要包括4个class文gQ远E对象;q程对象的接口;实现q程接口的对象的stubQ对象的ske
letonq?个class文g?/p>
在EJB中则臛_要包?0个classQ?/p>
Beanc,特定App Server的Bean实现c?/p>
Bean的remote接口Q特定App Server的remote接口实现c,特定App
Server的remote接口的实现类的stubcdskeletonc?/p>
Bean的home接口Q特定App Server的home接口实现c,特定App
Server的home接口的实现类的stubcdskeletonc?/p>
和RMI不同的是QEJB中这10个class真正需要用L写的只有3个,分别是Beancd它的remote接口Qhome接口
Q至于其它的7个class到底是怎么生成Q被打包在什么地方,或者是否需要更多的cLӞ会根据不同的App
Server表现出比较大的差异,不能一概而论?/p>
拿Weblogic的来说吧QWeblogic的Bean实现c,以及两个接口的Weblogic的实现类是在ejbc的时候被打包到EJB
的jar包里面的Q这3个class文g可以看到。而home接口和remote接口的Weblogic的实现类的stubcdskeleton
cL在EJB被部|到Weblogic的时候,由Weblogic动态生成stubcdSkeletoncȝ字节码,因此看不到这4个类
文g?/p>
对于一ơ客Lq程调用EJBQ要l过两个q程对象的多ơRMI循环。首先是通过JNDI查找Home接口Q获得Home
接口的实现类Q这个过E其实相当复杂,首先是找到Home接口的Weblogic实现c,然后创徏一个Home接口的Web
logic实现cȝstubcȝ对象实例Q将它序列化传送给客户端(注意stubcȝ实例是在W?ơRMI循环中,由服?/p>
器动态发送给客户端的Q因此不需要客L保存Home接口的Weblogic实现cȝstubc)Q最后客L获得该stu
bcȝ对象实例Q普通的RMI需要在客户端保存stubc,而EJB不需要,因ؓ服务器会把stubcȝ对象实例发送给
客户端)?/p>
客户端拿到服务器l它的Home接口的Weblogic实现cȝstubcd象实例以后,调用stubcȝcreateҎQ?在代
码上是home.create()Q但是后台要做很多事?,于是l过W?ơRMI循环Q在服务器端QHome接口的Weblogic
实现cȝskeletoncL到stubcȝ调用信息后,由它再去调用Home接口的Weblogic实现cȝcreateҎ?/p>
在服务端QHome接口的Weblogic实现cȝcreateҎ再去调用BeancȝWeblogic实现cȝejbCreateҎQ在?/p>
务端创徏或者分配一个EJB实例Q然后将q个EJB实例的远E接口的Weblogic实现cȝstubcd象实例序列化?/p>
送给客户端?/p>
客户端收到remote接口的Weblogic实现cȝstubcȝ对象实例Q对该对象实例的Ҏ调用Q在客户端代码中?/p>
际上是对remote接口的调用)Q将传送给服务器端remote接口的Weblogic实现cȝskeletoncd象,而skele
toncd象再调用相应的remote接口的Weblogic实现c,然后remote接口的Weblogic实现cd去调用BeancȝWe
blogic实现c,如此完成一ơEJB对象的远E调用?/p>
看了一遍帖子,感觉q是没有说太清楚Q既然写了帖子,想d把它说清楚?/p>
先拿普通RMI来说Q有4个classQ分别是q程对象Q对象的接口Q对象的stubcdskeletoncR而对象本w和?/p>
象的stubcd旉实现了接口类。而我们在客户端代码调用远E对象的时候,虽然在代码中操纵接口Q实质上
是在操纵stubc,例如Q?/p>
接口c:Hello
q程对象QHello_Server
stubc:Hello_Stub
skeletonc:Hello_Skeleton
客户端代码要q样写:
Hello h = new Hello_Stub();
h.getString();
我们不会q样写:
Hello_Stub h = new Hello_Stub();
h.getString();
因ؓ使用接口适用性更q,q更换了接口实现类Q也不需要更改代码。因此客L需要Hello.class和Hello_
Stub.classq两个文件。但是对于EJB来说Q就不需要Hello_Stub.classQ因为服务器会发送给它,但是Hello.
class文g客户端是省不了的Q必L。表面上我们的客L代码在操UHelloQ但别忘CHello只是一个接?/p>
Q抽象的Q实质上是在操纵Hello_Stub?/p>
拿Weblogic上的EJB举例子,10个class分别是:
Beanc:HelloBean Q用L写)
BeancȝWeblogic实现c:HelloBean_Impl QEJBC生成Q?br>
Home接口QHelloHome Q用L写)
Home接口的Weblogic实现c?((Hello Bean))_HomeImplQEJBC生成Q?br>
Home接口的Weblogic实现cȝstubc?((Hello Bean))_HomeImpl_WLStubQ部|的时候动态生成字节码Q?br>
Home接口的Weblogic实现cȝskeletonc?((Hello Bean))_HomeImpl_WLSkeletonQ部|的时候动态生成字节码
Q?br>
Remote接口Q?Hello Q用L写)
Remote接口的Weblogic实现c?((Hello Bean))_EOImplQEJBC生成Q?br>
Remote接口的Weblogic实现cȝstubc?((Hello Bean))_EOImpl_WLStubQ部|的时候动态生成字节码Q?br>
Remote接口的Weblogic实现cȝskeletonc?((Hello Bean))_EOImpl_WLSkeletonQ部|的时候动态生成字节码
Q?/p>
客户端只需要Hello.class和HelloHome.classq两个文件?/p>
((Hello Home)) home = (Home) ((Portable Remote Object)).narrow(ctx.lookup("Hello"), ((Hello
Home)).class);
q一行代码是从JNDI获得Home接口Q但是请CQ接口是抽象的,那么homeq个对象到底是什么类的对象实?/p>
呢?很简单,用toString()输出看一下就明白了,下面一行是输出l果Q?/p>
((Hello Bean))_HomeImpl_WLStub@18c458
q表明homeq个通过从服务器的JNDI树上查找获得的对象实际上是HelloBean_HomeImpl_WLStubcȝ一个实例?/p>
接下来客L代码Q?/p>
Hello h = home.create()
同样Hello只是一个抽象的接口Q那么h对象是什么东西呢Q打C下:
((Hello Bean))_EOImpl_WLStub@8fa0d1
原来是HelloBean_EOImpl_WLStub的一个对象实例?/p>
用这个例子来qC遍EJB调用q程Q?/p>
首先客户端JNDI查询Q服务端JNDI树上Helloq个名字实际上绑定的对象是HelloBean_HomeImpl_WLStubQ所?/p>
服务端将创徏HelloBean_HomeImpl_WLStub的一个对象实例,序列化返回给客户端?/p>
于是客户端得到home对象Q表面上是得到HelloHome接口的实例,实际上是q行了一ơ远E调用得CHelloBea
n_HomeImpl_WLStubcȝ对象实例Q别忘记了HelloBean_HomeImpl_WLStub也实CHelloHome接口?/p>
然后home.create()实质上就是HelloBean_HomeImpl_WLStub.create()Q该Ҏ发送信息给HelloBean_HomeIm
pl_WLSkeletonQ而HelloBean_HomeImpl_WLSkeleton接受C息后Q再去调用HelloBean_HomeImpl的create?/p>
法,x完成W?ơ完整的RMI循环?/p>
注意在这ơRMI循环q程中,q程对象是HelloBean_HomeImplQ远E对象的接口是HelloHomeQ对象的stub是Hel
loBean_HomeImpl_WLStubQ对象的skeleton是HelloBean_HomeImpl_WLSkeleton?/p>
然后HelloBean_HomeImpl再去调用HelloBean_Impl的ejbCreateҎQ而HelloBean_Impl的ejbCreateҎ负
责创建或者分配一个Bean实例Qƈ且创Z个HelloBean_EOImpl_WLStub的对象实例?/p>
q一步比较有的是,在前一步RMI循环中,q程对象HelloBean_HomeImpl在客L有一个代理类HelloBean_Ho
meImpl_WLStubQ但在这一步,HelloBean_HomeImpl自己却充当了HelloBean_Impl的代理类Q只不过HelloBean_
HomeImpl不在客户端,而是在服务端Q因此不q行RMI?/p>
然后HelloBean_EOImpl_WLStub的对象实例序列化q回l客LQ这一步也很有,上次RMIq程Q主角是Hello
Bean_HomeImpl和它的代理类HelloBean_HomeImpl_WLStubQ但q这一ơ换成了HelloBean_EOImpl和它的代理类H
elloBean_EOImpl_WLStub来玩了?/p>
Hello h = home.create();h.helloWorld();
假设Hello接口有一个helloWorldq程ҎQ那么表面上是在调用Hello接口的helloWorldҎQ实际上是在?/p>
用HelloBean_EOImpl_WLStub的helloWorldҎ?/p>
然后HelloBean_EOImpl_WLStub的helloWorldҎ发送信息给服务器上的HelloBean_EOImpl_WLSkeletonQ而H
elloBean_EOImpl_WLSkeleton收到信息以后Q再去调用HelloBean_EOImpl的helloWorldҎ。至此,完成W??/p>
完整的RMI循环q程?/p>
在刚才HelloBean_EOImpl是作E对象被调用的,它的代理cLHelloBean_EOImpl_WLStubQ但现在HelloBea
n_EOImpl要作为HelloBean_Impl的代理类了。现在HelloBean_EOImpl去调用HelloBean_Impl的helloWorldҎ
。注意!HelloBean_Impll承了HelloBeanQ而HelloBean中的helloWorldҎ是我们亲自编写的代码Q现在终
于调用到了我们编写的代码了!
xQ一ơEJB调用q程l于完成。在整个q程中,服务端主要要调用的类是HelloBean_ImplQ?Hello
Bean?_HomeImplQHelloBean_HomeImpl_WLSkeletonQHelloBean_EOImplQHelloBean_EOImpl_WLSkeleton。客
L主要调用的类是HelloBean_HomeImpl_WLStubQHelloBean_EOImpl_WLStubQ这两个cd客户端代码中q不
会直接出玎ͼ出现在代码中的类是他们的接口HelloHome和HelloQ因此客L需要这两个接口文gQ而Stub?/p>
服务器传送给他们的?/p>
4 理解体会
单讲Q就是ؓ了适应分布式开发的需要?
首先Q回到我最后给出的程图?
http://www.pbase.com/nobo123/image/27229257
Client端最原始的冲动,肯定是能直接调用#10.UserServiceBeanq了。那么第一个问题来了,
Client和Server不在一个JVM里?
q好办,我们不是有RMI吗,好,q个问题p么解决了Q?
1. UserServiceBeanInterface.getUserInfo()
2. UserServiceBeanStub
3. UserServiceBeanSkeleton
4. UserServiceBean
用着用着Q第二个问题来了Q?
UserServiceBean只有人用Q没人管理,transaction logic, security logic, bean instance pooling logic
q些不得不考虑的问题Q出水面了?
OKQ我们想到用一个delegateQEJBObjectQ来q行所有这些logic的管理。client和EJBObject打交道,EJBObj
ect调用UserServiceBean?
注意Q这个EJBObject也是一个InterfaceQ?6.UserServiceq个interface正是从它extends而来。ƈ且EJBObje
ct所理的这些logicQ正是AppServer的一部分?
现在的流E变ZQ?
EJBObject
1. UserService.getUserInfo()
2. UserServiceStub
3. UserServiceSkeleton
4. UserServiceImp
5. UserServiceBean
q已l和整幅N?6, #7, #8, #9, #10一一对应了?
现在能满x们的需求了吗?不,W三个问题又来了Q?
既然是分布式开发,那么我当然没理由只用一个Specified ServerQ我可能需要用到好几个不同的ServerQ?/p>
且EJBObject也需要管理呀
OKQؓ了适应你的需要,我们q得加再一个HomeObjectQ首先它来决定用哪个Server(当然Q是׃用JNDI
String讑֮?Q其ơ,它来理EJBObject?
注意Q这个EJBHome也是一个InterfaceQ?1.UserServiceHomeq个interface正是从它extends而来。ƈ且EJBHo
me理EJBObject的logicQ也是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
现在已经完全和流E图的调用顺序一致了?
//EJB的基是RMI IIOP,原理q不是很难,关键是实现v来比较绕Q一个简单的功能要用10个(或更多)cL实现Q但每一个都不是多余的?/strong>
//EJB的这U模式(或说RMIQ完全屏蔽了底层的网l,q很好的实现了对业务代码的保护?/strong>