PRC原理分析:从一个简单的DEMO开始

项目结构如下

在这里插入图片描述

一、service是服务层

实现了如下接口:
HelloServices 代码如下:

package consumer.service;
public interface HelloServices {
    String sayHi(String name);
}


HelloServiceImpl 代码如下:
public class HelloServiceImpl implements HelloServices {
    @Override
    public String sayHi(String name) {
        return "Hello "+name+" ^_^";
    }
}

上部分的代码,是声明微服务的接口规范(HelloServices)以及对应接口规范的具体实现(HelloServiceImpl )

通过上面的分析我们可以知道,微服务本身,是不具备任何底层的能力。
他只与业务相关

二、factory层,这里只是我个人的命名。

他主要是实现了如下接口
Service.class代码如下:功能说明参考注释

public interface Service {
   //停止服务
public void stop();

//开始服务
public void start() throws IOException;

//注册服务,注册服务,就是讲接口,以及对应的实现,放到了一个Map中
public void register(Class serviceInterface, Class impl);

//判断当前服务是否在运行
public boolean isRunning();

//获取使用的端口
public int getPort();
}

FactoryService.class的成员变量

private static ExecutorService executorService=
        Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

private static final HashMap<String,Class> serviceRegistry=
        new HashMap<>();

private static int port;

private static boolean isRunning = false;

1、ExecutorService是Java提供的线程池,也就是说,每次我们需要使用线程的时候,可以通过ExecutorService获得线程。它可以有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞,同时提供定时执行、定期执行、单线程、并发数控制等功能,也不用使用TimerTask了。

2、serviceRegistry 本质上是一个Map,存放的key是服务的全路径名称(接口),value是具体类(接口实现类)
相关的put操作如下:

serviceServer.register(HelloServices.class, HelloServiceImpl.class);

@Override
public void register(Class serviceInterface, Class impl) {
    print("Map key>"+serviceInterface.getName());
    print("Map value>"+impl);
    serviceRegistry.put(serviceInterface.getName(), impl);
}

start方法,是所有接口中最核心的接口

@Override
public void start() throws IOException{
    ServerSocket serverSocket=new ServerSocket();
    serverSocket.bind(new InetSocketAddress(port));
    try {
        while (true){
            executorService.execute(new ServiceTask(serverSocket.accept()));
        }
    }finally {
        serverSocket.close();
    }
}

start的相关类以及方法说明:

1、ServerSocket类
ServerSocket(int port):创建绑定到特定端口的服务器套接字。
accept():侦听并接受到此套接字的连接。
getInetAddress():返回此服务器套接字的本地地址。
2、executorService.execute(new Runable());
启动一个线程 ,加入线程池

具体线程如下:

public ServiceTask(Socket client){this.client=client;}

@Override
public void run() {
    //将对象作为输入输出流
    ObjectInputStream input=null;
    ObjectOutputStream output=null;

    try {
        input=new ObjectInputStream(client.getInputStream());
        String serviceName=input.readUTF();
        String methodName=input.readUTF();
        Class<?>[] paramterTypes= (Class<?>[]) input.readObject();
        Object[] arguments=(Object[]) input.readObject();
        Class serviceClass=serviceRegistry.get(serviceName);
        if(serviceClass==null){throw  new ClassNotFoundException();}
        Method method=serviceClass.getMethod(methodName,paramterTypes);
        Object result=method.invoke(serviceClass.newInstance(),arguments);
        output = new ObjectOutputStream(client.getOutputStream());
        output.writeObject(result);
        }
        ...
 }

相关方法说明:

readUTF():readUTF读取的必须是writeUTF写下的字符串。
扩展【read()、readLine()】与readUTF()的区别于联系。
Method getMethod(String name, Class<?>… parameterTypes)
//返回一个 Method 对象,它反映此 Class 对象所表示的类或接口的指定公共成员方法。
//getMethod第一个参数是方法名,第二个参数是该方法的参数类型,
//因为存在同方法名不同参数这种情况,所以只有同时指定方法名和参数类型才能唯一确定一个方法
Method method = XXX.getClass().getMethod(methodName,new Class[0]);

获取该类的Class Type,通过getMethod方法获取Method对象,通过调用invoke方法来执行对象的某个方法;
构造方法,传递socket连接的client
这个client,会被【消费者】连接,消费者连接到端口之后,进行请求。

