运行时信息(RunTime Type Information,RTTI)使得你可以在程序运行时发现和使用类型信息。RTTI主要用来运行时获取向上转型之后的对象到底是什么具体的类型。
Class对象
JAVA使用Class对象来执行RTTI。每个类都有一个Class对象,它用来创建这个类的所有对象,反过来说,每个类的所有对象都会关联同一个Class对象(对于数组来说,维数、类型一致的数组的Class对象才是相同的),每个对象的创建都依赖于Class对象的是否创建,Class对象的创建发生在类加载(java.lang.ClassLoader)的时候。
Java.lang.Class类实现了Serializable、GenericDeclaration、Type、AnnotatedElement四个接口,分别实现了可序列化、泛型定义、类型、元数据(注解)的功能。可以把Class对象理解为一个类在内存中的接口代理(它代理了这个类的类型信息、方法签名、属性),JVM加载一个类的时候首先创建Class对象,然后创建这个类的每个实例的时候都使用这个Class对象。
Class只有一个私有的无参构造方法,也就是说Class的对象创建只有JVM可以完成。如何验证同一个类的多个对象的Class对象是一个呢?我们知道==用来比较应用是否相等(也就是同一个引用),应为Class对象在JVM中只有一个,所以Class对象是否相等是JAVA对象中唯一可以使用==判断的。
如何获取Class对象
所有的引用数据类型(l类-类型)的类名、基本数据类型都可以通过.class方式获取其Class对象,通过这种方式获取class对象就做类的字面常量;
Class的forName(String name)传入一个类的完整路径也可以获得Class对象,但由于使用的是字符串,必须强制转换才可以获取泛型的Class<T>的Class对象,并且你可以获取这个方法可能抛出的ClassNotreFoundException异常。
对于应用数据类的引用(必须初始化),可以通过Object类继承的getClass()方法获取这个引用的Class对象,由于引用已经被初始化,所以这种方式也不会初始化静态域,因为静态域已经被初始化过。另外,前面两种方式如果说是创建Class对象,那么这种方式应该是取得Class对象,因为类的实例已经被创建,那么Class对象也一定早就被创建。
JAVA的反射机制
前面说过RTTI获取某个对象的确切类型,要求在这个对象在编译时已知,也就是必须已经在你的代码中存在完整的声明,但是如果是运行时才会知晓的对象(例如,网络中传递过来的字节),RTTI就没有办法工作了。
java.lang.class与java.lang.reflect.*包中的类提供了有别于RTTI(编译器在编译时打开和检查*.class文件)的反射机制(运行时打开和检查*.class文件)。JAVA的反射机制功能相当强大。
Java的动态代理
JAVA自带的动态代理是基于java.lang.reflect.Proxy、java.lang.reflect.InvocationHandler两个类来完成的,使用JAVA反射机制。
通常使用下面方法创建代理对象:
Object proxy = Proxy.newProxyInstance(定义代理对象的类加载器,
要代理的目标对象的归属接口数组,
回调接口InvocationHandler);
JDK的动态代理会动态的创建一个$Proxy0的类,这个类继承了Proxy并且实现了要代理的目标对象的接口,但是在JDK中是找不到这个类的,因为它是动态生成的。
public final class $Proxy0 extends Proxy implements 目标对象的接口1,接口2,…{
//构造方法
Public $Proxy0(InvocationHandler h){
… …
}
}
我们用下面的图说明上面的执行过程:
我们看到目标对象的方法调用被Proxy拦截,在InvocationHandler中的回调方法中通过反射调用。这种动态代理的方法实现了对类的方法的运行时修改。
JDK的动态代理有个缺点,那就是不能对类进行代理,只能对接口进行代理,想象一下我们的类如果没有实现任何接口,那么就无法使用这种方式进行动态代理(实际上是因为$Proxy0这个类继承了Proxy,JAVA的继承不允许出现多个父类)。但准确的说这个问题不应该是缺点,因为良好的系统,每一个类都是应该有一个接口的。
下面介绍以下InvocationHandler这个接口,它只有一个方法invoke()需要实现,这个方法会在目标对象的方法调用的时候被激活,你可以在这里控制目标对象的方法的调用,在调用前后插入一些其它操作(譬如:权限控制、日志记录、事物管理等)。
Invoke()方法的后两个参数很好理解,一个是调用的方法的Method对象,另一个是方法的参数,第一个参数有些需要注意的地方,这个proxy参数就是我们使用Proxy的静态对象创建的动态代理对象,也就是$Proxy0的实例。由于$Proxy0在JDK中不是静态存在的,因此不可以把第一个参数Object proxy强制装换为$proxy0类型,因为你根本就无法从ClassPath中导入$Proxy0。那么我们可以把$Proxy0转为目标对象的接口吗?因为$Proxy0是实现了目标对象的所有的接口的,答案是可以的。但实际上这样做的意义不大,因为你发发现转换为目标对象的接口之后,你调用接口中的任何一个方法,都会导致Invoke的调用陷入死循环而导致堆栈溢出。这是因为目标对象的大部分的方法都被代理了,你在invoke()通过代理对象转换之后的接口嗲用目标对象的方法,依然是走的代理对象。
那么invoke()方法的第一个参数到底是干什么用的呢?其实一般情况下这个参数是用不到,除非你想获得代理对象的类信息描述,因为它的getClass()方法的调用不会陷入死循环。
这里还要注意一个问题,那就是从Object身上继承的方法hashcode()等的调用也会导致陷入死循环,为什么getClass()不会呢?因为getClass()方法是final的,不可以被覆盖,所以也就不会被Proxy代理。但不要认为Proxy不可以对final的方法进行动态代理,因为Proxy面向的是接口,即使在实现类中把接口的方法都变成了final的,也不会影响到Proxy的动态代理。
- 大小: 17.2 KB
分享到:
相关推荐
java反射机制和动态代理的原理,熟悉反射机制和动态代理
主要讲述Java反射机制与设计模式之一:代理模式的原理与应用;同时详细讲述了Java对代理模式的支持以及Java中动态代理的原理,应用与实践。
总的来说,Java反射机制和动态代理是Java平台强大而灵活的特性,它们使得Java程序能够在运行时具有更高的灵活性和可扩展性。然而,使用反射也可能带来性能开销和安全风险,因此在实际应用中需要权衡利弊,合理使用。
总结来说,Java反射机制和动态代理是Java编程中不可或缺的部分,它们极大地扩展了Java的灵活性和可扩展性,使得程序员可以在运行时对程序进行更深层次的控制。了解和熟练掌握这两项技术,对于提高Java开发能力和解决...
总之,Java的反射机制和动态代理是强大的工具,可以帮助我们构建更加灵活、可扩展的系统。然而,它们也带来了一定的学习曲线和潜在的问题,如安全风险和性能损耗,因此在使用时应权衡利弊,合理利用。理解并熟练掌握...
总的来说,Java反射机制和动态代理为程序员提供了强大的灵活性,允许在运行时检查和操作类的结构,以及在不修改原有代码的情况下扩展对象的行为。然而,需要注意的是,反射和动态代理的过度使用可能会降低代码的...
JAVA反射机制与动态代理.part04
总的来说,Java反射机制与动态代理是强大的工具,它们让Java代码更具灵活性和扩展性,同时也增加了程序的复杂性。理解并熟练运用它们,将使你在解决复杂问题时游刃有余。在实践中,合理运用这些技术,可以提高代码的...
JAVA反射机制与动态代理.part01
JAVA反射机制与动态代理.part02
JAVA反射机制与动态代理.part05