1 RMI简介
在不考虑dubbo的情况下,一个JVM里如何调用另一个JVM的方法? 我能想到 + 用到的姿势有:
- HttpClient
- RestTemplate
- WebService
- FeignClient
- ServerSocket/Socket
最近又学到了一种姿势RMI — Remote Method Invocation(远程方法调用),本文将简单介绍一下在JDK原生的RMI使用姿势 + Spring封装后的RMI使用姿势。
2 JDK原生的RMI使用姿势
(1)首先定义一个接口
需要注意的是
:
- ①该接口要继承Remote接口
- ②接接口内的方法必须都要抛出RemoteException异常,否则启动报错
- ③服务端+消费端都需要拿到该接口
public interface OrderService extends Remote {
String RMI_URL = "rmi://127.0.0.1:9999/OrderService";
int port = 9999;
/***
*
* @param info
* @return
* @throws RemoteException 注意,这里必须抛出异常
*/
Map<String, String> getOrderInfo(Map<String, String> info) throws RemoteException;
}
(2)服务端
- 实现OrderService 接口
/***
* 为简化实现, 继承UnicastRemoteObject 类
*/
public class OrderServiceImpl extends UnicastRemoteObject implements OrderService {
/***必须要有一个无参构造,且抛出RemoteException异常*/
public OrderServiceImpl() throws RemoteException {
super();
}
@Override
public Map<String, String> getOrderInfo(Map<String, String> info) {
System.out.println("恭喜你, 调通了, 参数: " + JSON.toJSONString(info));
info.put("msg", "你好, 调通了! ");
return info;
}
}
- 暴露服务
@Test
public void rmiServerTest() throws IOException, AlreadyBoundException {
OrderService orderService = new OrderServiceImpl();
//注冊通讯端口
LocateRegistry.createRegistry(OrderService.port);
//注冊通讯路径,其实就是暴露服务
//消费端就可以通过该暴露的URL,找到暴露的OrderService的具体实现OrderServiceImpl
//并且拿到结果返回给消费方 ---> 是不是很神奇???
Naming.bind(OrderService.RMI_URL, orderService);
System.out.println("初始化rmi绑定");
System.in.read();
}
(3)消费端
@Test
public void rmiConsumerTest() throws RemoteException, NotBoundException, MalformedURLException {
//通过服务端暴露的URL,取到远程服务的实现
OrderService orderService = (OrderService) Naming.lookup(OrderService.RMI_URL);
//呼叫远程服务,并通过反射等获取到远程方法调用的结果
Map<String, String> info = new HashMap();
info.put("target", "orderService");
info.put("methodName", "getOrderInfo");
info.put("arg", "1");
Map<String, String> result = orderService.getOrderInfo(info);
System.out.println("远程调用结果为:" + result);
}
(4)测试
消费端
服务端
3 Spring封装后的RMI使用姿势
(1)首先定义一个接口
需要注意的是
:
该接口就没有太多要求了,唯一的要求是:
- 服务端+消费端都需要拿到该接口
public interface HelloService {
String RMI_URL = "rmi://127.0.0.1:8001/HelloService";
Map<String, String> getHelloInfo(Map<String, String> info);
}
(2)服务端
- 实现HelloService 接口
public class HelloServiceImpl implements HelloService {
@Override
public Map<String, String> getHelloInfo(Map<String, String> info) {
System.out.println("恭喜你, 调通了, 参数: " + JSON.toJSONString(info));
info.put("msg", "你好, 调通了! ");
return info;
}
}
- 暴露服务
@Test
public void springRmiServerTest() throws IOException {
RmiServiceExporter rmiServiceExporter = new RmiServiceExporter();
//设置暴露的主机名,127.0.0.1或localhost会报错 --- 这里我不细究了
//rmiServiceExporter.setRegistryHost("localhost");
//设置暴露服务的端口号
rmiServiceExporter.setRegistryPort(8001);
//设置暴露的服务名 远程调用地址应为 127.0.0.1:8001/HelloService
rmiServiceExporter.setServiceName("HelloService");
//设置暴露的接口
rmiServiceExporter.setServiceInterface(HelloService.class);
//指定暴露的具体服务
HelloService helloService = new HelloServiceImpl();
rmiServiceExporter.setService(helloService);
//复写父类的afterPropertiesSet
rmiServiceExporter.afterPropertiesSet();
System.out.println("服务暴露完成!!!");
System.in.read();
}
(3)消费端
@Test
public void rmiConsumerTest() {
RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
//指定要调用的远程服务接口
rmiProxyFactoryBean.setServiceInterface(HelloService.class);
//指定远程服务地址
rmiProxyFactoryBean.setServiceUrl(HelloService.RMI_URL);
//定义是否在首次配置远程服务后缓存该配置
rmiProxyFactoryBean.setCacheStub(true);
//是否在客户端启动的时候检测服务端服务可用性
rmiProxyFactoryBean.setLookupStubOnStartup(true);
//如果远程调用缓存配置报错,设置为true,允许重新调用
rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);
//复写父类的afterPropertiesSet方法
rmiProxyFactoryBean.afterPropertiesSet();
//呼叫远程服务,并通过反射等获取到远程方法调用的结果
HelloService helloService = (HelloService) rmiProxyFactoryBean.getObject();
Map<String, String> info = new HashMap();
info.put("target", "orderService");
info.put("methodName", "getOrderInfo");
info.put("arg", "1");
Map<String, String> result = helloService.getHelloInfo(info);
System.out.println("远程调用结果为:" + result);
}
(4)测试
消费端
服务端
4 思考 — 想真正读懂dubbo的rpc源码,或许应该放弃对下面内容的过度探索
其实这里有一个问题,困扰了我很久很久,就是消费端咋就找到了服务端的服务,并且在消费端一调用,就会直接调用到服务端的方法并获取到返回结果???
说实话3中的这两小段代码,我打着断点跟了很久,也没跟出个道道来!!! —> 之后有时间再深入研究吧。
其实如果你研究过dubbo源码的话,你会发现无论是用rmi协议、http协议还是hessian协议【其他的协议我没具体研究
】,基本都是这样一个套路,即:
在消费端通过
doRefer方法
获取到服务端的服务,并在消费端一调用,就会直接调用到服务端指定要暴露的服务
我自认为如果把这块逻辑的具体实现原理,当成一个无需理解的黑盒的话,dubbo的RPC底层原理,研究起来,会应该会让你省力许多!!!
end