java 反射简介

什么是反射?

Java的反射机制是在编译并不确定是哪个类被加载了,而是在程序运行的时候才加载、探知、自审。使用在编译期并不知道的类。这样的特点就是反射。

有什么用呢?

假如我们有两个程序员,一个程序员在写程序的时候,需要使用第二个程序员所写的类,但第二个程序员并没完成他所写的类。那么第一个程序员的代码能否通过编译呢?这是不能通过编译的。利用Java反射的机制,就可以让第一个程序员在没有得到第二个程序员所写的类的时候,来完成自身代码的编译。

Java的反射机制它知道类的基本结构,这种对Java类结构探知的能力,我们称为Java类的“自审”。大家都用过Jcreator和eclipse。当我们构建出一个对象的时候,去调用该对象的方法和属性的时候。一按点,编译工具就会自动的把该对象能够使用的所有的方法和属性全部都列出来,供用户进行选择。这就是利用了Java反射的原理,是对我们创建对象的探知、自审。

举例:

package reflection;

public class MytestReflection {
    public static void main(String[] args) {
        TestOne  one=null;
        try{
            Class  cla=Class.forName("reflection.TestOne");//进行reflection.TestOne类加载,返回一个Class对象
            System.out.println("********");
            one=(TestOne)cla.newInstance();//产生这个Class类对象的一个实例,调用该类无参的构造方法,作用等同于new TestOne()
        }catch(Exception e){
            e.printStackTrace();
        }
        TestOne two=new TestOne();
        System.out.println(one.getClass() == two.getClass());//比较两个TestOne对象的Class对象是否是同一个对象,在这里结果是true。说明如果两个对象的类型相同,那么它们会有相同的Class对象
    }
}

class TestOne{
    static{
        System.out.println("静态代码块运行");
    }
    TestOne(){
        System.out.println("构造方法");
    }

}

运行结果:

静态代码块运行
********
构造方法
构造方法
true

为什么呢?

在进行Class.forName("com.TestOne")的时候,实际上是对com.TestOne进行类加载,这时候,会把静态属性、方法以及静态代码块都加载到内存中。所以这时候会打印出"静态代码块运行"。但这时候,对象却还没有产生。所以"构造方法"这几个字不会打印。当执行cla.newInstance()的时候,就是利用反射机制将Class对象生成一个该类的一个实例。这时候对象就产生了。所以打印"构造方法"。当执行到TestOne two=new TestOne()语句时,又生成了一个对象。但这时候类已经加载完毕,静态的东西已经加载到内存中,而静态代码块只执行一次,所以不用再去加载类,所以只会打印"构造方法",而"静态代码块运行"不会打印。

反射机制不但可以例出该类对象所拥有的方法和属性,还可以获得该类的构造方法及通过构造方法获得实例。也可以动态的调用这个实例的成员方法。

整体流程

调用反射的总体流程如下:

  • 准备阶段:编译期装载所有的类,将每个类的元信息保存至Class类对象中,每一个类对应一个Class对象
  • 获取Class对象:调用x.class/x.getClass()/Class.forName() 获取x的Class对象clz(这些方法的底层都是native方法,是在JVM底层编写好的,涉及到了JVM底层,就先不进行探究了)
  • 进行实际反射操作:通过clz对象获取Field/Method/Constructor对象进行进一步操作

整体过程中,需要注意的是进行实际反射操作的这个阶段,我们需要关注的点有:

  • 我们是如何通过Class获取到Field/Method/Construcor的?
  • 获取到的Field是如何具有对象属性值的?
  • 获取到的Method是如何调用的?

如何通过Class获取Field/Method/Construcor

探究Class类源码的时候,我们发现Class类中包含的ReflectionData,用于保存进行反射操作的基础信息

    // reflection data that might get invalidated when JVM TI RedefineClasses() is called
    private static class ReflectionData<T> {
        volatile Field[] declaredFields;
        volatile Field[] publicFields;
        volatile Method[] declaredMethods;
        volatile Method[] publicMethods;
        volatile Constructor<T>[] declaredConstructors;
        volatile Constructor<T>[] publicConstructors;
        // Intermediate results for getFields and getMethods
        volatile Field[] declaredPublicFields;
        volatile Method[] declaredPublicMethods;
        volatile Class<?>[] interfaces;

        // Value of classRedefinedCount when we created this ReflectionData instance
        final int redefinedCount;

        ReflectionData(int redefinedCount) {
            this.redefinedCount = redefinedCount;
        }
    }

这显然是我们获取Field/Method/Constructor的直接来源,那么这个数据结构中的值又是从哪里来的呢?我们以Field的获取为例进行探究,我们先看看getDeclaredField这个方法:

min

发现调用的是:privateGetDeclaredFields

第一处是从reflectionData直接取,reflectionData是弱引用,这算是一种缓存获取;第二处是直接调用getDeclaredFields0()这个方法获取,这是一个native方法,应当是从JVM内直接获取

至于Method和Constructor的获取则是大同小异,

至此我们基本搞清楚了Class是如何获取Field/Method/Constructor的了

Field是如何具有对象属性值

很显然,因为Field对象是来自JVM的,JVM中自然保存着对象的详细属性值,因此通过反射获取到的Field就能包含着原始对象的属性值

获取到的Method如何调用

通过对源码的查看,调用Method的过程大致如下:

如上图所示,我们大致经历了一个这样的过程:

  • Method对象通过MethodAcessor的invoke调用方法 ->
  • 通过反射工厂生成MethodAcessor对象 ->
  • 生成NativeMethodAcessorImpl,最终由DelegatingMethodAccessorImpl代理 ->
  • 调用时先进入的是DelegatingMethodAccessorImpl的invoke方法 ->
  • DelegatingMethodAcessorImpl是代理对象,实质上最终调用的是NativeMethodAcessorImpl的invoke方法
  • 所有的方法反射都是先走NativeMethodAccessorImpl,默认调了15次之后,才生成一个GeneratedMethodAccessorXXX类,生成好之后就会走这个生成的类的invoke方法了
最后一点调用十五次阈值的原因在于:存在两种MethodAcessor,Native 版本一开始启动快,但是随着运行时间边长,速度变慢。Java 版本一开始加载慢,但是随着运行时间边长,速度变快。正是因为两种存在这些问题,所以第一次加载的时候我们会发现使用的是 NativeMethodAccessorImpl 的实现,而当反射调用次数超过 15 次之后,则使用 MethodAccessorGenerator 生成的 MethodAccessorImpl 对象去实现反射。

这其实是借助代理模式实现了一个性能优化手段,这种利用代理模式灵活适配的思想很值得学习

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值