欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【JavaEE】基于代理机制的RMI的实现

程序员文章站 2022-07-11 17:58:47
...

RMI技术:Remote Methed Invoke 远程方法调用
在CSFramework中,大量存在客户端向服务器端发出的请求;而这个请求到了服务器端,实质上是要执行一些服务器端的方法,并得到一个“响应”。那么,如果把请求当成一个“本地方法”,在客户端执行,而实质上,该方法只在服务器端存在真正的本体。
现在,我们使用RMI和代理机制来实现,不需要再像CSFramework那样通过action分发器来进行反射执行,也不需要进行耗费时间的长连接模式。

客户端使用代理:
执行相关方法时,实质上是通过代理执行的;在具体被执行的方法中,实际的操作是向服务器发出“请求”:请求客户端原本要执行的方法。
1、客户端需要连接RMI服务器;
2、向服务器发送要执行的方法的名称、参数类型、参数值;
3、等待服务器返回执行执行结果;这个结果可以看成“响应”。

服务器端:
服务器端必须能够建立服务器,而且不断侦听来自客户端的连接请求;这个连接请求实质上是客户端发出请求的第一步。接着,服务器开始接收客户端发来的,对应那个“方法”的名称、参数类型、参数值;服务器根据客户端发来的上述信息,找到相关方法,并执行;并且,将执行结果,返回给客户端。

在RMI的实际工作过程中,客户端只有接口,而不存在,也不需要存在该接口的实现类!客户端对RMI的方法的执行,实质上都是传递方法信息和方法参数,再从服务器端接收结果!因此,在客户端获取proxy的时候,能提供的仅仅是接口,没有可能提供该接口实现类。

综上所述:
RMI技术的核心:
1、代理技术;
2、反射机制;
3、网络传递参数;
4、网络传递返回值;
5、短连接。
当我们实现RMI后,将用RMI实现NetFramework,并在其中实现服务器端对客户端socket的轮询、在客户端通过模态框阻止用户无效操作。
其中,代理技术和反射机制,请看之前的博文:代理技术反射机制
这里会直接使用代理技术的jar包和反射机制技术。代理技术使用的是JDK代理,原因已经在代理技术博客里面提及到了,不再做详细解释。

下面请看代码:

public class RMIServer implements Runnable {
	private int port;
	private ServerSocket server;
	private volatile boolean startup;
	
	public RMIServer() {
	}

	public void setPort(int port) {
		this.port = port;
	}

	public void startup() {
		if (startup) {
			// TODO
			return;
		}
		
		try {
			server = new ServerSocket(port);
			startup = true;
			new Thread(this, "RMI Server").start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void shutdown() {
		if (!startup) {
			// TODO
			return;
		}
		
		close();
	}
	
	private void close() {
		startup = false;
		if (server != null && !server.isClosed()) {
			try {
				server.close();
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				server = null;
			}
		}
	}
	
	@Override
	public void run() {
		while (startup) {
			try {
				Socket client = server.accept();
				new RMIService(client);
			} catch (IOException e) {
				if (startup == true) {
					// TODO
					startup = false;
				}
			}
		}
	}
	
}
public class RMIClient {
	private String rmiServerIp;
	private int rmiServerPort;
	
	public RMIClient() {
	}

	public void setRmiServerIp(String rmiServerIp) {
		this.rmiServerIp = rmiServerIp;
	}

	public void setRmiServerPort(int rmiServerPort) {
		this.rmiServerPort = rmiServerPort;
	}
	
	public <T> T getProxy(Class<?> klass) {
		MecProxy mecProxy = new MecProxy();
		IMethdInvoker methdInvoker = new IMethdInvoker() {
			@SuppressWarnings("unchecked")
			@Override
			public  T methodInvoke(Object object, Method method, Object[] args) {
				Socket socket = null;
				DataOutputStream dos = null;
				DataInputStream dis = null;
				try {
					socket = new Socket(rmiServerIp, rmiServerPort);
					dos = new DataOutputStream(socket.getOutputStream());
					
					dos.writeUTF(method.toString());
					dos.writeUTF(getArgs(args));
					
					dis = new DataInputStream(socket.getInputStream());
					String resultString = dis.readUTF();
					
					Type returnType = method.getGenericReturnType();
					T result = (T) ArgumentMaker.gson.fromJson(resultString, returnType);
					
					return result;
				} catch (UnknownHostException e) {
					e.printStackTrace();
				} catch (IOException e) {
					e.printStackTrace();
				} 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();
						}
					}
				}

				return null;
			}
		};
		mecProxy.setMethodInvoker(methdInvoker);
		
		return mecProxy.getProxy(klass);
	}
	
	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();
	}
	
}
public class RMIService implements Runnable {
	private Socket socket;

