Bootstrap

实现一个RPC框架

实现一个RPC框架

1.RPC需要做什么

首先看一下RPC调用的流程:

在这里插入图片描述
由调用过程可得出:

  • provide需要向中心注册服务、向cunsumer提供服务
  • 注册中心需要接收provider的注册并保存注册信息、向cunsumer提供目标服务的所有provider的信息
  • cunsumer需要到中心获取服务provider列表、向provider发起请求

2.框架需要提供什么

a.provider

provider需要向中心注册服务,所以框架需要向provider提供注册机制,使provider能够向中心注册服务,同时还要把注册信息保存到本地,因为当consumer调用时,provider在获取到consumer的调用信息后,需要知道调用的是哪个类的哪个方法,所以服务信息不只要向中心注册,还要在本地保存。

provider还要启动一个网络服务,来接受consumer的请求并处理。

因此,规划框架为provider提供:

  • ProviderServiceManage:provider的服务管理器,提供register方法,和get方法,register负责将服务信息注册到中心和本地,get负责根据调用信息获取到实现类
  • ProviderHttpServer:负责启动provider网络服务,并注册服务信息

b.consumer

consumer发生调用时,需要先到中心获取服务列表,并通过负载均衡决定调用哪一个provider,然后向provider发送请求。

因此,规划框架为consumer提供:

  • ServiceDiscover:服务发现执行器,负责根据调用信息到注册中心获取服务列表
  • LoadBalance:负载均衡,负责根据传入的provider信息列表,根据算法返回一个本次要调用的provider的信息
  • HttpClient:网络客户端,负责发起远程调用

c.center

注册中心需要启动一个网络服务,接收provider的注册请求并记录,在consumer执行服务发现操作的时候把对应的provider列表返回。

因此,规划框架为center提供:

  • HttpServer:负责启动网络服务
  • CenterServiceManage:注册中心服务管理器,提供register和get方法,register负责将provider的注册信息保存,get负责根据调用信息获取对应的provider列表

3.实现

3.1目录规划

框架项目规划如下:

在这里插入图片描述
basic包下放置一些基础内容
common包下放置一些基本类
http包下放置网络服务相关
loadbalance包下放置负载均衡相关
proxy包放置代理
register包放置服务注册相关

3.2基础类

首先定义一些存放信息的DTO:

  • Invaoction:存放接口调用信息,当发生调用时,需要知道调用的是哪个服务的哪个方法,定位服务可以用服务名+版本号实现,定位方法需要知道方法名、参数列表,实现调用还需要参数,所以DTO的字段为:服务名、方法名、版本、参数列表、参数
  • URL:存放地址信息:主机名称和端口
  • DemoCenterInfo:负责记录注册中心地址,使provider和consumer某些方法获取注册中心信息时更方便

首先是Invocation:

package com.demo.common;

import java.io.Serializable;
import java.util.Arrays;

public class Invocation implements Serializable {

    private String serviceName;
    private String methodName;
    private Class[] parameterTypes;
    private Object[] parameters;
    private String version;

    public Invocation(String serviceName, String methodName, Class[] parameterTypes, Object[] parameters, String version) {
        this.serviceName = serviceName;
        this.methodName = methodName;
        this.parameterTypes = parameterTypes;
        this.parameters = parameters;
        this.version = version;
    }

    public String getServiceName() {
        return serviceName;
    }

    public void setServiceName(String serviceName) {
        this.serviceName = serviceName;
    }

    public String getMethodName() {
        return methodName;
    }

    public void setMethodName(String methodName) {
        this.methodName = methodName;
    }

    public Class[] getParameterTypes() {
        return parameterTypes;
    }

    public void setParameterTypes(Class[] parameterTypes) {
        this.parameterTypes = parameterTypes;
    }

    public Object[] getParameters() {
        return parameters;
    }

    public void setParameters(Object[] parameters) {
        this.parameters = parameters;
    }

    public String getVersion() {
        if (version == null || "".equals(version)) {
            return "1.0";
        }
        return version;
    }

