RMI 总结之代理

上一篇我们跟踪了RMI服务端(包含注册中心,以下提到的服务端均是这个含义)的源码。知道了服务端是如何管理服务的——通过一个内存数据结构,HashTable,来完成与服务相关的操作(存储、新增、修改、删除、查找):

        - 发布服务——调用Registry对象的bind()方法或者rebind()方法
        - 删除服务——调用Registry对象的unbind()方法
        - 查找服务——调用Registry对象的lookup()方法
        - 查询所有服务——调用Registry对象的list()方法
 
知道了服务端是通过一个Socket对象监听1099端口来接收客户端请求的;也知道了对外提供服务的其实是一个代理对象。Socket、代理,听着这一个个耳熟能详的名字,大家是不是觉得很Easy。不过,我还是想透过这篇文章,加深一下自己的认识。如果下面描述中有不对的地方,欢迎大家评论区留言批评指正,在此我先说声:谢谢!

前一节我们看到了UnicastServerRef#export(Remote, Object, boolean),发现程序会首先获取当前Remote对象的真实类型,比如Registry的实现类RegistryImpl;然后调用Util类的createProxy方法创建一个Remote对象。通过方法名,不难看出其主要目的是创建一个代理对象。进入该方法可以发现其中确实有这样一句代码:

InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef);

不过上一节创建RegistryImpl代理时并没有走到这句,而是走到了createStub(remoteClass, clientRef)这一行,最终创建了一个RegistryImpl_Stub对象。看到这里,我糊涂了:根据方法名可以确定这里要创建代理类,但RegistryImpl_Stub的创建怎么看着不像是创建代理呢?暂且不管这个,先看一下代理的知识吧!

代理,个人理解就是替人代劳,是中间商,享受一定的收益。比如同事请你代劳相亲(为什么我遇不到这种乐于助人的同事),又或者同事请你代劳吃饭(听着怎么这么奇怪)。好了这种玩笑话不说了,假如你是某工厂老板,生产出了一款产品,想将其推向市场,但又觉得这个工作比较繁琐,于是将其委托给了一家实力雄厚的销售公司,由其全权代理该产品的销售工作,这样你可以专注于自己产品的生产与改进。这个时候你和推销公司之间就是一种代理关系。在软件开发领域也有用到这种思想,比如spring中的事务。在我们工作中使用的代理通常分为两大类:

  1. 静态代理。所谓静态代理就是专门为某个类编写的代理类,该代理类专门为被代理类(又被称为目标类)工作。代理类和被代理类之间是一对一的关系
  2. 动态代理。所谓动态代理就是编写的代理类不专门针对某个类工作,而是为所有目标类工作。代理类与被代理类之间是一对多关系

静态代理

上面说过Util#createProxy()这个方法的主要作用是创建代理对象,而该方法中的代码createStub(remoteClass, clientRef)看着与代理无关,实际上是有关系的,所以上面疑问就是无知惹的祸。先来看一下类结构图:

RMI 总结之代理_第1张图片

从图中可以看出RegistryImpl和RegistryImpl_Stub同时实现了Registry接口,同时他们又间接的实现了Remote接口,而RegistryImpl_Stub又直接实现了Remote接口。下面我们对这两个类做一个比较:

RegistryImpl

RegistryImpl_Stub(Client)

1

bind()

bind()

2

list()

list()

3

lookup()

lookup()

4

rebind()

rebind()

5

unbind()

unbind()

7

createRegistry(int regPort)

8

setup(UnicastServerRef uref)

9

checkAccess(String op)

10

getID()

11

pathToURLs(String path)

12

registryFilter(ObjectInputFilter.FilterInfo filterInfo)

13

getAccessControlContext(int port)

14

getTextResource(String key)

15

initRegistryFilter()

注意:主要关注表中前五行的方法即可。客户端调用服务端的这五个方法时,会首先调用RegistryImpl_Stub类(这个类的对象存在于客户端,它是服务端RegistryImpl对象在客户端的代理)中的对应方法,比如lookup ()(该调用操作会经过一系列处理,然后将请求转发给服务端,由其进行处理并返回保存在服务端的服务对象)。下图RegistryImpl_Stub类中lookup ()的代码:

RMI 总结之代理_第2张图片

这里不对其调用细节进行展开,因为这涉及到TCP通信的内容,后续会进行补充。从这里我们可以看出这个RegistryImpl_Stub是一个代理类,其主要为RegistryImpl工作,所以它们之间是一对一的关系。由于这里的代理类要做的工作比较特殊(建立与服务端的连接,获取服务端的服务对象,调用服务端的服务对象),不像事务那样通用,所以这里没有使用jdk或cglib动态代理,而采用了静态代理。(这是个人理解,如果不对,还望指正!)。

