4.3 Java的Reflection机制
Reflection这个词,一般都直接翻译成“反射”。但是实际上这个词还有反躬自省、自我认识的意思,在Java一类的编程语言中表示具体对象(object)在运行中认识甚至操纵其自身结构的能力。在传统的C、C++这样的编译型语言中,假定一个数据结构中有五个函数指针,或者说有五个操作方法,那么编译器在编译的时候当然有关于这个数据结构的全面的信息,这些信息来自源代码中的相关声明和定义。但是一旦完成编译之后,这些信息实际上就转化成下标、地址、位移等方式隐藏和固化在可执行代码中,以后就不再有关于这个数据结构自身的描述,这些显式的信息在很大程度上就被丢弃了。这样,比方说,在程序执行的过程中(运行时),当你通过一个函数跳转表中的某个指针调用某个函数的时候,就无法知道通过这个跳转表还可以调用些什么别的函数,也不知道这个函数叫什么名称。另外,调用一个函数,或者访问一个字段,只能是根据预先(编译时)确定的地址或地址加位移的方式进行,而不能临时按函数名调用或按字段名访问。之所以如此,就是因为在编绎以后程序内部缺少了认识和操纵其自身的手段和能力。但是在Java以及一些解释执行的语言中,这个问题就好解决了,因为解释器(虚拟机)本来就需要保留此类结构信息,这是在执行过程中要用的。实际上,在Java语言中,任何一种类型,无论其为class、interface、enum、String,都有个Class对象,这就是对于这个类的结构描述。这样,比方说我们在运行时需要知道某个对象是否提供一个名叫“DoSomething”的方法,就可以让解释器查一下这个对象所属类的Class对象,看看里面有没有提供这个方法。这样的机制,就称为Reflection机制,因为它的核心和基础就是对于自身的认识。说“反射”也没有错,因为你通过镜子的反射可以看到你自己的面容。在Java语言中, JDK的java.lang.reflect这个packge就是为用户提供Reflection机制,把原本由解释器自用的功能开放给用户,而Proxy就是利用了这种机制的一项重要功能,Java自己的RMI机制的实现就利用了此项功能。
Proxy,即“代理”,是JDK提供的一个类,可以说是专为RMI定制的。其机理是这样:先为Server的服务定义一个界面,比方说ApplicationClientProtocolPB,如果Client与Server是在同一JVM上,中间也没有什么操作需要插进去,那就可以直接调用Server在此界面上提供的函数。但是,如果Client与Server不在同一JVM上,或者我们需要在Client与Server之间插入一些什么操作,例如增加一些Log以利调试,那么我们就可以在Client所在的那个JVM上为Server创建一个“代理”,即Proxy对象,通常就称为proxy,以proxy为中介就可以调用server上的服务。
Proxy对象的创建一定是与InvocationHandler联系在一起的。InvocationHandler是JDK中定义的一个界面,这个界面上只有一个操作方法,就是invoke()。创建Proxy对象之前一定要先定义一个实现了InvocationHandler界面的类,例如Invoker,并创建一个该类对象,例如invoker,然后就可以创建Proxy对象了。创建时的参数包括其所代理的一个或几个界面,以及实现了InvocationHandler界面的对象invoker。JDK在创建Proxy对象时会动态(运行时)生成一个隐形的对象,这个对象(类)实现了给定界面上所定义的所有方法,但是每个方法函数的代码都是相同的,那就是把描述其自身的Method对象作为参数,连同上面传下来的调用参数一起,调用Invoker.invoke()。这样,从Client看来跟以前的直接调用并无不同,因为Proxy提供了定义于界面上的所有方法,但是九九归一都跑到了Invoker.invoke(),于是我们就可以在这个方法中实现把有关本次调用的信息编码发送到Server所在的JVM,再由那里的相关程序加以解码,转化成对于那里的服务调用。或者,即使Client与Server是在同一JVM上,这也给了我们拦截和插入中间处理提供了手段。
事实上,Hadoop的RPC机制在Client一侧就是通过Proxy实现的。
下面我们就来看,在前述的Server和Client以及RPC这个类的基础上,Hadoop是如何利用ProtoBuf和Proxy搭建起自己的RPC机制,并以submitApplication()为例说明其内部的程序流程。