WebService、RPC、微服务的一些认知

跨系统间的数据交互

《应用的变迁》中提到过,随着大量垂直应用系统的诞生。各异构系统之间的通信、交换数据成为需求。同一个应用会有不同的客户端访问,或者不同的应用之间也会相互调用服务。

Socket通信原理图


通过IE来访问SocketServer,可以参考《HTTP协议详解与回顾》一文。程序员都是一群爱偷懒的人,能否在基于socket通信的基础上,进行改进,形成统一的标准、简化操作、更快速的进行业务开发。Web Service就这样产生了。

Web Service

WebService概念介绍

简单一句话使部署在Web服务器上的应用程序,向外界暴露出一个能够过Web方式访问的API,来进行数据的交互,达到异构平台的互通性。所谓的异构平台,是指Java语言开发的应用程序暴露出来的服务能够被C++语言开发的应用程序无障碍的进行数据交互。

WebService的规则

  • xml:soap消息使用xml进行序列化,该协议通常由http协议承载。
  • soap:simple object access协议,简单对象访问协议。由http协议承载。需要注意的是Map、泛型类对象无法进行传输。
  • wsdl:WebService描述语言,它也是xml实现的。服务接口描述。

WebService的四种访问方式

soap1.1、soap1.2、http-get、http-post

wsimport

WebService的推荐访问方式。该方式是对远程的WebService生产本地代理,再通过代理来访问WebService。

使用wsimport命令的要求

  • jdk版本要在jdk1.6.21及以上
  • 操作系统安装的jdk版本与eclipse默认指定的版本要一致

wsimport的使用

1
2
3
4
5
语法:wsimport [options] <wsdl_uri>
-wsdl_uri:wsdl的统一资源标识符
-d:指定要输出文件的位置
-s:表示要解析Java的源码,默认解析出来的是class字节码文件
-p:指定输出的包名

Java对于webservice的支持

JAX-WS:JAX-WS(Java API for XML Web Services)规范是一组XML web services的JAVA API,JAX-WS允许开发者可以选择RPC-oriented或者message-oriented 来实现自己的web services。

以注解的方式自定义发布WebService服务

  • 在需要对外提供服务的业务类上声明注解@WebService
  • 通过Endpoint类的静态方法发布一个WebService服务
    Endpoint.publish(address, implementor);
    address:发布的服务地址
    implementor:服务的实现对象




    第一次解析出错,是因为我本地设置了代理服务器IP。取消代理即可解析成功。
  • 生成jar包,使用jar -cvf jar包名.jar 清单文件所在的文件名

    该命令会把生成的jar包直接放在控制台的路径中,如需要更改导出路径,需要进入指定路径,再执行jar命令。
  • 测试一下所发布的服务

WDSL文件介绍

该文件的阅读是从下往上看。

1
2
3
4
5
6
7
8
9
service name:服务名
tns:是targetNameSpace的缩写,tns都能找到详细定义
port name:服务的访问方式
binding name:绑定名
type:对外提供WebService业务类的类名或者接口名
transport:传输方式,访问方式
operation name:对外提供WebService的方法名
input message:输入参数
output message:输出参数

常用注解说明

1
2
3
4
5
6
7
8
9
10
@WebService:使用该注解发布服务时,默认是只对public修饰的方法进行发布。
serviceName:更改发布的服务名
targetNamespace:更改目标命名空间
@WebMethod:修饰方法的注解
operationName:更改访问方法名
exclude:是否排除发布
@WebResult:修饰方法的返回值
name:更改显示返回值的名称
@WebParam:修饰方法的参数
name:更改显示参数的名称

发布具有WebService服务的接口流程:

  • 在接口类中声明@WebService注解。
  • 在接口类的实现类中也必须声明@WebService注解,并设置服务端点接口,指定对外提供服务的接口。endpointInterface=”接口类的全路径”。
  • 对服务方法发布有所更改的,在接口类中进行注解配置。

通过CXF框架发布WebService

  • ServerFactoryBean发布服务:使用该类不设置注解也可以发布WebService服务,不支持注解,不支持拦截器的添加。
  • JaxWsServerFactoryBean发布服务:该类支持注解,可以添加拦截器。

