讲讲Java反射机制

 

1 反射机制简介

反射之中包含了一个「反」字,所以想要解释反射就必须先从「正」开始解释。一般情况下,我们使用某个类时必定知道它是什么类,是用来做什么的。于是我们直接对这个类进行实例化,之后使用这个类对象进行操作。

上面这样子进行类对象的初始化,我们可以理解为「正」。

而反射则是一开始并不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象了。

这时候,我们使用 JDK 提供的反射API进行反射调用:

上面两段代码的执行结果,其实是完全一样的。但是其思路完全不一样,第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.jd.reflect.Apple)。

所以说什么是反射?反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。或者说能够分析类能力,即一种动态获取类的信息以及动态调用对象的方法的功能。

反射可以说是Java中最强大的技术了,它可以做的事情太多太多,很多优秀的开源框架都是通过反射完成的。例如Spring框架使用反射机制进行组件扫描,注解扫描,对象注入,对象创建。Mybatis框架通过读取sql,得到字段名称(与属性名相同),并用反射的方式将对象创建出来,之后调用其set方法进行参数注入。反射技术是学习Java语言和框架必不可少的!

2 反射常用API

2.1 通过反射创建类对象

在反射中,要获取一个类或调用一个类的方法,我们首先需要获取到该类的Class对象。通过反射创建类对象主要有两种方式:通过Class对象的newInstance()方法、通过 Constructor对象的 newInstance()方法。

第一种:通过Class对象的newInstance()方法

第二种:通过Constructor对象的newInstance()方法

通过Constructor对象创建类对象可以选择特定构造方法,而通过Class对象则只能使用默认的无参数构造方法。下面的代码就调用了一个有参数的构造方法进行了类对象的初始化。

2.2 通过反射获取构造器

获取Class对象所有的构造方法(包括:私有、受保护、默认、公有)

获取Class对象所有公有构造方法

根据参数类型获取Class对象对应构造方法

2.3 通过反射获取方法

获取Class对象所有的方法(包括:私有、受保护、默认、公有)

获取Class对象中所有的公有方法

根据名字和参数类型获取Class对象对应方法

3 反射的应用场景

1)Spring实例化对象:当程序启动时,Spring会不断的读取配置文件,将class值,属性名,属性值,实例化成一个一个的object对象,然后将这些对象放到一个map中,key值就是配置文件中的id值或者name值,也就是形成了所谓的IOC容器beanFactory,使用时,直接通过key值获取,然后强转成需要的类型即可直接使用,Spring会帮我们创建好实例化的对象。

2)反射 + 工厂模式:通过反射消除工厂中的多个分支,无论添加多少个子类,工厂类中的代码都不需要修改,只需要在操作的时候传入子类的类路径就可以了,实现了各个业务逻辑之间的完全分离,代码耦合性进一步降低。

3)JDBC连接数据库:数据库的连接信息一般都写在配置文件中,使用JDBC连接数据库时,使用 Class.forName()通过反射指定连接数据库的加载驱动类。不仅从代码上解耦和,而且需要更换数据库时,不需要进行代码的重新编译。

(1)和(3)通过反射来加载配置文件中定义的类(bean),使得Spring和JDBC连接数据库代码上解耦,更换新的bean的实现类或数据源时,只需修改配置文件即可,无需编译打包上线,使得项目在动态扩展或修改时更加方便。

4 反射源码解析

我们知道,反射最终调用的是Method类的invoke方法,下面我们来看看JDK的invoke方法到底做了些什么(基于JDK1.8版本源码)。

进入Method的invoke方法我们可以看到,一开始是进行了一些权限的检查,最后是调用了MethodAccessor类的invoke方法进行进一步处理,如下图红色方框所示。

点进去可以发现,invoke有三个具体的实现类,ma.invoke()到底调用的是哪个类的invoke方法,则需要看看MethodAccessor对象返回的到底是哪个类对象,所以我们需要进入acquireMethodAccessor()方法中看看。

