Spring中,用JMS搞RPC时会用到:

  • org.springframework.jms.remoting.JmsInvokerServiceExporter
  • org.springframework.jms.remoting.JmsInvokerProxyFactoryBean

spring在实现RPC的几种方式上都提供了风格一致的支持。
在这里我打算把几种RPC模型记录下来并作比较。

  • RMI
  • Hessian/Burlap
  • HTTP Invoker
  • JAX-WS

RMI

先从最基本的RMI开始。
RMI相关的API早在JDK1.1时就有了,我在这里简单描述一下RMI的原生实现(代码可以从别的地方参考)。

  • 声明一个远程接口,接口必须继承java.rmi.Remote,方法需要抛java.rmi.RemoteException
  • 为远程接口提供实现,实现类需要继承UnicastRemoteObject。
  • 或者可以使用rmi相关命令创建skelton和stub。
  • 启动一个RMI注册表并注册。

如果是spring实现RMI,方法会简单很多。
我们只需要用到两个类:

  • org.springframework.remoting.rmi.RmiServiceExporter
  • org.springframework.remoting.rmi.RmiProxyFactoryBean

我简单定义一下接口和实现类:

package pac.testcase.ws;
public interface MyService {
public boolean inviteMeIn();
public String welcome();
}
package pac.testcase.ws.impl;
import pac.testcase.ws.MyService;
public class MyServiceImpl implements MyService{
public boolean inviteMeIn() {
return true;
}
public String welcome() {
return "Everybody is welcome!!";
}
}

简简单单,不需要继承其他任何东西,非常pojo。

下面是spring相关配置:

<bean id="myService" class="pac.testcase.ws.impl.MyServiceImpl" />
<bean class="org.springframework.remoting.rmi.RmiServiceExporter"
p:service-ref="myService"
p:serviceName="welcomeService"
p:serviceInterface="pac.testcase.ws.MyService"
/>

将我们的pojo导出为RMI服务,在这里我采用默认配置。
地址在默认情况时如下:

/**
* Set the host of the registry for the exported RMI service,
* i.e. {@code rmi://HOST:port/name}
* <p>Default is localhost.
*/
public void setRegistryHost(String registryHost) {
this.registryHost = registryHost;
} /**
* Set the port of the registry for the exported RMI service,
* i.e. {@code rmi://host:PORT/name}
* <p>Default is {@code Registry.REGISTRY_PORT} (1099).
* @see java.rmi.registry.Registry#REGISTRY_PORT
*/
public void setRegistryPort(int registryPort) {
this.registryPort = registryPort;
}

客户端方面使用RmiProxyFactoryBean,被代理的服务就像一个简单的bean一样:

<bean id="clientSideService" class="org.springframework.remoting.rmi.RmiProxyFactoryBean"
p:serviceUrl="rmi://localhost:1099/welcomeService"
p:serviceInterface="pac.test.RemoteService"
/>

配置中的pac.test.RemoteService就是那个简单的bean,根据客户端的需要,在这里重新定义一下。

package pac.test;
public interface RemoteService {
public String welcome();
}

这样就可以在服务端调用了,不用做什么Naming.lookup(serviceUrl)之类的操作,远程调用变得透明。

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
RemoteService service = (RemoteService)context.getBean("clientSideService");
System.out.println(service.welcome());

RMI虽然简单高效,但使用RMI会存在一些问题,比如java序列化的版本问题或者防火墙问题(RMI不是基于HTTP的)。

Hessian / Burlap

Hessian和Burlap,现在进Caucho的网站都几乎见不到这方面的内容了。
我也不知道有没有人还会用这两个东东,虽然去年出了一个版本,但上一个版本是在2010年。
刚才在群里问了一下有没有人用,结果还真有人用Hessian,他们是C#和Java做通信。
Burlap性能更令人头疼,不知道还有没有人提及。
虽然不知道使用情况如何,但也在这里简单记录一下,拓展一下思维。

