概念:
什么是RMI?
RMI(Remote Method Invocation,远程方法调用)是用Java在JDK1.2中实现的,它大大增强了Java开发分布式应用的能力。Java作为一种风靡一时的网络开发语言,其巨大的威力就体现在它强大的开发分布式网络应用的能力上,而RMI就是开发百分之百纯Java的网络分布式应用系统的核心解决方案之一。其实它可以被看作是RPC的Java版本。但是传统RPC并不能很好地应用于分布式对象系统。而Java RMI 则支持存储于不同地址空间的程序级对象之间彼此进行通信,实现远程对象之间的无缝远程调用。(定义来自百度百科)。
很多时候客户机需要执行某个方法,但是不含有该方法的具体内容,(或者说客户机根本不应该拥有的方法的具体实现)。方法的实现存在于服务器中,客户端又如何获取其想要的结果的,RMI技术解决的正是这类问题。
RMI是一种网络技术,基于服务器客户机模式。RMI客户端执行某一个方法的时候,其执行内部的过程是:连接RMI服务器,将想要执行的方法信息以及参数通过网络的方式发送给RMI服务器,在服务器端收到方法信息以及参数后,在本地执行该方法,再通过网络传输的方式将方法执行的结果发送回客户端。
RMI涉及到的技术:
动态代理技术(JDK代理)
服务器客户机短连接
网络传输
反射机制
发送对象(序列化与反序列化)
为什么要使用动态代理呢?RMI客户端与服务器都必须含有相同的远程接口,接口中有可供客户端调用的方法,方法的实现类在客户端之中。RMI客户端调用方法可以使用这个远程接口的代理对象来调用方法,在代理执行方法的实现过程,就可以完成发送——取得结果这一过程,这样在客户端调用接口方法就仿佛是在执行本地方法一样。
服务端接收到客户端发送的信息(方法信息、参数)后,通过发送的信息找到对应的类的方法反射执行。将得到的result返回。
RMI过程非常短暂,服务器与客户端采用短连接即可,即,客户端得到方法的返回结果就可以关闭通信了。
网络传输的类的对象必须是序列化的(基本类型已经是序列化的了),保证其唯一性,这里我们选择使用Gson发送Json字符串(已序列化),但是如果远程方法的参数中含有类类型参数,这个类也是必须序列化的。
介于RMI服务器有可能会处理大量的请求,会频繁地创建线程执行方法,我们这里加入线程池。
下面是代码:
RMIClient:
public class RMIClient {
MethodInvoker methodInvoker;
public RMIClient() {
this.methodInvoker = new MethodInvoker();
}
public RMIClient setRMIServerIp(String ip) {
methodInvoker.setRmiServerIp(ip);
return this;
}
public RMIClient setRMIServerPort(int port) {
methodInvoker.setRmiServerPort(port);
return this;
}
//连接时间
public RMIClient setConnectDelay(int delay) {
methodInvoker.setConnectDelay(delay);
return this;
}
//获得想要执行的方法的接口的代理对象
public <T> T getProxy(Class<?> klass) {
MyProxy myproxy = new MyProxy();
myproxy.setMethodInvoker(this.methodInvoker);
return myproxy.getProxy(klass);
}
}
这里作说明:代理我用的是我编写代理工具,MethodInvoker是代理工具里的一个接口IMethodInvoker的实现类,我们知道代理对象执行方法的时,其方法真正实现是在InvocationHandler参数(这是一个接口)里的invoke方法里。我们在这个invoke方法里调用我们IMethodInvoker的方法。那么如果我们向代理对象注入我们编写的IMethodInvoker实现类对象。那么编写这个实现类对象的方法即是在编写代理类对象执行的方法。下面也贴上我写的关于代理的博文链接。
关于Gson以及ArgumentMaker类:Gson工具
MethodInvoker:
public class MethodInvoker implements IMethodInvoker{
public static final int DEFAULT_CONNECT_DELAY = 20000;
private String rmiServerIp;
private int rmiServerPort;
private int connectDelay;
public MethodInvoker() {
this.connectDelay = DEFAULT_CONNECT_DELAY;
}
public void setRmiServerIp(String rmiServerIp) {
this.rmiServerIp = rmiServerIp;
}
public void setRmiServerPort(int rmiServerPort) {
this.rmiServerPort = rmiServerPort;
}
public void setConnectDelay(int delay) {
this.connectDelay = delay;
}
private String getArgs(Object[] args) {
if (args == null) {
return "";
}
ArgumentMaker am = new ArgumentMaker();
for (int i = 0; i < args.length; i++) {
am.addArgument("arg"+i, args[i]);
}
return am.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 = this.rmiServerIp;
int port = this.rmiServerPort;
try {
socket = new Socket();
SocketAddress address = new InetSocketAddress(ip, port);
socket.connect(address, connectDelay);
dos = new DataOutputStream(socket.getOutputStream());
//发送方法信息以及参数信息
dos.writeUTF(method.toString());
dos.writeUTF(getArgs(args));
dis = new DataInputStream(socket.getInputStream());
String returnString = dis.readUTF();
Type returnType = method.getGenericReturnType(); //可以处理泛型
if ("void".equals(returnType.toString())) {
return null;
}
@SuppressWarnings("unchecked")
T result = (T) ArgumentMaker.getGson().fromJson(returnString, 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("连接异常!");
}
return null;
}
}
ConnectServerFailureException是我编写的一个异常,继承自Exception,没什么可讲的。
让我们看核心代码:
dos.writeUTF(method.toString());
dos.writeUTF(getArgs(args));
String returnString = dis.readUTF();
Type returnType = method.getGenericReturnType(); //可以处理泛型
if ("void".equals(returnType.toString())) {
return null;
}
@SuppressWarnings("unchecked")
T result = (T) ArgumentMaker.getGson().fromJson(returnString, returnType);
return result;
这里完成了发送方法信息和参数的方法。method.toString包含了一个方法的详细信息,(类名,方法名、参数类型信息等),客户端可以根据其找到对应的方法。
让我们看服务器端:
RMI服务器:
public class RMIServer implements Runnable{
public static final int CORE_POOL_SIZE = 10;
public static final int MAXIMUM_POOL_SIZE= 20;
public static final long KEEP_ALIVE_TIME = 5000;
public static final int CAPACITY = 20;
private ServerSocket server;
private int port;
private volatile boolean startup;
private ThreadPoolExecutor threadPool;
public RMIServer(int port) {
this.port = port;
}
public void setPort(int port) {
this.port = port;
}
public void startup() {
if (startup == true) {
System.out.println("服务器已启动, 请勿重复启动!");
return;
}
try {
threadPool = new ThreadPoolExecutor(
CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE_TIME,
TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(CAPACITY));
this.server = new ServerSocket(port);
startup = true;
new Thread(this, "RMIServer").start();
System.out.println("RMI服务器启动成功");
} catch (IOException e) {
e.printStackTrace();
}
}
public void close() {
this.startup = false;
if (server == null && !server.isClosed()) {
try {
this.server.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
this.server = null;
}
}
}
@Override
public void run() {
while (startup == true) {
try {
Socket client = this.server.accept();
threadPool.execute(new RMIService(client));
} catch (IOException e) {
if (startup == true) {
startup = false;
}
}
}
}
}
RMI服务器接受到有客户端请求后,创建一个RMIService线程去执行方法。
在看RMIService类前,让我们先看看几个辅助的类:
RMI工厂(用以记录远程接口以及实现类的映射关系,这里用的xml配置扫描方式,也可换为注解配置),其中维护一张map,以远程接口名为键,RMIDefinition为值。
public class RMIFactory {
private static final Map<String, RMIDefinition> rmiPool;
static {
rmiPool = new HashMap<String, RMIDefinition>();
}
public RMIFactory() {
}
public static void scanRMIMapping(String xmlPath) {
new XMLParser() {
@Override
public void dealElement(Element element, int index) {
String interfaceStr = element.getAttribute("interface");
String className = element.getAttribute("class");
try {
Class<?> klass = Class.forName(className);
Object object = klass.newInstance();
RMIDefinition definition = new RMIDefinition(object, klass);
rmiPool.put(interfaceStr, definition);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}.parseTag(XMLParser.getDocument(xmlPath), "mapping");
}
public static RMIDefinition getDefinition(String interfaceName) {
return rmiPool.get(interfaceName);
}
}
RMIDefinition是一个包装类,用以存储我们需要反射的方法的类信息和一个对象:
public class RMIDefinition {
private Object object;
private Class<?> klass;
public RMIDefinition() {
}
public RMIDefinition(Object object, Class<?> klass) {
this.object = object;
this.klass = klass;
}
public Object getObject() {
return object;
}
public void setObject(Object object) {
this.object = object;
}
public Class<?> getKlass() {
return klass;
}
public void setKlass(Class<?> klass) {
this.klass = klass;
}
}
MethodInfomation类:用以解析方法客户端发来的方法信息,里面用字符串裁切的手段去获得类名、方法名、参数类型的名称等,用反射机制获取method对象和参数的类型:
public class MethodInformation {
private String className;
private String methodName;
private String[] paraTypesStr;
public MethodInformation() {
}
public MethodInformation(String methodInfo) {
parseMethodInfomation(methodInfo);
}
private Class<?>[] getParameterTypes() throws ClassNotFoundException{
int count = paraTypesStr.length;
if (count <= 0) {
return new Class<?>[] {};
} else {
Class<?>[] result = new Class<?> [count];
for (int i = 0; i < count; i++) {
result[i] = StringToClass.getKlass(paraTypesStr[i]);
}
return result;
}
}
public Method getMethod(Class<?> klass) throws
ClassNotFoundException, NoSuchMethodException, SecurityException{
Class<?>[] parameterTypes = getParameterTypes();
Method method = klass.getDeclaredMethod(methodName, parameterTypes);
return method;
}
private void parseParameterTypes(String str) {
if (str.length() <= 0) {
paraTypesStr = new String[] {};
}
paraTypesStr = str.split(",");
}
private String getMethodString(String[] strs) {
for (int i = 0; i < strs.length; i++) {
if (strs[i].endsWith(")")) {
return strs[i];
}
}
return null;
}
public MethodInformation parseMethodInfomation(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 ParaStr = methodString.substring(leftBracketIndex+1, methodString.length()-1);
parseParameterTypes(ParaStr);
return this;
}
public String getClassName() {
return className;
}
public String getMethodName() {
return methodName;
}
public String[] getParaTypesStr() {
return paraTypesStr;
}
}
里面有一个StringToClass类是我编写的工具包的一个类,内容也非常简单。
//基本类型直接在map中获得Class<?>对象,非基本类型通过Class.forName方法获得
public class StringToClass {
private static final Map<String, Class<?>> primeClassPool = new HashMap<String, Class<?>>();
static {
primeClassPool.put("byte", byte.class);
primeClassPool.put("boolean", boolean.class);
primeClassPool.put("char", char.class);
primeClassPool.put("short", short.class);
primeClassPool.put("int", int.class);
primeClassPool.put("long", long.class);
primeClassPool.put("float", float.class);
primeClassPool.put("double", double.class);
}
public StringToClass() {
}
public static Class<?> getKlass(String className) {
try {
return (primeClassPool.containsKey(className)) ?
(primeClassPool.get(className)) :
Class.forName(className);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
return null;
}
}
下面看我们真正处理客户端请求的类RMIService:
public class RMIService implements Runnable{
private Socket socket;
public RMIService(Socket socket) {
this.socket = socket;
}
private Object[] getparameterValues(Method method, String argString) {
Object[] result = null;
Parameter[] parameters = method.getParameters();
int count = parameters.length;
if (count <= 0) {
return new Object[]{};
}
result = new Object[count];
ArgumentMaker am = new ArgumentMaker(argString);
for (int i = 0; i < count; i++) {
result[i] = am.getValue("arg"+i, parameters[i].getParameterizedType());
}
return result;
}
private Object invokeMethod(String methodInfo, String argString) {
MethodInformation mi = new MethodInformation(methodInfo);
String interfaceName = mi.getClassName();
RMIDefinition rmiDefinition = RMIFactory.getDefinition(interfaceName);
Object object = rmiDefinition.getObject();
Class<?> klass = rmiDefinition.getKlass();
Object result = null;
try {
Method method = mi.getMethod(klass);
Object[] args = getparameterValues(method, argString);
result = method.invoke(object, args);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (SecurityException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return result;
}
@Override
public void run() {
try {
DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
String methodInfo = dis.readUTF();
String argString = dis.readUTF();
Object result = invokeMethod(methodInfo, argString);
dos.writeUTF(ArgumentMaker.getGson().toJson(result));
dis.close();
dos.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
核心代码:
String methodInfo = dis.readUTF();
String argString = dis.readUTF();
Object result = invokeMethod(methodInfo, argString);
dos.writeUTF(ArgumentMaker.getGson().toJson(result));
下面进行测试:
xml配置文件:
<?xml version="1.0" encoding="UTF-8"?>
<RMIMapping>
<mapping interface="com.mec.rmi.test.IAppInterface"
class="com.mec.rmi.test.AppImpl"></mapping>
</RMIMapping>
远程接口:
public interface IAppInterface {
String getInfo(StuInfo info);
}
其中涉及的类StuInfo
public class StuInfo implements Serializable{
private static final long serialVersionUID = -926878547988260203L;
private String stuName;
private int serialNum;
private boolean status;
public StuInfo() {
}
public StuInfo(String stuName, int serialNum, boolean status) {
this.stuName = stuName;
this.serialNum = serialNum;
this.status = status;
}
public String getStuName() {
return stuName;
}
public void setStuName(String stuName) {
this.stuName = stuName;
}
public int getSerialNum() {
return serialNum;
}
public void setSerialNum(int serialNum) {
this.serialNum = serialNum;
}
public boolean isStatus() {
return status;
}
public void setStatus(boolean status) {
this.status = status;
}
@Override
public String toString() {
return "姓名: " + stuName + " , "
+ "班级序号: " + serialNum + " , "
+ (status ? "在读。" : "已毕业。");
}
}
实现类:
public class AppImpl implements IAppInterface {
@Override
public String getInfo(StuInfo info) {
return "[" + info.toString() + "]";
}
}
服务端测试:
public class TestForRmIServer {
public static void main(String[] args) {
RMIFactory.scanRMIMapping("/RMIMapping.xml");
new RMIServer(54188).startup();
}
}
客户端测试:
public class TestForRMIClient {
public static void main(String[] args) {
RMIClient client = new RMIClient().setRMIServerIp("127.0.0.1")
.setRMIServerPort(54188);
IAppInterface i = client.getProxy(IAppInterface.class);
StuInfo info = new StuInfo("张三", 28, true);
System.out.println(i.getInfo(info));
}
}
测试结果:
分析:我们可以看到,在客户端,生成一个远程接口的代理对象,调用接口里的方法,我们获得的结果是在实现类的方法执行的结果,前面也谈到了,实现类是在服务端才存在的,也就是说,看起来这里是调用了本地的一个方法,实际上我们调用到了远程(即客户端)的远程接口实现类方法。我们的RMI模拟成功!
感悟:工具思想是java编程非常重要的思想,我在这里也用到了非常多以前编写的工具类,RMI本身也是一个非常犀利的工具,我在后序的一些大型项目(CSFramework(服务器客户机模式网络框架), 服务发现,分布式多文件云传输框架中都会用到)。