【JavaEE】基于代理机制的RMI的实现
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)。
上一篇: 统计学~描述性统计实践【第二周】
下一篇: Mysql 自动提交