【dubbo源码解析~番外篇】--- JDK和Spring的RMI使用姿势简介

本文详细介绍了RMI(Remote Method Invocation)的基本概念及在JDK原生环境下的使用方法,随后展示了如何利用Spring框架封装RMI进行服务暴露与消费,最后提出了理解Dubbo RPC源码的思考。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本文对应源码地址:https://github.com/nieandsun/dubbo-study



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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

nrsc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值