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

1 篇文章 0 订阅
1 篇文章 0 订阅

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 + "]";
	}
	
}

在这里插入图片描述

	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();
	}

结果如下:
在这里插入图片描述
写到现在,我们的RMI工具已经完成,后面,将用RMI实现NetFramework(升级版的CSFrameWork)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值