Hessian和Burlap都是由Caucho提供的,Hessian是Resin的一部分。
这两个东西就像同一件事物的两个部件,比如像这样的枪+链锯?

Hessian是binary transport protocol,但与RMI不同的是他不是java序列化对象,所以他可以和其他语言的程序通信,比如C++、C#、Python、Ruby什么的。
Burlap是基于XML的,自然也可以支持很多不同的语言。
当然,同样地传输内容下,XML的传输量会大一些。
如果要说有什么好处的话也只有可读性了。

实在懒得添加依赖再提供原生实现,但他并不复杂。

Creating a Hessian service using Java has four steps:

  • Create an Java interface as the public API
  • Create a client using HessianProxyFactory
  • Create the Service implementation class
  • Configure the service in your servlet engine.

在这里我主要记录一下如何在spring中导出与调用Hessian service。
正如上面所说,我需要把服务配置到servlet engine中;
服务端和客户端都需要添加一个dependency:

<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.33</version>
</dependency>

正好我这边有个使用springMVC的应用,我就在这个基础上导出Hessian service。

<servlet>
<servlet-name>springServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/spring-mvc.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

简单写一个接口与实现:

package pac.king.common.rpc;
public interface MyHessianService {
public String justHadEnoughParties();
}
package pac.king.common.rpc.impl;
import pac.king.common.rpc.MyHessianService;
public class MyHessianServiceImpl implements MyHessianService {
public String justHadEnoughParties() {
return "Please save me..";
}
}

我在spring-mvc.xml中曾经做了如下配置,并在*Controller中使用了RequestMapping注解去给URL做映射。
但这并不妨碍我导出Hessian service再为其映射一个URL:

<context:component-scan base-package="pac.king.controller"
use-default-filters="false">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Controller" />
</context:component-scan>
<bean id="urlMapping" class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
<property name="mappings">
<value>
/service=myHessianService
</value>
</property>
</bean>

导出Hessian service:

<bean id="myHessianServiceImpl" class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
<bean id="myHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/>

现在可以调用了,我需要在客户端声明一个接口(pac.test.HessianService),再用代理去调用:

<bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/>

调用:

ApplicationContext context = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
HessianService service = (HessianService)context.getBean("myHessianClient");
System.out.println(service.justHadEnoughParties());

console输出:

对于Burlap,几乎与Hessian的配置没什么区别;
只需要把HessianServiceExporter改为BurlapServiceExporter,
并将HessianProxyFactoryBean改为BurlapProxyFactoryBean即可。

RMI使用Java的序列化,而Hessian/Burlap则为了不同语言之间通信而使用私有的序列化。
如果我需要基于HTTP,但我并不需要多语言支持,我只想用Java...

HttpInvoker

我应该说这是基于Http的RMI吗?
虽然看起来两全其美,但也存在让人"遗憾"的地方,
(事实上不怎么遗憾的说,我曾经做过没有Spring的项目,连持久层框架都是自己实现,做得越久越痛苦...)
他没有所谓"原生"的实现,他是Spring的一部分,只能在Spring应用中使用。

Spring为这些RPC通信模型提供的相关类在命名上都有一致,都是:

  • 服务端:*ServiceExporter
  • 客户端:*ProxyFactoryBean

自然地,HttpInvoker将用到

  • org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter
  • org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean

基于HttpInvoker的服务也像Hessian那样由DispatcherServlet进行分发。
鉴于很多相同的地方,我打算继续使用在上一篇中用Hessian通信的接口和实现类。

我几乎不用做任何工作,URL映射也不需要修改,我只需要将服务端的配置修改一下:

<bean id="myHessianServiceImpl" class="pac.king.common.rpc.impl.MyHessianServiceImpl" />
<!-- <bean id="myHessianService" class="org.springframework.remoting.caucho.HessianServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/> -->
<bean id="myHessianService" class="org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter"
p:service-ref="myHessianServiceImpl"
p:serviceInterface="pac.king.common.rpc.MyHessianService"
/>

