RMI(远程方法调用[RemoteMethodInvacation])

RMI:远程方法调用(Remote Method Invocation)。能够让在某个java虚拟机上的对象像调用本地对象一样调用另一个java 虚拟机中的对象上的方法。

这是一种基于网络的技术;本地机执行一个函数,而这个函数实质上是在服务器端的。也就是说,表面上是客户端在调用一个函数,但本质上是服务器在执行这个函数,并通过网络返回函数的执行结果。

几个基本问题可以确定:
1、建立服务器;
2、客户端连接服务器;
3、在客户端执行一个方法,而该方法应该在服务器上执行;
4、为了保证其工具性质,可以通过接口完成方法的确定。(我们服务器和客户端都有相同的某个接口类,而客户端执行的方法实则是服务器端执行的,则在服务器端需要实现这个接口类,并在这个实现类上写上注释,以便和接口类一一对应。)

进一步分析:
客户端在执行一个方法的时候,需要在执行过程中,连接服务器,并在连接成功后,将这个方法的名字、
参数都传递给服务器;然后等待服务器返回方法的返回值;
服务器在接到客户端连接请求后,立刻接收方法名称和参数,并反射调用该方法;
最后,将这个方法的执行结果返回给客户端。

客户端只要将这些接口通过代理模式得到代理(用到接口的话就用JDKProxy),那么,就可以完成上述动作。

我们将按照步骤一步一步来:

首先建立服务器RMIServer:

public class RMIServer implements Runnable {
	private int rmiPort;
	private ServerSocket server;
	private volatile boolean goon;
	private ThreadPoolExecutor threadPool;
	private boolean shutdownNow;

	public RMIServer() {
	}
	
	public RMIServer(int rmiPort) {
		this.rmiPort = rmiPort;
	}

	public boolean isShutdownNow() {
		return shutdownNow;
	}

	public void setShutdownNow(boolean shutdownNow) {
		this.shutdownNow = shutdownNow;
	}

	public void setRmiPort(int rmiPort) {
		this.rmiPort = rmiPort;
	}