WebService访问流程:通过CXF发布的服务

  • 检测本地代理描述的WSDL是否与服务端的WSDL一致,俗称握手。
  • 通过soap协议实现通信,采用post请求,数据封装在满足soap规约的xml中。
  • 返回数据同样采用soap通信,数据封装在满足soap规约的xml中。

    CXF与Spring框架整合发布WebService

  • web.xml的文件配置。由CXFServlet处理(项目名/ws/*)的访问请求。
  • spring的applicationContext.xml文件配置。
  • 服务接口
  • 查看发布的服务说明

  • 发布成功,即可按照wsimport命令生成本地服务代理,在本地项目中访问远程服务

RPC框架原理

RPC,它是一种进程间的通信方式。允许像调用本地服务一样调用远程服务。这里说明一下进程间的通信方式,在我们熟知的操作系统中(Windows、Linux),一个进程想要直接跟另一个进程进行数据交互,是禁止的,安全壁垒。但是在Android系统中,它提供了另外一种方式JNI,这是笔者目前所认知到唯一一种操作系统,提供跨进程通信的方式。这里不在细谈。回到主题,RPC框架的目标是让远程服务调用更加简单、透明。跟WebService一样,屏蔽底层的操作细节。

核心技术点

  • 服务调用相关信息,相当于API说明。类似于WebService的WSDL文件,描述所发布的服务。
  • 远程代理对象。服务调用者调用的服务实际是远程服务的本地代理。
  • RPC框架通信与具体的协议无关。
  • 远程通信需要将数据对象转换成二进制码流进行网络传输,就需要进行序列化。序列化框架的选择。

简单的RPC框架实现

基于Java原生的序列化接口、socket通信、动态代理、反射机制,来实现一个最简单的RPC框架。

RPC服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package rpc.exporter;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午7:58:27
* @version 1.0
* @description 服务端提供者
*/
public class RpcExporter {
// 获取固定的线程池
static Executor executor = Executors.newFixedThreadPool(Runtime
.getRuntime().availableProcessors());
public static void exporter(String hostName, int port) throws Exception {
ServerSocket server = new ServerSocket();
server.bind(new InetSocketAddress(hostName, port));
try {
while (true) {
executor.execute(new ExecutorTask(server.accept()));
}
} finally {
server.close();
}
}
// 实现子线程的内部类,用于接收指定主机与端口的服务处理。
private static class ExecutorTask implements Runnable {
Socket client = null;
public ExecutorTask(Socket client) {
this.client = client;
}
@Override
public void run() {
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
// 从远处客户端的socket中获取对象输入流
input = new ObjectInputStream(client.getInputStream());
// 获取接口类名
String interfaceName = input.readUTF();
Class<?> service = Class.forName(interfaceName);
System.out.println(interfaceName);
// 获取接口方法名
String methodName = input.readUTF();
System.out.println(methodName);
// 获取接口方法的参数类型
Class<?>[] parameterTypes = (Class<?>[]) input.readObject();
// 获取接口方法的参数值
Object[] arguments = (Object[]) input.readObject();
// 反射出需要执行的方法
Method method = service.getMethod(methodName, parameterTypes);
// 执行该方法并获取结果
Object result = method.invoke(service.newInstance(), arguments);
output = new ObjectOutputStream(client.getOutputStream());
output.writeObject(result);
} catch (Exception e) {
e.printStackTrace();
} finally {
//关流
if (input != null) {
try {
input.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (output != null) {
try {
output.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (client != null) {
try {
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
}
1
2
3
4
5
6
7
8
9
10
11
12
package rpc.service;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午7:50:01
* @version 1.0
* @description 服务提供者的接口
*/
public interface EchoService {
public String echo(String ping);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package rpc.service.impl;
import rpc.service.EchoService;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午6:59:51
* @version 1.0
* @description 服务提供者的接口实现类
*/
public class EchoServiceImpl implements EchoService {
@Override
public String echo(String ping) {
return ping != null ? ping + "--->I am OK":"I am OK";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package rpc;
import rpc.exporter.RpcExporter;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午10:00:06
* @version 1.0
* @description 服务端
*/
public class RpcTest {
public static void main(String[] args) {
//子线程启动服务端
new Thread(new Runnable() {
@Override
public void run() {
try {
RpcExporter.exporter("localhost", 8088);
} catch (Exception e) {
e.printStackTrace();
}
}
}).start();
}
}

RPC客户端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package rpc.importer;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.Socket;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午9:41:13
* @version 1.0
* @description RPC客户端
*/
public class RpcImporter<T> {
@SuppressWarnings("unchecked")
public T importer(final Class<?> serviceClass, final InetSocketAddress addr) {
//采用JDK的动态代理返回代理对象。参数一:类加载器。参数二:基于接口产生代理对象。参数三:执行方法。
return (T) Proxy.newProxyInstance(serviceClass.getClassLoader(),
new Class<?>[] { serviceClass.getInterfaces()[0] },
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
Socket socket = null;
ObjectInputStream input = null;
ObjectOutputStream output = null;
try {
socket = new Socket();
//远程连接通信
socket.connect(addr);
output = new ObjectOutputStream(socket
.getOutputStream());
output.writeUTF(serviceClass.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);
input = new ObjectInputStream(socket
.getInputStream());
return input.readObject();
} finally {
if (socket != null) {
socket.close();
}
if (input != null) {
input.close();
}
if (output != null) {
output.close();
}
}
}
});
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package rpc.test;
import java.net.InetSocketAddress;
import rpc.importer.RpcImporter;
import rpc.service.EchoService;
import rpc.service.impl.EchoServiceImpl;
/**
* @Company
* @author dhs
* @date 2017年1月31日 下午10:10:06
* @version 1.0
* @description 客户端
*/
public class RpcTest {
public static void main(String[] args) {
RpcImporter<EchoService> importer = new RpcImporter<EchoService>();
EchoService echoService = importer.importer(EchoServiceImpl.class,
new InetSocketAddress("localhost", 8088));
System.out.println(echoService.echo("Are you OK?"));
}
}

测试通信

  • 需要在客户端项目,导入服务端生成的jar包。
  • 启动服务端项目。
  • 客户端与服务端通信。

微服务中消费者远程服务调用流程

谢谢你请我吃糖果

--------- 本文结束,感谢您的审阅 ---------
0%