简述spring集成模块中的远程服务
文章初衷为个人学习笔记,存在措辞不当和理解不深入,多包涵、指教。本例主要是在SpringBoot中开发,纯Spring需要一定调整。
RPC和REST区别
RPC(远程服务调用,remote procedure call)是客户端与服务端中间的会话。
实际项目开发中,不可避免存在需要引用其他项目功能,使用远程服务暴露这些功能是其中一种解决方法。
REST(表述性状态转移 ,Representational State Transfer)一种错误的认证是,将其归纳为一种基于Web的
远程服务调用,然REST与RPC没有任何直接关系,RPC是面向服务,RSET正如其名,是一种描述资源状
态以最适合客户端或服务器的形式,从一端转移至另一端。
Spring 中的多种远程调用技术支持RPC
Spring支持多种不同的RPC模型,包含RMI、Caucho的Hessian和Burlab、Spring自带的HTTP invoker。
和独立于平台的JAX-RPC和JAX-WS。
这个模型在几种Spring远程服务实现方式通用,主要流程是,服务提供方(以下简称服务端)需要将开发服务作为一个Spring导出bean,客户端通过一个Spring代理向服务端发起调用。在此过程中,由于采用了Spring作为远程服务的管理者,Spring会捕获一些异常(如java.rmi.RemoteException等),将其重新抛出为非检查性异常,可以让使用者选择是否显示捕获这些异常。
RMI方式
RMI最初在JDK1.1引入到Java平台中,为Java开发者提供了一种实现Java程序之间交互的方式。在此之前,远程调用的方式主要是通过购买第三方产品(ORB)和编写Socket方式实现。
若不在Spring环境中使用RMI话,其主要涉及以下几个步骤:
- 编写一个服务实现类,该类中方法必须抛出java.rmi.RemoteException
- 编写一个继承 至java.rmi.Remote的服务接口
- 运行RMI编译器(rmic),创建客户端和服务端类
- 启动一个RMI注册表,以便持有这些服务
- 在RMI注册表中注册服务
但在Spring使用则无须这么多步骤,得益于Spring的环境支撑,仅需要编写实现服务功能的POJO即可。
Spring中使用RMI服务端主要代码如下:
public interface RemoteTestService {
String say(String userName);
}
public interface RmiTestService extends RemoteTestService{
}
@Service
public class RmiTestServiceImp implements RmiTestService {
@Override
public String say(String userName) {
return "hello," + userName + ",i am a RmiTestService";
}
}
@Bean
public RmiServiceExporter rmiServiceExporter(RmiTestService service) {
RmiServiceExporter exporter = new RmiServiceExporter();
exporter.setService(service);
exporter.setServiceName("rmiTestService");
exporter.setServiceInterface(RmiTestService.class);
//注意,RMI方法需要绑定到某端口的注册表上,默认是1099
exporter.setRegistryPort(9000);
return exporter;
}
服务端通过Spring的RmiServiceExporter将 RmiTestService 导出为一个RMI服务,并设置绑定注册表端口,以便于客户端访问RmiTestService 服务。
RMI方式客户端主要代码如下:
public static final String SERVER_URL = "127.0.0.1";
@Bean
public RmiProxyFactoryBean rmiProxyFactoryBean() {
RmiProxyFactoryBean factoryBean = new RmiProxyFactoryBean();
factoryBean.setServiceUrl("rmi://" + SERVER_URL + ":9000/rmiTestService");
factoryBean.setServiceInterface(RmiTestService.class);
return factoryBean;
}
public interface RemoteTestService {
String say(String userName);
}
public interface RmiTestService extends RemoteTestService{
}
@RestController
@RequestMapping("rt")
public class RemoteTestController {
@Autowired
RmiTestService rmiTestService;
@GetMapping("rmi")
public String rmi(@RequestParam("userName") String userName) {
return rmiTestService.say(userName);
}
}
测试采用Swagger方式。测试结果如图:
RMI注意事项:
- 服务端和客户端必须保证是基于Java,且由于使用 了基于Java的序列化方式,因此双方的JDK版本必须一致。
- 服务端和客户端接口类相同
- 导出类不设置端口默认是1099端口
- 由于使用任意端口的问题,实际使用中会受到防火墙的限制,多用于内网环境下
Hessian和Burlab方式
Hessian 和 Burlab 是Caucho Technology提供的两种基于HTTP的轻量级远程服务解决方案。借助于尽可能简单的API和通信协议,致力于简化Web服务。
Hessian 与RMI类似,使用二进制进行客户端和服务端交互,但对比RMI方式来说又有些许不同,因为采用其自身的序列化化机制,Hessian产生的二进制消息可以移植到其他非Java的语言,如PHP、C等,只要Hessian支持该语言。
Burlab是一种基于XML方式的远程调用技术,可以轻松移植到能够解析XML方式的语言上 。且对比Hessian,因为XML的可读性比二进制强,但在带宽上Hessian方式更具优势。
在Spring中使用Hessian与RMI方式大同小异。
添加maven依赖:
<dependency>
<groupId>com.caucho</groupId>
<artifactId>hessian</artifactId>
<version>4.0.38</version>
</dependency>
服务端主要代码:
@Bean(name = "/hessianServiceExporter")
public HessianServiceExporter hessianExporter(HessianTestService hessianTestService) {
HessianServiceExporter exporter = new HessianServiceExporter();
exporter.setService(hessianTestService);
exporter.setServiceInterface(HessianTestService.class);
return exporter;
}
public interface HessianTestService extends RemoteTestService{
}
@Service
public class HessianTestServiceImp implements HessianTestService {
@Override
public String say(String userName) {
return "hello," + userName + ",i am a HessianTestService";
}
}
因为是基于HTTP实现的远程调用,因此需要在导出类中配置映射地址,以便客户端请求。
客户端主要代码:
@Bean
public HessianProxyFactoryBean hessianProxyFactoryBean() {
HessianProxyFactoryBean factoryBean = new HessianProxyFactoryBean();
factoryBean.setServiceUrl("http://" + SERVER_URL + ":8000/hessianServiceExporter");
factoryBean.setServiceInterface(HessianTestService.class);
return factoryBean;
}
@GetMapping("hessian")
public String hessian(@RequestParam("userName") String userName) {
return hessianTestService.say(userName);
}
(此处不在放置测试结果)
注意事项:
- 需要引入第三方依赖
- Hessian和Burlab采用其私有的序列化机制,如果数据模型过于复杂,可能无法实现序列化
Spring 内置的HttpInvoker
Spring开发团队意识到RMI和基于HTTP(Hessian)的服务的空白(不足之处),一方面,RMI使用标准Java的序列化机制但由于其任意端口而无法越过防火墙,另一方面,Hessian虽然可以 很好穿透防火墙,但其使用其私有的序列化机制,使用中存在一些问题。因此Spring 的HttpInvoker应运而生,其取两者之长合一,使用简便。
其使用方法与Hessian方式类似,不做过多演示。
主要代码:
@Bean(name = "/httpInvokerServiceExporter")
public HttpInvokerServiceExporter httpInvokerServiceExporter(HttpInvokerTestService service) {
HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
exporter.setService(service);
exporter.setServiceInterface(HttpInvokerTestService.class);
return exporter;
}
@Bean
public HttpInvokerProxyFactoryBean httpInvokerProxyFactoryBean() {
HttpInvokerProxyFactoryBean factoryBean = new HttpInvokerProxyFactoryBean();
factoryBean.setServiceUrl("http://" + SERVER_URL + ":8000/httpInvokerServiceExporter");
factoryBean.setServiceInterface(HttpInvokerTestService.class);
return factoryBean;
}
HttpInvoker由于其实Spring内置的,因此默认客户端、服务端双方皆是Spring应用,也即Java应用,且由于其采用Java的序列化机制,双方的JDK版本也必须保持一致。
JAX-WS方式
近年来SOA(面向服务架构)颇为流行,其核心理念:应用程序可以并且应该被设计成依赖于一组公共的核心服务,而不是为每个应用都重新实现相同的功能。
JAX可以发布基于平台独立的SOA服务,但代码设置较多,这里仅演示在Spring中快速开发SOA服务的方式。
服务端核心代码
public interface Jax_wsTestService extends RemoteTestService {
}
@Service
@WebService(serviceName="Jax_wsTestService",targetNamespace="http://Jax_wsTestService/service",name="Jax_wsTestService",portName="Jax_wsTestServicePort")
public class Jax_wsTestServiceImp implements Jax_wsTestService {
@Override
public String say(String userName) {
return "hello," + userName + ",i am a Jax_wsTestService";
}
}
@Bean
public SimpleJaxWsServiceExporter jaxWsServiceExporter() {
SimpleJaxWsServiceExporter exporter=new SimpleJaxWsServiceExporter();
//默认与当前应用使用相同路径,即http://127.0.0.1:8000/
exporter.setBaseAddress("http://127.0.0.1:8002/services/");
return exporter;
}
@Bean
public JaxWsPortProxyFactoryBean jaxWsPortProxyFactoryBean()throws Exception{
JaxWsPortProxyFactoryBean jaxProxy=new JaxWsPortProxyFactoryBean();
jaxProxy.setWsdlDocumentUrl(new URL("http://localhost:8002/services/Jax_wsTestService?wsdl"));
jaxProxy.setServiceInterface(Jax_wsTestService.class);
//命名空间,服务名等可从service上的注解指定
return jaxProxy;
}
在需要导出的服务上通过WebService注解指明导出服务类
的命名空间、服务名等信息,仅需要配置一个简单的导出类,其会自动将含Jax注释的类导出。
相关代码:
github链接 https://github.com/junxiaoyao/springStudy.git