基于Spring的RPC通讯模型的使用与比较

 更新时间:2018年09月19日 10:02:31   作者:JMCui  
这篇文章主要介绍了基于Spring的RPC通讯模型的使用与比较,详细的介绍了RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker,感兴趣的可以了解一下

一、概念和原理

RPC(remote procedure call),远程过程调用,是客户端应用和服务端之间的会话。在客户端,它所需要的一些功能并不在该应用的实现范围之内,所以应用要向提供这些功能的其他系统寻求帮助。而远程应用通过远程服务暴露这些功能。RPC 是同步操作,会阻塞调用代码的执行,直到被调用的过程执行完毕。

Spring支持多种不同的RPC模型,包括RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker:

客户端:

在所有的模型中,服务都是作为 Spring 所管理的 bean 配置到我们的应用中。这是通过一个代理工厂 bean 实现的,这个bean能够把远程服务像本地对象一样装配到其他bean的属性中。

客户端向代理发起调用,就像代理提供了这些服务一样。代理代表客户端和远程服务进行通信,由它负责处理连接的细节并向远程服务发起调用。

服务端:

Spring 使用远程导出器(remote exporter)将bean方法发布为远程服务。

二、RMI

RMI 最初在JDK 1.1被引入到Java平台中,它为Java开发者提供了一种强大的方法来实现Java程序间的交互。

Spring 提供了简单的方式来发布RMI服务,在服务端,RmiServiceExporter 可以把任何 Spring 管理的bean发布为RMI服务 ,如图所示,RmiServiceExporter 把bean包装在一个适配器类中,然后适配器类被绑定到RMI注册表中,并且代理到服务类的请求。

/**
   * 服务端:
   * <p>
   * 1、默认情况下,RmiServiceExporter 会尝试绑定到本地机器1099端口上的RMI注册表。
   * 2、如果在这个端口没有发现RMI注册表,RmiServiceExporter 将会启动一个注册表。
   * 3、可重写注册表的路径和端口,这个是个大坑,当你设置了registryHost属性的时候,源码中就不创建Registry,而是直接去获取,可是我们自己也没有创建,所以就会报连接不上。
   *
   * @param userService
   * @return
   */
  @Bean(name = "rmiServiceExporter")
  public RmiExporter rmiServiceExporter(UserService userService, Environment environment) {
    String registryHost = environment.getProperty("registryHost");
    int registryPort = environment.getProperty("registryPort", Integer.class);
    RmiExporter rmiExporter = new RmiExporter();
    rmiExporter.setService(userService); //要把该bean(即rmiServiceImpl)发布为一个RMI服务
    rmiExporter.setServiceName("RmiService"); //命名RMI 服务
    rmiExporter.setServiceInterface(UserService.class); //指定服务所实现的接口
    rmiExporter.setRegistryHost(registryHost);
    rmiExporter.setRegistryPort(registryPort);
    return rmiExporter;
  }
/**
 * Created by XiuYin.Cui on 2018/5/14.
 * 
 * 解决设置 registryHost 后,报连接拒绝的问题。
 */
public class RmiExporter extends RmiServiceExporter {

  @Override
  protected Registry getRegistry(String registryHost, int registryPort, RMIClientSocketFactory clientSocketFactory,
                  RMIServerSocketFactory serverSocketFactory) throws RemoteException {


    if (registryHost != null) {
      try {
        if (logger.isInfoEnabled()) {
          logger.info("Looking for RMI registry at port '" + registryPort + "' of host [" + registryHost + "]");
        }
        //把spring源代码中这里try起来,报异常就创建一个
        Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
        testRegistry(reg);
        return reg;
      } catch (RemoteException ex) {
        LocateRegistry.createRegistry(registryPort);
        Registry reg = LocateRegistry.getRegistry(registryHost, registryPort, clientSocketFactory);
        testRegistry(reg);
        return reg;
      }
    } else {
      return getRegistry(registryPort, clientSocketFactory, serverSocketFactory);
    }
  }
}

接下来,来看看客户端是怎么使用这些远程服务的吧!Spring的RmiProxyFactoryBean是一个工厂bean,该bean可以为RMI服务创建代理。该代理代表客户端来负责与远程的RMI服务进行通信。客户端通过服务的接口与代理进行交互,就如同远程服务就是一个本地的POJO。

