之前看到设计一个RPC框架的时候有说到Client端用到了动态代理技术,一直不太明白动态代理有什么作用,用在了什么地方,今天写一个博客来记录一下。
RPC框架最重要的三个点:
1. 编解码(序列化与反序列化)
2. 通信协议
3. 网络传输
而在Client端使用动态代理的作用是什么呢?
RPC调用流程
-
服务消费方(client)调用以本地调用方式调用服务;
-
client stub接收到调用后负责将方法、参数等组装成能够进行网络传输的消息体;
-
client stub找到服务地址,并将消息发送到服务端;
-
server stub收到消息后进行解码;
-
server stub根据解码结果调用本地的服务;
-
本地服务执行并将结果返回给server stub;
-
server stub将返回结果打包成消息并发送至消费方;
-
client stub接收到消息,并进行解码;
-
服务消费方得到最终结果。
RPC的目标就是要2~8这些步骤都封装起来,让用户对这些细节透明。
其实就是为了透明化远程服务调用
怎么封装通信细节才能让用户像以本地调用方式调用远程服务呢?对java来说就是使用代理!
java代理有两种方式:1) jdk 动态代理;2)字节码生成。尽管字节码生成方式实现的代理更为强大和高效,但代码维护不易,大部分公司实现RPC框架时还是选择动态代理方式。
下面简单介绍下动态代理怎么实现我们的需求。
我们需要实现一个OrderService服务类,来模拟位于远程服务器上Service类。
import rpc.cyb.service.OrderService;
public class OrderServiceImpl implements OrderService {
//模拟获取一个订单
@Override
public String getOrderNum() {
return "获取的订单号为:129023929384745";
}
}
之后我们需要实现一个本地注册的类LocalRegister,目的是为了实现接口名和实现类的映射,比如我们传入接口OrderService.class,程序就知道我们要使用的是OrderService的哪个实现类了。
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
//本地注册
public class LocalRegister {
//存放接口名和实现类的映射
private static Map<String, Class<?>> interfacesMap = new ConcurrentHashMap<>();
/**
* 接口名和实现类的映射
* @param interfaceName 接口的全类名
* @param implClass 对应实现类的class对象
*/
public static void regist(String interfaceName, Class<?> implClass) {
interfacesMap.put(interfaceName, implClass);
}
public static Class<?> get(String interfaceName) {
return interfacesMap.get(interfaceName);
}
}
实现了上面两步我们就可以来写最重要的Proxy代理类了,代理类的invoke方法中封装了与远端服务通信的细节。
import rpc.cyb.register.LocalRegister;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.concurrent.TimeUnit;
public class MyProxy implements InvocationHandler {
//需要代理的目标对象
private Object target;
public MyProxy(Object target){
this.target = target;
}
//获取代理对象
public static Object getProxy(Class<?> target){
//从本地注册中获取实现类的Class对象
String interfaceName = target.getName();
Class<?> implClass = LocalRegister.get(interfaceName);
MyProxy invocationHandler = null;
try {
//用的是接口实现类来实例化对象
invocationHandler = new MyProxy(implClass.newInstance());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
assert invocationHandler != null;
return Proxy.newProxyInstance(target.getClassLoader(), implClass.getInterfaces(),invocationHandler);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// ...执行通信相关逻辑
// ...
System.out.println("发起RPC调用...");
TimeUnit.SECONDS.sleep(1);
System.out.println("收到远程反馈");
System.out.println("反序列化...");
System.out.println("得到结果:");
return method.invoke(target,args);
}
}
最后是消费端,使用了动态代理,用户可以像调用本地方法一样调用远程方法
import rpc.cyb.proxy.MyProxy;
import rpc.cyb.register.LocalRegister;
import rpc.cyb.service.OrderService;
import rpc.cyb.service.UserService;
import rpc.cyb.service.impl.OrderServiceImpl;
import rpc.cyb.service.impl.UserServiceImpl;
public class RpcClient {
static {
//初始化接口和实现类对应关系
LocalRegister.regist("rpc.cyb.service.UserService", UserServiceImpl.class);
LocalRegister.regist("rpc.cyb.service.OrderService", OrderServiceImpl.class);
}
public static void main(String[] args) {
//用户可以像调用本地方法一样调用远程方法
OrderService orderService = (OrderService) MyProxy.getProxy(OrderService.class);
System.out.println(orderService.getOrderNum());
}
}
运行结果如下: