Dubbo入门(中篇)

《Dubbo+Zookeeper入门(上篇)》

架构的演进

在这里插入图片描述

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

发布了243 篇原创文章 · 获赞 87 · 访问量 7万+

猜你喜欢

转载自blog.csdn.net/IT_10/article/details/104107818