    public void setVersion(String version) {
        this.version = version;
    }

    @Override
    public String toString() {
        return "serviceName" + "=" + serviceName + "," +
                "methodName" + "=" + methodName + "," +
                "version" + "=" + version + "," +
                "parameterTypes" + "=" + Arrays.toString(parameterTypes) + "," +
                "parameters" + "=" + Arrays.toString(parameters);
    }

}


接下来实现URL:

package com.demo.common;

import java.io.Serializable;

public class URL implements Serializable {

    private String hostname;
    private Integer port;

    public String getHostname() {
        return hostname;
    }

    public Integer getPort() {
        return port;
    }

    public void setHostname(String hostname) {
        this.hostname = hostname;
    }

    public void setPort(Integer port) {
        this.port = port;
    }
}

最后是DemoCenterInfo:

package com.demo.common;

public class DemoCenterInfo {

    private static String hostname;
    private static Integer port;

    public static Integer getPort() {
        return port;
    }

    public static String getHostname() {
        return hostname;
    }

    public static void setPort(Integer port) {
        DemoCenterInfo.port = port;
    }

    public static void setHostName(String hostName) {
        DemoCenterInfo.hostname = hostName;
    }
}

3.3实现网络服务

基础类已经实现完成了,接下来就是网络服务的实现。通过对调用过程的分析可得:provider需要向中心发送请求进行注册、consumer需要向中心发送请求获取服务列表以及向provider发送请求进行远程调用,所以需要一个客户端,来进行网络请求的发送;provider需要启动一个服务,接受并处理来自consumer的调用请求,而center需要启动一个服务接收并处理provider的注册、接收并处理consumer的服务列表获取(服务发现)。

首先定义一个网络客户端,主要是发送请求使用,定义一个接口类,用于规范实现类,存放在com.demo.basic.http下:

package com.demo.basic.http;

import com.demo.common.Invocation;
import java.lang.reflect.Method;

public interface IHttpClient {

    public Object send(String hostname, Integer port, Invocation invocation);
    
    public <T> T send(String hostname, Integer port, Invocation invocation, Method method);

}

定义了两个方法,第一个是未指定返回值类型的发送请求,第二个是将方法信息也传入,实现类可以根据方法的信息去编码返回值,用以返回期待类型的数据。

实现类如下:

package com.demo.http;

import com.demo.basic.datacode.DeCode;
import com.demo.basic.http.IHttpClient;
import com.demo.common.Invocation;
import org.apache.commons.io.IOUtils;
import com.alibaba.fastjson.JSONObject;