	RMIService(Socket socket) {
		this.socket = socket;
		new Thread(this).start();
	}
	
	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());
		}
		
		return res;
	}
	
	private Object invokeMethod(String methodInfo, String argString) { 
		MethodInformation methodInformation = new MethodInformation()
				.parse(methodInfo);
		
		String interfaceName = methodInformation.getClassName();
		RMIDefinition rmiDefinition = RMIFactory.getRmiDefinition(interfaceName);
		Object object = rmiDefinition.getObject();
		Class<?> klass = rmiDefinition.getKlass();
		
		Object result = null;
		try {
			Method method = methodInformation.getMethod(klass);
			Object[] parameterValues = getParameterValues(method, argString);
			result = method.invoke(object, parameterValues);
		} 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 = null;
			result = invokeMethod(methodInfo, argString);
			dos.writeUTF(ArgumentMaker.gson.toJson(result));
			
			dis.close();
			dos.close();
			socket.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}
public class MethodInformation {
	private String className;
	private String methodName;
	private String[] strParameterTypes;
	
	public MethodInformation() {
	}

	public MethodInformation(String methodInfo) {
		parse(methodInfo);
	}
	
	private Class<?>[] getParameterTypes() throws ClassNotFoundException {
		int cnt = strParameterTypes.length;
		if (cnt <= 0) {
			return new Class<?>[] {};
		}
		Class<?>[] res = new Class<?>[cnt];
		for (int i = 0; i < cnt; i++) {
			Class<?> klass = Class.forName(strParameterTypes[i]);
			res[i] = klass;
		}
		
		return res;
	}
	
	public Method getMethod(Class<?> klass) 
			throws ClassNotFoundException, NoSuchMethodException, SecurityException {
		Class<?>[] paraTypes = getParameterTypes();
		Method method = klass.getDeclaredMethod(methodName, paraTypes);
		return method;
	}
	
	private void parseParameterTypes(String str) {
		if (str.length() <= 0) {
			strParameterTypes = new String[] {};
			return;
		}
		
		strParameterTypes = str.split(",");
	}
	
	public MethodInformation parse(String methodInfo) {
		String[] strs = methodInfo.split(" ");
		String methodString = strs[strs.length - 1];
		int leftBracketIndex = methodString.indexOf("(");
		String methodFullName = methodString.substring(0, leftBracketIndex);
		
		int lastDotIndex = methodFullName.lastIndexOf(".");
		className = methodFullName.substring(0, lastDotIndex);
		methodName = methodFullName.substring(lastDotIndex + 1);
		
		String strParameterTypes = methodString.substring(leftBracketIndex + 1, methodString.length() - 1);
		parseParameterTypes(strParameterTypes);
		
		return this;
	}

	public String getClassName() {
		return className;
	}

	public String getMethodName() {
		return methodName;
	}

	public String[] getStrParameterTypes() {
		return strParameterTypes;
	}
	
}

上面的代码,其实不难看懂,无非是服务器端和客户端的一些列操作。
客户端通过匿名内部类实现了IMethodInvoker接口,其功能是客户端连接服务器端,并进行消息的发送,然后等待接收,接收完之后,立刻断开连接并关闭通信信道。客户端需要把这个匿名内部类进行设置,设置到代理机制工具中,完成代理机制工具的初始化。
服务器端则是通过建立一个线程,通过循环一直侦听着来自客户端的连接请求,一旦连接上,则开始开辟一个线程去处理与这个客户端的会话(通过建立通信信道去处理)。当处理完之后,立马关闭通信信道并断开与这个客户端的连接,保证了这套机制的高效性。
具体的处理过程,其实很朴素,需要大家慢慢分析,不难理解。
这时有一个很重要的问题还没解决,就是关于工厂,客户端在传递参数的时候,会传递Method信息,我们需要从工厂中获得Method信息,然后执行,再将结果发送给客户端。这时,对于方法的扫描就成为了重点。对于扫描,我们并不陌生了,之前写过的很多博客都用到了扫描,很多次都用的是注解,那么这次就写一次xml扫描,对于注解扫描,我也会简单提及到。

public class RMIFactory {
	private static final Map<String, RMIDefinition> rmiPool
			= new HashMap<>();
	
	public RMIFactory() {
	}
	