	public void startRMIServer() {
		if (goon == true) {
			return;
		}
		try {
			threadPool = new ThreadPoolExecutor(10, 50, 5000, TimeUnit.MILLISECONDS,new LinkedBlockingDeque());
			server = new ServerSocket(rmiPort);
			goon = true;
//                      开启侦听线程

			new Thread(this, "RMIServer").start();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void shutdownRMIServer() {
		if (goon == false) {
			return;
		}
		goon = false;
		try {
			// 这里可以通过配置文件,让用户选择关闭线程池的方式
			if (isShutdownNow()) {
				threadPool.shutdownNow();
			} else {
				threadPool.shutdown();
			}
			
			server.close();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public boolean isRMIServerStartup() {
		return goon;
	}

	@Override
	public void run() {
		while (goon) {
			try {  //一旦侦听到就建立通信信道并连接
                               //其实这里可以用到线程池,使其并行处理
				Socket socket = server.accept();
				System.out.println("threadPool.execute(new RMIActioner(socket))");
				RMIActioner actioner = new RMIActioner(socket);
				threadPool.execute(actioner);
			} catch (IOException e) {
				goon = false;
			}
		}
	}
}

RMIActioner类:用来建立通信信道并接收参数并执行方法和发送回客户端。(RMIActoner类里面的ArgumentMaker类)

public class RMIActioner implements Runnable {
	private Socket socket;
	private DataInputStream dis;
	private DataOutputStream dos;
	
	public RMIActioner(Socket socket) {
		try {
			this.socket = socket;
                        //建立通信信道
			dis = new DataInputStream(socket.getInputStream());
			dos = new DataOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
        //客户端将发过来消息,将其通过此方法构造将要执行的参数数组
	private Object[] getParameterValues(Method method, String argusString) {
		ArgumentMaker maker = new ArgumentMaker(argusString);
		Parameter[] parameters = method.getParameters();
		int paraCount = parameters.length;
		
		if (paraCount == 0) {
			return new Object[] {};
		}
		
		Object[] res = new Object[paraCount];
		for (int index = 0; index < paraCount; index++) {
			String paraName = "arg" + index;
			Object value = maker.getValue(paraName,
					parameters[index].getParameterizedType());
			res[index] = value;
			System.out.println(parameters[index]);
		}
		return res;
	}
	
	@Override
	public void run() {
		try {
                        //得到对方发来的方法名和方法参数数组的json字符串
			String methodName = dis.readUTF();
			String argsString = dis.readUTF();
			// 根据methodName,不但要找到方法,还要找到类;
			// 真正执行method,并返回值
                        //此时的MethoidDefinition放的是method和执行method的Object对象
                        //并在将在服务器端上要执行接口类的实现对象上加上注释,在服务器启动时扫描放入MethoidDefinition,并放入RMIMethodFactory。
			MethoidDefinition methodDefinition = RMIMethodFactory.getMethod(methodName);
			
			Object object = methodDefinition.getObject();
			Method method = methodDefinition.getMethod();
			
			Object[] values = getParameterValues(method, argsString);
                        //实际上方法的执行在服务器此处
			result = method.invoke(object, values);
			try {
				System.out.println("正在执行RMI调用");
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
                        //将执行的结果用json发回去
			dos.writeUTF(ArgumentMaker.gson.toJson(result));
                        //发回去之后就应该关闭此通道
			close();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			e.printStackTrace();
		}
	}
	
	private void close() {
		try {
			if (dis != null) {
				dis.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dis = null;
		}
		try {
			if (dos != null) {
				dos.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dos = null;
		}
		try {
			if (socket != null && !socket.isClosed()) {
				socket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			socket = null;
		}
	}
}

MethodDefine类和MethodFactory类在最后给出;

接下来建造我们的客户端:

public class RMIClient {
	private Socket socket;
	private DataInputStream dis;
	private DataOutputStream dos;
	private IRMIServerSelector rmiServerSelector;
	private String ip;
	private int port;
	
	private Method method;
	private Object[] argus;
	
	private Object result;
	
	public RMIClient() {
	}

	public void setIp(String ip) {
		this.ip = ip;
	}

	public void setPort(int port) {
		this.port = port;
	}
	
	public Object getResult() {
		return this.result;
	}
	
	public void setMethod(Method method) {
		this.method = method;
	}

	public void setArgus(Object[] argus) {
		this.argus = argus;
	}

	public void setRmiServerSelector(IRMIServerSelector rmiServerSelector) {
		this.rmiServerSelector = rmiServerSelector;
	}
	
	public Object invokeMethod() {
		try {
			if (rmiServerSelector != null) {
				INetNode netNode = rmiServerSelector.getRmiServer();
				ip = netNode.getIP();
				port = netNode.getPort();
			}
                        //在调用方法时建立通信信道
			this.socket = new Socket(ip, port);
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
		        //把参数按照顺序构成参数的json字符串发送过去,并将methodName发过去,可以让服务器找到实现类。
			ArgumentMaker argumentMaker = new ArgumentMaker();
			for (int index = 0; index < argus.length; index++) {
				argumentMaker.addArg("arg" + index, argus[index]);
			}
			String argsString = argumentMaker.toString();
			
			try {
				dos.writeUTF(String.valueOf(method.toString().hashCode()));
				dos.writeUTF(argsString);
				//然后收到服务器发过来已经完成方法执行返回的对象。
				String result = dis.readUTF();
                                //收到后就将关闭通信信道,释放资源。
				close();				
			} catch (IOException e) {
				e.printStackTrace();
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		return this.result;
	}
	
	private void close() {
		try {
			if (dis != null) {
				dis.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dis = null;
		}
		try {
			if (dos != null) {
				dos.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			dos = null;
		}
		try {
			if (socket != null && !socket.isClosed()) {
				socket.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			socket = null;
		}
	}
}

此时我们客户端如何去调用此invokeMethod呢,我们给一个代理类JDKProxy去处理接口中方法。(代理机制技术)

ClientProxy类:客户端代理调用invokeMethod,以及可以在invokeMethod之前之后加上处理代码:

public class ClientProxy {
	private RMIClient rmiClient;
	
	public ClientProxy() {
	}

	public void setRmiClient(RMIClient rmiClient) {
		this.rmiClient = rmiClient;
	}
	
	@SuppressWarnings("unchecked")
	public  T getProxy(Class interfaces) {
		return (T) Proxy.newProxyInstance(interfaces.getClassLoader(),
				new Class[] {interfaces},
				new InvocationHandler() {
					
					@Override
					public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {       //将将要调用的方法和参数设置到入Client中
						rmiClient.setMethod(method);
						rmiClient.setArgus(args);
						//得到调用的结果并返回。
						Object result = rmiClient.invokeMethod();
						
						return result;
					}
				});
	}
}

接下来的这些类都是其中提到的类:

1.MethodDefinition类:

public class MethoidDefinition {
	private Object object;
	private Method method;
	
	public MethoidDefinition() {
	}
	
	public MethoidDefinition(Object object, Method method) {
		this.method = method;
		this.object = object;
	}
	
	Object getObject() {
		return object;
	}
	
	void setObject(Object object) {
		this.object = object;
	}
	
	Method getMethod() {
		return method;
	}
	
	void setMethod(Method method) {
		this.method = method;
	}
	
	@Override
	public String toString() {
		return method.toString();
	}
}

2.MethodFactory类(用到包扫描技术):

public class RMIMethodFactory {
	private static final Map methodPool;
	
	static {
		methodPool = new HashMap();
	}
	
	public RMIMethodFactory() {
	}
	
	private void registry(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.toString().hashCode());
				
				Method klassMethod = klass.getDeclaredMethod(method.getName(), method.getParameterTypes());
				MethoidDefinition definition = new MethoidDefinition(object, klassMethod);
				methodPool.put(methodId, definition);
			}
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		}
		
	}
	
	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.rmiInterfaces();
				
				for (Class interfaces : interfacess) {
					registry(interfaces, klass);
				}
			}
		}.packageScanner(packageName);
	}
	
	static MethoidDefinition getMethod(String methodID) {
		return methodPool.get(methodID);
	}
}

 

3.接口实现类中要写的注释类:

@Retention(RUNTIME)
@Target(TYPE)
public @interface RMIInterface {
	Class[] rmiInterfaces();
}

4.接口类和实现类:

public interface IStudentService {
	StudentInfo fixStudentInfo(StudentInfo student);
	StudentInfo fixStudentInfo(StudentInfo student, int a);
}
//注释中为接口类类型
@RMIInterface(rmiInterfaces = {IStudentService.class})
public class StudentService implements IStudentService {
	public StudentService() {
	}
	@Override
	public StudentInfo fixStudentInfo(StudentInfo student) {
		student.setPassword(String.valueOf(student.getPassword().hashCode()));
		System.out.println("方法一");
		return student;
	}
	@Override
	public StudentInfo fixStudentInfo(StudentInfo student, int a) {
		student.setPassword(String.valueOf(student.getPassword().hashCode()));
		System.out.println("方法二");
		return student;
	}
}

此为返回类型StudentInfo类:

public class StudentInfo {
	private String name;
	private String password;
	private boolean status;
	
	public StudentInfo() {
	}

	public StudentInfo(String name, String password, boolean status) {
		this.name = name;
		this.password = password;
		this.status = status;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getPassword() {
		return password;
	}

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

	public boolean isStatus() {
		return status;
	}

	public void setStatus(boolean status) {
		this.status = status;
	}

	@Override
	public String toString() {
		return "[" + name + "](" + password + "):" 
				+ (status ? "√" : "×");
	}
}

给出Demo类:

package com.smy.rmi.demo;

import com.smy.rmi.core.ClientProxy;
import com.smy.rmi.core.RMIClient;
import com.smy.rmi.core.RMIMethodFactory;
import com.smy.rmi.core.RMIServer;
import com.smy.rmi.service.IStudentService;
import com.smy.rmi.service.StudentInfo;

public class Demo {
	public final static int rmiPort = 54188;
	public static final String rmiIP = "192.168.79.1";
	
	public static void main(String[] args) {
		RMIMethodFactory factory = new RMIMethodFactory();
		factory.scanPackage("com.smy.rmi.service");
		
		RMIServer server = new RMIServer(rmiPort);
		server.startRMIServer();
		
		ClientProxy clientProxy = new ClientProxy();
		clientProxy.setRmiClient(new RMIClient(rmiIP, rmiPort));
		
		IStudentService iStudentService = clientProxy.getProxy(IStudentService.class);
		
		StudentInfo stu1 = iStudentService.fixStudentInfo(new StudentInfo("quan", "123", false));
		System.out.println(stu1);
		
		StudentInfo stu2 = iStudentService.fixStudentInfo(new StudentInfo("ma", "555", false), 5);
		System.out.println(stu2);
		
		server.shutdownRMIServer();
	}

}

执行结果为:

RMI(远程方法调用[RemoteMethodInvacation])_第1张图片

 从测试结果可以看出的的确确是将参数传过去了,而且将密码转化为hashcode传回来了。

你可能感兴趣的:(Java,短连接,RMI)