import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpClient implements IHttpClient {
    @Override
    public Object send(String hostname, Integer port, Invocation invocation) {
        try {
            URL url = new URL("http", hostname, port, "/");
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);

            // 配置
            OutputStream outputStream = httpURLConnection.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);

            oos.writeObject(invocation);
            oos.flush();
            oos.close();

            InputStream inputStream = httpURLConnection.getInputStream();

            // 读取结果并返回
            String resultBeforeDecode = IOUtils.toString(inputStream);
            // 解码
            return DeCode.decode(resultBeforeDecode);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public <T> T send(String hostname, Integer port, Invocation invocation, Method method) {
        try {
            URL url = new URL("http", hostname, port, "/");
            HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setRequestMethod("POST");
            httpURLConnection.setDoOutput(true);

            // 配置
            OutputStream outputStream = httpURLConnection.getOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(outputStream);

            oos.writeObject(invocation);
            oos.flush();
            oos.close();

            InputStream inputStream = httpURLConnection.getInputStream();

            // 读取结果并返回
            String resultBeforeDecode = IOUtils.toString(inputStream);
            // 解码
            return DeCode.decode(resultBeforeDecode, method);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

两个方法都使用到了一个DeCode.decode方法,是因为在使用网络传输数据时,需要将实例转码成字节流。因此实现了EnCode.encode方法将实例通过JSONObject的toString方法转为字符串,然后使用IOUtils.write方法写到流里,客户端接收到请求后取出字符串,交给DeCode.decode方法进行解码,解码成字符串后通过JSONObject还原成实例。

客户端还使用到了一个工具类:IOUtils,用于将流读取成字符串。

DeCode代码如下:

package com.demo.basic.datacode;

import com.alibaba.fastjson.JSONObject;

import java.lang.reflect.Method;

public class DeCode {

    public static String decode(String data) {
        // 这里可替换为自己的算法(encode的逆运算)
        return data;
    }

    public static <T> T decode(String str, Method method) {
        // 实现解码
        return JSONObject.parseObject(str, method.getGenericReturnType());
    }

}

EnCode的代码如下:

package com.demo.basic.datacode;

import com.alibaba.fastjson.JSONObject;

public class EnCode {

    public static String encode(Object obj) {
        return JSONObject.toJSONString(obj);
    }

}

至此,发送网络请求并获取返回值的部分已经实现了,接下来是启动tomcat的部分。

首先定义一个接口类,规范实现类的方法:

package com.demo.basic.http;

import org.apache.catalina.startup.Tomcat;

import javax.servlet.http.HttpServlet;

public interface IHttpServer {

    public void startServer(String hostname, Integer port, HttpServlet httpServlet);

    public Tomcat createServer(String hostname, Integer port, HttpServlet httpServlet);
    
    public void runServer(Tomcat tomcat) throws LifecycleException;

    public void wait(Tomcat tomcat);

}

其中startServer用于启动一个网络服务并进入请求等待,适用于中心端调用,启动服务后就进入等待,避免程序结束运行;createServer用于创建并返回一个tomcat实例,适用于peovider调用,创建tomcat实例后,provider可以进行服务注册等操作。runServer用于异步启动服务,wait用于进入等待,避免程序结束运行。

实现类如下:

package com.demo.http;

import com.demo.basic.http.IHttpServer;
import org.apache.catalina.*;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.Tomcat;

import javax.servlet.http.HttpServlet;
import java.util.concurrent.CompletableFuture;

public class HttpServer implements IHttpServer {
    @Override
    public void startServer(String hostname, Integer port, HttpServlet httpServlet) {
        // 启动一个tomcat服务
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);

        Host host = new StandardHost();
        host.setName(hostname);

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", httpServlet);
        context.addServletMappingDecoded("/*", "dispatcher");

        try {
            tomcat.start();
            tomcat.getServer().await();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public Tomcat createServer(String hostname, Integer port, HttpServlet httpServlet) {
        // 启动一个tomcat服务
        Tomcat tomcat = new Tomcat();

        Server server = tomcat.getServer();
        Service service = server.findService("Tomcat");

        Connector connector = new Connector();
        connector.setPort(port);

        Engine engine = new StandardEngine();
        engine.setDefaultHost(hostname);

        Host host = new StandardHost();
        host.setName(hostname);

        String contextPath = "";
        Context context = new StandardContext();
        context.setPath(contextPath);
        context.addLifecycleListener(new Tomcat.FixContextListener());

        host.addChild(context);
        engine.addChild(host);

        service.setContainer(engine);
        service.addConnector(connector);

        tomcat.addServlet(contextPath, "dispatcher", httpServlet);
        context.addServletMappingDecoded("/*", "dispatcher");
        return tomcat;
    }

    @Override
    public void runServer(Tomcat tomcat) throws LifecycleException {
        tomcat.start();
    }

    @Override
    public void wait(Tomcat tomcat) {
        tomcat.getServer().await();
    }

}

至此,启动网络服务的代码完成了,可以发现,startServer和createServer都接收了一个HttpServlet类型的入参,也就是创建或者启动服务的时候需要一个HttpServlet实例,这个实例是做什么的呢?

HttpServlet实例就是用来处理网络请求的,具体的处理逻辑就写在HttpServlet的service方法内。当tomcat接收到一个请求时,会调用httpServlet的service方法。

注册中心的servlet需要具有的功能就是注册和服务发现,provider的servlet需要实现的功能就是目标服务调用。自己实现的servlet需要继承于javax.servlet.http.HttpServlet,我们只需要重写其中的service方法即可。

首先实现注册中心的servlet:

package com.demo.http.servlet;

import com.demo.basic.datacode.EnCode;
import com.demo.register.CenterServiceManage;
import com.demo.common.Invocation;
import com.demo.common.URL;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.util.List;

public class DemoCenterServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求的逻辑
        // 首先将req中携带的请求信息进行解析
        // 因为项目已经定义Invocation作为请求信息的载体
        // 所以默认中心端接收的请求中携带的数据就是Invocation的实例
        try {
            Invocation invocation =(Invocation) new ObjectInputStream(req.getInputStream()).readObject();
            // 接下来进行请求的分析
            // 约定provider和consumer调用中心端时,将所请求的类型(注册还是服务发现)存放于methodName
            switch (invocation.getMethodName()) {
                case "register":
                    // 服务注册 参数列表: 服务名、服务版本、服务地址(URL实例来表示)
                    CenterServiceManage.register(invocation.getParameters()[0].toString(), invocation.getParameters()[1].toString(), (URL) invocation.getParameters()[2]);
                    IOUtils.write("注册成功", resp.getOutputStream());
                    break;
                case "discover":
                    // 服务发现
                    List<URL> urls = CenterServiceManage.get(invocation.getParameters()[0].toString(), invocation.getParameters()[1].toString());
                    String result = EnCode.encode(urls);
                    IOUtils.write(result, resp.getOutputStream());
                    break;
                default:
                    IOUtils.write("处理失败:所请求的方法在服务器上未发现", resp.getOutputStream());
            }
        } catch (Exception e) {
            // 当发生异常时,将错误信息返回
            IOUtils.write(e.getMessage(), resp.getOutputStream());
        }

    }

}

CenterServiceManage是中心端服务管理器,后续会进行实现。

接下来是provider的servlet,所要做的就是定位到目标方法并执行,将返回值进行编码后写入流中返回:

package com.demo.http.servlet;

import com.demo.basic.datacode.EnCode;
import com.demo.register.ProviderServiceManage;
import com.demo.common.Invocation;
import org.apache.commons.io.IOUtils;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.lang.reflect.Method;

public class ProviderServlet extends HttpServlet {

    @Override
    protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        // 处理请求的逻辑
        try {
            // 获取请求信息 服务名、版本号 -》 定位一个服务   然后通过methodName和parameterTypes定位方法
            Invocation invocation = (Invocation) new ObjectInputStream(req.getInputStream()).readObject();
            // 获取到请求信息后 ProviderServiceManage是provider的服务管理器 getServiceImplClass是根据服务名和版本号获取服务实现类的方法
            Class serviceImplClass = ProviderServiceManage.getServiceImplClass(invocation.getServiceName(), invocation.getVersion());
            // 获取方法
            Method method = serviceImplClass.getMethod(invocation.getMethodName(), invocation.getParameterTypes());
            // 执行
            Object result = method.invoke(serviceImplClass.newInstance(), invocation.getParameters());
            // 结果编码
            String resultAfterEnCode = EnCode.encode(result);
            IOUtils.write(resultAfterEnCode, resp.getOutputStream());
        } catch (Exception e) {
            // 当发生异常时,将错误信息返回
            IOUtils.write(e.getMessage(), resp.getOutputStream());
        }
    }

}

ProviderServiceManage.getServiceImplClass方法是是根据服务名和版本号获取服务实现类的方法,在接下来的服务注册和发现中会进行实现。

3.4实现服务注册

provider和center提供网络服务、provider和consumer发送网络请求的代码都已经实现了,接下来就是服务的注册。

首先实现服务注册功能:provider通过HttpClient发送请求,将服务名、服务版本号、自己的地址信息发送给注册中心center,然后在本地也进行服务名、版本号、实现类信息的保存,这样当consumer调用过来的时候才能找到对应的实现类。center需要做的就是将服务名、版本号相同的地址,存放在一起,当consumer来进行服务发现时,将对应服务名和版本号的所有provider地址都返回给consumer,由consumer来决定调用哪个provider(也就是负载均衡)。

首先实现provider的服务注册,定义一个provider的服务管理器,当发生注册行为的时候,自动向中心和本地进行注册,当相应consumer调用时,根据服务名和版本号获取对应实现类:

package com.demo.register;

import com.demo.common.Invocation;
import com.demo.common.DemoCenterInfo;
import com.demo.common.URL;
import com.demo.http.HttpClient;

import java.util.HashMap;
import java.util.Map;

public class ProviderServiceManage {

    private static Map<String, Class> serviceReflectMap;

    public static void register(String serviceName, String version, URL url, Class implClass) {
        if (serviceReflectMap == null) {
            serviceReflectMap = new HashMap<>();
        }
        // 首先向中心进行注册
        HttpClient httpClient = new HttpClient();
        Invocation invocation = new Invocation(null, "register", new Class[]{String.class, String.class, URL.class}, new Object[]{serviceName, version, url}, null);
        httpClient.send(DemoCenterInfo.getHostname(), DemoCenterInfo.getPort(), invocation);
        // 本地注册
        serviceReflectMap.put(serviceName + version, implClass);
    }

    public static Class getServiceImplClass(String serviceName, String version) {
    	// 获取服务
        return serviceReflectMap.getOrDefault(serviceName + version, null);
    }

}

接下来实现注册中心使用的服务管理器:

package com.demo.register;

import com.demo.common.URL;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CenterServiceManage {

    private static Map<String, List<URL>> serviceReflectMap;
    
    // 提供一个初始化方法,可以在启动服务的时候进行初始化,避免多线程处理register的时候发生覆盖的情况
    public static void init() {
        if (serviceReflectMap == null) {
            serviceReflectMap = new HashMap<>();
        }
    }

    public static void register(String serviceName, String version, URL url) {
        if (serviceReflectMap == null) {
            serviceReflectMap = new HashMap<>();
        }
        serviceReflectMap.computeIfAbsent(serviceName + version, k -> new ArrayList<>()).add(url);
    }

    public static List<URL> get(String serviceName, String version) {
        if (serviceReflectMap == null) {
            serviceReflectMap = new HashMap<>();
        }
        return serviceReflectMap.computeIfAbsent(serviceName +version, k -> new ArrayList<>());
    }

}

3.5实现代理、服务发现、负载均衡

接下来就是consumer的核心代码,代理的实现,这里使用java自带的反射机制实现。代理的获取使用代理工厂实现,工厂接收一个接口类和版本,然后进行服务发现和负载均衡后,生成一个代理返回给调用者。

代理工厂代码如下:

package com.demo.proxy;

import com.demo.common.DemoCenterInfo;
import com.demo.common.Invocation;
import com.demo.common.URL;
import com.demo.http.HttpClient;
import com.demo.loadbalance.LoadBalance;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;

public class ProxyFactory {

    // 这里我们约定,provider进行注册时,服务名称serviceName就是interfaceClass的全名
    public static <T> T getProxy(Class interfaceClass, String version) {
        Object proxyInstance = Proxy.newProxyInstance(interfaceClass.getClassLoader(), new Class[]{interfaceClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 获取到这个方法的目的是  进行返回值的解析
                Method discoverMethod = ProxyFactory.class.getMethod("discover", String.class, String.class);
                Invocation discoverInvocation = new Invocation("","discover", new Class[]{String.class, String.class}, new Object[]{interfaceClass.getName(), version}, null);
                // 通过网络发送请求
                HttpClient httpClient = new HttpClient();
                // 服务发现
                List<URL> urls = httpClient.send(DemoCenterInfo.getHostname(), DemoCenterInfo.getPort(), discoverInvocation, discoverMethod);

                Invocation invocation = new Invocation(interfaceClass.getName(), method.getName(), method.getParameterTypes(), args, version);

                // 负载均衡
                URL url = LoadBalance.get(urls);

                return httpClient.send(url.getHostname(), url.getPort(), invocation, method);
            }
        });
        return (T) proxyInstance;
    }

    public List<URL> discover(String serviceName, String version) {
        return null;
    }

}

