在常用的各种框架中,反射与动态代理很常见,也很重要。本篇就对这一小节的内容基础性地总结。
首先需要了解什么是类型信息,以及RTTI与反射的关系与区别。
Java中,使用Class对象来表示所有类的对象。利用Class对象来获取类中的成员变量,构造函数以及方法,这些内容我们称之为类型信息。RTTI的含义是,在运行时识别一个对象的类型,但有一个前提,就是类型在编译时必须已知,这样才能用RTTI识别,并利用这些信息做一些有用的事情。但是如果在编译时,程序没有办法获知到这个对象所属的类,怎样才能使用这个类呢?答案是,我们可以在运行时,让JVM加载这个类的Class对象。由于没有经过编译期的检查,所以这里在使用之前,JVM也要简单地检查这个对象,看它属于哪个特定的类,而后加载这个类的Class对象。所以RTTI与反射的区别就在于,前者是在编译时打开和检查.class文件,运行时加载所需的类的Class对象,而后者则是在运行时打开和检查.class文件,并加载所需要类的Class对象。
举个例子:
class Test{ public static void main(String[] args){ new Dev(); } }
在对它进行编译时,如果包下没有Dev这个类,那么编译会报错:
C:\Users\BruceChan\Desktop\test>javac Test.java Test.java:3: 错误: 找不到符号 new Dev(); ^ 符号: 类 Dev 位置: 类 Test 1 个错误
但如果我们修改为反射的方式:
class Test{ public static void main(String[] args){ try{ Class.forName("Dev"); }catch(ClassNotFoundException e){ System.out.print(e.toString()); } } }
对其进行编译时会发现,即便在没有Dev这个类存在的情况下,编译还是会顺利进行,因为它是在运行时,用到这个Dev类时,才会加载这个类。
---------------------------------------------------------------------------------->
接下来,这里用代码的形式小节一下反射技术中,常用的套路。反射中,有三种方法可以获得类型的Class对象:
try { Class cls1 = Class.forName("com.changjiang.test.RFP01.testReflect.MyClass"); } catch (ClassNotFoundException e) { e.printStackTrace(); } MyClass myClass = new MyClass(); Class cls2 = myClass.getClass(); Class cls3 = MyClass.class;
通过该类的Class对象,可以获得它的属性,方法,构造函数,这里只说一下它的方法调用,首先给出原始类:
package com.changjiang.test.RFP01.testReflect; import java.util.Date; public class MyClass { private int myInt; private String myString; public MyClass(){ } public MyClass(int a){ this.myInt = a; System.out.println(a); } public void Method2Void(){ } public int Method2Int(){ System.out.println("Method2Int has run"); return 0; } public String Method2String(){ return ""; } public Object Method2Object() { return new Date(); } public void Method3Param(int a, String b){ System.out.println("Method3Param has run with param-a:" + a + " and param-b:" + b); } }
再给出几个方法的调用以及输出:
package com.changjiang.test.RFP01; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import com.changjiang.test.RFP01.testReflect.MyClass; public class Main { public static void main(String[] args) { MyClass myClass = new MyClass(); Class cls = myClass.getClass(); try{ Method m = cls.getMethod("Method2Int", new Class[]{});//获取一个类的方法 m.invoke(myClass, new Object[]{});//精髓所在,调用这个类的方法 Method m2 = cls.getMethod("Method3Param", new Class[]{int.class, String.class}); m2.invoke(myClass, new Object[]{5, "fake"});//调用这个类的方法,带参数的 Constructor<MyClass> cons = cls.getConstructor(new Class[]{int.class}); cons.newInstance(47); }catch(Exception e){ e.printStackTrace(); } Method[] ms = cls.getMethods(); for(Method m:ms){ System.out.print(m.getName()+"\t"); } } } output:
Method2Int has run
Method3Param has run with param-a:5 and param-b:fake
47
Method2Int Method3Param Method2String Method2Object Method2Void wait wait wait equals toString hashCode getClass notify notifyAll
---------------------------------------------------------------------------------->
OK,说到这里,我们该了解一下代理模式。清楚之后,我们再看动态代理。代理模式,简单地说,就是一个接口,两个实现。实现一中我们需要做一些事情,在实现二中,我们不仅要做实现一中的事情,而且可以在此之前与之后都添加我们想要做的别的事情。具体内容看看代码。
package com.changjiang.test.RFP01.testReflect; public class SampleProxy { public static void main(String[] args) { new BaseSample1().doSomething(); System.out.println("--------------------------------"); new ProxySample(new BaseSample1()).doSomething();; } } interface BaseInterface{ public void doSomething(); } class BaseSample1 implements BaseInterface{ public void doSomething() { System.out.println("BaseSample1"); } } class ProxySample implements BaseInterface{ private BaseInterface bi; public ProxySample(BaseInterface bi) { this.bi = bi; } public void doSomething() { System.out.println("before"); bi.doSomething(); System.out.println("after"); } }
这里的接口,被两个类实现,一个是原生类,另一个是代理类,而代理类中不但实现了这个接口,还需要将代理类通过构造器赋给自己的属性中,而后在接口实现方法中使用原始类的方法。
---------------------------------------------------------------------------------->
接下来看看动态代理,主要是如何实现动态代理以及核心类Proxy与其方法newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InovationHandler h)中几个参数的详细含义与作用剖析。(下篇)