自己实现一次Java的rpc调用

自己实现一次Java的rpc调用

自己实现一次RPC其实并不难,只需要略懂点Socket编程,和 Proxy动态代理

公共类

公共的服务接口

public interface FooService {
	String foo(String string);
}

自定义关于远程调用的方法描述类

import java.io.Serializable;
public class TargetMethod implements Serializable {
	private static final long serialVersionUID = -7542219517072936368L;
	//调用的方法名称
	private String methodName;
	//参数列表
	private Object[] params;
	//参数的类型列表
	private Class<?>[] paramsClass;
	public String getMethodName() {
		return methodName;
	}
	public void setMethodName(String methodName) {
		this.methodName = methodName;
	}
	public Object[] getParams() {
		return params;
	}
	public void setParams(Object[] params) {
		this.params = params;
		this.paramsClass = new Class<?>[params.length];
		for (int x = 0; x < params.length; x++) {
			paramsClass[x] = params[x].getClass();
		}
	}
	public Class<?>[] getParamsClass() {
		return paramsClass;
	}
	public void setParamsClass(Class<?>[] paramsClass) {
		this.paramsClass = paramsClass;
	}
}

服务提供者

FooService的实现类

public class FooServiceImpl implements FooService {
	
	@Override
	public String foo(String string) {
		return "Hello," + string;
	}
}

对外暴露服务

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.ServerSocket;
import java.net.Socket;

public class Provider {
	public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//服务的实现
		FooServiceImpl fooServiceImpl = new FooServiceImpl();
		
		//在1024端口暴露服务
		try (ServerSocket serverSocket = new ServerSocket(1024)) {
			while (true) {
				
				Socket socket = serverSocket.accept();
				ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
				
				//读取方法描述,并且通过反射执行方法
				TargetMethod targetMethod = (TargetMethod) objectInputStream.readObject();
				Method method = fooServiceImpl.getClass().getDeclaredMethod(targetMethod.getMethodName(), targetMethod.getParamsClass());
				Object result = method.invoke(fooServiceImpl, targetMethod.getParams());
				
				//往客户端写入执行结果
				ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
				objectOutputStream.writeObject(result);
				objectOutputStream.flush();
				
				socket.shutdownOutput();
			}
		}
	}
}

服务消费者

代理对象生成工厂

import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.net.Socket;

public class ProxyFactory {

	/**
	 * 生成一个rpc的代理对象
	 * @param clazz
	 * @return
	 */
	public static FooService getProxy(Class<? super FooService> clazz) {
		return (FooService) Proxy.newProxyInstance(ProxyFactory.class.getClassLoader(), new Class<?>[] {clazz}, new InvocationHandler() {
			@Override
			public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
				try(Socket socket = new Socket("127.0.0.1",1024)){
					//创建方法描述。写入当前执行的方法名称,参数
					TargetMethod targetMethod = new TargetMethod();
					targetMethod.setMethodName(method.getName());
					targetMethod.setParams(args);
					
					//写入到消费服务者,执行远程调用
					ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
					objectOutputStream.writeObject(targetMethod);
					objectOutputStream.flush();
					socket.shutdownOutput();
					
					//获取到执行结果
					ObjectInputStream objectInputStream = new ObjectInputStream(socket.getInputStream());
					Object result = objectInputStream.readObject();
					
					socket.close();
					return result;
				}
			}
		});
	}
}

远程调用服务

public class Customer {
	public static void main(String[] args) {
		FooService fooService = ProxyFactory.getProxy(FooService.class);
		//执行代理方法,把参数提交给远程服务器完成计算后返回计算结果
		String result = fooService.foo("KevinBlandy");
		System.out.println(result);		//Hello,KevinBlandy
	}
}

总结

  1. 把本地的方法的参数,名称等等封装为对象,序列化位字节数据提交给远程服务器
  2. 远程服务器读取到参数名称,参数等等数据,在本地反射执行实现实现类的方法,并且通过网络返回计算后的结果
  3. 必须先启动服务提供者,再执行远程调用