最近尝试阅读了一番rmi的源码,尝试理解一下rmi的基本工作原理。
目录
1. 服务发布过程
2. 注册服务发布过程
3. 客户端调用过程
4. 时序图与梳理图
首先,奉上rmi在java中最简单的例子调用。
我们定义一个HelloService服务。
public interface HelloService extends Remote{
void sayHello() throws RemoteException;
}
以及它的实现
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
protected HelloServiceImpl() throws RemoteException{
super();
}
@Override
public void sayHello() throws RemoteException{
CommonUtil.println("你好啊,接收到调用");
}
}
还有它的server
public class Server {
public static void main(String[] argds) throws RemoteException, MalformedURLException {
HelloServiceImpl helloService = new HelloServiceImpl();
LocateRegistry.createRegistry(1099);
Naming.rebind("rmi://127.0.0.1/Hello", helloService);
CommonUtil.println("服务启动");
}
}
最后还有客户端调用
public class Client {
public static void main(String[] a) throws RemoteException, NotBoundException, MalformedURLException {
HelloService helloService = (HelloService) Naming.lookup("rmi://127.0.0.1/Hello");
helloService.sayHello();
}
}
(一)服务发布过程
我们首先从rmi的服务发布过程看起。
1.新建一个HelloServiceImpl实例
2.进入构造方法可以在看到,再进入super(),可以看到,调用的是UnicastRemoteObject的构造方法。
protected UnicastRemoteObject(int port) throws RemoteException
{
this.port = port;
exportObject((Remote) this, port);
}
3.进入exportObject()方法,顾名思义,这个方法作用就是发布一个HelloService服务的。参数是当前HelloService实例(继承RemoteServer,RemoteServer实现Remote接口),以及默认为0的参数。可以看到,又新建包装了一个UnicastServerRef作为参数传递进去,UnicastServerRef主要作用就是发布我们这个实例服务。
public static Remote exportObject(Remote obj, int port)
throws RemoteException
{
return exportObject(obj, new UnicastServerRef(port));
}
4.继续深入exportObject,可以看到,英文注释写着发布使用指定关联的对象,这里如果发布的obj是我们提供服务的实例(继承了UnicastRemoteObject),就使用我们新建指定的UnicastServerRef作为ref,这样的话关联起来,sref执行exportObject方法就是发布我们的实例了
/**
* Exports the specified object using the specified server ref.
*/
private static Remote exportObject(Remote obj, UnicastServerRef sref)
throws RemoteException
{
// if obj extends UnicastRemoteObject, set its ref.
if (obj instanceof UnicastRemoteObject) {
((UnicastRemoteObject) obj).ref = sref;
}
return sref.exportObject(obj, null, false);
}
5.注意继续进入UnicastServerRef的exportObject方法,重要代码部分可以看见,Util.createProxy利用我们的实例的类型信息,ref,等创建一个代理对象,这个代理对象实际上就是我们的实例Stub对象,它的作用是作为代理调用我们服务器的发布的服务的实例方法,比如HelloService服务会动态创建一个HelloServiceImpl_Stub类,去代理调用我们的HelloServiceImpl实例方法。同时HelloService_Stub内部封装了接收网络请求的操作,使得网络调用对于服务器来说透明
Class var4 = var1.getClass();
Remote var5;
try {
var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
} catch (IllegalArgumentException var7) {
throw new ExportException("remote object implements illegal remote interface", var7);
}
if (var5 instanceof RemoteStub) {
this.setSkeleton(var1);
}
Target var6 = new Target(var1, this, var5, this.ref.getObjID(), var3);
this.ref.exportObject(var6);
this.hashToMethod_Map = (Map)hashToMethod_Maps.get(var4);
return var5;
6.进入Util.createProxy看一下如何创建代理,var2 || !ignoreStubClasses && stubClassExists(var3)判断我们有没有创建Stub,没有的话调用createStub,参数就是我们实例类名,和ref
public static Remote createProxy(Class<?> var0, RemoteRef var1, boolean var2) throws StubNotFoundException {
Class var3;
try {
var3 = getRemoteClass(var0);
} catch (ClassNotFoundException var9) {
throw new StubNotFoundException("object does not implement a remote interface: " + var0.getName());
}
if (var2 || !ignoreStubClasses && stubClassExists(var3)) {
return createStub(var3, var1);
} else {
//......
}
}
7.再进入createStub方法,加载返回一个HelloServiceImpl_Stub实例,可以看到这里可能会抛出StubNotFoundException ,是啊,我们的HelloServiceImpl_Stub类型文件是谁来生成的啊?查了一下资料是这样说的
编译桩(在JAVA 1.5及以后版本中,如果远程对象实现类继承了UnicastRemoteObject或Activatable,则无需此步,由JVM自动完成。否则需手工利用RMIC工具编译生成此实现类对应的桩类,并放到和实现类相同的编译目录下);
private static RemoteStub createStub(Class<?> var0, RemoteRef var1) throws StubNotFoundException {
String var2 = var0.getName() + "_Stub";
try {
Class var3 = Class.forName(var2, false, var0.getClassLoader());
Constructor var4 = var3.getConstructor(stubConsParamTypes);
return (RemoteStub)var4.newInstance(var1);
} catch (ClassNotFoundException var5) {
throw new StubNotFoundException("Stub class not found: " + var2, var5);
}
8.好了,现在得到一个HelloServiceImpl_Stub,我们要怎么做?回到5.中的代码,可以看到,这里把代理对象和一堆的信息都封装到Target ,然后继续进行exportObject,这里异步就是我们最终发布一个服务的一步。
9.最终进入到这样一个方法,listten中就是开启socket监听请求的方法。
public void exportObject(Target var1) throws RemoteException {
synchronized(this) {
this.listen();
++this.exportCount;
}
//......
10.进入listen方法,关键代码,看起来像是开启一个线程循环监听端口
this.server = var1.newServerSocket();
Thread var3 = (Thread)AccessController.doPrivileged(new NewThreadAction(new TCPTransport.AcceptLoop(this.server), "TCP Accept-" + var2, true));
var3.start();
11.进入AcceptLoop,最终看到一个方法executeAcceptLoop执行,关键内容如下
TCPTransport.connectionThreadPool.execute(TCPTransport.this.new ConnectionHandler(var1, var3));
12.接收到客户端调用时,进入ConnectionHandler,实现了run方法,关键代码,这里处理代理接收到的消息
TCPTransport.this.handleMessages(var14, true);
13.进入方法handleMessages方法,关键内容,这个地方进行真正的服务器实例的方法调用
StreamRemoteCall var6 = new StreamRemoteCall(var1);
if (!this.serviceCall(var6)) {
return;
}
14.进入serviceCall,执行 Target var5 = ObjectTable.getTarget(new ObjectEndpoint(var40, var41));获取到我们发布时封装的Target对象,利用它来进行真正的服务调用。
(二)注册发布过程
为什么会有注册发布过程?因为服务虽然发布了,但是我们的客户端没有渠道获得调用我们已经发布的服务的途径,因此我们的服务需要注册到rmi注册表。并且启动独立的注册中心服务,客户端调用时,先去注册中心找到感兴趣的服务的stub下载到本地,然后进行远程调用。
1.LocateRegistry.createRegistry(1099);新建一个RegistryImpl
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
2.新建方法中,重要部分是this.setup(new UnicastServerRef(var2)),进去一看,最终还是和服务发布一样调用UnicastRemoteObject的exportObject
public RegistryImpl(final int var1) throws RemoteException {
if (var1 == 1099 && System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
RegistryImpl.this.setup(new UnicastServerRef(var1x));
return null;
}
}, (AccessControlContext)null, new SocketPermission("localhost:" + var1, "listen,accept"));
} catch (PrivilegedActionException var3) {
throw (RemoteException)var3.getException();
}
} else {
LiveRef var2 = new LiveRef(id, var1);
this.setup(new UnicastServerRef(var2));
}
}
3.回到服务器发布过程5.可以看到重复的过程,不同的是,根据RegistryImpl,创建的代理对象可不是jvm动态生成的stub,是早就已经编写好的RemoteStub,注册中心服务调用的代理对象,其实和动态生成的差不多,不过就是用来调用注册中心的服务而不是实例的服务。把stub设置为Skeleton,也就是我们注册中心服务调用的代理对象
if (var5 instanceof RemoteStub) {
this.setSkeleton(var1);
}
4.就下来老过程,从【服务发布过程8.】开始看,发布注册中心服务,开启线程循环监听端口,最终从Object’Table获取到RegistryImpl_Stub对象,调用服务器RegistryImpl_Stub.lookUp,查找感兴趣的Stub,通过网络返回给客户端,进行远程调用。
(三)客户端调用过程
1.最终目的是lookUp获取服务的Stub,但是由谁来发起lookUp?
HelloService helloService = (HelloService) Naming.lookup("rmi://127.0.0.1/Hello");
2.进入lookUp,
public static Remote lookup(String name)
throws NotBoundException,
java.net.MalformedURLException,
RemoteException
{
ParsedNamingURL parsed = parseURL(name);
//客户端本地创建一个RegistryImpl_Stub
Registry registry = getRegistry(parsed);
if (parsed.name == null)
return registry;
//远程服务调用服务器的RegistryImpl_Stub服务进行looUp,最终获取到感兴趣服务的LookUp
return registry.lookup(parsed.name);
}
(四)调用梳理图
再上一张网上找的图
总结: 图中的skeleton指的就是我们的HelloServiceImpl_Stub之流,每个Stub都是为我们的网络调用做封装处理,保证了繁琐的网络操作透明化,让我们在客户端调用远程服务就像在本地调用一样,这是一种用于远程过程调用的应用程序编程接口,是纯java的 网络分布式应用系统的核心解决方案之一。