| 前言
前一章我们讲解了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系列合集】