@Bean(name = "rmiUserServiceClient")
  public RmiProxyFactoryBean RmiUserServiceClient(){
    RmiProxyFactoryBean rmiProxyFactoryBean = new RmiProxyFactoryBean();
    rmiProxyFactoryBean.setServiceUrl("rmi://127.0.0.1:9999/RmiService");
    rmiProxyFactoryBean.setServiceInterface(UserService.class);
    rmiProxyFactoryBean.setLookupStubOnStartup(false);//不在容器启动后创建与Server端的连接
    rmiProxyFactoryBean.setRefreshStubOnConnectFailure(true);//连接出错的时候自动重连
    rmiProxyFactoryBean.afterPropertiesSet();
    return rmiProxyFactoryBean;
  }
 @Resource(name="rmiUserServiceClient")
  private UserService userService;

RMI 的缺陷:

1、RMI很难穿越防火墙,这是因为RMI使用任意端口来交互——这是防火墙通常所不允许的。
2、RMI是基于Java的。这意味着客户端和服务端必须都是用java开发。因为RMI使用了Java的序列化机制,所以通过网络传输的对象类型必须要保证在调用两端的Java运行时中是完全相同的版本。

tips:最近发现Dubbo 底层也是用 RMI 实现的,它把 zookeeper 当作注册表。

三、Hessian 和 Burlap

hessian 和 Burlap 是 Caucho Technology 的两种基于HTTP的轻量级远程服务解决方案。借助于尽可能简单的API和通信协议,它们都致力于简化Web服务。

hessian,像RMI一样,使用二进制消息进行客户端和服务端的交互。但是它与RMI不同的是,它的二进制消息可以移植到其他非Java的语言中。由于它是基于二进制的,所以它在带宽上更具优势。

Burlap 是一种基于XML的远程调用技术,这使得它可以自然而然的移植到任何能够解析XML的语言上。正因为它基于XML,所以相比起Hessian的二进制格式而言,Burlap可读性更强。但是和其他基于XML的远程技术(例如SOAP或XML-RPC)不同,Burlap的消息结构尽可能的简单。

下面我们会介绍 hessian 的使用。Spring 不推荐使用Burlap,BurlapServiceExporter 在4.0后被废弃,不再提供支持。5.0 后直接从开发包丢弃了。

服务端,类似于 RmiServiceExporter ,hessian 也有一个HessianServiceExporter 将 Spring 管理的 bean 发布为 Hessian 服务,不同于RMI的是,HessianServiceExporter是一个Spring MVC控制器,它接收Hessian请求(HTTP协议的请求),并将这些请求转换成对被导出POJO的方法调用。既然是HTTP请求,那我们就必须配置Spring 的DispatcherServlet ,并配置HandlerMapping,将相应的URL映射给HessianServiceExporter。

/**
   * hessian没有注册表,不需要设置 serviceName
   */
  @Bean(name = "hessianServiceExporter")
  public HessianServiceExporter hessianServiceExporter(UserService userService) {
    HessianServiceExporter hessianServiceExporter = new HessianServiceExporter();
    hessianServiceExporter.setService(userService);
    hessianServiceExporter.setServiceInterface(UserService.class);
    return hessianServiceExporter;
  }
  /**
   * 需要配置一个URL映射来确保DispatcherServlet把请求转给HessianServiceExporter
   */
  @Bean(name = "handlerMapping")
  public HandlerMapping handlerMapping() {
    SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
    Properties mappings = new Properties();
    mappings.setProperty("/user.service", "hessianServiceExporter");
    handlerMapping.setMappings(mappings);
    return handlerMapping;
  }

客户端,类似于RmiProxyFactoryBean ,Hessian 也有一个代理工厂Bean——HessianProxyFactoryBean,来创建代理与远程服务进行通信:

@Bean(name = "hessianUserServiceClient")
  public HessianProxyFactoryBean hessianUserServiceClient(){
    HessianProxyFactoryBean proxy = new HessianProxyFactoryBean();
    proxy.setServiceUrl("http://127.0.0.1:8080/user.service");
    proxy.setServiceInterface(UserService.class);
    return proxy;
  }
  @Resource(name="hessianUserServiceClient")
  private UserService userService; 

Hessian 的缺陷:

hessian 和 Burlap 都是基于HTTP的,它们都解决了RMI所头疼的防火墙渗透问题。但是当传递过来的RPC消息中包含序列化对象时,RMI就完胜 Hessian 和 Burlap 了。因为 Hessian 和 Burlap 都采用了私有的序列化机制,而RMI使用的是Java本身的序列化机制。

四、HttpInvoker