代理工厂中使用到了一个负载均衡,代码如下:

package com.demo.loadbalance;

import com.demo.common.URL;

import java.util.List;
import java.util.Random;

public class LoadBalance {

    public static URL get(List<URL> urls) {
        // 随机获取
        Random random = new Random();
        int i = random.nextInt(urls.size());
        return urls.get(i);
    }

}

至此,一个简单的RPC框架就完成了

3.6启动

定义一个启动中心服务的类如下:

package com.demo;

import com.demo.http.HttpServer;
import com.demo.http.servlet.DemoCenterServlet;

public class CenterApplication {
    
    public static void main(String[] args) {
        HttpServer httpServer = new HttpServer();
        httpServer.startServer("localhost", 9090, new DemoCenterServlet());
    }
    
}

运行main方法,可以看到服务已经启动:
在这里插入图片描述

4.测试

4.1定义一个接口包

首先定义一个存放接口的工程,provider和consumer都引用这个工程,命名为provider-api,定义测试接口如下:

package com.provider.api;

import com.demo.common.Invocation;

import java.util.List;

public interface TestService {

    public List<Invocation> testOne(int nums);

}

项目结构如下:

在这里插入图片描述

4.2实现类

定义provider工程,实现上述接口并启动服务,注册服务到中心,监听consumer的请求,项目结构如下:
在这里插入图片描述
实现类的代码如下:

