Spring - 几种RPC模型的使用与比较

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又非常适合。

时间: 2024-11-24 02:05:55

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

【Spring】几种RPC模型的使用与比较——Hessian/Burlap

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

【Spring】几种RPC模型的使用与比较——RMI

上回写到<基于JMS的RPC>时使用到了:·org.springframework.jms.remoting.JmsInvokerServiceExporter·org.springframework.jms.remoting.JmsInvokerProxyFactoryBean spring在实现RPC的几种方式上都提供了风格一致的支持.在这里我打算把几种RPC模型记录下来并作比较. ·RMI·Hessian/Burlap·HTTP Invoker·JAX-WS 先从最基本的RMI开始.RM

【Spring】几种RPC模型的使用与比较——JAX-WS

看来使用web service是不可避免的.我曾对这个有些抵触,因为他给我印象总是麻烦+慢(后来虽然方便了许多,但还是很慢).然后再去搜索"advantages of web service"什么的试着再让自己接受他.简单记录一下如何用Spring导出Endpoint. 假设我想在有一个Spring应用,我需要把一个Pojo或者一部分方法导出为Web Service.但这会有一个问题--Endpoint的生命周期是由JAX-WS runtime来管理(The lifecycle of

Spring远程服务(RPC)

Spring支持几种不同的RPC模型,包括远程方法调用(RMI).Caucho的Hessian和Burlap和Spring自带的HTTP invoker.如下: 无论选择哪一种RPC模型,我们都会发现Spring对每一种模型都提供了风格一致的支持.在所有的模型中,服务都作为Spring所管理的Bean配置到我们的应用中.这是采用一个代理工厂Bean实现的,这个Bean能够像本地对象一样将远程服务装配到其他Bean的属性中去.它的工作原理如下:

rabbitmq五种消息模型整理

目录 0. 配置项目 1. 基本消息模型 1.1 生产者发送消息 1.2 消费者获取消息(自动ACK) 1.3 消息确认机制(ACK) 1.4 消费者获取消息(手动ACK) 1.5 自动ACK存在的问题 1.6 演示手动ACK 2. work消息模型 2.1 生产者 2.2 消费者1 2.3 消费者2 2.4 能者多劳 3. 订阅模型分类 4. 订阅模型-Fanout 4.1 生产者 4.2 消费者1 4.3 消费者2 4.4 测试 5. 订阅模型-Direct 5.1 生产者 5.2 消费者1

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

全局事务 全局事务支持对多个事务性资源的操作,通常是关系型数据库和消息队列.应用服务器通过JTA管理全局性事务,API非常烦琐.UserTransaction通常需要从JNDI获取,意味着需要与JNDI绑定在一起,且JTA一般只在应用服务器可用,降低了应用代码的可重用性. 本地事务 本地事务面向具体的资源,例如与JDBC连接关联的事务.本地事务易于使用,但不能跨多个事务性资源.使用JDBC管理事务的代码不能在全局JTA事务中运行,因此不能确保跨多个资源的正确性.且本地事务侵入了编程模型. Spr

spring mvc(4)处理模型数据

处理模型数据 Spring MVC 提供了以下几种途径输出模型数据: – ModelAndView: 处理方法返回值类型为 ModelAndView时, 方法体即可通过该对象添加 模型数据 – Map 及 Model: 入参为org.springframework.ui.Model.org.springframework.ui. ModelMap 或 java.uti.Map 时,处理方法返回时,Map 中的数据会自动添加到模型中. – @SessionAttributes: 将模型中的某个属性

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

关于Spring @RequestBody 自动映射模型 2016年10月18日 22:17:12 稻子丶 阅读数:5049 在很多时候,Spring的注解为我们提供了很多方便,但只知道其用法,不懂其执行原理,有时候出错了,很难快速的定位出错原因,今天我想把自己对于@Requestbody这个注解的一点想法和大家分享下. 首先Spring处理一个请求时,请求的入口就是大家在配置文件中配置的 DispathcherServlet 这分发类,其实这个类能够接受到request的原理就是它实现了Ser

一文让你搞懂Spring的统一事务模型

Spring事务的知识体系 进入主题之前,先来了解一下Spring事务,都有哪些内容: Spring事务包含对分布式事务和单机事务的支持,我们用的比较多的是单机事务,也就是只操作一个数据库的事务. 单机事务,按照用法分,又可以分为编程式事务模型(TransactionTemplate)和声明式事务模型(@Transactional注解),后者可以理解为 aop + 编程式事务模型. 编程式事务模型里面涉及到很多知识点,比如统一事务模型.事务传播级别.事务隔离级别等. 我们今天要讲的是其中一点,统