# Spring Boot Remoting – Spring RMI 注解示例

> 原文： [https://howtodoinjava.com/spring-boot2/spring-remoting-rmi-hessian/](https://howtodoinjava.com/spring-boot2/spring-remoting-rmi-hessian/)

在此[ Spring boot 2 rmi 示例](https://howtodoinjava.com/spring-boot-tutorials/)中，了解 spring 如何提供不同的机制来调用驻留在不同 JVM 中并且很可能在不同服务器中的远程方法。 这称为 RMI（**远程方法调用**）概念。

RMI 出现在 EJB 的早期。 在当今的 HTTP / HTTPS 世界中，SOAP 和 REST 在实现任何服务中都占主导地位，但是在 Spring 框架中，远程调用仍然是一种选择。 今天，我们将在这个领域看到两个新协议，主要是 **Hessian** 和 **RMI** 。

## 1\. 什么是远程调用？

Spring 框架提供了一系列工具，这些工具统称为 **Spring 远程调用**。 这些工具使我们可以在远程系统上调用远程服务，就像它们在本地可用一样。

通常不需要，但是在某些情况下，我们仍然倾向于使用远程调用。 使用远程调用的主要原因是性能和安全性。 基于 HTTP 的服务会影响性能，因为我们必须处理请求和响应（封送/拆组）。 更重要的是，如果仅使用这些技术就已经将服务器公开为远程服务，则客户端除了 RMI 别无选择。

## 2\. Spring 远程调用技术

以下主要是 spring 帮助我们实现远程调用的方法。

*   **远程方法调用（RMI）** – 在 Spring，通过在服务器端使用`RmiServiceExporter`和在客户端使用`RmiProxyFactoryBean`实现了 RMI。
*   **Spring 的 HTTP 调用程序** – 这是通过使用`HttpInvokerProxyFactoryBean`和`HttpInvokerServiceExporter`进行远程调用的另一种方式。 这基本上是通过 HTTP 的[ Java 序列化](https://howtodoinjava.com/java/serialization/a-mini-guide-for-implementing-serializable-interface-in-java/)，支持任何 Java [接口](https://howtodoinjava.com/oops/exploring-interfaces-and-abstract-classes-in-java/)。
*   **Hessian** – 它是 Caucho 提供的基于 HTTP 的轻量级二进制协议。 我们可以使用 Hessian Spring 的`HessianProxyFactoryBean`和`HessianServiceExporter`的 Spring 包装器来公开和使用远程服务。 内部在传输时使用二进制数据
*   **Burlap** – Burlap 是用于远程处理的基于 XML 的协议。 它也是由 Caucho 开发的。 它类似于 Hessian，唯一的区别是 Hessian 是二进制，而 Burlap 是 XML。 与 Hessian 一样，Burlap 需要通过 HTTP 托管。与 Hessian 相似，它具有`BurlapServiceExporter`和`BurlapProxyFactoryBean`类。

    请注意，由于尚未积极开发 Burlap，自 Spring 4.0 起就不再支持它。

## 3\. Spring RMI 远程调用 – 创建 RMI 服务

如上所述，我们将使用 Spring Boot 为 RMI 和 Hessian 开发项目。 我们将创建两个 Maven 项目。 服务器的第一个项目是托管实现，另一个是客户端部分。 客户端应用程序将通过相应的远程协议与服务器通信，以调用远程方法。

#### 3.1. 创建 Maven 项目

创建两个名为`spring-rmi-server`和`spring-rmi-client`的简单的 Spring Boot 项目，它们具有`spring-boot-web`依赖，以将其托管在 Web 服务器中。

为此，我们需要转到 https://start.spring.io/ 并提供 Maven 坐标并选择依赖项。 下载包含框架项目的 zip 文件。 然后，一旦解压缩到合适的文件夹中，我们就需要将其导入 eclipse 中。

客户端项目中不需要 Web 依赖项，因为我们将其作为独立的应用程序运行。

![](img/60af72c7afae4cb981c15c2da3aad276.jpg)

Spring 项目创建

#### 3.2. 创建 Spring RMI 服务

编写我们要公开的 Java 接口和 RMI 服务的实现。 在本教程中，我们将创建一个简单的 hello world 应用程序，该应用程序将根据输入向用户打招呼。 这是必需的接口及其实现。

`HelloWorldRMI.java`

```java
package com.example.springrmiserver.service;

public interface HelloWorldRMI 
{
	public String sayHelloRmi(String msg);
}

```

`HelloWorldRMIimpl.java`

```java
package com.example.springrmiserver.service;

import java.util.Date;

public class HelloWorldRMIimpl implements HelloWorldRMI {

	@Override
	public String sayHelloRmi(String msg) {
		System.out.println("================Server Side ========================");
		System.out.println("Inside Rmi IMPL - Incoming msg : " + msg);
		return "Hello " + msg + " :: Response time - > " + new Date();
	}
}

```

#### 3.3. Spring RMI 服务配置

在服务器项目中创建一个 spring bean 配置类，以使用`RmiServiceExporter`将接口和实现注册到 RMI 导出器。 我们需要提供它的端点名称以进行公开。 这将在基于 rmi 的 url（例如`rmi://localhost:1099?`）中公开实现类。

*   `ServiceInterface` – 我们需要提供要发布的接口的类。
*   `Service` – 这是服务实现类的实例。 为了简单起见，在这里我创建了一个新实例，但是我们可以在此处轻松给出我们要公开的另一个 spring bean。
*   `ServiceName` – 如上所述，这是我们要与 rmi url 中的服务关联的服务名称，客户端将使用该服务名称来调用服务。

`Config.java`

```java
package com.example.springrmiserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.rmi.RmiServiceExporter;
import org.springframework.remoting.support.RemoteExporter;

import com.example.springrmiserver.service.HelloWorldRMI;
import com.example.springrmiserver.service.HelloWorldRMIimpl;

@Configuration
public class Config {

	@Bean
	RemoteExporter registerRMIExporter() {

		RmiServiceExporter exporter = new RmiServiceExporter();
		exporter.setServiceName("helloworldrmi");
		exporter.setServiceInterface(HelloWorldRMI.class);
		exporter.setService(new HelloWorldRMIimpl());

		return exporter;
	}
}

```

#### 3.4. 构建和部署 RMI 服务

现在，我们可以完成最终构建并启动提供的 tomcat 服务器。 确保服务器正常启动，并记下特别是 RMI 端口的端口，该端口应为`1099`。 我们将在客户端使用此端口访问服务。

`Console`

```java
2018-09-07 21:37:22.872  INFO 6452 --- [main] o.s.remoting.rmi.RmiServiceExporter      
: Binding service 'helloworldrmi' to RMI registry: 
RegistryImpl[UnicastServerRef [liveRef: [endpoint:[192.168.1.7:1099](local),objID:[0:0:0, 0]]]]

```

## 4\. Spring 远程调用 – 创建 RMI 客户端

#### 4.1. 在客户端项目中创建完全相同的服务接口

在客户端应用程序中创建相同的 rmi 接口。 这很重要，rmi 将使用此接口作为服务器端接口的框架，否则我们将获得异常。

#### 4.2. Spring RMI 客户端配置

在客户端项目中配置`RmiProxyFactoryBean` bean 与远程服务连接。 这是所需的简单默认配置。

`SpringRmiClientApplication.java`

```java
package com.example.springrmiclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.rmi.RmiProxyFactoryBean;

import com.example.springrmiserver.service.HelloWorldRMI;

@SpringBootApplication
public class SpringRmiClientApplication {

	@Bean
	RmiProxyFactoryBean rmiProxy() {
		RmiProxyFactoryBean bean = new RmiProxyFactoryBean();
		bean.setServiceInterface(HelloWorldRMI.class);
		bean.setServiceUrl("rmi://localhost:1099/helloworldrmi");

		return bean;
	}

	public static void main(String[] args) 
	{
		HelloWorldRMI helloWorldRMI = SpringApplication.run(SpringRmiClientApplication.class, args)
												.getBean(HelloWorldRMI.class);

		System.out.println("================Client Side ========================");

		System.out.println(helloWorldRMI.sayHelloRmi("Sajal"));
	}
}

```

#### 4.3. Spring RMI 远程调用示例

现在，我们需要创建一个主要方法，在该方法中，我们将从 spring 上下文中查找实现接口 bean。 有了实例后，我们现在就可以像调用任何其他本地 java 方法一样调用服务器端方法。

如果一切顺利，将调用您的服务器端方法，检查服务器和客户端中的日志，该日志将可见。

```java
================Server Side ========================
Inside Rmi IMPL - Incoming msg : Sajal
2018-09-07 21:39:14.776  INFO Registering beans for JMX exposure on startup
2018-09-07 21:39:14.808  INFO Started SpringRmiClientApplication in 1.866 seconds (JVM running for 2.368)
================Client Side ========================
Hello Sajal :: Response time - > Fri Sep 07 21:39:14 IST 2018

```

## 5\. Spring Hessian 远程

Hessian 远程调用也与 RMI 远程调用非常相似。 唯一的变化是我们需要服务器端的 Hessian 相关服务导出器和客户端端的基于 Hessian 的代理 bean 工厂。

#### 5.1. Hessian 的 Maven 依赖

`pom.xml`

```java

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

```

#### 5.2. Hessian 的服务接口和实现

像 RMI 一样，我们将创建一对接口及其实现。 我们也可以重用 RMI，但是我在工作区中创建了一个新集。

`HelloWorld.java`

```java
package com.howtodoinjava.hessianserver.hessian;

public interface HelloWorld {
	public String sayHelloWithHessian(String msg);
}

```

`HelloWorldImpl.java`

```java
package com.howtodoinjava.hessianserver.hessian;

import java.util.Date;

public class HelloWorldImpl implements HelloWorld {

	@Override
	public String sayHelloWithHessian(String msg) {
		System.out.println("=============server side==============");
		System.out.println("msg : " + msg);
		return "Hello " + msg + " Response time :: " + new Date();
	}
}

```

#### 5.3. Hessian 服务器端配置 – 导出器

这是服务导出程序，它与 RMI 相同。 我刚刚更改了粗麻布相关的课程。

`HessianConfiguration.java`

```java
package com.howtodoinjava.hessianserver;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.remoting.caucho.HessianServiceExporter;
import org.springframework.remoting.support.RemoteExporter;

import com.howtodoinjava.hessianserver.hessian.HelloWorld;
import com.howtodoinjava.hessianserver.hessian.HelloWorldImpl;

@Configuration
public class HessianConfiguration {

	@Bean(name = "/hellohessian")
	RemoteExporter sayHelloServiceHessian() {
		HessianServiceExporter exporter = new HessianServiceExporter();
		exporter.setService(new HelloWorldImpl());
		exporter.setServiceInterface(HelloWorld.class);
		return exporter;
	}

}

```

#### 5.4. Hessian 的客户端配置 – 代理 Bean

此处的配置与 RMI 相同，但 URL 不同。Hessian 使用 HTTP 作为传输协议。 这是一个二进制协议，意味着所有数据都以二进制格式传输到服务器。 其余的复杂性由 Hessian 和 spring 处理。

`HessianConfiguration.java`

```java
package com.howtodoinjava.example.hessianclient;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.remoting.caucho.HessianProxyFactoryBean;

import com.howtodoinjava.hessianserver.hessian.HelloWorld;

@SpringBootApplication
public class HessianClientApplication {

	@Bean
	public HessianProxyFactoryBean hessianInvoker() {
		HessianProxyFactoryBean invoker = new HessianProxyFactoryBean();
		invoker.setServiceUrl("http://localhost:8080/hellohessian");
		invoker.setServiceInterface(HelloWorld.class);
		return invoker;
	}

	public static void main(String[] args) {
		ConfigurableApplicationContext context =  SpringApplication.run(HessianClientApplication.class, args);
		System.out.println("========Client Side===============");
		HelloWorld helloWorld =   	context.getBean(HelloWorld.class);
		System.out.println(helloWorld.sayHelloWithHessian("Sajal"));
	}
}

```

#### 5.5. Spring Hessian 远程调用示例

一旦运行服务器，就运行客户端应用程序。 我们应该看到已经调用了服务器端方法。

这是来自服务器和客户端的日志。

`Server logs`

```java
2018-09-07 21:46:09.367  INFO 1264 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization started
2018-09-07 21:46:09.403  INFO 1264 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : FrameworkServlet 'dispatcherServlet': initialization completed in 36 ms
=============server side==============
msg : Sajal

```

`Client logs`

```java
2018-09-07 21:46:08.903  INFO 6856 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2018-09-07 21:46:09.197  INFO 6856 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 9090 (http) with context path ''
2018-09-07 21:46:09.205  INFO 6856 --- [           main] c.h.e.h.HessianClientApplication         : Started HessianClientApplication in 6.069 seconds (JVM running for 7.092)
========Client Side===============
Hello Sajal Response time :: Fri Sep 07 21:46:09 IST 2018

```

## 6\. 总结

今天，我们已经看到了如何将远程方法公开为服务，以及如何使用不同的远程协议从客户端使用这些远程方法。 在此演示中，与网络服务不同，我们没有任何这样的载荷，在网络服务中，我们需要进行额外的处理以进行封送/编组。

通过这种方法，可以避免载荷处理的开销，但是需要注意的是，服务器和客户端都应仅是 Java，而不能从其他语言/运行时调用，例如 REST，我们可以轻松地从 JavaScript 调用。

牢记所有这些，我们可以选择所需的适当方法。

学习愉快！

参考文献：

[Spring 5 远程调用文档](https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#remoting)

[下载源码](https://github.com/lokeshgupta1981/SpringExamples/tree/master/spring-rmi)