详解 RMI技术 的基本实现
RMI机制 作为我们现在的 短链接的基础,也是非常重要的
并且最重要的是,在未来的 分布式、微服务架构中,RPC技术起到了决定性的 作用
而本篇博文的主题 —— RMI技术,就是RPC技术的一个 Java版的缩影
相信在未来的学习成长中,同学们会领略到RMI技术的 强悍之处
那么,话不多说,现在就开始本篇博文的讲解吧:
定义:
Remote Methed Invoke,
即:远程方法调用
是允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法
下面,本人来通过一张图,来展示下RMI技术的使用:
在本人《C/SFramework》专栏中,存在着大量客户端向服务器端发出的请求;
而这个请求到了服务器端,实质上是要执行一些服务器端的方法,并得到一个“响应”。
那么,如果把请求当成一个“本地方法”,
在客户端执行,而实质上,该方法只在服务器端存在真正的本体
那么,使用RMI技术,有什么好处呢?
好处:
分布式Java应用程序之间的远程通信提供服务,
即:提供分布式服务
现在,本人就来讲解下
RMI技术的核心:
- 代理技术(也就是本人之前博文中所讲解的MecProxy小工具)
- 反射机制(用于调用对端的方法);
- 网络技术(用于传输“请求”与“结果”);
- 短连接(RMI技术的主要意义)
那么,说了这么多,本人怎么可能不实现下呢?
首先,让我们思考下 该如何实现?
实现原理:
- 客户端使用代理 来 执行相关方法
- 在服务器端存在具体被执行的方法
那么,接下来,我们来具体实现下上述结论:
现在,本人来提供 五个工具类:
工具类:
Class类型转换器 —— TypeConverter:
请观看本人博文 —— 《【小工具】Class类型转换器 —— TypeConverter》
参数序列化/反序列化器 —— ArgumentMaker:
请观看本人博文 —— 《【小工具】参数序列化/反序列化器 —— ArgumentMaker》
Properties解析器 —— PropertiesParser:
请观看本人博文 —— 《【小工具】Properties解析器 —— PropertiesParser》
XML解析器 —— XMLParser:
请观看本人博文 —— 《【小工具】XML解析器 —— XMLParser》
代理建造器 —— ProxyBuilder:
请观看本人博文 —— 《【小工具】代理建造器 —— ProxyBuilder》
现在,本人来说明下 所需的 Jar包支持:
Jar包 支持:
- cglib-nodep
- gson
- fastjson
(三者 均为 代理建造器 小工具 需要)
RMI工厂(核心):
基本思路:
将从 配置文件 中 读取到的 RMI映射关系
存储到 一个Pool中,以便我们之后远程方法调用时的需要
代码实现:
首先,为了我们能够处理 客户端发来的“请求”,
本人来给出三个类:
封装要执行的方法的信息的MethodInformation类:
MethodInformation类:
package edu.youzg.rmi_impl.core;
import edu.youzg.util.TypeConverter;
import java.lang.reflect.Method;
/**
* 记录 客户端调用方法 的“全部信息”
*/
public class MethodInformation {
private String className;
private String methodName;
private String[] strParameterTypes;
public MethodInformation() {
}
public MethodInformation(String methodInfo) {
this.parseString(methodInfo);
}
/**
* 获取 该方法的 参数类型数组
*
* @return 该方法的 参数类型数组
* @throws ClassNotFoundException
*/
private Class<?>[] getParameterTypes() throws ClassNotFoundException {
int cnt = this.strParameterTypes.length;
if (cnt <= 0) {
return new Class<?>[]{};
}
Class<?>[] res = new Class<?>[cnt];
for (int i = 0; i < cnt; i++) {
res[i] = TypeConverter.toType(strParameterTypes[i]);
}
return res;
}
/**
* 获取该方法 的 Method对象<br/>便于反射调用
*
* @param klass
* @return
* @throws ClassNotFoundException
* @throws NoSuchMethodException
* @throws SecurityException
*/
public Method getMethod(Class<?> klass)
throws ClassNotFoundException, NoSuchMethodException, SecurityException {
Class<?>[] paraTypes = getParameterTypes();
Method method = klass.getDeclaredMethod(methodName, paraTypes);
return method;
}
/**
* 从拆分出来的 String数组 中,挑出 方法的签名
*
* @param strs
* @return
*/
private String getMethodString(String[] strs) {
for (int i = 0; i < strs.length; i++) {
if (strs[i].endsWith(")")) {
return strs[i];
}
}
return null;
}
/**
* 将整个参数字符串,分割成 一个个的 String类型参数 数组
*
* @param parameterTypesStr
*/
private void parseParameterString(String parameterTypesStr) {
if (parameterTypesStr.length() <= 0) {
this.strParameterTypes = new String[]{}; // 使其不为null,便于后续步骤中当作参数使用
return;
}
this.strParameterTypes = parameterTypesStr.split(",");
}
/**
* 将给定的字符串 解析为 该变量的成员属性
*
* @param methodInfo
* @return 该变量本身,便于链式调用
*/
public MethodInformation parseString(String methodInfo) {
// “方法字符串”的构成 —— 类全路径名.方法名(参数字符串)
String[] strs = methodInfo.split(" ");
String methodString = getMethodString(strs);
int leftBracketIndex = methodString.indexOf("(");
String methodFullName = methodString.substring(0, leftBracketIndex);
int lastDotIndex = methodFullName.lastIndexOf(".");
this.className = methodFullName.substring(0, lastDotIndex);
this.methodName = methodFullName.substring(lastDotIndex + 1);
String parameterTypesStr = methodString.substring(leftBracketIndex + 1, methodString.length() - 1);
parseParameterString(parameterTypesStr);
return this;
}
public String getClassName() {
return this.className;
}
public String getMethodName() {
return this.methodName;
}
public String[] getStrParameterTypes() {
return this.strParameterTypes;
}
}
接下来是用于封装 通过反射机制执行指定方法 所需的类与对象 的类:
RMIDefinition 类:
package edu.youzg.rmi_impl.core;
/**
* RMI方法信息:<br/>
* 用于保存一个类的 Class对象 和 一个实体类对象<br/>
* 便于我们之后的调用
*/
public class RMIDefinition {
private Class<?> klass;
private Object object;
public RMIDefinition() {
}
public RMIDefinition(Class<?> klass, Object object) {
this.klass = klass;
this.object = object;
}
public Class<?> getKlass() {
return klass;
}
public void setKlass(Class<?> klass) {
this.klass = klass;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
}
最后是通过扫描配置文件,形成映射关系的 RMIFactory类:
RMIFactory类:
package edu.youzg.rmi_impl.core;
import edu.youzg.util.XMLParser;
import edu.youzg.util.exceptions.XMLIsInexistentException;
import org.w3c.dom.Element;
import java.util.HashMap;
import java.util.Map;
/**
* 扫描指定的配置文件,<br/>
* 并将映射关系,存储到一个map中
*/
public class RMIFactory {
private static final Map<String, RMIDefinition> rmiPool
= new HashMap<>();
public RMIFactory() {
}
/**
* 扫描指定的配置文件,<br/>
* 并将映射关系,存储到一个map中
* @param xmlPath 目标xml文件的路径
*/
public static void scanRMIMapping(String xmlPath) {
try {
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String interfaceStr = element.getAttribute("interface");
String classStr = element.getAttribute("class");
try {
Class<?> klass = Class.forName(classStr);
Object object = klass.newInstance();
RMIDefinition rmiDefinition = new RMIDefinition(klass, object);
rmiPool.put(interfaceStr, rmiDefinition);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}.parseTag(XMLParser.getDocument(xmlPath), "mapping");
} catch (XMLIsInexistentException e) {
e.printStackTrace();
}
}
/**
* 通过接口名,查询对应的RMIDefinition
* @param interfaceName 目标接口名
* @return 该接口 对应的 RMIDefinition
*/
public static RMIDefinition getRmiDefinition(String interfaceName) {
return rmiPool.get(interfaceName);
}
}
接下来,我们来编写 客户端:
客户端:
基本思路:
- 客户端需要连接RMI服务器;
- 向服务器发送要执行的方法的名称、参数类型、参数值;
- 等待服务器返回执行执行结果,
这个结果可以看成“响应”
实现代码:
自定义异常 —— ConnectServerFailureException 类:
package edu.youzg.util.rmi_impl.exceptions;
/**
* 连接RMI服务器失败 异常
*/
public class ConnectServerFailureException extends Exception {
private static final long serialVersionUID = -4943840549663693585L;
public ConnectServerFailureException() {
}
public ConnectServerFailureException(String message) {
super(message);
}
public ConnectServerFailureException(Throwable cause) {
super(cause);
}
public ConnectServerFailureException(String message, Throwable cause) {
super(message, cause);
}
public ConnectServerFailureException(String message, Throwable cause, boolean enableSuppression,
boolean writableStackTrace) {
super(message, cause, enableSuppression, writableStackTrace);
}
}
RMI 代理处理逻辑 —— RMI方法执行器:
package edu.youzg.rmi_impl.client;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.Socket;
import com.alibaba.fastjson.JSON;
import edu.youzg.rmi_impl.exceptions.ConnectServerFailureException;
import edu.youzg.util.ArgumentMaker;
import edu.youzg.util.proxy.IMethodInvoker;
/**
* RMI方法执行器
*/
public class RMIMethodInvoker implements IMethodInvoker {
private String rmiServerIp;
private int rmiServerPort;
public RMIMethodInvoker() {
}
/**
* 将所有参数,都转换为:<br/>以argi为键,<br/>参数值的json字符串 为值 <br/>的map 的json字符串
*
* @param args 目标 参数数组
* @return 相应的 json字符串
*/
private String getArgs(Object[] args) {
if (args == null || args.length <= 0) {
return "";
}
ArgumentMaker argumentMaker = new ArgumentMaker();
for (int i = 0; i < args.length; i++) {
argumentMaker.add("arg" + i, args[i]);
}
return argumentMaker.toString();
}
@Override
public <T> T methodInvoke(Object object, Method method, Object[] args) throws ConnectServerFailureException {
Socket socket = null;
DataInputStream dis = null;
DataOutputStream dos = null;
boolean ok = true;
String ip = null;
int port = 6666;
try {
ip = this.rmiServerIp;
port = this.rmiServerPort;
socket = new Socket(ip, port);
/*
* 发送method 的字符串
* 发送参数数组的map化json
**/
dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(method.toString());
dos.writeUTF(getArgs(args));
/*
* 接收 结果json
**/
dis = new DataInputStream(socket.getInputStream());
String resultStr = dis.readUTF();
/*
* 转换结果为目标类型,并返回
**/
Type returnType = method.getGenericReturnType();
T result = null;
if (returnType.toString().equals("void")) { // 若是void类型,gson无法转换,交由fastjson转换
result = JSON.parseObject(resultStr, returnType);
} else { // 若不是void类型,为了避免fastjson无法转换集合,交由gson转换
result = ArgumentMaker.gson.fromJson(resultStr, returnType);
}
return result;
} catch (IOException e) {
ok = false;
} finally { //无论如何,释放资源
if (dis != null) {
try {
dis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (dos != null) {
try {
dos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socket != null && !socket.isClosed()) {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
if (!ok) {
throw new ConnectServerFailureException("连接RMI服务器失败!");
}
return null;
}
public String getRmiServerIp() {
return rmiServerIp;
}
public void setRmiServerIp(String rmiServerIp) {
this.rmiServerIp = rmiServerIp;
}
public int getRmiServerPort() {
return rmiServerPort;
}
public void setRmiServerPort(int rmiServerPort) {
this.rmiServerPort = rmiServerPort;
}
}
RMI客户端 —— RMIClient 类:
package edu.youzg.rmi_impl.client;
import edu.youzg.util.PropertiesParser;
import edu.youzg.util.proxy.ProxyBuilder;
/**
* RMI客户端
*/
public class RMIClient {
private RMIMethodInvoker methodInvoker;
public RMIClient() {
this.methodInvoker
= new RMIMethodInvoker();
}
public RMIClient(RMIMethodInvoker methodInvoker) {
this.methodInvoker = methodInvoker;
}
public void initRmiClient(String configFilePath) {
PropertiesParser.loadProperties(configFilePath);
String rmiPortStr = PropertiesParser.value("rmiServerPort");
String rmiIpStr = PropertiesParser.value("rmiServerIp");
if (rmiIpStr.length() > 0) {
this.methodInvoker.setRmiServerIp(rmiIpStr);
}
if (rmiPortStr.length() > 0) {
this.methodInvoker.setRmiServerPort(Integer.valueOf(rmiPortStr));
}
}
public void setRmiServerIp(String rmiServerIp) {
this.methodInvoker.setRmiServerIp(rmiServerIp);
}
public void setRmiServerPort(int rmiServerPort) {
this.methodInvoker.setRmiServerPort(Integer.valueOf(rmiServerPort));
}
// 由于在“RMI机制”中,无论是jdk代理,还是cglib代理,都可以使用
// 为了省去导jar包的麻烦,以及效率问题,本人在此处,就使用jdk提供的代理
/**
* 获取代理对象
* @param klass
* @param <T>
* @return
*/
public <T> T getProxy(Class<?> klass) {
ProxyBuilder builder = new ProxyBuilder();
builder.setMethodInvoker(this.methodInvoker);
return builder.creatProxy(klass);
}
}
那么,客户端我们处理好了,现在我们来思考下服务器端该如何编写:
服务器端:
基本思路:
服务器端必须能够建立服务器,
而且不断侦听来自客户端的连接请求;
这个连接请求实质上是客户端发出请求的第一步
接着,服务器开始接收客户端发来的,对应那个“方法”的名称、参数类型、参数值;
服务器根据客户端发来的上述信息,找到相关方法,并执行;
最后,将执行结果,返回给客户端
实现代码:
那么,现在我们来实现下RMI服务器端的代码:
RMIServer类:
package edu.youzg.rmi_impl.server;
import edu.youzg.util.PropertiesParser;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* RMI服务器端
*/
public class RMIServer implements Runnable {
private static final int CORE_THREAD_COUNT = 20;
private static final int MAX_THREAD_COUNT = 180;
private static final long ALIVE_TIME = 5000;
private int rmiPort;
private ServerSocket server;
private volatile boolean startUp;
private ThreadPoolExecutor threadPool;
private int coreThreadCount;
private int maxThreadCount;
private long aliveTime;
public RMIServer() {
this.coreThreadCount = CORE_THREAD_COUNT;
this.maxThreadCount = MAX_THREAD_COUNT;
this.aliveTime = ALIVE_TIME;
}
public void setRmiPort(int rmiPort) {
this.rmiPort = rmiPort;
}
public void setCoreThreadCount(int coreThreadCount) {
this.coreThreadCount = coreThreadCount;
}
public void setMaxThreadCount(int maxThreadCount) {
this.maxThreadCount = maxThreadCount;
}
public void setAliveTime(long aliveTime) {
this.aliveTime = aliveTime;
}
public void initRmiServer(String configFilePath) {
PropertiesParser.loadProperties(configFilePath);
String rmiPortStr = PropertiesParser.value("rmiServerPort");
if (rmiPortStr.length() > 0) {
this.rmiPort = Integer.valueOf(rmiPortStr);
}
String poolCoreThreadCount = PropertiesParser.value("coreThreadCount");
if (poolCoreThreadCount.length() > 0) {
this.coreThreadCount = Integer.valueOf(poolCoreThreadCount);
}
String poolMaxThreadCount = PropertiesParser.value("maxThreadCount");
if (poolMaxThreadCount.length() > 0) {
this.maxThreadCount = Integer.valueOf(poolMaxThreadCount);
}
String poolAliveTime = PropertiesParser.value("aliveTime");
if (poolAliveTime.length() > 0) {
this.aliveTime = Integer.valueOf(poolAliveTime);
}
}
public void startUp() {
if (startUp) {
return;
}
try {
this.threadPool = new ThreadPoolExecutor(
coreThreadCount,
maxThreadCount,
aliveTime,
TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
this.server = new ServerSocket(this.rmiPort);
this.startUp = true;
new Thread(this, "RMI Server").start();
} catch (Exception e) {
e.printStackTrace();
}
}
private void close() {
this.startUp = false;
if (this.server == null || this.server.isClosed()) {
return;
}
try {
this.server.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
this.server = null;
}
}
public void shutdown() {
if (!startUp) {
return;
}
close();
this.threadPool.shutdown();
}
@Override
public void run() {
while (startUp) {
try {
Socket client = this.server.accept();
threadPool.execute(new RMIService(client));
} catch (IOException e) {
if (this.startUp == true) {
this.startUp = false;
}
}
}
}
}
那么,现在本人来编写下上段代码中,使用的服务器端处理客户端请求的线程类:
RMIService类:
package edu.youzg.rmi_impl.server;
import com.alibaba.fastjson.JSON;
import edu.youzg.rmi_impl.core.MethodInformation;
import edu.youzg.rmi_impl.core.RMIDefinition;
import edu.youzg.rmi_impl.core.RMIFactory;
import edu.youzg.util.ArgumentMaker;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.Socket;
public class RMIService implements Runnable {
private Socket socket;
public RMIService(Socket socket) {
this.socket = socket;
}
/**
* 解析字符串,获取 参数的值
* @param method
* @param argString
* @return
*/
private Object[] getParameterValues(Method method, String argString) {
Object[] res = null;
Parameter[] parameters = method.getParameters();
int cnt = parameters.length;
if (cnt <= 0) {
return new Object[] {};
}
res = new Object[cnt];
ArgumentMaker argumentMaker = new ArgumentMaker(argString);
for (int i = 0; i < cnt; i++) {
res[i] = argumentMaker.getValue("arg" + i, parameters[i].getParameterizedType()); // getParameterizedType 带泛型
}
return res;
}
/**
* 调用该方法
* @param methodInfo
* @param argStr
* @return
*/
private Object invokeMethod(String methodInfo, String argStr) {
MethodInformation methodInfomation = new MethodInformation()
.parseString(methodInfo);
String interfaceName = methodInfomation.getClassName(); // RMI客户端传过来的参数 是 接口名称
RMIDefinition rmiDefinition = RMIFactory.getRmiDefinition(interfaceName);
Class<?> klass = rmiDefinition.getKlass();
Object object = rmiDefinition.getObject();
Object result = null;
try {
Method method = methodInfomation.getMethod(klass);
Object[] values = getParameterValues(method, argStr);
result = method.invoke(object, values);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(this.socket.getInputStream());
String methodInfo = dis.readUTF();
String argStr = dis.readUTF();
DataOutputStream dos = new DataOutputStream(this.socket.getOutputStream());
Object result = null;
result = invokeMethod(methodInfo, argStr);
dos.writeUTF(JSON.toJSONString(result));
dis.close();
dos.close();
this.socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
那么,现在我们来测试下上面的代码成功不成功:
测试:
首先,本人来给出一个用于保存信息的类:
package edu.youzg.test;
/**
* @Author: Youzg
* @CreateTime: 2020-07-15 17:41
* @Description: 带你深究Java的本质!
*/
public class User {
private String name;
private Integer age;
public User() {
}
public User(String name, Integer age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
@Override
public String toString() {
return "用户" + this.name + "今年" + this.age + "岁了!";
}
}
现在,本人来给出一个dao层接口:
package edu.youzg.test;
/**
* @Author: Youzg
* @CreateTime: 2020-07-15 17:43
* @Description: 带你深究Java的本质!
*/
public interface IUserDao {
void login(String name, int age);
}
接着,本人再来给出这个接口的实现类:
package edu.youzg.test;
/**
* @Author: Youzg
* @CreateTime: 2020-07-15 17:44
* @Description: 带你深究Java的本质!
*/
public class UserDaoImpl implements IUserDao {
@Override
public void login(String name, int age) {
System.out.println("登录功能被调用");
System.out.println(new User(name, age));
}
}
相应地,本人来给出一个配置文件:
配置文件:
客户端连接初始化 配置文件:
rmiServerIp=localhost
rmiServerPort=6666
服务器端初始化 配置文件:
rmiServerPort=6666
coreThreadCount=25
maxThreadCount=200
aliveTime=3000
RMI映射文件:
<?xml version="1.0" encoding="UTF-8"?>
<RmiMapping>
<mapping interface="edu.youzg.test.IUserDao"
class="edu.youzg.test.UserDaoImpl"></mapping>
</RmiMapping>
那么,现在我们来通过一个测试类来看看运行结果:
测试类:
服务器端:
package edu.youzg.test;
import edu.youzg.rmiImpl.core.RMIFactory;
import edu.youzg.rmiImpl.server.RMIServer;
/**
* @Author: Youzg
* @CreateTime: 2020-07-15 17:45
* @Description: 带你深究Java的本质!
*/
public class RSTest {
public static void main(String[] args) {
RMIFactory.scanRMIMapping("/resource/RMIMapping.xml");
RMIServer server = new RMIServer();
server.initRmiServer("/resource/rmiServerConfig.properties");
server.startUp();
}
}
客户端:
package edu.youzg.test;
import edu.youzg.rmiImpl.client.RMIClient;
/**
* @Author: Youzg
* @CreateTime: 2020-07-15 17:45
* @Description: 带你深究Java的本质!
*/
public class RCTest {
public static void main(String[] args) {
RMIClient client = new RMIClient();
client.initRmiClient("/resource/rmiClientConfig.properties");
IUserDao dao = client.getProxy(IUserDao.class); //获取该接口的代理对象
//通过代理对象“执行该方法”:
//其本质上是将方法所需信息传输给服务器端,
//服务器端从配置文件中找到该接口相应的实现类,
//执行该实现类的具体方法,并将返回值返回给客户端
dao.login("youzg", 6666);
System.out.println("登录成功!");
}
}
目录结构
现在,本人来展示下运行结果:
运行结果:
服务器端:
客户端:
那么,可以看到:
我们基本上实现了RMI技术!
若有需要上述源码的同学,本人已将本文所讲解到的代码打成了Jar包:
工具 Jar包:
如有需要,请点击下方链接:
Youzg-RMI-Impl
心得体会 与 未来展望:
RMI技术可以说是RPC技术的基础
如下图所示,RPC技术的基本原理:
从上图中我们能够看得出二者之间的联系!
在我们之后的学习中,分布式、微服务等架构模式,都会采用到RPC技术
因此,在本篇博文中,希望同学们能够对此加以重视!