上一篇我们简单了解了RPC及其概念,并且实现了RMI框架进行服务发布,并且用系统自带的注册中心和代
码启动注册中心的方式将服务注册到了注册中心中,并且实现了两种方式的服务消费,那么我们不禁好奇,
这个由JDK官方早期就开发简易实现的RPC框架,如何实现的服务注册?注册中心启动过程是如何做的?如
何实现的服务消费?那么带着这些疑问,开启本篇文章的学习之旅
RegistryImpl服务端启动原理
还记得上篇我们手动启动RMI的注册中心的时候编写的代码吗?
Registry registry = LocateRegistry.createRegistry(9999);
没错就是这一句,经过上篇文章学习,我们知道createRegistry调用以后会启动一个RMI的注册中心,那么我们就从这个方法开始剖析吧:
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
从这里可以看出来createRegistry方法其实内部创建了一个RegistryImpl类
public RegistryImpl(final int var1) throws RemoteException {
//如果端口为默认的1099并且java安全管理器不为空
if (var1 == 1099 && System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(new PrivilegedExceptionAction<Void>() {
public Void run() throws RemoteException {
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
//向上转型将ref引用对象构建并且传递给setup方法
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));
}
}
我们可以看到根据端口号创建了ref引用对象,并且传递进了setup方法,我们来看卡setup方法:
private void setup(UnicastServerRef var1) throws RemoteException {
this.ref = var1;//传来的ref引用
var1.exportObject(this, (Object)null, true);//ref对象调用了exportObject方法
}
setup方法里面调用了ref引用的exportObject方法,并且传递了三个参数,这里的三个参数分别为Remote类型实例、Object类型参数、以及一个布尔值参数,这里的Remote类型的实例在上篇我们曾介绍过,此类接口的实例都是RMI的远程对象,那么我们来看看exportObject方法做了什么操作:
public Remote exportObject(Remote var1, Object var2, boolean var3) throws RemoteException {
//获取远程对象的class
Class var4 = var1.getClass();
Remote var5;
try {
//通过代理的方式创建ref引用
var5 = Util.createProxy(var4, this.getClientRef(), this.forceStubUse);
} catch (IllegalArgumentException var7) {
throw new ExportException("remote object implements illegal remote interface", var7);
}
//如果创建出来的引用属于RemoteStub类及其子类,调用setSkeleton方法
if (var5 instanceof RemoteStub) {
this.setSkeleton(var1);
}
//根据传递进来的远程对象、当前类实例、当前ref引用的id以及传递的boolean参数来创建出Target实例,并再次传递到exportObject方法中
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;
}
这里我们可以注意到两个重要的点,其中一个就是当代理引用类型属于RemoteStub及其子类的时候,会将代理传递给setSkeleton方法,而setSkeleton方法仅仅是校验当前代理的class是否存在,不存在将会创建一个Skeleton,即
this.setSkeleton(var1);
而另外一个点即为通过代理等参数合集创建出Target实例后,再次将Target实例传入ref引用实例的exportObject方法,即
this.ref.exportObject(var6);//这里的ref就是我们上一步setUp方法传递来的LiveRef实例
到目前为止依然都是RMI的赋值和创建工作,接下来开始我们就会进入到连接层相关的内容了,接着我们开始 LiveRef类的exportObject方法,代码如下:
public void exportObject(Target var1) throws RemoteException {
this.ep.exportObject(var1);
}
这里可以看到内部调用了ep的exportObject方法,这个ep对象则是LiveRef对象构造的时候传递来的Endpoint实例对象,那么初始化是什么时候调用的呢?还记得RegistryImpl类的构造方法吗?内部实现了LiveRef类的创建并且传递给UnicastServerRef类进行包装将包装后的UnicastServerRef类实例传递到setUp方法中,即:
LiveRef var1x = new LiveRef(RegistryImpl.id, var1);
RegistryImpl.this.setup(new UnicastServerRef(var1x));
默认的Endpoint接口的实现类为TCPEndpoint,而TCPEndpoint类的exportObject方法内部又调用了自身的transport对象实例的exportObject方法,我们跟进transport类(TCPTransport)的exportObject方法,代码如下:
public void exportObject(Target var1) throws RemoteException {
synchronized(this) {
this.listen();//开启监听,根据传递来的port,创建sokect连接,创建TCPTransport实例
++this.exportCount;
}
boolean var2 = false;
boolean var12 = false;
try {
var12 = true;
super.exportObject(var1);//调用父类的exportObject方法
var2 = true;
var12 = false;
} finally {
if (var12) {
if (!var2) {
synchronized(this) {
this.decrementExportCount();//释放一个exportCount,并且关闭对应的socket连接
}
}
}
}
if (!var2) {
synchronized(this) {
this.decrementExportCount();
}
}
}
可以看到这里就是真正的创建Target对象暴露出去,调用TCPTransport的listen()方法,listen()方法创建了一个ServerSocket,并且启动了一条线程等待客户端的请求。接着调用父类Transport的exportObject()将Target对象存放进ObjectTable中 ,至此我们已经成功的将RegistryImpl 对象创建并且监听等待来自客户端的请求
RMI服务端绑定原理
现在我们已经创建好了RMI的注册中心,是该创建服务端绑定到注册中心了
Naming.rebind("user", userHandler);
我们通过Naming类的方式进行绑定,这样我们可以顺便看看如何调用第二种启动方式的,即:
registry.rebind("user", userHandler);
我们进入Naming类的rebind方法,代码如下:
public static void rebind(String name, Remote obj)
throws RemoteException, java.net.MalformedURLException
{
ParsedNamingURL parsed = parseURL(name);//根据name解析url
Registry registry = getRegistry(parsed);//获取registry实例
if (obj == null)
throw new NullPointerException("cannot bind to null");
registry.rebind(parsed.name, obj);//这里使用持有的registry实例的rebind方法进行服务绑定操作
}
可以看到这里通过getRegistry(parsed)方法获取了registry实例对象,然后调用了registry.rebind方法进行服务绑定操作,没错,这里就是第二种服务启动方式的代码啦!而getRegistry(parsed)方法做了什么呢?其实就是调用了LocateRegistry.getRegistry(parsed.host, parsed.port);方法,这也是我们第二种创建服务的时候获取registry实例的方式,那么一切都知道了,我们来看看服务如何绑定的吧,进入registry实例的rebind方法(RegistryImpl类):
public void rebind(String var1, Remote var2) throws RemoteException, AccessException {
checkAccess("Registry.rebind");
this.bindings.put(var1, var2);//将url和服务引用存入hashTable
}
可以看出来这里就是将解析出来的url和远程对象存入定义好的hashTable中,bindings则是提前定义好的HashTable,如下:
private Hashtable<String, Remote> bindings = new Hashtable(101);
如此,服务便存入注册中心中进行绑定了
RMI客户端请求原理
我们知道客户端请求调用服务端是通过registry.lookup方式获取服务端的远程对象引用,代码如下:
UserHandler handler = (UserHandler) registry.lookup("user");
首先我们来到RegistryImpl_Stub 类的lookup方法,代码如下:
public Remote lookup(String var1) throws AccessException, NotBoundException, RemoteException {
try {
RemoteCall var2 = super.ref.newCall(this, operations, 2, 4905912898345647071L);//从ref引用的newCall方法获取RemoteCall实例
try {
ObjectOutput var3 = var2.getOutputStream();//从RemoteCall实例中获取输出流,将数据写入
var3.writeObject(var1);
} catch (IOException var18) {
throw new MarshalException("error marshalling arguments", var18);
}
super.ref.invoke(var2);//将RemoteCall实例传递给ref引用的invoke方法
Remote var23;
try {
ObjectInput var6 = var2.getInputStream();//获取输入流,读取传来的数据
var23 = (Remote)var6.readObject();
} catch (IOException var15) {
throw new UnmarshalException("error unmarshalling return", var15);
} catch (ClassNotFoundException var16) {
throw new UnmarshalException("error unmarshalling return", var16);
} finally {
super.ref.done(var2);
}
return var23;
} catch (RuntimeException var19) {
throw var19;
} catch (RemoteException var20) {
throw var20;
} catch (NotBoundException var21) {
throw var21;
} catch (Exception var22) {
throw new UnexpectedException("undeclared checked exception", var22);
}
}
这里涉及到了ref对象实例(RemoteRef )的newCall方法,获取RemoteCall实例,此实例将用来进行与客户端的数据传输交互,并且将当前的实例传递给ref的invoke方法,所以我们接着看看RemoteRef 的newCall方法(UnicastRef 类),代码如下:
public RemoteCall newCall(RemoteObject var1, Operation[] var2, int var3, long var4) throws RemoteException {
clientRefLog.log(Log.BRIEF, "get connection");
Connection var6 = this.ref.getChannel().newConnection();//从ref引用对象中开启连接
try {
clientRefLog.log(Log.VERBOSE, "create call context");
if (clientCallLog.isLoggable(Log.VERBOSE)) {
this.logClientCall(var1, var2[var3]);
}
StreamRemoteCall var7 = new StreamRemoteCall(var6, this.ref.getObjID(), var3, var4);//将我们的服务信息(端口等)传递给StreamRemoteCall实例进行构建
try {
this.marshalCustomCallData(var7.getOutputStream());//传输定制化数据(默认无代码实现)
} catch (IOException var9) {
throw new MarshalException("error marshaling custom call data");
}
return var7;
} catch (RemoteException var10) {
this.ref.getChannel().free(var6, false);//调用通信管道中的定时清理连接的方法,内部实现了定时任务调度,清理无用的连接
throw var10;
}
}
可以看出来这里有很多ref引用的操作,而这个ref对象是什么呢?没错就是之前创建的LiveRef实例,通过该实例内部的Channel管道创建连接,进行数据交互,至此RMI的服务注册与调用流程我们已经学习完毕
网友评论