大家好,我是成都12期学员晋良金,今天为大家讲一下RMI。
1、背景介绍
RMI是Remote Method Invoke的缩写,是JDK提供的一个完善的、简单易用的远程调用框架,它要求客户端和服务器端都是Java程序。
2、知识剖析
如何去使用RMI呢?首先Remote接口用于标示其方法可以从非本地虚拟机上调用的接口。任何远程对象都必须直接或者间接实现此接口。只有在"远程接口"(扩展java.rmi.Remote的接口)中指定的这些方法才可以远程使用。也就是说要远程调用的方法必须在扩展remote接口的接口中声明,并且要抛出RemoteException异常才能被远程调用。远程对象必须实现java.server.UniCastRemoteObject。这样才能保证客户端访问获得远程对象的时候,该远程对象将会把自身的一个拷贝序列化后以socket的形式发送给客户端,此时客户端就会获得这个拷贝作为存根stub,而服务器本身已存在的远程对象称之为骨架。
3、常见问题
4、解决方案
5、编码实战
(1)服务端端接口继承Remote接口,告诉虚拟机此接口可以远程调用,方法抛出RemoteException异常。
public interface MyService extends Remote { //方法必须跑抽RemoteException String sendMessage(String message) throws RemoteException; }
(2)服务端实现类继承UnicastRemoteObject 接口,并且需要在构造方法中抛出RemoteException 异常
public class MyServiceImpl extends UnicastRemoteObject implements MyService { //构造方法必须抛出RemoteException public MyServiceImpl() throws RemoteException { super(); } public String sendMessage(String message) { System.out.println("调用服务端的sendMessage()"); return message + "是的!"; } }
(3)启动服务端。需要将对象注册到注册表之中以供远程调用。
public class Service { public static void main(String[] args) { try { //注册端口号 LocateRegistry.createRegistry(1099); MyService myService = new MyServiceImpl(); Context context = new InitialContext(); //注册对象,将对象与服务名绑定 //bind():如果服务名已与其他对象绑定则抛出NameAlreadyBoundException异常 //rebind():存在则新建 context.rebind("rmi://localhost:1099/MyService", myService); } catch (Exception e) { e.printStackTrace(); } System.out.println("创建服务端成功。"); } }
(4)客户端调用。需要一个跟服务端相同的接口,查找同名对象,即可调用远程方法。
public class Client { public static void main(String[] args) { try { Context context = new InitialContext(); //lookup():查找与指定名称相同的对象 MyService myService = (MyService) context.lookup("rmi://localhost:1099/MyService"); System.out.println(myService.sendMessage("调用服务端成功了吗?")); } catch (Exception e) { e.printStackTrace(); } } }
6、扩展思考
7、参考文献
8、更多讨论
(1)如何实现对两个service的随机调用?
使用一个随机数对2求余实现随机。规定奇数调用service1、偶数调用service2。
(2)在一个service挂掉之后调用另一个service,如何实现?
写一个工具类,将两个service都实例了,使用try catch实现。
(3)RMI是如何实现的?
方法调用从客户对象经占位程序(Stub)、远程引用层(Remote Reference Layer)和传输层(Transport Layer)向下,传递给主机,然后再次经传 输层,向上穿过远程调用层和骨干网(Skeleton),到达服务器对象。 占位程序扮演着远程服务器对象的代理的角色,使该对象可被客户激活。 远程引用层处理语义、管理单一或多重对象的通信,决定调用是应发往一个服务器还是多个。传输层管理实际的连接,并且追踪可以接受方法调用的远程对象。服务器端的骨干网完成对服务器对象实际的方法调用,并获取返回值。返回值向下经远程引用层、服务器端的传输层传递回客户端,再向上经传输层和远程调用层返回。最后,占位程序获得返回值。
可以说,RMI由3个部分构成,第一个是RMIService即JDK提供的一个可以独立运行的程序(bin目录下的rmiregistry),第二个是RMIServer即我们自己编写的一个java项目,这个项目对外提供服务。第三个是RMIClient即我们自己编写的另外一个java项目,这个项目远程使用RMIServer提供的服务。
首先,RMIService必须先启动并开始监听对应的端口。
其次,RMIServer将自己提供的服务的实现类注册到RMIService上,并指定一个访问的路径(或者说名称)供RMIClient使用。
最后,RMIClient使用事先知道(或和RMIServer约定好)的路径(或名称)到RMIService上去寻找这个服务,并使用这个服务在本地的接口调用服务的具体方法。
通俗的讲完了再稍微技术的讲下:
首先,在一个JVM中启动rmiregistry服务,启动时可以指定服务监听的端口,也可以使用默认的端口。
其次,RMIServer在本地先实例化一个提供服务的实现类,然后通过RMI提供的Naming,Context,Registry等类的bind或rebind方法将刚才实例化好的实现类注册到RMIService上并对外暴露一个名称。
最后,RMIClient通过本地的接口和一个已知的名称(即RMIServer暴露出的名称)再使用RMI提供的Naming,Context,Registry等类的lookup方法从RMIService那拿到实现类。这样虽然本地没有这个类的实现类,但所有的方法都在接口里了,想怎么调就怎么调吧。
值得注意的是理论上讲RMIService,RMIServer,RMIClient可以部署到3个不同的JVM中,这个时候的执行的顺序是RMIService---RMIServer—RMIClient。另外也可以由RMIServer来启动RMIService这时候执行的顺序是RMIServer—RMIService—RMIClient。