	public static void scanRMIMapping(String xmlPath) {
		new XMLParser() {
			@Override
			public void dealElement(Element element, int index) {
				String strInterface = element.getAttribute("interface");
				String strClass = element.getAttribute("class");
				
				try {
					Class<?> klass = Class.forName(strClass);
					Object object = klass.newInstance();
					
					RMIDefinition definition = new RMIDefinition(klass, object);
					
					rmiPool.put(strInterface, definition);
				} catch (ClassNotFoundException e) {
					e.printStackTrace();
				} catch (InstantiationException e) {
					e.printStackTrace();
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}.parseTag(XMLParser.getDocument(xmlPath), "mapping");
	}
	
	public static RMIDefinition getRmiDefinition(String interfaceName) {
		return rmiPool.get(interfaceName);
	}
	
}
public class RMIDefinition {
	private Class<?> klass;
	private Object object;
	
	RMIDefinition() {
	}

	RMIDefinition(Class<?> klass, Object object) {
		this.klass = klass;
		this.object = object;
	}

	public Class<?> getKlass() {
		return klass;
	}

	void setKlass(Class<?> klass) {
		this.klass = klass;
	}

	public Object getObject() {
		return object;
	}

	void setObject(Object object) {
		this.object = object;
	}
	
}

值得注意的是,这次的Factory的pool是以接口的名字为键,Definition类(包括类的Class<?> 和 object)为值。因为我们在客户端传递的是Method类型,Method类型的字符串包含了方法所在的接口的详细信息,所以我们可以通过解析,得到接口,进而得到类,进而找到方法执行。
对于用注解扫描的方式,首先我们需要定义一个注解类RMIInterface:

@Retention(RUNTIME)
@Target(TYPE)
public @interface RMIInterface {
	Class<?>[] rmiInterface();
}

这个注解有一个属性,且必须实现,因为我们要写清楚这个类实现了哪个接口。这个和我们所定义的Factory有关系,我们的pool是以接口的名字为键,所以,在扫描的时候,为了把这个类放入pool里面,必须要注明实现的接口。用数组的原因是因为有可能有的类实现了多个接口,一次性都要扫描到。
讲到这里其实有人会有一个疑问,假如有好几个类会实现同一个接口该怎么办?
其实,仔细想想,我们实现的时候,对于接口的某个方法,完全可以在一个类里面实现,没必要去用好几个类去实现同一个方法,如果确实这么做了,那么我们直接覆盖原来的方法,反射机制执行的时候只执行最后实现的方法就可以了。
下面看注解类的Factory类:

public class RMIMethodFactory {
	private static final Map<String, MethodDefinition> methodPool;
	static {
		methodPool = new HashMap<>();
	}
	
	public RMIMethodFactory() {
	}
	
	public void scanPackage(String packageName) {
		new PackageScanner() {
			@Override
			public void dealClass(Class<?> klass) {
				if (!klass.isAnnotationPresent(RMIInterface.class)) {
					return;
				}
				RMIInterface rmiInterface = klass.getAnnotation(RMIInterface.class);
				Class<?>[] interfacess = rmiInterface.rmiInterface();
				
				for (Class<?> interfaces : interfacess) {
					registryClass(interfaces, klass);
				}
			}
		}.packageScanner(packageName);
	}
	
	public void registryClass(Class<?> interfaces, Class<?> klass) {
		if (interfaces == null || klass == null 
				|| !interfaces.isInterface() || klass.isInterface()
				|| !interfaces.isAssignableFrom(klass)) {
			return;
		}
		
		try {
			Object object = klass.newInstance();
			
			Method[] methods = interfaces.getDeclaredMethods();
			for (Method method : methods) {
				String methodId = String.valueOf(method.toGenericString().hashCode());
				
				Class<?>[] parameterTypes = method.getParameterTypes();
				Method classMethod = klass.getDeclaredMethod(method.getName(), parameterTypes);
				
				MethodDefinition definition = new MethodDefinition(object, classMethod);
				methodPool.put(methodId, definition);
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}
	
	static MethodDefinition getMethod(String methodId) {
		return methodPool.get(methodId);
	}
}

至此,我们已经把RMI工具实现了,在用的时候,我们只需要提供自己的一个接口类型,和一个实现类,对实现类加上注解或者将这两个类放入到xml配置文件里面就可以执行。当然,在执行之前,需要先进性Factory的扫描,对于服务器端,我们需要进行开启和关闭。
下面,我采用xml配置的方式进行测试:

public interface IAppInterface {
	UserInfo userLogin(String id, String password);
}
public class AppFunction implements IAppInterface {

	@Override
	public UserInfo userLogin(String id, String password) {
		UserInfo user = new UserInfo(id, "张三丰", password);
		
		return user;
	}
}
public class UserInfo {
	private String id;
	private String nick;
	private String password;
	
	public UserInfo() {
	}

	public UserInfo(String id, String nick, String password) {
		this.id = id;
		this.nick = nick;
		this.password = password;
	}

	public String getId() {
		return id;
	}

	public void setId(String id) {
		this.id = id;
	}

	public String getNick() {
		return nick;
	}

	public void setNick(String nick) {
		this.nick = nick;
	}

	public String getPassword() {
		return password;
	}

	public void setPassword(String password) {
		this.password = password;
	}

	@Override
	public String toString() {
		return "UserInfo [id=" + id + ", nick=" + nick + ", password=" + password + "]";
	}
	
}

【JavaEE】基于代理机制的RMI的实现

	public static void main(String[] args) {
		RMIFactory.scanRMIMapping("/RMIMapping.xml");
		RMIServer server = new RMIServer();
		server.setPort(54188);
		
		RMIClient client = new RMIClient();
		client.setRmiServerIp("localhost");
		client.setRmiServerPort(54188);
		
		server.startup();
		
		IAppInterface app = client.getProxy(IAppInterface.class);
		UserInfo user = app.userLogin("123456", "654321");
		System.out.println(user);
		
		server.shutdown();
	}

结果如下:
【JavaEE】基于代理机制的RMI的实现
写到现在,我们的RMI工具已经完成,后面,将用RMI实现NetFramework(升级版的CSFrameWork)。