引言
在前两篇博客中,我们介绍了RPC框架的概念和原理,并且使用java运行了tomcat,本章将会进一步了解rpc的核心功能。
本文文件目录
处理RPC请求
- 获取请求信息
- 在HttpServer的Tomcat配置中添加下列语句,这让我们可以将请求接收到DispatcherServlet中
public class HttpServer {
public void start(String hostname,Integer port){
.....
//接收到的请求都会经由DispatcherServlet处理
tomcat.addServlet(contextPath,"dispatcher ",new DispatcherServlet());
context.addServletMappingDecoded("/*","dispatcher");
try{
tomcat.start();
tomcat.getServer().await();
}catch (LifecycleException e){
e.printStackTrace();
}
}
}
- 在protocol下创建DispatcherServlet类,该类将作为中转,通过多个handler处理不同的请求
//DispatcherServlet只是中转站,可以通过多个handler处理不同请求
public class DispatcherServlet extends HttpServlet {
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
new HttpServerHandler().handler(req,resp);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
}
}
}
- 解析请求信息并定位实现方法
- 在common下创建Invocation类,并实现序列化接口,该类用于将当前调用的服务的信息封装,如服务接口、方法名、参数列表
public class Invocation implements Serializable {
private String interfaceName;//当前调用的哪个接口
private String methodName;//哪个方法
private Class[] parameterTypes;//方法的参数类型
private Object[] parameters;//调用时传入的参数值
public Invocation(String interfaceName, String methodName, Class[] parameterTypes, Object[] parameters) {
this.interfaceName = interfaceName;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.parameters = parameters;
}
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 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;
}
}
- 在protocol下创建HttpServerHandler类,用来接收请求,并根据Invocation对象解析请求后定位真正的实现类
public class HttpServerHandler {
//接受请求
public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//处理请求-->调用哪个接口、方法、方法参数(核心)
//进行反序列化,获取invacation对象
Invocation invocation=(Invocation) new ObjectInputStream(req.getInputStream()).readObject();
//拿到方法名、接口名等,再根据拿到的这些东西定位实现类
String interfaceName=invocation.getInterfaceName();
...
}
}
服务本地注册
我们已经解析到了invocation的封装信息,如何根据信息找到对应实现类呢?——本地注册
- 创建register包,并在包下创建LocalRegister类,用于在注册时传入接口名(可以是其他信息,这里用接口名)与对应实现类对象
public class LocalRegister {
private static Map<String,Class> map=new HashMap<>();
//注册时传入接口名与对应实现类对象
public static void regist(String interfaceName,Class implClass){
map.put(interfaceName,implClass);
}
public static Class get(String interfaceName){
return map.get(interfaceName);
}
}
- 在HttpServletHandler类中,通过本地注册获取接口名对应的实现类,进而获取其他信息(方法、参数类型等),生成方法对象,使用该对象进行方法的调用,写入结果
public class HttpServerHandler {
//接受请求
public void handler(HttpServletRequest req, HttpServletResponse resp) throws IOException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IllegalAccessException, InvocationTargetException {
//处理请求-->调用哪个接口、方法、方法参数(核心)
//进行反序列化,获取invacation对象
Invocation invocation=(Invocation) new ObjectInputStream(req.getInputStream()).readObject();
//拿到方法名、接口名等,再根据拿到的这些东西定位实现类
String interfaceName=invocation.getInterfaceName();
//如何根据接口找到对应的实现类?-->本地注册
//如果invocation没有给版本号,可以定义一个默认的版本号
//通过本地注册后得到的映射关系获取接口名对应的实现类
Class classImpl= LocalRegister.get(interfaceName,"1.0");
//通过实现类获取方法、参数列表等,生成方法对象method
Method method=classImpl.getMethod(invocation.getMethodName(),invocation.getParameterTypes());
//通过反射的方式执行当前实现类对应的对象,可以取出参数值向方法传参
String result=(String)method.invoke(classImpl.newInstance(),invocation.getParameters());
//完成结果写入
IOUtils.write(result,resp.getOutputStream());
}
}
- 在Provider类中通过本地服务的方式获取接口名和实现类对象
public class Provider {
public static void main(String[] args) {
//希望启动时可以接收网络请求,可以用Netty、tomcat
//启动器先进行注册,获取接口名和实现类对象
//出现多个实现类时启动时可以指定版本号,根据不同版本执行不同的实现类方法
LocalRegister.regist(HelloService.class.getName(),HelloServiceImpl.class);
LocalRegister.regist(HelloService.class.getName(),HelloServiceImpl2.class);
//rpc框架负责启动网络
HttpServer httpServer=new HttpServer();
httpServer.start("localhost",8080);
}
}
多版本支持
在实际项目中,服务的版本迭代是非常常见的情况。为了兼容不同的版本,我们可以在RPC框架中加入多版本支持。
前提:在Provider应用下创建HelloServiceImpl2实现类,作为HelloServiceImpl的2.0版本,方法签与方法体可以同1.0版本,也可以自行改变
- 请求中携带版本号
- 在注册类中加入版本字段实现注册时传入版本号
public class LocalRegister {
private static Map<String,Class> map=new HashMap<>();
//注册时传入接口名与对应实现类对象
//支持多版本实现
public static void regist(String interfaceName,String version,Class implClass){
map.put(interfaceName+version,implClass);
}
public static Class get(String interfaceName,String version){
return map.get(interfaceName);
}
}
- 服务注册时指定版本号
在服务注册过程中,我们可以为每个服务方法指定版本号。这样,当有多个版本的服务方法存在时,我们可以根据请求中指定的版本号找到合适的服务对象进行调用。
- Provider中启动tomcat前进行注册时传入版本信息
public class Provider {
public static void main(String[] args) {
//希望启动时可以接收网络请求,可以用Netty、tomcat
//启动器先进行注册,获取接口名和实现类对象
//出现多个实现类时启动时可以指定版本号,根据不同版本执行不同的实现类方法
LocalRegister.regist(HelloService.class.getName(),"1.0",HelloServiceImpl.class);
LocalRegister.regist(HelloService.class.getName(),"2.0",HelloServiceImpl2.class);
//rpc框架负责启动网络
HttpServer httpServer=new HttpServer();
httpServer.start("localhost",8080);
}
}
- Provider中通过不同版本的字段调用不同版本的方法
public class Provider {
public static void main(String[] args) {
//希望启动时可以接收网络请求,可以用Netty、tomcat
//启动器先进行注册,获取接口名和实现类对象
//出现多个实现类时启动时可以指定版本号,根据不同版本执行不同的实现类方法
LocalRegister.regist(HelloService.class.getName(),"1.0",HelloServiceImpl.class);
LocalRegister.regist(HelloService.class.getName(),"2.0",HelloServiceImpl2.class);
//rpc框架负责启动网络
HttpServer httpServer=new HttpServer();
httpServer.start("localhost",8080);
}
}
结语
通过本篇博客,我们详细介绍了如何处理RPC请求、服务的本地注册和多版本实现。这些功能是一个完整RPC框架不可或缺的部分。在实际应用开发中,我们可以根据具体需求进行相应的扩展和优化。