一、RMI概述
java RMI(remote method invocation)即远程方法调用,是允许运行在一个java虚拟机上的对象调用运行在另外一个java虚拟机上的对象的方法,JAVA RMI实现JAVA程序之间跨越JVM的远程通信。通过RMI可以让调用远程JVM上对象方法,仿佛调用本地JVM上对象方法一样简单、快捷。
二、RMI框架

RMI主要有三个角色:RMI客户端、RMI服务端、注册表
RMI过程大体如下:
1.客户端从RMI注册表中查询并获取远程对应引用。客户端首先会与Stub进行交互,stub将远程方法所需的参数进行序列化后,传递给远程应用层RRL
2.stub和远程对象具有相同的接口和方法列表,当客户端调用远程对象时,实际是有stub对象代理的。RRL将stub本地引用转换为服务端上对象的远程引用后,再将调用传递给传输层,传输层执行TCP发送
3.RMI服务端传输层监听到请求后,将引用转发给服务端的RRL。
4.服务端RRL将客户端发送的远程应用转换为本地虚拟机引用后,传递给Skeleton。
5.Skeleton读取参数,最后由服务端进行实际方法调用。
6.如果RMI客户端调用存在返回值,则以此向下传递。
7.客户端接收到返回值后,再以此向上传递。然后由stub反序列化返回值,最终传递给RMI客户端
相关类关系如下:

三、RMI开发流程
1.RMI服务端
(1)远程调用对象类:
1.1 定义一个继承Remote接口的interface A,接口中的方法需要抛出RemoteException
1.2 远程调用对象类需继承UnicastRemoteObject类和interface A
(2)启动注册表并绑定对象
2.RMI客户端
(1)定义用于接收远程对象的Remote子接口,只需实现java.rmi.Remote接口即可。但要求必须与服务器端对等的Remote子接口保持一致,即有相同的接口名称、包路径和方法列表等
(2)通过注册表查询需要调用的对象,并强制转换成客户端定义的类
(3)进行方法调用
远程调用对象类
import java.rmi.Remote;
import java.rmi.RemoteException;
public interface Hello extends Remote {
public String welcome(String name) throws RemoteException;
}
import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class HelloImpl extends UnicastRemoteObject implements Hello{ public HelloImpl() throws RemoteException { } public String welcome(String name) throws RemoteException { return "Hello, " + name; } }
创建RMI服务端
import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Server { public static void main(String[] args) throws RemoteException { // 创建对象 Hello hello = new HelloImpl(); // 创建注册表 Registry registry = LocateRegistry.createRegistry(10999); // 绑定对象到注册表,并给他取名为hello registry.rebind("hello",hello); System.out.println("创建服务端成功!"); } }
创建RMI客户端
import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class Client { public static void main(String[] args) throws RemoteException, NotBoundException { // 获取到注册表的代理 Registry registry = LocateRegistry.getRegistry("localhost", 10999); // 利用注册表的代理去查询远程注册表中名为hello的对象 Hello hello = (Hello) registry.lookup("hello"); // 调用远程方法 System.out.println(hello.welcome("tridentj")); } }
简单本地运行的效果如下:


也可以通过Naming类创建,Naming相当于对Registry进行了封装,部分Naming源代码如下:


Naming封装简单示例:
import java.rmi.Naming; import java.rmi.registry.LocateRegistry; public class NamingServer { public static void main(String[] args) { try { HelloImpl hello = new HelloImpl(); //注册RMI端口 LocateRegistry.createRegistry(10999); //绑定对象 Naming.bind("rmi://localhost:10999/hello",hello); System.out.println("启动RMI服务成功!"); }catch (Exception e){ e.printStackTrace(); } } }
import java.rmi.Naming; public class NamingClient { public static void main(String[] args){ try { Hello hello = (Hello) Naming.lookup("rmi://localhost:10999/hello"); System.out.println(hello.welcome("tridentj")); }catch (Exception e){ e.printStackTrace(); } } }
运行效果如下:


四、JNDI
JNDI(JAVA Naming And Directory Interface)是JAVA命名和目录接口。应用程序可以通过JNDI API去调用其他资源,包括JDBC、LDAP、RMI、DNS、NIS、windows注册表等等。JNDI将不同的资源进行统一封装,形成一套API,通过这套API,应用程序可以不用关心需要交互的资源细节,方便快捷的与资源进行交互,从而降低开发难度,提高效率。
使用JNDI创建服务对象
//配置JNDI工厂和JNDI的url
//使用Hashtable或Properties(继承自Hashtable)
Properties pro = new Properties();
pro.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory");
pro.put(Context.PROVIDER_URL,"rmi://localhost:10999");
//创建初始化环境
Context ctx = new InitialContext(pro);
Context.INITIAL_CONTEXT_FACTORY指定JNDI具体处理的类名称,例如RMI为com.sun.jndi.rmi.registry.RegistryContextFactory,LDAP为com.sun.jndi.ldap.LdapCtxFactory
JNDI-RMI远程方法调用
RMI的服务工厂类为com.sun.jndi.rmi.registry.RegistryContextFactory
RMI服务端
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIService { public static void main(String[] args)throws Exception{ //创建rmi映射表 Registry registry = LocateRegistry.createRegistry(10999); Hello hello = new HelloImpl(); //对象板顶到注册表 registry.bind("hello", hello); System.out.println("RMI服务启动成功!"); } }
JNDI-RMI客户端
import javax.naming.Context; import javax.naming.InitialContext; import java.util.Properties; public class JNDIRMIClient { public static void main(String[] args) throws Exception{ //配置JNDI工厂和JNDI的url Properties pro = new Properties(); pro.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.rmi.registry.RegistryContextFactory"); pro.put(Context.PROVIDER_URL,"rmi://localhost:10999"); //创建初始化环境 Context ctx = new InitialContext(pro); //jndi方式获取远程对象 Hello rHello = (Hello) ctx.lookup("rmi://localhost:10999/hello"); /** * 即便配置了context.PROVIDER_URL,当在lookup中修改成其他的url,程序以lookup中为准 * 相当于提前配置的PROVIDER_URL未起作用,故若lookup地址可控制,则存在风险点。 * */ //context.PROVIDER_URL已指定 //Hello rHello = (Hello) ctx.lookup("hello"); System.out.println(rHello.sayHello("tridentj")); } }
运行效果

