写一个简单的RPC其实很简单,服务的提供者,服务的消费者,注册中心以及协议。
- 编写服务的提供者
2.编写注册中心:此次注册中心我们将服务注册到MAP集合中,Map<String,Map<URL,Class>>,外面map的key存服务接口的全类名,URL封装了调用服务的ip和port,里面的value指定具体的实现类,注册中心类提供注册服务并暴露服务和发现服务功能:
首先编写一个url类:
package com.chao.pojo;
import java.util.Objects;
public class URL {
private String hostname;
private Integer port;
public URL() {
}
public URL(String hostname, Integer port) {
this.hostname = hostname;
this.port = port;
}
public String getHostname() {
return hostname;
}
public void setHostname(String hostname) {
this.hostname = hostname;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
//重写equals和hashcode是为了比较内容,如果不进行重写那么就是比较地址
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
URL url = (URL) o;
return Objects.equals(hostname, url.hostname) && Objects.equals(port, url.port);
}
@Override
public int hashCode() {
return Objects.hash(hostname, port);
}
}
package com.chao.registry;
import com.chao.pojo.URL;
import java.util.HashMap;
import java.util.Map;
public class NativeRegistry {
//构建注册中心
private static Map<String, Map<URL,Class>> registryCenter = new HashMap<String,Map<URL,Class>>();
/*
* 注册服务
* */
public static void regist(String interfaceName,URL url,Class implClass) {
Map map = new HashMap();
map.put(url,implClass);
registryCenter.put(interfaceName,map);
}
/*
* 获取服务信息
*
* */
public static Class get(String interfaceName,URL url) {
Map<URL, Class> urlClassMap = registryCenter.get(interfaceName);
Class aClass = urlClassMap.get(url);
return aClass;
}
}
服务的注册:
package com.chao.provider;
import com.chao.pojo.URL;
import com.chao.registry.NativeRegistry;
import com.chao.service.HelloService;
import com.chao.service.impl.HelloServiceImpl;
public class ServiceProvider {
public static void main(String[] args) {
//真正的注册服务
NativeRegistry.regist(HelloService.class.getName(),new URL("localhost",8080), HelloServiceImpl.class);
//System.out.println(HelloService.class.getName());
}
}
- 服务的暴露
服务之间调用的通信协议采用的是http协议,所以在服务provider中启动tomcat暴露服务
添加内嵌tomcat的依赖
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-core</artifactId>
<version>9.0.13</version>
</dependency>
配置tomcat:
package com.chao.tomcat;
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;
public class HttpServer {
/*
* tomcat服务启动
* 参考tomcat配置
* <Server port="8005" shutdown="SHUTDOWN">
<Service name="Catalina">
<Connector port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443"
URLEncoding= "UTF-8"/>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="true">
<Context path="" doBase="WORKDIR" reloadable="true"/>
</Host>
</Engine>
</Service>
</Server>
*
*
* */
public void start(String hostname,int port) {
//实例一个tomcat
Tomcat tomcat = new Tomcat();
//构建server
Server server = tomcat.getServer();
//获取service
Service service = server.findService("Tomcat");
//构建Connector
Connector connector = new Connector();
connector.setPort(port);
connector.setURIEncoding("UTF-8");
//构建Engine
Engine engine = new StandardEngine();
engine.setDefaultHost(hostname);
//构建Host
Host host = new StandardHost();
host.setName(hostname);
//构建Context
String contextPath = "";
Context context = new StandardContext();
context.setPath(contextPath);
context.addLifecycleListener(new Tomcat.FixContextListener());//生命周期监听器
//然后按照server.xml,一层层把子节点添加到父节点
host.addChild(context);
engine.addChild(host);
service.setContainer(engine);
service.addConnector(connector);
//service在getServer时被添加到server节点了
//tomcat是一个servlet,设置路径与映射
tomcat.addServlet(contextPath,"dispatcher",new DispatcherServlet());
context.addServletMappingDecoded("/*","dispatcher");
try {
tomcat.start();//启动tomcat
tomcat.getServer().await();//接受请求
}catch (LifecycleException e) {
e.printStackTrace();
}
}
}
- 创建一个HttpServletHandler:
package com.chao.tomcat;
import com.chao.pojo.Invocation;
import com.chao.pojo.URL;
import com.chao.registry.NativeRegistry;
import sun.nio.ch.IOUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class HttpServletHandler {
public void handle(HttpServletRequest req, HttpServletResponse resp) {
try {
//服务请求的处理逻辑
//1. 通过请求流获取请求服务调用的参数
InputStream inputStream = req.getInputStream();
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Invocation invocation = (Invocation) objectInputStream.readObject();
//2. 从注册中心获取服务的列表
Class implClass = NativeRegistry.get(invocation.getInterfaceName(),new URL("localhost",8080));
//3. 调用服务 反射
Method method = implClass.getMethod(invocation.getMethodName(),invocation.getParaTypes());
String result = (String) method.invoke(implClass.newInstance(),invocation.getParams());
//4. 结果返回
IOUtils.write(result,resp.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
创建Invocation这个类:
package com.chao.pojo;
public class Invocation {
private String interfaceName;
private String methodName;
private Object[] params;
private Class[] paraTypes;
public Invocation() {
}
public Invocation(String interfaceName, String methodName, Object[] params, Class[] paraTypes) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.params = params;
this.paraTypes = paraTypes;
}
public String getInterfaceName() {
return interfaceName;
}
public void setInterfaceName(String interfaceName) {
this.interfaceName = interfaceName;
}
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[] getParaTypes() {
return paraTypes;
}
public void setParaTypes(Class[] paraTypes) {
this.paraTypes = paraTypes;
}
}
注意:要实现IOUtils需要导入一个依赖
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
</dependency>
- 启动tomcat ,暴露服务
运行tomcat:
至此服务的提供者和注册中心就完成了
- 编写服务端
创建一个HelloClient
package com.chao.consumer;
import com.chao.pojo.Invocation;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
public class HelloClient {
public String post(String hostName, int port, Invocation invocation) throws IOException {
//1.进行连接
URL url = new URL("http", hostName, port, "/client");
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setDoOutput(true);
//2.发送调用的信息
OutputStream outputStream = urlConnection.getOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(invocation);
objectOutputStream.flush();
objectOutputStream.close();
//3.将输入流转为字符串,获取远程调用的结果
InputStream inputStream = urlConnection.getInputStream();
return IOUtils.toString(inputStream);
}
}
创建一个ConsumerMain
package com.chao.consumer;
import com.chao.pojo.Invocation;
import com.chao.service.HelloService;
import java.io.IOException;
public class ConsumerMain {
public static void main(String[] args) throws IOException {
Invocation invocation = new Invocation(HelloService.class.getName(), "sayHello", new Object[]{"myRPC客户端"}, new Class[]{String.class});
HttpClient httpClient = new HttpClient();
String result = httpClient.post("localhost", 8080, invocation);
System.out.println(result);
}
}
结果:先启动tomcat同时服务的提供发已经注册到自定义的map(注册中心),在启动服务的消费者
成功实现手写RPC