学长来引流啦,刚毕业一年,在魔都某大厂,大家可以关注公众号【离心计划】,我会出一些高质量的实战教程,让大家对于技术有更多的了解
| 前言
前一章我们讲解了RPC的协议与序列化,有了这两层的铺垫,我们可以以约定的格式将对象转换成二进制数据在两端之间传递了,然而我们漏掉了一个重要步骤,从你调用了UserService的getUserByName到客户端携带着你传递的方法参数去请求服务端之间,是谁在暗渡陈仓,谁在帮你负重前行?这边便引出了动态代理,真正帮我们完成这部分工作的是代理类。
| 静态代理
比如我们现在有一个需求,需要在执行sayHello之前执行一条日志:
public interface Hello {
void sayHello();
}
class Target implements Hello{
public void sayHello(){
System.out.println("----This is a log----");
}
}
这里有一个Target类继承接口Hello,里面的sayHello方法我们希望在执行它之前输出一条日志,那我们的做法有下面几种:
直接在sayHello中加日志
创建另一个class,在另一个class中写日志逻辑
第一种就不说了,我们实现了第二种:
interface Hello {
void sayHello();
}
class Target implements Hello{
@Override
public void sayHello(){
System.out.println("Hello world");
}
}
//实现一个代理类
class ProxyTarget{
public ProxyTarget(Target trg){
this.trg=trg;
}
public void loggerHello(){
System.out.println("----This is a log----");
trg.sayHello();
}
private Target trg;
}
public class Main {
public static void main(String[] args) {
Target trg = new Target();
ProxyTarget ptrg = new ProxyTarget(trg);
ptrg.loggerHello();
}
}
我们实现了一个代理类 Proxy_Target,在构造函数中把Target类对象作为参数输入进来,然后在logger_hello方法中先打印日志,然后继续执行原来的say_hello,这就是 静态代理的写法.
如果我们的需求是在Hello中每一个方法前都打印一条日志,那么代理类会很复杂,没有提效,这也是静态代理的问题所在。那么,解决这个问题的办法就在于,我们这个代理类是否可以帮我们自动生成,不用自己去写,我们只用关注“在执行方法前打印一条日志”这个需求就行了,就很nice,这就是动态代理做的事情。
| 动态代理
Java中动态代理分为两种:JDK动态代理和Cglib动态代理。我们以jdk动态代理为主介绍下用法,我们先硬编码看看怎么实现上面日志的需求
public class Main {
public static void main(String[] args) throws Throwable{
//得到一个代理类
Class hello_proxy = Proxy.getProxyClass(Hello.class.getClassLoader(),Hello.class);
//得到代参构造器
Constructor constructor = hello_proxy.getConstructor(InvocationHandler.class);
//反射创建代理
Hello hello = (Hello) constructor.newInstance(new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Target trg = new Target();
System.out.println("This is a Dynamic Log");
Object result = method.invoke(trg,args);
return result;
}
});
hello.say_hello();
}
}
大致的流程就是
先通过Proxy工具类创建一个新的Class对象
拿到构造器
用构造器创建一个新的Hello实例,并重写invoke逻辑
这个invoke函数在每次调用代理类中的每个方法时都会被调用,其中的method就是用户指定调用的方法。因为动态代理其实涉及到了反射的知识,如果读者阅读廖雪峰老师的文章浅学一下作为铺垫:https://www.liaoxuefeng.com/wiki/1252599548343744/1255945147512512
然后我们优化一下代码
public class Proxy_Object {
private Object obj;
public Object bind(Object obj){
this.obj=obj;
return Proxy.newProxyInstance(
this.obj.getClass().getClassLoader(),this.obj.getClass().getInterfaces(),this::invoke
);
}
public Object invoke(Object proxy, Method method,Object[] args) throws Throwable{
Object result = null;
try{
System.out.println("This is a Dynamic Log");
result = method.invoke(this.obj,args);
}
catch (Exception e){
e.printStackTrace();
}
return result;
}
}
这里我们推荐直接使用Proxy.newProxyInstance方法创建代理类,里面的参数指定了构造器、Class以及invoke函数,然后我们就可以这样去调用:
public class Main {
public static void main(String[] args) {
Proxy_Object p_obj = new Proxy_Object();
Target trg = new Target();
Hello hello = (Hello) p_obj.bind(trg);
hello.say_hello();
}
}
这样无论我们在Hello中新增什么方法,都能实现“调用前打印日志”的需求
那么放到RPC中,我们的代理类需要帮我们做的,不是打印日志而是完成对指定远程方法的调用而已,也就是我们只需要关注invoke中的逻辑,我们可以有下面的伪代码
public Object invoke(Object proxy, Method method,Object[] args){
//1.组装请求
RpcRequest request = buildRequest();
//2.调用远程服务
Object result = callRemoteService(request);
return result;
}
这样我们调用每个服务的方法,都能像调用本地方法一样,拿到远程服务的返回结果了,这部分“繁重”的工作就交给了动态代理帮我们做掉了。
| 小结
这一章我们讲解了动态代理在RPC中作用,以及简单介绍了一下动态代理,如果大家对动态代理想要更深的了解,可以自行查阅,毕竟是什么和怎么用是每个工具的第一步,而后再深入了解才会更有用的更有底气。
下一章我们将进入实战阶段,理论的部分我们暂时到这里,我们下周见~
【RPC系列合集】