接口开发 — — RPC远程过程调用实现
我们在日常开发过程中,经常会遇到与第三方交互的情况,这个时候就会涉及到RPC(Remote Procedure Call)远程过程调用。
比如下表:
其实,这就涉及到RPC的实现了,那么什么是RPC呢?RPC又是怎么实现的呢?
1 介绍
RPC(Remote Procedure Call):远程过程调用,是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。请求程序就是一个客户端,而服务提供程序就是一个服务器。
2 应用场景
3 实现方式
3.1 RMI
RMI:Java提供的基于Java平台的RPC远程调用技术,服务消费者和服务提供者是Java平台。
3.1.1 RMI实现步骤
新建rmi-provider项目
结构图:
3.1.1.1 创建UserService接口
用于提供服务;实现接口时,需要抛出异常【强制】
/**
* 创建需要发布的服务对应的业务接口
* Remote 接口用于标识其方法可以从非本地虚拟机上调用的接口
*/
public interface UserService extends Remote {
public String helloRMI(String name) throws RemoteException;
}
3.1.1.2 创建UserServiceImpl实现类
一定要继承UnicastRemoteObject类,不然会发布失败
/**
* 创建发布的服务对应的实现类
*/
public class UserServiceImpl extends UnicastRemoteObject implements UserService {
public UserServiceImpl() throws RemoteException {
super();
}
@Override
public String helloRMI(String name) {
return "hello:" + name;
}
}
3.1.1.3 发布远程服务【ProviderApp】
/**
* 发布远程服务
*/
public class ProviderApp {
public static void main(String[] args) {
try {
//发布服务的端口
LocateRegistry.createRegistry(8888);
//发布远程服务的URL
String name = "rmi://localhost:8888/rmi";
//创建一个提供具体服务的远程对象
UserService userService = new UserServiceImpl();
//给提供远程服务的对象绑定一个URL
Naming.bind(name, userService);
System.out.println("发布RMI远程服务成功");
} catch (Exception e) {
System.out.println("发布失败.....");
e.printStackTrace();
}
}
}
结果:
3.1.1.4 服务消费者实现【拷贝UserService接口】
创建rmi-consumer项目
项目结构:
//从服务提供端拷贝来的接口【与远程服务提供方保持一致】
public interface UserService {
public String helloRMI(String name);
}
3.1.1.5 消费远程服务【ConsumerApp】
import java.rmi.Naming;
//消费远程服务
public class ConsumerApp {
public static void main(String[] args) {
try {
//访问远程服务的URL
String name = "rmi://localhost:8888/rmi";
//通过发布的远程服务的URL,获得远程服务的代理对象
UserService userService = (UserService) Naming.lookup(name);
System.out.println("获得远程服务的代理对象" + userService.getClass().getName());
//通过远程服务的代理对象调用远程服务的方法
String result = userService.helloRMI("jack");
System.out.println("result:" + result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
结果:
3.1.2 RMI实现过程的常见错误
java的 rmi远程调用给分布式编程带来极大的方便,在使用rmi过程中若遇到以下两个问题,可以尝试如下的解决方法
- 错误一:
java.rmi.server.ExportException : remote object implements illegal remote
interface; nested exception is : java.lang.IllegalArgumentException :
illegal remote method encountered : ,
"解决办法":如果这样提示说明接口函数没有涉及异常抛出,在所提示的函数后面加
上throws RemoteException就可以解决
- 错误二:
exception: java.rmi.UnmarshalException: error unmarshalling return; nested
exception is: java.io.WriteAbortedException: writing aborted;
java.io.NotSerializableException :
"解决办法":若这样提示,表明某个类是不能被序列化的,需要在该类上加上implements
Serializable,就可以解决
需要注意的是,Java的RMI远程调用的两个站点可能需要编写相同的代码,保证不提示什么错误,就可以正常运行
3.1.3 RMI总结
名称 | 含义 |
---|---|
Remote接口 | 标识某个方法可以被远程调用 |
UnicastRemoteObject类 | 实现Remote远程对象的导出 |
Naming | 给提供远程服务的对象绑定URL,通过远程的URL,获得提供远程服务的代理对象 |
LocateRegistry类 | 指定发布的远程服务的方法接口 |
实现步骤:
①创建服务接口
②实现接口内容【具体服务内容】
③发布服务
④消费方拷贝服务接口
⑤消费方消费远程服务
3.2 WebService【http+xml】
3.2.1 定义
通过Http协议,请求发送XML和响应XML的RPC远程调用技术,最大的特征就是使用XML进行数据交互,可以实现跨平台调用。
WebService也叫XML Web Service
,WebSerice是一种可以接收从Internet或者Intranet上的其他系统中传递过来的请求,轻量级的独立的通讯服务。是通过SOAP在Web上提供对的软件服务,使用WSDL文件进行说明,并通过UUDI进行注册。
总的来说,WebService就是一种
跨编程语言和操作系统平台的远程调用技术
。
从多个维度理解WebService:
- 从表面看:WebService就是一个应用程序向外界暴露出一个能够通过Web进行调用的API,也就是说能用编程的方法通过Web来调用这个程序。【调用这个WebService的应用程序叫做客户端;提供这个WebService的应用程序叫做服务端】
- 从深层次看:WebService是建立可交互操作的分布式应用程序的新平台,是一个平台,一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何你喜欢的语言,在任何你喜欢的平台上写WebService,只要我们可以通过WebService标准对这些服务进行查询和访问。
拓展:【WebSocket】
WebSocket是H5支持的新特性,WebSocket他是为了解决客户端发起多个http请求到服务器资源浏览器必须要经过长时间的轮训问题而生的,他实现了多路复用,他是全双工通信。在webSocket协议下客服端和浏览器可以同时发送信息。
- WebService:实现RPC
- WebSocket:实现多路复用
3.2.2 核心要素【SOAP、WSDL、UDDI】
SOAP、WSDL、UDDI(UniversalDescriptionDiscovery andIntegration)三者构成了WebService的三要素。
-
SOAP(Simple Object Access Protocol):
WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果都是采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML格式就是SOAP协议。SOAP提供了标准的RPC方法来调用WebService。
SOAP协议组成:
SOAP协议 = HTTP协议+XML数据格式SOAP协议定义了SOAP消息的格式,SOAP协议是基于HTTP协议的,SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。打个比喻就是:HTTP是普通公路,XML是中间的绿色隔离带和两边的防护栏,SOAP就是普通公路经过隔离带和防护栏改造过的高速公路。
-
WSDL
就像是我们区商店买东西,首先要知道商店里有什么东西可以买,然后再来购买。商家的做法就是张贴海报。WebService也一样,WebSerive客户端要调用一个WebSerivce服务,首先要知道这个服务的地址在哪,以及这个服务里有什么方法可以调用。所以,WebService服务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,服务是什么(服务中有哪些方法,方法接受的参数是什么,返回值是什么),服务的网络地址用哪个URL地址表示,服务通过什么方式来调用。WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述WebService及其函数、参数、返回值。它是WebService客户端和服务端都能理解的标准格式。因为是基于XML的,所以WSDL既是机器可读的,又是人可阅读的,这是一个很大的好处。【一些开发工具可以根据我们的WebService生成WSDL文档,又可以导入WSDL文档,生成调用相应的WebService的代理类代码】。
-
UDDI
UDDI(Universal Description, Discover, and Integeration)是一个主要针对Web服务供应商和使用者的新项目。在用户能够调用Web服务之前,必须确定这个服务内包含哪些方法,找到调用的接口的定义,还要在服务端来编制软件,UDDI是一种根据描述文档来以电脑系统查找相应服务的机制。
UDDI利用SOAP消息机制(标准的XML/HTTP)来发布,编辑,浏览以及查找注册信息。它采用XML格式来封装各种不同类型的数据,并且发送到注册中心或由注册中心来返回需要的数据。
3.2.3 调用原理
实现一个完整的Web服务工作流程:
- 【注册】Web服务提供者设计实现Web服务,并将调试正确后的Web服务通过Web服务中介者发布,并在UDDI注册中心注册
- 【请求服务】Web服务请求者向Web服务中介者请求特定的服务,中介者根据请求查询UDDI注册中心,为请求者寻找满足请求的服务
- 【返回服务描述信息】Web服务中介者向Web服务请求者返回满足条件的Web服务描述信息,该信息用WSDL写成,各种支持Web服务的机器都能阅读。
- 【发送SOAP消息】利用从Web服务中介者返回的描述信息生成相应的SOAP消息,发送给Web服务提供者,以实现Web服务调用
- 【返回执行结果】Web服务提供者按SOAP消息执行相应的Web服务,并将服务结果返回给Web服务请求者
3.2.4 使用场景
"适用范围":应用程序跨平台、跨网络。适用于应用程序集成B2B集成、代码和数据重用以及通过
Web进行客户端和服务器通信的场合。
"不适用场景":WebService会降低应用程序的性能,因此一台机器或者局域网里面运行的同构应用
程序就不应该用Web Service进行通信
详细说明:https://blog.csdn.net/weidawei0609/article/details/7915071
3.2.5 实现步骤
创建服务提供方ws_server项目
服务端结构:
3.2.5.1 创建WeatherService接口【创建服务端】
/**
* 天气服务接口
*/
@WebService
public interface WeatherService {
@WebMethod
String getWeatherByCityName(String name);
}
3.2.5.2 创建WeatherService接口实现类
//创建服务实现类
@WebService
public class WeatherServiceImpl implements WeatherService {
@Override
public String getWeatherByCityName(String name) {
return name + "今天艳阳高照!!!";
}
}
3.2.5.3 发布天气服务
//发布天气
public class Main {
public static void main(String[] args) {
//发布天气服务【这里只是简单的进行测试】
Endpoint.publish("http://localhost:8085/test_server/weather",
new WeatherServiceImpl());
System.out.println("发布天气服务成功...");
}
}
至此,服务端程序创建发布成功,运行后可在浏览器中访问http://localhost:8085/test_server/weather?wsdl
可以看到,我们的服务已经发布成功了。
3.2.5.4 创建客户端,在cmd窗口运行命令
创建客户端项目
生成客户端代码有很多种方式,这里我们演示JDL自带的wsimport工具
- 运行命令前:
- 运行命令
选中src,鼠标右键,选择Open in terminal打开cmd,当然,我们也可以win+R输入cmd,
打开dos命令,进入到ws_client项目的src目录。
输入wsimport -keep http://localhost:8085/test_server/weather?wsdl
,点击回车,出现下图情况即为生成成功。
(还可以将wsimport -keep http://localhost:8085/test_server/weather?wsdl
生成的文档以wsdl为后缀保存到项目中,输入wsimport -keep wsdl文件的路径执行)
- 运行结果
3.2.5.5 客户端调用服务端
/**
* WebService客户端测试
*/
public class ClientTest {
public static void main(String[] args) {
//创建服务类对象
WeatherServiceImplService service = new WeatherServiceImplService();
//创建远程服务的代理对象
WeatherServiceImpl weatherService = service.getWeatherServiceImplPort();
System.out.println(weatherService.getClass().getName());
//进行远程服务调用
String weather = weatherService.getWeatherByCityName("杭州");
System.out.println(weather);
}
}
运行结果:
3.2.6 总结
名称 | 作用 |
---|---|
@WebService | 指定发布远程的服务(默认类中的所有方法) |
@WebMethod | 指定类中特定的方法发布远程服务 |
EndPoint | 发布具体的远程服务,给提供远程服务的对象绑定一个URL |
3.3 HttpClient【http+json】
HttpClient:Http客户端工具,Java程序通过HttpClient发送Http协议的请求,直接获得远程资源。
3.3.1 定义
就目前来说,HTTP协议是在Internet上使用得最多、最重要的协议的了,越来越多的Java应用程序需要直接通过HTTP协议来访问网络资源。
HttpClient是Apache Jakarta Common下的子项目,提供了高效的、最新的、功能丰富的支持HTTP协议的客户端编程工具包。实现了所有HTTP的方法(GET、POST、PUT、HEAD等八种),支持RestFul。
3.3.2 实现步骤
3.3.2.1 创建服务端【Controller层】
@Controller
public class DemoController {
@RequestMapping("/demo")
@ResponseBody
public String demo(String param){
return "demo" + param;
}
}
服务端启动类:
@SpringBootApplication
public class HttpClientServerApplication {
public static void main(String[] args) {
SpringApplication.run(HttpClientServerApplication.class, args);
}
}
3.3.2.2 创建客户端【导入httpClient依赖】
导入依赖:
<!--导入依赖:httpClient-->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.10</version>
</dependency>
3.3.2.3 GET方式请求
public class GetDemo {
public static void main(String[] args) {
try{
//创建http工具(理解成:浏览器) 发起请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//请求路径
URIBuilder uriBuilder = new URIBuilder("http://localhost:8080/demo");
uriBuilder.addParameter("param", "get123");
//创建HttpGet的请求对象
HttpGet get = new HttpGet(uriBuilder.build());
//创建响应对象
CloseableHttpResponse response = httpClient.execute(get);
//由于响应体是字符串,因此把HttpEntity类型转换为字符串类型,并设置字符编码
String result = EntityUtils.toString(response.getEntity(), "utf-8");
//输出结果
System.out.println(result);
//释放资源[可以提到finally中释放]
response.close();
httpClient.close();
} catch (URISyntaxException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
3.3.2.4 POST方式请求
public class PostDemo {
public static void main(String[] args) {
try{
//创建Http工具(相当于:浏览器) 发起请求,解析响应
CloseableHttpClient httpClient = HttpClients.createDefault();
//创建HttpPOST对象
HttpPost post = new HttpPost("http://localhost:8080/demo");
//所有请求参数
ArrayList<NameValuePair> params = new ArrayList<>();
params.add(new BasicNameValuePair("param", "123"));
//创建HttpEntity接口的文本实现类对象,放入参数并设置编码
HttpEntity httpEntity = new UrlEncodedFormEntity(params, "utf-8");
//放入到HttpPost对象中
post.setEntity(httpEntity);
//创建响应对象
CloseableHttpResponse response = httpClient.execute(post);
//由于响应体是字符串,因此把HttpEntity类型转换为字符串类型
String result = EntityUtils.toString(response.getEntity());
//输出结果
System.out.println(result);
//释放资源[一般是在finally里释放]
response.close();
httpClient.close();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
} catch (ClientProtocolException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
参考文章:
https://blog.csdn.net/qq_34845394/article/details/86478208
https://www.jb51.net/article/225494.htm