RMI,是Remote Method Invocation(远程方法调用)的缩写
jvm1的类要使用jvm2的一个类的方法可以使用RMI实现
大致思路:jvm2把自己的一个类通过注册表的形式发布出来(通过字符串和类的映射关系),jvm通过这个字符串(类似于远程引用)去获取服务端的类的代理对象,然后调用其中的方法。
RMI包括以下三个部分:
- Registry: 提供服务注册与服务获取。即Server端向Registry注册服务,比如地址、端口等一些信息,Client端从Registry获取远程对象的一些信息,如地址、端口等,然后进行远程调用。
- Server: 远程方法的提供者,并向Registry注册自身提供的服务
- Client: 远程方法的消费者,从Registry获取远程方法的相关信息并且调用
jdk中java.rmi包
Remote:
一个interface,这个interface中没有声明任何方法。只有定义在“remote interface",即继承了Remote
的接口中的方法,才可以被远程调用。
RemoteException
RemoteException是所有在远程调用中所抛出异常的超类,所有能够被远程调用的方法声明,都需要抛出此异常
Naming
提供向注册中心保存远程对象引用或者从注册中心获取远程对象引用的方法。这个类中的方法都是静态方法,每一个方法都包含了一个类型为String的name参数, 这个参数是URL格式,形如://host:port/name
Registry
一个interface, 其功能和Naming
类似,每个方法都有一个String类型的name参数,但是这个name不是URL格式,是远程对象的一个命名。Registry的实例可以通过方法LocateRegistry.getRegistry()
获得
LocateRegistry
用于获取到注册中心的一个连接,这个连接可以用于获取一个远程对象的引用。也可以创建一个注册中心。
RemoteObject
重新覆写了Object
对象中的equals,hashCode,toString方法,从而可以用于远程调用
UnicastRemoteObject
用于RMI Server中导出一个远程对象并获得一个stub。这个stub封装了底层细节,用于和远程对象进行通信。
Unreferenced
一个interface, 声明了方法:void unreferenced()
如果一个远程对象实现了此接口,则这个远程对象在没有任何客户端引用的时候,这个方法会被调用。
rmi使用
Java中的RMI包括两种情况,一种是采用反射机制,另一种是采用实现Remote接口的方式。
一采用反射机制:
关于此种方式不过多讲述,请参看孙卫琴老师的java网络编程P242中10.2小节在远程方法调用中运用反射机制
二实现Remote接口:
1、扩展Remote接口:定义一个接口是java.rmi包中Remote的子接口,在该接口中定义一个方法用来规定远程对象哪些方法是客户可以请求的。代码如下
package rmi; import java.rmi.Remote; public interface RemoteSubject extends Remote {
public void setHeigh(double height); public void setWidth(double width); public double getArea(); }
2、创建远程对象:远程对象需要实现上述扩展的Remote接口,另外,RMI为了让一个对象成为远程对象还需要做一些初始化工作,该工作java中的UnicastRemoteObject已经帮我们做好了,我们只需要继承它既可。即远程对象需要实现上述扩展的Remote接口同时继承UnicastRemoteObject类(位于java.rmi.server包中)
package rmi; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class RemoteConcreteSubject extends UnicastRemoteObject implements RemoteSubject { private double width,height; protected RemoteConcreteSubject() throws RemoteException { super(); } @Override public void setHeigh(double height) { this.height=height; } @Override public void setWidth(double width) { this.width=width; } public double getArea() { return width*height; } }
3、产生存根(即代理):我们使用rmic命令 远程对象类名.java文件,则会自动产生一个存根字节码文件,注意存根是在服务器端产生的,通常提供一个web服务让客户端下载该存根
4、启动注册:rmiregistry:在启动远程服务器创建远程对象之前,RMI要求远程服务器必须首先启动注册rmiregistry,启动之后服务器才可以创建远程对象,然后将该远程对象注册到rmiregistry所管理的注册表中。在远程终端执行rmiregistry命令即可启动注册。
5、启动远程对象服务:即服务器端运行的业务逻辑代码,远程服务通过使用java.rmi包中的Naming类调用rebind(String name,Remote obj)方法绑定一个远程对象到rmiregistry:所管理的注册表中,该方法的name参数是URL格式,obj是远程对象,将来客户端的代理将通过name找到远程对象obj.。
package rmi; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.RemoteException; public class BindRemoteObjectServer { public static void main(String[] args) { try { RemoteConcreteSubject remoteObject=new RemoteConcreteSubject(); Naming.rebind("rmi://127.0.0.1/rect",remoteObject); System.out.println("ready for client...."); } catch (RemoteException e) { e.printStackTrace(); } catch (MalformedURLException e) { e.printStackTrace(); } } }
6、运行客户端程序:远程服务器端启动远程服务之后,客户端就可以运行相关程序,访问和使用远程对象。客户端使用java.rmi包中的Naming类的lookup(String name)返回一个远程对象的代理,即使用存根产生一个和远程对象具备相同接口的对象,lookup(String nane)方法中的name参数的值必须是远程对象注册的name,如:“rmi://127.0.0.1/rect”
客户端可以像使用远程对象一样来使用lookup(String nane)方法返回的远程对象的代理,这就类似上述反射机制中通过反射得到服务器端的类之后就可以调用其中个的一些方法。
package rmi; import java.net.MalformedURLException; import java.rmi.Naming; import java.rmi.NotBoundException; import java.rmi.Remote; import java.rmi.RemoteException; public class ClientApplication { public static void main(String[] args) { try { Remote remoteObject=Naming.lookup("rmi://127.0.0.1/rect"); RemoteSubject remoteSubject=(RemoteSubject)remoteObject; remoteSubject.setWidth(127); remoteSubject.setHeigh(520); double area=remoteSubject.getArea(); System.out.println(area); } catch (MalformedURLException | RemoteException | NotBoundException e) { e.printStackTrace(); } } }