服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - Java教程 - 基于Spring的RPC通讯模型的使用与比较

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

2021-06-01 11:25JMCui Java教程

这篇文章主要介绍了基于Spring的RPC通讯模型的使用与比较,详细的介绍了RMI、Caucho的Hessian和Burlap以及Spring自带的HTTP invoker,感兴趣的可以了解一下

一、概念和原理

 

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

spring支持多种不同的rpc模型,包括rmi、caucho的hessian和burlap以及spring自带的http invoker:

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

客户端:

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

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

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

服务端:

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

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

二、rmi

 

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
   * 服务端:
   * <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;
  }
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
/**
 * 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。

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

?
1
2
3
4
5
6
7
8
9
10
@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;
  }
?
1
2
@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。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
   * 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,来创建代理与远程服务进行通信:

?
1
2
3
4
5
6
7
@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;
  }
?
1
2
@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 将请求分发给它...

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/*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代理来与远程服务通信:

?
1
2
3
4
5
6
7
@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

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

原文链接:https://www.cnblogs.com/jmcui/p/9044212.html

延伸 · 阅读

精彩推荐