这里我们可以总结一下,创建静态代理的过程:

  1. 创建公共接口,比如这里的Registry接口,其主要定义了以下几个方法:lookup(String name)、bind(String name, Remote obj)、unbind(String name)、rebind(String name, Remote obj)、list()
  2. 创建目标类,即被代理类,其主要完成真正的业务处理,比如这里的RegistryImpl类(其主要完成服务的查找、服务的绑定、服务的重绑定、服务的解绑、服务的查询)
  3. 创建代理类,比如这里的RegistryImpl_Stub(其主要完成与目标类之间的通信辅助工作,比如这里与服务器之间建立连接的操作,发送数据的操作等)

注意代理类可以直接管理被代理类,也可以不直接管理被代理类直接管理被代理类方式中,代理类和被代理类一般位于同一jvm进程中,这时的代理类可以直接与被代理类进行通信,也就是可以直接调用被代理类中的相关方法而在不直接管理方式中,代理类和被代理类一般位于不同的jvm进程中,就比如RegistryImpl和RegistryImpl_Stub,这时代理类无法直接与被代理类进行通信,只能通过间接方式进行通信

下面我们用一个案例来加深对静态代理的理解。有这样一个需求:现在有一个业务交易在线上稳定运行,但是因为某些原因,业务提出要对所有交易进行验证,如果当前交易的账户处于管控状态,则限制其每次交易金额只能小于等于200块,如果大于200块,直接拒绝并提示用户。接到这个需求我们可以考虑采用这里的静态代理来实现。首先公共接口和目标类都存在,我们只需要创建一个目标类的代理类,比如ConsumeServiceProxy.java,该类直接管理目标类,并实现ConsumeService接口,伪代码如下所示:

// 注意:这里注入的一定是目标类

@Autowired

private ConsumeService consumeService;

public void consume() {

  // 进行风险校验,如果校验不同过则直接拒绝交易

  // 校验通过,则调用目标类中的某个方法

consume.consume();

}

使用这种方式的好处在有:老代码处理逻辑不动,降低老代码出错概率;使开发者专注于新业务开发。

动态代理

前面说过动态代理就是编写的代理类不专门为某个类工作,而是为所有目标类工作,此时代理类和目标类之间的关系是一对多的关系。实现动态代理的方式有多种,最常见的两种是jdk动态代理和cglib动态代理。这里我们主要讲jdk动态代理。先回到那段看着跟jdk动态代理很像的代码处:

        InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef);

从这段代码可以看出InvocationHandler和RemoteObjectInvocationHandler看着很像,它们有没有关系呢?答案是有的。先看下面这个类图:

RMI 总结之代理_第3张图片

由图可知RemoteObjectInvocationHandler类实现了InvocationHandler接口,并实现了其中的invoke()方法。该接口代理的是RemoteRef类型的实现类,比如代码这块的UnicastRef对象。创建完RemoteObjectInvocationHandler对象后,后面有这样一句代码:

Proxy.newProxyInstance(loader, interfaces, handler);

这段代码的主要作用就是创建代理对象,这里的实际类型为Remote。先看一下RMIServiceImpl的类结构图:

RMI 总结之代理_第4张图片

图中的RMIServiceImpl和RMIService是自己定义的。它们间接实现了Remote接口。所以这里创建代理的完整代码如下图所示:

RMI 总结之代理_第5张图片

创建完Remote代理对象之后,会返回给上层调用者,即UnicastServerRef的exportObject()方法,该方法会Remote类型的impl对象、Remote类型的代理对象等合并创建成一个Target对象,具体代码为:Target target = new Target(impl, this, stub, ref.getObjID(), permanent);,之后再调用UnicastRef对象的export()方法,将该Target对象传递进去。export()之后的代码就不再深入了,我们继续回到创建代理那部分代码。根据这里的用法,我们可以总结一下使用Java动态代理的步骤。具体步骤如下所示:

  1. 创建代理接口,比如这里的Remote接口
  2. 创建代理接口实现类,即被代理类,也被称为目标类,比如这里的RMIServiceImpl类。根据该类创建的对象被称为目标对象(被代理对象)
  3. 创建InvocationHandler实现类,即这里的RemoteObjectInvocationHandler类
  4. 调用Proxy.newProxyInstance(被代理对象的类加载器, 被代理类实现的接口集合, InvocationHandler实现类)

