架构的演进
RPC
《Dubbo+Zookeeper入门(上篇)》中用一张图解释了RPC的基本原理,这里再进一步了解RPC:
RPC采用客户机/服务器模式。请求程序就是一个客户机,而服务提供程序就是一个服务器。首先,调用进程发送一个有进程参数的调用信息到服务进程,然后等待应答信息。在服务器端,进程保持睡眠状态直到调用信息的到达为止。当一个调用信息到达,服务器获得进程参数,计算结果,发送答复信息,然后等待下一个调用信息,最后,客户端调用过程接收答复信息,获得进程结果,然后调用执行继续进行。
下面通过模拟实现一个简单的RPC,核心部分如下
RPC客户端动态代理:
package cn.ith.rpc;
import java.lang.reflect.Proxy;
//RPC客户端动态代理
public class RpcProxy<T> {
public T getProxy(String host, int port, Class interfaceClass) {
return (T) Proxy.newProxyInstance(getClass().getClassLoader(),
new Class[]{interfaceClass},
new RemoteInvocationHandler(host, port, interfaceClass));
}
}
客户端动态代理的具体实现:
package cn.ith.rpc;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.net.Socket;
//客户端动态代理的具体实现
public class RemoteInvocationHandler implements InvocationHandler {
private String host; //远程调用的主机ip
private int port; //远程调用的端口号
private Class interfaceClass; //远程调用的类
public RemoteInvocationHandler(String host, int port, Class interfaceClass) {
this.host = host;
this.port = port;
this.interfaceClass = interfaceClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//建立socket连接
Socket socket = new Socket(host, port);
//封装客户端请求消息
RpcMessage rpcMessage = new RpcMessage();
//反射获取类名、方法名、方法的参数类型
rpcMessage.setClassName(interfaceClass.getName());
rpcMessage.setMethodName(method.getName());
rpcMessage.setParams(args);
rpcMessage.setTypes(method.getParameterTypes());
//获取套输出接字流,发送消息给服务端
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(rpcMessage);
oos.flush();
//获取套接字输出流,读取服务端返回结果
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
Object result = ois.readObject();
return result;
}
}
用于封装远程调用的类名、方法名、请求参数、请求参数的类型的类:
package cn.ith.rpc;
import java.io.Serializable;
//用于封装远程调用的类名、方法名、请求参数、请求参数的类型的类
public class RpcMessage implements Serializable {
private String className; //请求的类名
private String methodName; //请求的方法名
private Object[] params; //请求参数
private Class[] types; //请求参数的类型
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Object[] getParams() {
return params;
}
public void setParams(Object[] params) {
this.params = params;
}
public Class[] getTypes() {
return types;
}
public void setTypes(Class[] types) {
this.types = types;
}
}
Rpc服务端实现:
package cn.ith.rpc;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
//Rpc服务端实现
public class RpcServer {
/**
* 开启服务
* @param port
*/
public void start(int port) {
//指定一个线程池
ExecutorService threadPool = Executors.newCachedThreadPool();
try {
//开启一个socket
ServerSocket serverSocket = new ServerSocket(port);
while(true) {
//监听客户端请求
Socket socket = serverSocket.accept();
//拿到一个线程处理客户端请求
threadPool.execute(new ProcessHandler(socket));
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
实现服务端的具体逻辑:
package cn.ith.rpc;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.net.Socket;
//实现服务端的具体逻辑
public class ProcessHandler implements Runnable {
private Socket socket;
public ProcessHandler(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
//处理服务端核心逻辑
//1.接收到消息
//2.反射调用本都方法
//3.返回结果
try {
//获取套接字输入流,从输入流中读取数据(接受客户端消息)
ObjectInputStream ois = new ObjectInputStream(socket.getInputStream());
RpcMessage rpcMessage = (RpcMessage)ois.readObject();
//获取接口名
String clazzName = rpcMessage.getClassName();
Class clazz = null; //接口对应的实现类
if(Registry.map.containsKey(clazzName)) {
clazz = Registry.map.get(clazzName);
}
Method method = null;
Object result = null;
try {
method = clazz.getMethod(rpcMessage.getMethodName(), rpcMessage.getTypes());
result = method.invoke(clazz.newInstance(), rpcMessage.getParams());
} catch (Exception e) {
e.printStackTrace();
}
//返回结果
ObjectOutputStream oos = new ObjectOutputStream(socket.getOutputStream());
oos.writeObject(result);
oos.flush();
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
服务端程序:
public class Server {
public static void main(String[] args) {
Registry.map.put(ProductService.class.getName(), ProductServiceImpl.class);
new RpcServer().start(8888);
}
}
客户端程序:
import cn.ith.api.service.ProductService;
import cn.ith.rpc.RpcProxy;
public class Client {
public static void main(String[] args) {
RpcProxy proxy = new RpcProxy();
ProductService productService = (ProductService)proxy.getProxy("localhost", 8888, ProductService.class);
System.out.println(productService.getById(10L));
}
}
运行服务端和客户端程序,结果如下:
源码:
链接:https://pan.baidu.com/s/1D1iuJxlhyvRtMmRr4YszHw
提取码:ot4f
SPI
SPI(Service Provider Interface),是一种服务发现机制。SPI的本质是将接口实现类的全限定名配置在文件中,并由服务加载器读取配置文件,加载实现类。这样可以在运行时,动态获得到接口的实现类。
也就是说SPI就是为了拿到接口的实现类。
JDK SPI
Java对SPI的实现是通过ServiceLoader类,读取配置文件,得到实现类,并反射实例化类对象。
下面通过一个例子来了解ServiceLoader
接口Shape:
package cn.ith.spi.service;
public interface Shape {
public void draw();
}
实现类Circle :
package cn.ith.spi.service.impl;
import cn.ith.spi.service.Shape;
public class Circle implements Shape {
@Override
public void draw() {
System.out.println("draw a circle");
}
}
实现类Rectangle :
package cn.ith.spi.service.impl;
import cn.ith.spi.service.Shape;
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("draw a rectangle");
}
}
有ServiceLoader加载读取配置文件,ServiceLoader部分源码如下,"META-INF/services/"
指定配置文件根路径:
配置文件名为接口的权限定名,内容为接口实现类的全限定名:
cn.ith.spi.service.impl.Circle
cn.ith.spi.service.impl.Rectangle
使用ServiceLoader:
public class JavaSPI {
public static void main(String[] args) {
ServiceLoader<Shape> serviceLoader = ServiceLoader.load(Shape.class);
for (Shape shape : serviceLoader) {
shape.draw();
}
}
}
结果:
Dubbo SPI
ServiceLoader有一个缺陷就是所有配置的实现类都会被加载并实例化。Dubbo对SPI的实现(ExtensionLoader类)就避免了这种缺陷。
下面通过一个例子来学习Dubbo对SPI的实现。
在上面的例子上进行修改
加入依赖:
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>dubbo</artifactId>
<version>2.6.2</version>
</dependency>
Shape类加上@SPI
注解:
package cn.ith.spi.service;
import com.alibaba.dubbo.common.extension.SPI;
//使用SPI注解,并指定默认实现类
@SPI("circle")
public interface Shape {
public void draw();
}
在resources\META-INF\dubbo
目录下创建文件夹cn.ith.spi.service.Shape
,内容如下:
circle = cn.ith.spi.service.impl.Circle
rectangle = cn.ith.spi.service.impl.Rectangle
main方法:
public static void main(String[] args) {
ExtensionLoader<Shape> extensionLoader = ExtensionLoader.getExtensionLoader(Shape.class);
//获取默认的接口实现类对象
Shape shape = extensionLoader.getDefaultExtension();
shape.draw();
//获取指定的接口实现类对象
shape = extensionLoader.getExtension("rectangle");
shape.draw();
}
链接:https://pan.baidu.com/s/1RcFgqp9Mm3xrIarO1N9D6A
提取码:fxcj