相应地,客户端也只需要修改一下class:

<!-- <bean id="myHessianClient" class="org.springframework.remoting.caucho.HessianProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/> -->
<bean id="myHessianClient" class="org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean"
p:serviceUrl="http://localhost:8080/runtrain/service"
p:serviceInterface="pac.test.HessianService"
/>

这样就保证了效率又解决了防火墙的问题,
后来我听有人说,他们用Hessian做跨语言通信时,基于Http这个特征并不能解决防火墙的问题。
不知道他们具体情况如何,似乎没说到一块儿...

看了Hessian之后突然感觉Web service这种东西好笨重啊(虽然也有一些方法可以克服部分问题)。
既然有Hessian,那为什么还要用Web service这种东西呢?
我突然开始怀疑起他存在的意义。
搜了一下,结果都是比较RPC通信模型的效率,没有人说他们为什么还要用(都应该有存在的意义吧)...
如果仅仅是效率的话都用Hessian不就得了?
带着这个问题我逛了逛stackoverflow,然后我得到了下面几种答案。

  • 多数人手中拿着锤子的时候,他们倾向于将所有问题都当作钉子,他们通常不会试着去寻找别的工具。导致Web service泛滥的原因也是这个。
  • 我觉得你应该重新看看Web service的优势(结果有人说了跨语言和SOA...果然关键还是相对什么做比较...)
  • Web service比那些non-xml的通信方式慢?这种相对的速度问题更多的取决于业务需求和你自己的代码实现(这个说法也同样适用于反射)。

最后我还是没有得到让我满意的答案,倒是复习了Web service...
很多类似场景下人们都将Web service视为"standard"option。
既然如此...那就看看Web service吧。

JAX-WS

看来使用web service是不可避免的。
我曾对这个有些抵触,因为他给我印象总是麻烦+慢(后来虽然方便了许多,但还是很慢)。
然后再去搜索"advantages of web service"什么的试着再让自己接受他。
简单记录一下如何用Spring导出Endpoint。

假设我想在有一个Spring应用,我需要把一个Pojo或者一部分方法导出为Web Service。
但这会有一个问题——Endpoint的生命周期是由JAX-WS runtime来管理(The lifecycle of such an endpoint instance will be managed by the JAX-WS runtime),
Spring context中的Bean无法autowire到Endpoint中,而我要导出的那些东东都用到了Spring管理的Bean。

对此,我们有两个解决方法:

  • org.springframework.web.context.support.SpringBeanAutowiringSupport
  • JaxWsServiceExporter

我上面括号中的那段话是引用的SpringBeanAutowiringSupport的javaDoc。
使用该类的典型案例就是bean注入到JAX-WS endpoint类中(人家注释上写的),任何一个生命周期不是由Spring来管理的场景都可以用到他。
而我们只需要继承这个类,也就是说创建一个实例时会调用父类的无参构造方法,我们来看看他的构造方法:

/**
* This constructor performs injection on this instance,
* based on the current web application context.
* <p>Intended for use as a base class.
* @see #processInjectionBasedOnCurrentContext
*/
public SpringBeanAutowiringSupport() {
processInjectionBasedOnCurrentContext(this);
} /**
* Process {@code @Autowired} injection for the given target object,
* based on the current web application context.
* <p>Intended for use as a delegate.
* @param target the target object to process
* @see org.springframework.web.context.ContextLoader#getCurrentWebApplicationContext()
*/
public static void processInjectionBasedOnCurrentContext(Object target) {
Assert.notNull(target, "Target object must not be null");
WebApplicationContext cc = ContextLoader.getCurrentWebApplicationContext();
if (cc != null) {
AutowiredAnnotationBeanPostProcessor bpp = new AutowiredAnnotationBeanPostProcessor();
bpp.setBeanFactory(cc.getAutowireCapableBeanFactory());
bpp.processInjection(target);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Current WebApplicationContext is not available for processing of " +
ClassUtils.getShortName(target.getClass()) + ": " +
"Make sure this class gets constructed in a Spring web application. Proceeding without injection.");
}
}
}