package com.provider;

import com.demo.common.Invocation;
import com.provider.api.TestService;

import java.util.ArrayList;
import java.util.List;

public class TestServiceImpl implements TestService {
    @Override
    public List<Invocation> testOne(int nums) {
        List<Invocation> result = new ArrayList<>();
        while (nums > 0) {
            result.add(new Invocation(String.valueOf(nums), String.valueOf(nums), new Class[]{}, new Object[]{}));
            nums --;
        }
        return result;
    }
}

启动类的代码如下:

package com.provider;

import com.demo.common.DemoCenterInfo;
import com.demo.common.URL;
import com.demo.http.HttpServer;
import com.demo.http.servlet.ProviderServlet;
import com.demo.register.ProviderServiceManage;
import com.provider.api.TestService;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.startup.Tomcat;

public class ProviderApplication {

    public static void main(String[] args) throws LifecycleException {
        // 首先配置注册中心信息
        DemoCenterInfo.setHostName("localhost");
        DemoCenterInfo.setPort(9090);
        // 启动服务
        HttpServer httpServer = new HttpServer();
        // 配置使用ProviderServlet处理请求
        Tomcat tomcat = httpServer.createServer("localhost", 9091, new ProviderServlet());
        httpServer.runServer(tomcat);
        // 注册服务
        URL url = new URL();
        url.setHostname("localhost");
        url.setPort(9091);
        ProviderServiceManage.register(TestService.class.getName(), "1.0.0", url, TestServiceImpl.class);
        // 等待请求,不退出程序
        httpServer.wait(tomcat);
    }

}

运行启动类,如下,服务启动成功:
在这里插入图片描述

4.3consumer调用

定义一个consumer模块,定义一个测试程序,项目结构如下:

在这里插入图片描述
测试程序如下:

package com.consumer;

import com.demo.common.DemoCenterInfo;
import com.demo.common.Invocation;
import com.demo.proxy.ProxyFactory;
import com.provider.api.TestService;

import java.util.List;

public class TestApplication {

    public static void main(String[] args) {
        DemoCenterInfo.setHostName("localhost");
        DemoCenterInfo.setPort(9090);
        TestService testService = ProxyFactory.getProxy(TestService.class, "1.0.0");
        List<Invocation> result1 = testService.testOne(5);
        List<Invocation> result2 = testService.testOne(8);
        System.out.println(result1);
        System.out.println("============================");
        System.out.println(result2);
    }

}

运行测试程序,结果如下:
在这里插入图片描述
consumer调用到了provider提供的服务,测试通过。

;