15.1 Spring 远程调用概览

远程调用是客户端应用和服务端之间的会话。在客户端,它所需要的一些功能并不在该应用的实现范围之内,所以应用要向能提供这些功能的其他系统寻求帮助。而远程应用通过远程服务暴露这些功能。

假设我们想把 Spittr 应用中的某些功能发布为远程服务并提供给其他应用来使用。或许除了现有的基于浏览器的用户界面,我们还想为 Spittr 应用提供桌面应用或移动端应用,如图 15.1 所示。为了实现此想 法,我们需要把 SpitterService 接口的基本功能发布为远程服务。

图 15.1 第三方客户端能够远程调用 Spittr 的服务,从而实现与 Spittr 应用交互

其他应用与 Spittr 之间的会话开始于客户端应用的一个远程过程调用(remote procedure call,RPC)。从表面上看,RPC 类似于调用一个本地对象的一个方法。这两者都是同步操作,会阻塞调用代码的执行,直到被调用的过程执行完毕。

它们的差别仅仅是距离的问题,类似于人与人之间的交流。如果我们在公共场所的饮水机旁讨论周末足球比赛的结果,那我们就是在进行一个本地会话 —— 两人之间的会话发生在同一房间内。同样,本地方法调用是指同一个应用中的两个代码块之间的执行流交换。

另一方面,如果我们拿起电话打给另一个城市的客户端,那我们之间的会话就是通过电话网络远程进行的。类似地,RPC 调用就是执行流从一个应用传递给另一个应用,理论上另一个应用部署在跨网络的一台远程机器上。

正如我之前所述,Spring 支持多种不同的 RPC 模型,包括 RMI、 Caucho 的 Hessian 和 Burlap 以及 Spring 自带的 HTTP invoker。表 15.1 概述了每一个 RPC 模型,并简要讨论了它们所适用的不同场景。

RPC 模型

适用场景

远程方法调用 (RMI)

不考虑网络限制时(例如防火墙),访问/发布基于 Java 的服务

Hessian 或 Burlap

考虑网络限制时,通过 HTTP 访问/发布基于 Java 的服务。Hessian 是二进制协议,而 Burlap 是基于 XML 的

HTTP invoker

考虑网络限制,并希望使用基于 XML 或专有的序列化机制实现 Java 序列化时,访问/发布基于 Spring 的服务

JAX-RPC 和 JAX-WS

访问/发布平台独立的、基于 SOAP 的 Web 服务

不管你选择哪种远程调用模型,我们会发现 Spring 都提供了风格一致的支持。这意味着一旦理解了如何配置 Spring 来使用其中的一种模型,如果我们决定使用另外一种模型的话,将拥有非常低的学习曲线。

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

图 15.2 在 Spring 中,远程服务被代理,所以它们能够像其他 Spring bean 一样被装配到客户端代码中

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

更重要的是,如果调用远程服务时发生 java.rmi.RemoteException 异常,代理会处理此异常并重新抛出非检查型异常 RemoteAccessException。远程异常通常预示着系统发生了无法优雅恢复的问题,如网络或配置问题。既然客户端通常无法从远程异常中恢复,那么重新抛出 RemoteAccessException 异常就能让客户端来决定是否处理此异常。

在服务器端,我们可以使用表 15.1 所列出的任意一种模型将 Spring 管理的 bean 发布为远程服务。图 15.3 展示了远程导出器(remote exporter)如何将 bean 方法发布为远程服务。

图 15.3 使用远程导出器将 Spring 管理的 bean 发布为远程服务

无论我们开发的是使用远程服务的代码,还是实现这些服务的代码,或者两者兼而有之,在 Spring 中,使用远程服务纯粹是一个配置问题。我们不需要编写任何 Java 代码就可以支持远程调用。我们的服务 bean 也不需要关心它们是否参与了一个 RPC(当然,任何传递给远程调用的 bean 或从远程调用返回的 bean 可能需要实现 java.io.Serializable 接口)。

让我们通过 RMI —— Java 最初的远程调用技术 —— 来开始探索 Spring 对远程调用的支持吧。