那就试试看:

@Service
@WebService(serviceName="testMyService")
public class MyServiceEndpoint extends SpringBeanAutowiringSupport{
@Autowired
MyService myService; @WebMethod
public String sayHiFarAway(String name){
return myService.sayHiTo(name);
}
}

接着发布一下:

ApplicationContext context = new ClassPathXmlApplicationContext("classpath*:applicationContext*.xml");
Endpoint.publish("http://localhost:8080/myservices", (MyServiceEndpoint)context.getBean(MyServiceEndpoint.class));

调用:

javax.xml.ws.Service service = javax.xml.ws.Service.create(url, new QName("http://endpoint.king.pac/","testMyService"));
QName q = new QName("http://endpoint.king.pac/","MyServiceEndpointPort");
MyClientService client = service.getPort(q,MyClientService.class);
System.out.println(client.sayHiFarAway("King"));

写一个EndPoint还要继承和业务无关的类,让人不爽...而且发布和调用都麻烦。
那试试SimpleJaxWsServiceExporter,只需要简单的配置就可以导出一个EndPoint。
但是他也有需要注意的地方,引用一下该类的javaDoc:

Note that this exporter will only work if the JAX-WS runtime actually supports publishing with an address argument, i.e. if the JAX-WS runtime ships an internal HTTP server. This is the case with the JAX-WS runtime that's inclued in Sun's JDK 1.6 but not with the standalone JAX-WS 2.1 RI.

SimpleJaxWsServiceExporter会自动detect所有被WebService注解的类,因此只需要在配置中声明即可;此时Endpoint地址直接使用默认的localhost:8080:

<bean class="org.springframework.remoting.jaxws.SimpleJaxWsServiceExporter" />

接着改善一下客户端的调用,使用JaxWsPortProxyFactoryBean:

<bean id="clientSide" class="org.springframework.remoting.jaxws.JaxWsPortProxyFactoryBean"
p:wsdlDocumentUrl="http://localhost:8080/testMyService?wsdl"
p:serviceName="testMyService"
p:portName="MyServiceEndpointPort"
p:serviceInterface="pac.king.endpoint.MyClientService"
p:namespaceUri="http://endpoint.king.pac/"
/>

这样就可以像使用普通bean一样使用service了:

MyClientService client = (MyClientService)context.getBean("clientSide");
System.out.println(client.sayHiFarAway("King"));

用起来方便了不少,但仍然无法改变一个事实:

Web service requests are larger than requests encoded with a binary protocol.

还有就是http/https的问题。
如果使用不当,我可能需要多做些工作去处理HTTP做不来的事情,而这时候RMI又非常适合。

