RMI 远程方法调用,本次实现的RMI是借助配置,将想要运行的方法表示出来,借由java反射机制(获取类、对象、运行方法)和代理(方法执行前后的连接和断开)。
整体分两个部分:服务器端和客户端。
服务器端:等待客户端连接,待客户端将所需的方法、对象等发送到服务器后进行方法的执行,接着将结果返回给客户端。
客户端:由客户配置好所需类、方法,当需要调用时连接服务器并发送所需信息,等待服务器端执行。
初始化过程:
因为用的代理时JDK代理,需要对应的接口,需要用户提前配置好。
public static void initRMIInterfaces(String filePath) throws Exception {
PropertiesParser.loadProperties(filePath);
Set<String> keys = PropertiesParser.keySet();
for (String key : keys) {
String interfaceName = key;
String className = PropertiesParser.getValue(key);
Class<?> interfaze = Class.forName(interfaceName);
Class<?> klass = Class.forName(className);
if (!interfaze.isInterface()) {
throw new Exception("[" + interfaze.getName() + "]不是接口!");
}
if (!interfaze.isAssignableFrom(klass)) {
throw new Exception("类[" + klass.getName() + "]不是[" + interfaze.getName() + "]的实现类!");
}
Object object = klass.newInstance();
dealInterfaceMethod(interfaze, klass, object);
}
}
依据用户配置好的文件,获取接口名,该接口需要和类对应,要判断是否是接口,是否与所给类有关。若都满足,则要开始依据类处理接口的方法。
private static void dealInterfaceMethod(Class<?> interfaze, Class<?> klass, Object object)
throws Exception {
Method[] methods = interfaze.getDeclaredMethods();
for (Method interfaceMethod : methods) {
System.out.println("扫描到:" + interfaceMethod.toString());
String key = String.valueOf(interfaceMethod.toString().hashCode());
String methodName = interfaceMethod.getName();
Class<?>[] parameterTypes = interfaceMethod.getParameterTypes();
Method classMethod = klass.getDeclaredMethod(methodName, parameterTypes);
RMIInterfaceDefinition rmiid = new RMIInterfaceDefinition(klass, object, classMethod);
RMIInterfaceFactory.rmiInterPool.put(key, rmiid);
}
}
由获取的接口得到所有方法,依据接口的方法名、所需参数在类中找到对应方法,将这个方法和对应的id存起来以备后续由服务器调用。
代理的初始化:
public <T> T getProxy(Class<?> interfaze) {
ClassLoader classLoader = interfaze.getClassLoader();
Class<?>[] interfaces = new Class<?>[] {interfaze};
return (T) Proxy.newProxyInstance(classLoader, interfaces,
new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (RMIProxy.this.rmiClient == null) {
throw new Exception("未定义RMIClient");
}
NodeAddress serverAddress = rmiClient.getAddress();
Socket socket = rmiClient.connectToServer(
serverAddress.getRmiIp(), serverAddress.getRmiPort());
RMIClientCommunication rmiCommunication = new RMIClientCommunication(socket);
// 连接服务器
// 传递method信息和实参args
// 等待并接收服务器返回的method执行结果
return rmiCommunication.doRmiMethod(interfaze, method, args);
}
});
}
这里代理用JDK代理,在调用方法中,先进行客户端连接服务器,连接成功后生成对应的communication并执行方法。
Object doRmiMethod(Class<?> interfaze, Method method, Object[] args)
throws Throwable{
System.out.println("method:" + method.toString());
String methodString = String.valueOf(method.toString().hashCode());
this.dos.writeUTF(methodString);
this.dos.writeUTF(argsToString(args));
String strResult = this.dis.readUTF();
return ArgumentMaker.gson.fromJson(strResult, method.getGenericReturnType());
}
执行方法过程就是将方法id,所需参数传给服务器,并获取结果,该结果由JSON处理。
服务器端的处理:
public void run() {
try {
String methodId = this.dis.readUTF();
String strParameter = this.dis.readUTF();
RMIInterfaceDefinition rmiid = RMIInterfaceFactory.getMethod(methodId);
if (rmiid == null) {
throw new Exception("未找到方法键:" + methodId);
}
Object object = rmiid.getObject();
Method method = rmiid.getMethod();
Object[] parameterValues = getParameterValues(method, strParameter);
Object result = method.invoke(object, parameterValues);
this.dos.writeUTF(ArgumentMaker.gson.toJson(result));
}
服务器端是一个一直在监听的服务器,当客户端将方法id、所需参数发送后,依据id查找之前配置好的接口、类中的对应方法兵执行,再将执行结果JSON后发给客户端。
上述即RMI整体思路。
额外知识点:
参数存储:利用gson和HashMap<String, String>存储,HashMap的键为参数名,值为对应的参数值的gson字符串。
并且在客户端与服务器之间传递这个图时,也是将该图借助json字符串传递:
服务器端:
private static Type mapType = new TypeToken<Map<String, String>>() {}.getType();
public ArgumentMaker(String parameterString) {
this.argMap = gson.fromJson(parameterString, mapType);
}
取参数:
public Object getParameter(String name, Class<?> type) {
String strValue = this.argMap.get(name);
return gson.fromJson(strValue, type);
}
客户端:
添加参数:
public ArgumentMaker add(String parameterName, Object parameterValue) {
this.argMap.put(parameterName, gson.toJson(parameterValue));
return this;
}