RMI 和 Hessian 各有自己的缺陷,一方面,RMI使用Java标准的对象序列化机制,但是很难穿透防火墙。另一方面,Hessian和Burlap能很好地穿透防火墙,但是使用私有的对象序列化机制。就这样,Spring的HTTP invoker应运而生了。HTTP invoker是一个新的远程调用模型,作为Spring框架的一部分,能够执行基于HTTP的远程调用,并使用Java的序列化机制。

HttpInvoker 的使用和 Hessian 很类似,HttpInvokerServiceExporter 也是一个Spring MVC 控制器,也是通过DispatcherServlet 将请求分发给它...

/*Http Invoker*/
  @Bean(name = "httpInvokerServiceExporter")
  public HttpInvokerServiceExporter httpInvokerServiceExporter(UserService userService){
    HttpInvokerServiceExporter httpInvokerServiceExporter = new HttpInvokerServiceExporter();
    httpInvokerServiceExporter.setService(userService);
    httpInvokerServiceExporter.setServiceInterface(UserService.class);
    return httpInvokerServiceExporter;
  }
  /**
   * 需要配置一个URL映射来确保DispatcherServlet把请求转给HessianServiceExporter
   */
  @Bean(name = "handlerMapping")
  public HandlerMapping handlerMapping() {
    SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping();
    Properties mappings = new Properties();
    mappings.setProperty("/user.service", "hessianServiceExporter");
    mappings.setProperty("/userInvoker.service", "httpInvokerServiceExporter");
    handlerMapping.setMappings(mappings);
    return handlerMapping;
  }

客户端,像 RmiProxyFactoryBean 和 HessianProxyFactoryBean 一样,HttpInvoker 也提供了一个代理工厂Bean——HttpInvokerProxyFactoryBean,用于创建HttpInvoker代理来与远程服务通信:

@Bean(name = "httpInvokerUserServiceClient")
  public HttpInvokerProxyFactoryBean httpInvokerUserServiceClient(){
    HttpInvokerProxyFactoryBean proxy = new HttpInvokerProxyFactoryBean();
    proxy.setServiceUrl("http://127.0.0.1:8080//userInvoker.service");
    proxy.setServiceInterface(UserService.class);
    return proxy;
  }

参考资料:《Spring 实战第四版》

演示源代码链接:https://github.com/JMCuixy/SpringForRpc

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持脚本之家。

相关文章

  • Java Volatile关键字同步机制详解

    Java Volatile关键字同步机制详解

    这篇文章主要介绍了Java Volatile关键字同步机制详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SpringBoot整合Liquibase的示例代码

    SpringBoot整合Liquibase的示例代码

    本篇文章给大家介绍SpringBoot整合Liquibase的两种情况分析,看似整合问题很简单,但是很容易出错,下面小编给大家介绍下整合步骤,感兴趣的朋友跟随小编一起看看吧
    2022-02-02
  • springboot整合shardingsphere和seata实现分布式事务的实践

    springboot整合shardingsphere和seata实现分布式事务的实践

    本文主要介绍了springboot整合shardingsphere和seata实现分布式事务的实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • Spring Boot Actuator管理日志的实现

    Spring Boot Actuator管理日志的实现

    本文主要介绍了Spring Boot Actuator管理日志的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-07-07
  • MyBatis配置文件元素示例详解

    MyBatis配置文件元素示例详解

    在MyBatis框架的核心配置文件中,<configuration>元素是配置文件的根元素,其他元素都要在<contiguration>元素内配置,这篇文章主要介绍了MyBatis配置文件元素,需要的朋友可以参考下
    2023-06-06
  • java基础之 Arrays.toString()方法详解

    java基础之 Arrays.toString()方法详解

    这篇文章主要介绍了java基础之 Arrays.toString()方法详解,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2021-02-02
  • common-upload上传文件功能封装类分享

    common-upload上传文件功能封装类分享

    本文介绍一个common-upload上传封装类,为了更方便的上传文件,对common-upload进行了一个简单的封装,大家参考使用吧
    2014-01-01
  • 使用@Value注解从配置文件中读取数组

    使用@Value注解从配置文件中读取数组

    这篇文章主要介绍了使用@Value注解从配置文件中读取数组的操作,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • 详解Java中static关键字和内部类的使用

    详解Java中static关键字和内部类的使用

    这篇文章主要为大家详细介绍了Java中static关键字和内部类的使用,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2022-08-08
  • JDK14的新特性:instanceof模式匹配的使用

    JDK14的新特性:instanceof模式匹配的使用

    这篇文章主要介绍了JDK 14的新特性:instanceof模式匹配的使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-05-05

最新评论