一、为什么加入动态代理
\quad\quad 如果没有动态代理,远程调用时,需要对每个类都要建立代理,这样会导致代码十分冗余,我们通过Java中Proxy,动态的构建类,来实现AOP的功能。
二、加入动态代理实现流程
2.1 服务端
2.1.1 调用信息序列化
\quad\quad 因为需要通过反射的方式执行方法,因此我们需要将函数的全部执行信息都要进行序列化,以便进行二进制传输。
public class RpcRequest implements Serializable {
private static final long serialVersionUID = 6011503509272346423L;
//类名
public String className;
//方法名
private String methodName;
//参数类型
private Class<?>[] parameterTypes;
//参数
private Object[] arguments;
public String getClassName() {
return className;
}
public void setClassName(String className) {
this.className = className;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public Class<?>[] getParameterTypes() {
return parameterTypes;
}
public void setParameterTypes(Class<?>[] parameterTypes) {
this.parameterTypes = parameterTypes;
}
public Object[] getArguments() {
return arguments;
}
public void setArguments(Object[] arguments) {
this.arguments = arguments;
}
public RpcRequest() {
}
public RpcRequest(String className, String methodName, Class<?>[] parameterTypes, Object[] arguments) {
this.className = className;
this.methodName = methodName;
this.parameterTypes = parameterTypes;
this.arguments = arguments;
}
}
2.1.2 服务端逻辑
\quad\quad 连接socket,接收客户端传递的数据,并执行相应方法,并将计算结果传递给客户端。
public class serverDPC {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8889);
try{
//等待连接
Socket accept = serverSocket.accept();
//建立连接后,从socket中获取输入流
ObjectInputStream objectInputStream = new ObjectInputStream(accept.getInputStream());
//从输入流中读取对象
RpcRequest rpcRequest=(RpcRequest)objectInputStream.readObject();
// 读取类名
String classFullName=rpcRequest.getClassName();
// 读取方法名
String methodName=rpcRequest.getMethodName();
// 读取方法入参类型
Class<?>[] parameterTypes=rpcRequest.getParameterTypes();
// 读取方法调用入参
Object[] parameters=rpcRequest.getArguments();
//因为readObject()从输入流获取的对象无法实例化(此时只是字符意思),故需要反射机制
//获取反射机制类
Class<?> aClass = Class.forName(classFullName);
Method method = aClass.getMethod(methodName, parameterTypes);
//通过反射执行方法
Object invoke = method.invoke(aClass.newInstance(), parameters);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(accept.getOutputStream());
objectOutputStream.writeObject(invoke);
} catch (ClassNotFoundException | NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
}
}
}
2.2 客户端
2.2.1 客户端代理类
\quad\quad 对于InvocationHandler,相当于AOP中的拦截器,所有调用代理类的方法都会被拦截下来转到InvocationHandler中的invoke中执行。因此,我们在Consumer实现时,需要在invoke方法中,将类的调用信息(类名,方法名、参数等)序列化后打包,通过socket发送给服务端来执行远程过程调用。
\quad\quad 编写计算器代理类逻辑:
- 以引用的方式指向被代理类
- 在invoke方法中建立socket连接
- 将被代理类的调用信息(类名、方法名、参数)序列化后传递给服务端
- 接收服务端返回的计算结果
public class ConsumerProxy implements InvocationHandler {
//被代理类
private final Class<?> serviceClass;
public ConsumerProxy(Class<?> serviceClass) {
this.serviceClass = serviceClass;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Socket socket = new Socket("127.0.0.1", 8889);
ObjectOutputStream objectOutputStream = new ObjectOutputStream(socket.getOutputStream());
//数据打包
RpcRequest rpcRequest = new RpcRequest(serviceClass.getName(), method.getName(), method.getParameterTypes(), args);
//通过socket发送数据
objectOutputStream.writeObject(rpcRequest);
//接收返回数据
ObjectInputStream inputStream = new ObjectInputStream(socket.getInputStream());
return inputStream.readObject();
}
}
2.3 通过RPC远程调用
- 创建代理类的InvocationHandler,相当于AOP拦截器,使得调用代理类的方法都会转入invoke中执行。
- 创建代理类(调用方法已经在invoke中完成)。
- 调用相应的方法。
public class ClientDPC {
public static void main(String[] args) {
//创建代理类的InvocationHandler
ConsumerProxy proxy=new ConsumerProxy(CalculatorImpl.class);
//创建代理类
//代理类会声明被代理类的接口
Calculator cal=(Calculator) Proxy.newProxyInstance(Calculator.class.getClassLoader(),new Class<?>[]{Calculator.class},proxy);
//调用
int a=cal.add(5,1);
System.out.println("RPC CONNECTED!"+a);
}
}