从acquireMethodAccessor()方法我们可以看到,代码先判断是否存在对应的MethodAccessor对象,如果存在那么就复用之前的MethodAccessor对象,否则调用ReflectionFactory对象的newMethodAccessor方法生成一个MethodAccessor对象。

在ReflectionFactory类的newMethodAccessor方法里,可以看到先是检查初始化,然后判断是否开启Inflation机制实现(默认是false,通过-Dsun.reflect.noInflation=true来修改参数),如果开启了且当前Class对象的声明对象Class不是VM匿名类就会使用动态实现方式(即Java版本)。

如果没有开启就会生成一个委派实现,首先是生成了一个NativeMethodAccessorImpl对象,然后将NativeMethodAccessorImpl对象交给DelegatingMethodAccessorImpl对象代理。我们查看DelegatingMethodAccessorImpl类的构造方法可以知道,其实是将NativeMethodAccessorImpl对象赋值给DelegatingMethodAccessorImpl类的delegate属性。

Java版本和Native版本相比,执行速度要快上20倍,这是因为Java版本直接执行字节码,不用从java到c++ 再到java 的转换,但是因为生成字节码的操作比较耗费时间,所以如果仅一次调用的话反而是Native版本快3到4倍。

为了防止很多反射调用只调用一次,java 虚拟机设置了一个阀值等于15(通过-Dsun.reflect.inflationThreshold 参数来调整),当一个反射调用次数达到15次时,委派实现的委派对象由Native版本转换为Java版本,这个过程称之为Inflation(膨胀机制)。

所以说默认情况下(noInflation=false),第一次反射的时候,ReflectionFactory类的newMethodAccessor方法最终返回DelegatingMethodAccessorImpl类对象。所以我们在前面的ma.invoke()里,其将会进入DelegatingMethodAccessorImpl类的invoke方法中。

进入DelegatingMethodAccessorImpl类的invoke方法后,这里调用了delegate属性的invoke方法,它又有两个实现类,分别是:DelegatingMethodAccessorImpl和NativeMethodAccessorImpl。按照我们前面说到的,这里的delegate其实是一个NativeMethodAccessorImpl对象,所以这里会进入NativeMethodAccessorImpl的invoke方法。

在NativeMethodAccessorImpl的invoke方法里,其会判断调用次数是否超过阀值(inflationThreshold=15)、当前Class对象的声明对象Class是否为VM匿名类。如果超过该阀值或者不是VM匿名类,那么就会生成另一个MethodAccessor对象,并将原来DelegatingMethodAccessorImpl对象中的delegate属性指向最新的MethodAccessor对象。

到这里,其实我们可以知道MethodAccessor对象其实就是具体去生成反射类的入口。MethodAccessor实现有两个版本,一个是Native版本,一个是Java版本,为了权衡两种版本的特性,Sun公司的JDK使用了Inflation机制,让Java方法在被反射调用时,开头的几次调用使用Native版,等反射调用次数超过阈值时则生成一个专用的MethodAccessor实现类,生成其中的invoke方法的字节码,以后对该Java方法的反射调用就会使用Java版。

5 总结

反射的思想:反射就像是一面镜子一样,在运行时才看到自己是谁,可获取到自己的信息,甚至实例化对象。

反射的作用:在运行时才确定实例化对象,使程序更加健壮,面对需求变更时,可以最大程度地做到不修改程序源码应对不同的场景,实例化不同类型的对象。

反射的应用场景常见的有3个:Spring的IOC容器,反射+工厂模式 使工厂类更稳定,JDBC连接数据库时加载驱动类。

反射的优点:增加程序的灵活性(面对需求变更时,可以灵活地实例化不同对象)。

反射的缺点:破坏类的封装性(可以强制访问private修饰的信息);性能损耗(反射相比直接实例化对象、调用方法、访问变量,中间需要非常多的检查步骤和解析步骤,JVM无法对它们优化)。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值