Spring - 几种RPC模型的使用与比较的更多相关文章

  1. RabbitMQ除开RPC的五种消模型----原生API

    2.五种消息模型 RabbitMQ提供了6种消息模型,但是第6种其实是RPC,并不是MQ,因此不予学习.那么也就剩下5种. 但是其实3.4.5这三种都属于订阅模型,只不过进行路由的方式不同. 通过一个 ...

  2. rabbitmq五种消息模型整理

    目录 0. 配置项目 1. 基本消息模型 1.1 生产者发送消息 1.2 消费者获取消息(自动ACK) 1.3 消息确认机制(ACK) 1.4 消费者获取消息(手动ACK) 1.5 自动ACK存在的问 ...

  3. 译: 6. RabbitMQ Spring AMQP 之 RPC

    Remote procedure call (RPC) 在第二篇教程中,我们学习了如何使用工作队列在多个工作人员之间分配耗时的任务. 但是如果我们需要在远程计算机上运行一个函数并等待结果呢?嗯,这是一 ...

  4. Spring框架事务支持模型的优势

    全局事务 全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列.应用服务器通过JTA管理全局性事务,API非常烦琐.UserTransaction通常需要从JNDI获取,意味着需要与JND ...

  5. spring mvc(4)处理模型数据

    处理模型数据 Spring MVC 提供了以下几种途径输出模型数据: – ModelAndView: 处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加 模型数据 – Map ...

  6. RabbitMQ之五种消息模型

    首先什么是MQ MQ全称是Message Queue,即消息对列!消息队列是典型的:生产者.消费者模型.生产者不断向消息队列中生产消息,消费者不断的从队列中获取消息.因为消息的生产和消费都是异步的,而 ...

  7. 关于Spring @RequestBody 自动映射模型原理

    关于Spring @RequestBody 自动映射模型 2016年10月18日 22:17:12 稻子丶 阅读数:5049   在很多时候,Spring的注解为我们提供了很多方便,但只知道其用法,不 ...

  8. 简述linux同步与异步、阻塞与非阻塞概念以及五种IO模型

    1.概念剖析 相信很多从事linux后台开发工作的都接触过同步&异步.阻塞&非阻塞这样的概念,也相信都曾经产生过误解,比如认为同步就是阻塞.异步就是非阻塞,下面我们先剖析下这几个概念分 ...

  9. Linux下5种IO模型的小结

    概述 接触网络编程,我们时常会与各种与IO相关的概念打交道:同步(Synchronous).异步(ASynchronous).阻塞(blocking)和非阻塞(non-blocking).关于概念的区 ...

随机推荐

  1. ODBC方式操作oracle数据库

    ODBC方式操作oracle数据库 一.查询语句: var strConn = System.Configuration.ConfigurationManager.ConnectionStrings[ ...

  2. drf 三级菜单后台序列化以及数据库的设计

    第一种, model.py结构: class GoodsCategory(models.Model): """ 商品类别 """ CATEG ...

  3. luoguP3359 改造异或树

    https://www.luogu.org/problemnew/show/P3359 因为 a ^ b ^ b = a,所以我们预处理 1 到所有点的距离,将删边的操作反过来变成加边,对于每一个联通 ...

  4. 模拟实现strstr和strrstr

    strstr函数用于判断str2是否是str1的子串,如果是,则返回str2在str1中首次出现位置的地址,如果不是则返回NULL.其模拟实现代码如下:#include<iostream> ...

  5. 关于logstash-out-mongodb插件说明

    从kafka获取数据,存到mongodb中.适合空间查询geo_point设置.配置文件如下: input {  kafka {    type => "test"    a ...

  6. View-Controller-Containment

    willMove(toParentViewController:) 调用时机 调用addChildViewController(_:)以钱会被自动调用 调用removeFromParentViewCo ...

  7. CSS 两个行内块元素,宽度相加刚好等于父盒子容器的元素,但第二个元素掉在第二行解决办法

    我们可以发现:两个行内块元素,宽度相加刚好等于父盒子容器的元素,但第二个元素掉在第二行,这是什么问题呢? 我们先来看一下效果: <!DOCTYPE html> <html lang= ...

  8. 海思hi35xx 开发学习(1):海思媒体处理平台架构

    处理平台架构图: 主要分为: 视频输入(VI):VI 模块捕获视频图像,可对其做剪切.去噪等处理,并输出多路不同分辨率的图像数据. 视频处理(VPSS):VPSS 模块接收 VI 和解码模块发送过来的 ...

  9. Linux新手随手笔记1.9-使用Apache搭建网站

    搭建网站 网站服务:让用户能够通过浏览器访问到的服务器上的文档资源. 对比Windows 和Linux部署服务方法对比 Windows:IIS Linux      :Apache ,nginx Ap ...

  10. 日志一直打印 DEBUG o.s.amqp.rabbit.listener.BlockingQueueConsumer

    <?xml version="1.0" encoding="UTF-8"?> <configuration> <logger na ...