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();
}
}
执行结果为:
从测试结果可以看出的的确确是将参数传过去了,而且将密码转化为hashcode传回来了。