> For the complete documentation index, see [llms.txt](https://potoyang.gitbook.io/spring-in-action-v4/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://potoyang.gitbook.io/spring-in-action-v4/untitled-9/untitled-5.md).

# 15.1　Spring 远程调用概览

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

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

![图 15.1 第三方客户端能够远程调用 Spittr 的服务，从而实现与 Spittr 应用交互](https://3784153818-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmcjU5gG__lBrRbUxBO%2F-LnlI1DOgpWtXOAAPA2k%2F-LnlIOGlmS8_KXi_fN-6%2F15.1%20%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1.jpg?alt=media\&token=5a081ff9-4fbc-464a-879b-02ab046406c8)

其他应用与 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 一样被装配到客户端代码中](https://3784153818-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmcjU5gG__lBrRbUxBO%2F-LnlI1DOgpWtXOAAPA2k%2F-LnlJaeRf1JmjdIbiSof%2F15.2%20spring%20%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1%E4%BB%A3%E7%90%86.jpg?alt=media\&token=61744f84-c0b5-4371-8fb5-cc81e1751484)

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

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

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

![图 15.3 使用远程导出器将 Spring 管理的 bean 发布为远程服务](https://3784153818-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LmcjU5gG__lBrRbUxBO%2F-LnlI1DOgpWtXOAAPA2k%2F-LnlK1ENxJxrM1XdGHH9%2F15.3%20%E5%8F%91%E5%B8%83%E4%B8%BA%E8%BF%9C%E7%A8%8B%E6%9C%8D%E5%8A%A1.jpg?alt=media\&token=4f2a5ef7-6f01-409d-864e-650ee55ef44f)

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

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