JAVA RMI源码阅读理解

最近尝试阅读了一番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的 网络分布式应用系统的核心解决方案之一。

猜你喜欢

转载自blog.csdn.net/qq_20597727/article/details/80861906