定义
Remote Method Invocation,远程方法调用。是纯Java的网络分布式应用系统的核心解决方案之一。Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。
RPC 的Java版本,EJB的基础技术。
RMI 采用java远程消息交换协议JRMP(Java Remote Method Protocol)进行通讯,是构建在TCP/IP协议上的一种远程调用方法。
RMI 采用stubs(存根)和skeletons(框架)来进行远程对象的通讯。
实现
步骤
- 定义一个扩展了Remote接口的接口,该接口中的每一个方法必须声明它将产生一个RemoteException异常;
- 定义一个实现该接口并继承UnicastRemoteObject的类;
- 使用rmic程序将该实现类生成远程实现所需的存根和框架;
- 创建一个客户程序和服务器进行RMI调用;
- 启动rmiregistry并运行自己的服务程序和客户程序。
服务端
编写服务代码
服务端项目文件结构
--server
----IHello.class
----HelloImpl.class
----HelloServer.class
1
2
3
4
5
6
7
8
9
10
11
12
|
public interface IHello extends Remote {
public String sayHello(String name) throws RemoteException;
}
public class HelloImpl extends UnicastRemoteObject implements IHello {
private static final long serialVersionUID = -171947229144133464L;
public HelloImpl() throws RemoteException{
super();
}
public String sayHello(String name) throws RemoteException {
return "Hello," + name;
}
}
|
rmic 工具生成sutb存根类
该工具在JDK的bin文件夹下,jdk1.2以后的RMI可以通过反射API可以直接将请求发送给真实类,所以不需要skeleton类了。
sutb存根为远程方法类在本地的代理,是在服务端代码的基础上生成的,需要HelloImpl.class文件。
1
|
rmic com.ynthm.demo.rmi.service.HelloImpl
|
该命令生成HelloImpl_Stub.class文件
rmiregistry 工具注册服务
端口号默认为1099
1
2
3
|
rmiregistry 12345
# 查看进程
ps -ef|grep rmiregistry
|
或者服务端代码中添加 LocateRegistry.createRegistry(12345);
启动服务
1
2
3
4
5
6
7
8
9
10
11
|
public class HelloServer {
public static void main(String[] args) {
try{
IHello h = new HelloImpl();
Naming.bind("rmi://192.168.58.164:12345/Hello", h);
System.out.println("HelloServer启动成功");
}catch(Exception e){
e.printStackTrace();
}
}
}
|
客户端
客户端文件结构
--client
----HelloClient.class
--server
----IHello.class
----HelloImpl_Stub.class
启动客户端
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
public class HelloClient {
public static void main(String[] args) {
try {
IHello h = (IHello)Naming.lookup("rmi://192.168.58.164:12345/Hello");
System.out.println(h.sayHello("ynthm"));
} catch (MalformedURLException e) {
System.out.println("url格式异常");
} catch (RemoteException e) {
System.out.println("创建对象异常");
e.printStackTrace();
} catch (NotBoundException e) {
System.out.println("对象未绑定");
}
}
}
|
浅析原理
Client<–>stub<–>[NETWORK]<–>skeleton<–>Server
在jdk1.2之后,skeleton就被合并到server中了,所以看起来是这个样子滴:
Client<—>stub<—>[NETWORK]<—>Server_with_skeleton
- 首先RMIRegistry 运行在server端,RMIRegistry 自身也是一个远程对象。有一点需要注意的是:所有的远程对象(继承了UnicastRemoteObject对象)都会在sever上任意的端口导出自己,因为RMIRegistry 也是一个远程对象,他也在server上导出自己,只是这个端口是广为人知的1099,所有的client都知道这个端口。
- 服务端运行在server上,在UnicastRemoteObject构造函数里面,它把自己导出在server上一个任意端口上,这个端口client是不知道的。
- 当你调用Naming.rebind()的时候,会传入一个HelloImpl 的引用作为第2个参数。Naming 就会构造一个stub对象。
- 当客户端使用 Naming.lookup()的时候,会传入public name 作为参数,RMIRegistry 就会返回stub给客户端调用。
Naming够着stub的过程
1
2
3
4
5
6
7
8
|
a.Naming会使用getClass来获取类的名字,这里就是HelloImpl
b.加上后缀_Stub 变成了 HelloImpl _Stub
c.加载 HelloImpl_Stub.class 到虚拟机中
d.从h中获取RemoteRef 对象
e.就是这个ref对象中封装了服务端细节,包括服务端的hostname、port
f.获取了RemoteRef 对象之后,就可以构造stub对象了。
g.传递stud对象到RMIRegistry中进行绑定,即(publicname,stub)
f.RMIRegistry 中内部使用一个hashmap来存储(publicname,stub)
|
Stub HelloImpl_Stub.class 源码中构造参数需要个RemoteRef类型的对象
Spring 对RMI的支持
RmiServiceExporter 暴露你的服务
RmiProxyFactoryBean 使用服务
Spring 屏蔽 RMI 本身的复杂性,服务端Skeleton和客户端Stub等的处理细节,这些对于服务开发和服务使用的人员来说,都是透明的,无需过度关注,而集中精力开发你的商业逻辑。
服务端发布服务
1
2
3
4
5
6
7
8
9
10
11
|
public interface AccountService {
int queryBalance(String mobileNo);
}
public class MobileAccountServiceImpl implements AccountService {
public int queryBalance(String mobileNo) {
if (mobileNo != null){
return 100;
}
return 0;
}
}
|
配置server.xml
1
2
3
4
5
6
7
8
9
10
11
|
<beans>
<bean id="serviceExporter" class="org.springframework.remoting.rmi.RmiServiceExporter">
<property name="serviceName" value="MobileAccountService" />
<property name="service" ref="accountService" />
<property name="serviceInterface"
value="org.ynthm.rmi.interface.AccountService" />
<property name="registryPort" value="12345" />
<property name="servicePort" value="8088" />
</bean>
<bean id="accountService" class="org.ynthm.service.rmi.MobileAccountServiceImpl" />
</beans>
|
服务端发布服务,供客户端进行调用
1
2
3
4
5
6
7
8
9
|
public class RmiServer {
public static void main(String[] args) throws InterruptedException {
new ClassPathXmlApplicationContext("org/ynthm/service/rmi/server.xml");
Object lock = new Object();
synchronized (lock) {
lock.wait();
}
}
}
|
客户端调用服务
客户端配置 client.xml
1
2
3
4
5
6
7
|
<beans>
<bean id="mobAccountService" class="org.springframework.remoting.rmi.RmiProxyFacoryBean">
<property name="serviceUrl" value="rmi://192.168.1.101:12345/MobileAccountService" />
<property name="serviceInterface"
value="org.ynthm.rmi.interface.AccountService" />
</bean>
</beans>
|
配置中将一个serviceUrl和serviceInterface注入给RmiProxyFactoryBean,即可进行远程方法调用。
1
2
3
4
5
6
7
8
9
10
|
public class RmiClient {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext(
"org/ynthm/client/rmi/client.xml");
AccountService accountService = (AccountService) ctx
.getBean("mobAccountService");
String result = accountService.queryBalance("13800138000");
System.out.println(result);
}
}
|