Spring Boot Remoting – Spring RMI 注解示例

Spring Boot Remoting – Spring RMI 注解示例

原文: https://howtodoinjava.com/spring-boot2/spring-remoting-rmi-hessian/

在此 Spring boot 2 rmi 示例中,了解 spring 如何提供不同的机制来调用驻留在不同 JVM 中并且很可能在不同服务器中的远程方法。 这称为 RMI(远程方法调用)概念。

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

1. 什么是远程调用?

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

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

2. Spring 远程调用技术

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

  • 远程方法调用(RMI) – 在 Spring,通过在服务器端使用RmiServiceExporter和在客户端使用RmiProxyFactoryBean实现了 RMI。
  • Spring 的 HTTP 调用程序 – 这是通过使用HttpInvokerProxyFactoryBeanHttpInvokerServiceExporter进行远程调用的另一种方式。 这基本上是通过 HTTP 的 Java 序列化,支持任何 Java 接口
  • Hessian – 它是 Caucho 提供的基于 HTTP 的轻量级二进制协议。 我们可以使用 Hessian Spring 的HessianProxyFactoryBeanHessianServiceExporter的 Spring 包装器来公开和使用远程服务。 内部在传输时使用二进制数据
  • Burlap – Burlap 是用于远程处理的基于 XML 的协议。 它也是由 Caucho 开发的。 它类似于 Hessian,唯一的区别是 Hessian 是二进制,而 Burlap 是 XML。 与 Hessian 一样,Burlap 需要通过 HTTP 托管。与 Hessian 相似,它具有BurlapServiceExporterBurlapProxyFactoryBean类。

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

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

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

3.1. 创建 Maven 项目

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

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

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

Spring 项目创建

3.2. 创建 Spring RMI 服务

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

HelloWorldRMI.java

package com.example.springrmiserver.service;

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

HelloWorldRMIimpl.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

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

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

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 方法一样调用服务器端方法。

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

================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


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

5.2. Hessian 的服务接口和实现

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

HelloWorld.java

package com.howtodoinjava.hessianserver.hessian;

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

HelloWorldImpl.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

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

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

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

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 远程调用文档

下载源码