前言碎语
刚开始学习时其实是跳过了这么一个知识点的(因为确定难懂又暂时没什么用),后面听说了在框架中反射是基本的原理,我就又滚回来了(出来混迟早要还的,深以为然)
反射机制是什么?
反射机制是能在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取信息的以及动态调用对象的方法的功能成为java反射机制
反射是为了动态的加载java类,使得程序在编译时不需要知道某些类的具体信息,只有在运行的时候根据输入的类(补全了某些类的信息),来动态的加载该类,并运行其中的方法。
反射机制有什么用?
看过一篇博客,举了这么一个通俗易懂的例子:
两个程序员A和B一起工作,因为工作的原因,两个人的任务是分开完成的,同时也是为了保证工程进度;但是程序员A的任务中需要用到程序员B的代码,那么如何在保证A的任务能够进行下去同时又能保证A和B一起推进任务呢?
这时就需要用到了java反射的机制。按照上面的说法,我们可以在A的代码中先对所需要B中的某个类进行**代理使用,这样可以保证A的程序编译通过;然后在程序运行的时候,通过某种方式(传参数)来获取到真正想要调用的类。这样在程序运行时就会使用到该类的动态代理对象
,从而完成任务。
反射机制的原理
看了上面的介绍以,我就在想这是怎么实现的(感觉这个反射是为了骗过编译器啊….)
首先说一下动态加载,我们知道jvm在运行java程序前会先加载所使用到的类进行编译,而有的类是在编译时期不知道的,只有在运行的时候才会加载,此谓动态加载。在实现动态加载类时,又有一个动态的代理机制
在里面:
所谓动态代理,就是程序在运行的时候,对于一个接口和实现类,可以由JVM生成一个代理对象来帮助你使用接口或类中的方法(而不需要显式的去实例化一个类的对象)
这样我们可以在程序中直接使用代理对象,完成操作。
反射机制的常规用法
对于编译时知道类的信息的情况就不说了,上一篇Class里面有涉及,具体使用时查API文档就好,这里说编译时期不知道类的信息的情况
- 定义一个动态代理类,该类必须实现
InvocationHandler
接口
class DynamicProxy implements InvocationHandler
{
// 这个是要代理的对象
private Object subject;
// 构造方法,给要代理的对象赋初值
public DynamicProxy(Object subject)
{
this.subject = subject;
}
@Override
public Object invoke(Object object, Method method, Object[] args)
throws Throwable
{
// 在代理真实对象前我们可以添加一些自己的操作
// blablabla...
// 当代理对象调用真实对象的方法时,其会自动的跳转到代理对象的invoke方法来进行调用
method.invoke(subject, args);
// 在代理真实对象后也可以添加一些操作
// blablabla...
return null;
}
}
- 使用Class类来获取所需要代理的类;java中Class的用法
Class testClass = Class.forName(str);// str可以以字符串的形式传入
- 创建一个动态代理对象并开始使用被代理对象中的方法;
// 需要代理的真实对象
Object realObject = new Object();
// 将真实对象传入,最后是通过代理对象来调用其方法的
InvocationHandler handler = new DynamicProxy(realObject);
/*
* 通过Proxy的newProxyInstance方法来创建我们的代理对象
* 第一个参数 handler.getClass().getClassLoader() ,我们这里使用handler这个类的ClassLoader对象来加载代理对象
* 第二个参数realObject.getClass().getInterfaces(),这里为代理对象提供的接口是真实对象所实现的接口,表示要代理的是该真实对象,这样就能调用这组接口中的方法了
* 第三个参数handler, 将这个代理对象关联到了上方的 InvocationHandler 这个对象上
*/
Object obj = (Object)Proxy.newProxyInstance(handler.getClass().getClassLoader(), realObject.getClass().getInterfaces(), handler);
// obj.xxx(); // 这样使用代理对象来直接调用被代理类的方法
这里有需要注意的地方:Proxy的newProxyInstance方法的第二个参数,意思是代理对象去实现了被代理对像的接口,这样代理对象才可以去使用被代理对象实现接口或继承类中的方法。同时如果打印出代理对象的obj.getClass().getName()
会显示$proxy0
,因为这是JVM自动动态生成的代理对象(与使用时自己初始化代理对象不是一个意思,初始化主要是为了传入被代理对象),这是一种固定的命名方式。
使用反射的利弊
优点:
反射提高了程序的灵活性以及扩展性,降低了耦合性(依赖关系),提高了程序的适应能力;允许程序在不知道具体类的信息的情况下,创建和控制任何类的对象。
缺点:
- 性能问题:使用反射时是一种解释操作,用于字段和方法接入时要远慢于直接代码(这事必然啊,总要能理解吧),所以反射机制主要应用于灵活性和扩展性要求很高的系统框架上,普通程序不建议使用
- 模糊程序内部逻辑:反射绕过了源代码的技术会带来后期的维护问题,毕竟看反射代码更难
总结
其实当我们使用编译器,在对象后面加上.
时,编译器会自动列出该对象中的所有属性以及方法,这里就用到了这个原理,并称之为java类的自审,可以探知到类的基本结构。
上面的内容都是我在学习时自己提出的问题,越来越感觉在学一个知识点时主动挖掘它的原理是重要的(虽然有的并不需要也并不会想到),开始萌发要看看JVM底层原理的书的念头了….