北大青鸟中关村JAVA中最简单的分布式调用RMI系统中目前存在两个JAVA服务,分别是服务A、服务B。现在服务A想要调用服务B中的某个服务,我们怎么实现呢?有人觉得这不很简单,服务B暴露一个服务接口,服务A通过RPC的方式来访问这个接口,这里的RPC可以引用第三方实现,也可以通过简单的REST请求的方式实现。是的,解决这场景的方法有很多,其实JAVA自身也提供了一种更简单的方式,即通过RMI实现跨JVM虚拟机的远程调用。虽然它和现在主流的RPC相比,可能显得比较无力。但是其设计思想,加上它的简单易用,我们不妨来看一下。RMI简介RMI(RemoteMethodInvocation)是一种用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象。远程方法调用特性使Java编程人员能够在网络环境中分布操作。特点是JAVA自带的功能,无需集成任何的外部扩展;数据传输是面向对象的;北大青鸟中关村动态下载对象资源;仅限JAVA间通信;通信协议服务间的通信通过TCP传输。协议约定为rmi://,仅限JAVA之间的远程通信;成员RMIRegistry:作为存储远程服务的代理对象的仓库Server:服务端,暴露远程对象,并将其代理对象注册进RMIRegistryClient:客户端,查找远程代理对象,远程调用服务对象北大青鸟中关村运行机制从上图可以看出,虽然RMI目前看上去有点过时了,但其思想和现在的服务注册与发现还是很相似的。归纳起来,包含以下几点:1.启动注册中心2.服务端:暴露服务北大青鸟中关村3.服务端:服务注册4.客户端:获取服务地址(代理对象)5.客户端:远程调用服务使用方法启动RMIRegistry这里启动仓库有两种方式,一种是在程序中启动:importjava.rmi.registry.LocateRegistry;Registryregistry=LocateRegistry.createRegistry(REGISTRY_PORT);另一种通过命令启动:/usr/bin/rmiregistryREGISTRY_PORT获取RMIRegistry通过环境变量java.rmi.server.hostname来设置仓库地址importjava.rmi.registry.LocateRegistry;Registryregistry=LocateRegistry.getRegistry(REGISTRY_PORT)定义远程服务接口接口继承Remote接口方法必须抛出RemoteExceptionimportjava.rmi.Remote;publicinterfaceRemoteServiceextendsRemote{//defineyourfunctionObjectrun()throwsRemoteException;}UnicastRemoteObject.exportObject(Remoteobj,intport)创建Remote对象的代理类,并实现Serializable接口北大青鸟中关村在TCP上暴露远程服务port为0表示使用匿名随机端口(使用1~1023的已知端口时,注意权限问题)importjava.rmi.server.UnicastRemoteObject;RemoteremoteProxy=UnicastRemoteObject.exportObject(your_remote_service,0);注册远程对象到RMIRegistry(在Registry中的都是对象的远程代理类,并非真正的对象)获取Registry的远程代理类,然后调用它的rebind将代理对象注册进仓库中(Naming.rebind(Stringname,Remoteobj)本质上也是解析name中的仓库地址,获取仓库的代理对象,进而进行远程注册)//本地创建或远程获取RegistryRegistryregistry=...registry.rebind(Stringname,Remoteobj);查找远程调用对象Registryregistry=LocateRegistry.getRegistry(REGISTRY_PORT);Remoteobj=registry.lookup(REMOTE_NAME);示例###准备工作:定义远程对象接口packagecom.test.remote;importjava.rmi.Remote;importjava.rmi.RemoteException;publicinterfaceRemoteServiceextendsRemote{Objectrun()throwsRemoteException;Objectrun(Objectobj)throwsRemoteException;}###服务B:注册远程服务实现远程服务对象packagecom.test.serviceB.publishService;importcom.test.remote.RemoteService;importjava.rmi.RemoteException;publicclasspService1implementsRemote北大青鸟中关村Service{publicObjectrun(){System.out.println(invokepService1.);returnsuccess;}publicObjectrun(Objectobj)throwsRemoteException{System.out.println(invokepService1,paramsis+obj.toString());returnsuccess;}}启动服务创建RMIRegistry(也可在通过命令rmiregistry在应用外创建)实例化远程服务导出远程对象,使其能接受远程调用将导出的远程对象绑定到仓库中等待服务调用publicclassBoot{privatestaticfinalStringREMOTE_P1=serviceB:p1;privatestaticfinalintREGISTRY_PORT=9999;publicstaticvoidmain(String[]args)throwsRemoteException{//实例化远程对象,并创建远程代理类RemoteServicep1=newpService1();Remotestub1=UnicastRemoteObject.exportObject(p1,0);//本地创建Registry,并注册远程代理类Registryregistry=LocateRegistry.createRegistry(REGISTRY_PORT);registry.rebind(REMOTE_P1,stub1);System.out.println(servicebbound);}}###服务A:调用远程服务启动服务连接仓库在Registry中查找所调用服务的远程代理类调用代理类方法北大青鸟中关村publicclassBoot{privatestaticfinalStringREMOTE_P1=serviceB:p1;privatestaticfinalintREGISTRY_PORT=9999;publicstaticvoidmain(String[]args)throwsRemoteException{try{Registryregistry=LocateRegistry.getRegistry(REGISTRY_PORT);//从仓库中获取远程代理类RemoteServicep1=(RemoteService)registry.lookup(REMOTE_P1);//远程动态代理Stringres1=(String)p1.run();System.out.printf(Theremotecallfor%s%s\n,REMOTE_P1,res1);}catch(NotBoundExceptione){e.printStackTrace();}catch(RemoteExceptione){e.printStackTrace();}}}演示结果启动服务Bservicebbound启动服务ATheremotecallforserviceB:p1successProcessfinishedwithexitcode0查看服务B调用情况servicebboundinvokepService1.高级用法上面示例没有涉及到远程调用的传参问题。如果需要传参,且传参的类型不是基本类型时,远程服务就需要动态的去下载资源。这里通过设置环境变量来实现远程下载:北大青鸟中关村java.rmi.server.codebase:远程资源下载路径(必须是绝对路径),可以是file://,ftp://,http://等形式的;java.rmi.server.useCodebaseOnly:默认为true,表示仅依赖当前的codebase,如果使用外部的codebase(服务B需要使用服务A提供的下载地址时),需将此参数设置为false;对于跨主机的访问,RMI加入了安全管理器(SecurityManager),那么也需要对应的安全策略文件java.security.policy:指定策略文件地址;其他设置:java.rmi.server.hostname:设置仓库的主机地址;sun.rmi.transport.tcp.handshakeTimeout:设置连接仓库的超时时间;核心代码关于源码的阅读,网上曾经看到一句话讲的很好,“源码阅读的什么样程度算好,阅读到自己能放过自己了,那就够了。”我一般喜欢带着问题来阅读,这里我从几个问题入手,简单分享下我的理解。获取到的Registry对象到底是什么东西?从这段代码来分析:Registryregistry=LocateRegistry.getRegistry(REGISTRY_PORT);北大青鸟中关村远程对象到底是什么,原始对象又在哪里呢?从这段代码来分析:Remoteobj=UnicastRemoteObject.exportObject(Remoteobj,intport);北大青鸟中关村知道了仓库中存放、获取的都是远程对象的代理类,那么实际的远程通信是如何完成的?知道JDK动态代理的同学,肯定有一个invoke方法,是方法调用的关键。这里的invoke()具体代码在UnicastRef中。北大青鸟中关村北大青鸟中关村问题从源码中可以看出,远程调用每次都会新建一个connection,感觉这里会成为一个性能的瓶颈。