run方法执行说明:
1、获取client中,客户端中输入流。(按照消费者中心的消息发送规则,获取到相关的输入流)

/*
参数 proxy 指代理类,
method表示被代理的方法,
args为 method 中的参数数组,
返回值Object为代理实例的方法调用返回的值
*/
output = new ObjectOutputStream(socket.getOutputStream());
output.writeUTF(serviceInterface.getName());
output.writeUTF(method.getName());
output.writeObject(method.getParameterTypes());
output.writeObject(args);

3、获取到serviceName即,serviceInterface.getName()
获取到methodName即,method.getName()
获取到 Class<?>[] paramterTypes 即 method.getParameterTypes()【参数类型数组】
获取到 Object[] arguments 即、args【参数对象数组】
4、通过客户端在socket中传输的serviceName,
在注册中心的注册列表【HashMap<String,Class> serviceRegistry】中进行查询
可以获取到注册列表中,以及被注册了的该服务名称,所对应的具体类
5、得到了具体类之后,使用

serviceClass.getMethod(methodName,paramterTypes);

可以获取到具体的方法,因为java语言中,有重载的存在,此方法的参数中除了需要方法名称以外,还需要方法的具体参数
6、得到具体的方法之后,可以代理执行此方法

method.invoke(serviceClass.newInstance(),arguments);

invoke参数中的arguments,是传递到此方法的参数

7、通过步骤6,可以获取到方法执行后的结果。再讲结果写入socket,返回到消费者。

消费者如何接受的呢?这里我们就要讲到消费者中心的动态代理的实现。

public class RPCClient<T> {
    public static <T> T getRemoteProxyObj(final Class<?> serviceInterface
    , final InetSocketAddress addr) {
        return (T) Proxy.newProxyInstance(serviceInterface.getClassLoader(), new Class<?>[]{serviceInterface},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Socket socket = null;
                        ObjectOutputStream output = null;
                        ObjectInputStream input = null;
                        try {
                            socket = new Socket();
                            socket.connect(addr);
                            output = new ObjectOutputStream(socket.getOutputStream());
                            output.writeUTF(serviceInterface.getName());
                            output.writeUTF(method.getName());
                            output.writeObject(method.getParameterTypes());
                            output.writeObject(args);
                            input = new ObjectInputStream(socket.getInputStream());
                            return input.readObject();
                        } 
                        ......
                    }
                });
    }
}

getRemoteProxyObj方法,是客户端调用远程服务的核心原理。
该方法有两个参数
Class<?> serviceInterface。需要远程服务的接口。消费者只关心服务的接口是什么,接口中定义有哪些方法,并不关心方法的具体实现。
InetSocketAddress addr。与服务通信的连接地址

/*
参数 proxy 指代理类,
method表示被代理的方法,
args为 method 中的参数数组,
返回值Object为代理实例的方法调用返回的值
*/

这里,再写一遍invoke方法的参数说明
然后,我们再来看此方法内部究竟实现了什么。
【扩展:Proxy 动态代理的原理】
通过如下代码实现对象的创建:

HelloServices service = RPCClient.
        getRemoteProxyObj(HelloServices.class, 
        new InetSocketAddress("localhost", 8088));

通过如下代码实现方法调用:

String str=service.sayHi("test")

请求流程简述:
1、客户端得到接口,以及和服务器通信的地址
2、声明动态代理。
3、当客户端,请求调用接口的方法时,实际执行的是invoke内的代码。
4、在invoke中,会通过socket向服务器发送输入流
5、在invoke中,等待服务器响应输出流(参考service部分,请求的业务实现是在service层,service层执行了方法,并写入了执行结果到输出流)
6、在invoke中,返回输出流中的Object对象,到调用的方法。

(T) Proxy.newProxyInstance(
    serviceInterface.getClassLoader(), 
    new Class<?>[]{serviceInterface},
    new InvocationHandler() {}
);

动态代理说明:
1、在java中规定,要想产生一个对象的代理对象,那么这个对象必须要有一个接口
2、参数说明
loader: 用哪个类加载器去加载代理对象
interfaces:动态代理类需要实现的接口
Handler:动态代理方法在执行时,会调用Handler里面的invoke方法去执行

参考
https://www.cnblogs.com/codingexperience/p/5930752.html#4098476

猜你喜欢

转载自blog.csdn.net/m13797378901/article/details/86538744