下面我们还是用一个案例来加深对java动态代理的理解。需求依旧是上面的需求,只不过业务方不再只要求消费交易做个判断,而是要求系统中所有与金钱有关的交易都进行这个判断。这个时候如果按照静态代理写法,就需要添加多个代理类,很显然工作量增加了。如果是我肯定是不愿意的,但是如果大家对代码量要求很多,那多写写也无妨(毕竟人家要求每月提交的代码量必须超过12000行呢)。不过,我不想这样写,因为这种纯粹的体力工作不能带来任何精神上的快感!下面我们就按照动态代理的步骤尝试一下吧!

  1. 首先,所有的交易接口和交易实现类都存在了,这就相当于第一步和第二步已经提前实现了
  2. 接着,我们直接进行第三步,创建实现了InvocationHandler接口的实现类VarifyInvocationHandler,其代码如下所示:
  3. 在使用者类中,比如controller中注入代理类,可以直接这样写:Object obj = Proxy.newProxyInstance(被代理对象的类加载器, 被代理类实现的接口集合, InvocationHandler实现类);,之后利用obj调用Object中的相关方法(这里的Object是一个代称,指的是一个个的交易接口,比如Consume等等。这句话是提醒我自己的,因为我的记性总是不好)
public class VarifyInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{
      // 进行业务校验
      // 校验通过,则执行被代理对象的方法,并获得返回值
      Object result = method.invoke(目标对象, args);
      System.out.println("演出后的增强:结算费用并纳税");
      return result;
    }
}

这里有两个问题要在使用时仔细考虑一下

  1. 如何将目标对象传递到InvocationHandler实现类中
  2. 如何在controller中注入代理对象

通过上面我们了解了java动态代理的开发步骤,那么其原理究竟是上面呢?我们知道上面c步骤创建完代理对象后,调用Object中的方法时,会走VarifyInvocationHandler中的invoke()方法,然后执行前置业务逻辑,接着执行业务逻辑,最后执行后置业务逻辑。那VarifyInvocationHandler和真实的业务处理实现类究竟是怎么关联起来的呢?先看下面的这段代码:

import java.lang.invoke.MethodHandles;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;


final class $Proxy0 extends Proxy implements Actor {
    private static final Method m0;
    private static final Method m1;
    private static final Method m2;
    private static final Method m3;
    private static final Method m4;
    private static final Method m5;

    public $Proxy0(InvocationHandler var1) { super(var1); }

    public final int hashCode() {
        try {
            return (Integer)super.h.invoke(this, m0, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final boolean equals(Object var1) {
        try {
            return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String toString() {
        try {
            return (String)super.h.invoke(this, m2, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final String dance() {
        try {
            return (String)super.h.invoke(this, m3, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void sing(int var1) {
        try {
            super.h.invoke(this, m4, new Object[]{var1});
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    public final void rap() {
        try {
            super.h.invoke(this, m5, (Object[])null);
        } catch (RuntimeException | Error var2) {
            throw var2;
        } catch (Throwable var3) {
            throw new UndeclaredThrowableException(var3);
        }
    }

    static {
        try {
            m0 = Class.forName("java.lang.Object").getMethod("hashCode");
            m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
            m2 = Class.forName("java.lang.Object").getMethod("toString");
            m3 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("dance");
            m4 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("sing", Integer.TYPE);
            m5 = Class.forName("org.com.chinasofti.queue.Actor").getMethod("rap");
        } catch (NoSuchMethodException var2) {
            throw new NoSuchMethodError(var2.getMessage());
        } catch (ClassNotFoundException var3) {
            throw new NoClassDefFoundError(var3.getMessage());
        }
    }

    private static MethodHandles.Lookup proxyClassLookup(MethodHandles.Lookup var0) throws IllegalAccessException {
        if (var0.lookupClass() == Proxy.class && var0.hasFullPrivilegeAccess()) {
            return MethodHandles.lookup();
        } else {
            throw new IllegalAccessException(var0.toString());
        }
    }

}

根据代码可以知道类中Method类型的m0\m1\m2\m3\m4\m5分别代表的是hashCode()\equals()\toString()\dance()\sing()\rap()。另外类中还有一个InvocationHandler类型的变量h,这个来源于其继承的父类Proxy,jvm在创建$Proxy0对象时,会把我们创建的实现了InvocationHandler接口的VarifyInvocationHandler对象注入其中,以方便后续使用。继续看$Proxy0这个类的代码,其又实现了Actor接口,这接口就是我们自己定义的代理接口(也可以称为被代理接口),之后就实现了代理接口中的所有方法,其中的处理逻辑就是调用实现了InvocationHandler接口的VarifyInvocationHandler对象中的invoke()方法。具体看下面这张图:

RMI 总结之代理_第6张图片

从图中可以看出我们上面的理解和代码逻辑一致。那这里的this表示的是$Proxy0类型的当前代理对象。那这个代理对象究竟怎么和最终的业务处理实现类关联起来的呢?看下面这张图(图中代码是前文的代码示例):

RMI 总结之代理_第7张图片

图中的目标对象就是代理对象和最终业务对象关联的关键。这个目标对象在我的测试代码中是直接定义在main()方法中的,实战开发中需要考虑如何将这个对象传递进来,可以按照下图这种方式实现:

RMI 总结之代理_第8张图片

你可能感兴趣的:(Java,RMI,java,开发语言)