什么是反射?

反射,一种计算机处理方式。是程序可以访问、检测和修改它本身状态或行为的一种能力。java反射使得我们可以在程序运行时动态加载一个类,动态获取类的基本信息和定义的方法,构造函数,域等。除了检阅类信息外,还可以动态创建类的实例,执行类实例的方法,获取类实例的域值。反射使java这种静态语言有了动态的特性。

类的加载

java反射机制是围绕Class类展开的,在深入java反射原理之前,需要对类加载机制有一个大致的了解。jvm使用ClassLoader将字节码文件(class文件)加载到方法区内存中:

Class clazz = ClassLoader.getSystemClassLoader().loadClass("com.mypackage.MyClass");

可见ClassLoader根据类的完全限定名加载类并返回了一个Class对象,而java反射的所有起源都是从这个class类开始的。

ReflectionData

为了提高反射的性能,缓存显然是必须的。class类内部有一个useCaches静态变量来标记是否使用缓存,这个值可以通过外部配置项sun.reflect.noCaches进行开关。class类内部提供了一个ReflectionData内部类用来存放反射数据的缓存,并声明了一个reflectionData域,由于稍后进行按需延迟加载并缓存,所以这个域并没有指向一个实例化的ReflectionData对象。

    //标记是否使用缓存,可以通过外部配置项sun.reflect.noCaches进行禁用。
private static boolean useCaches = true; static class ReflectionData<T> {
volatile Field[] declaredFields;
volatile Field[] publicFields;
volatile Method[] declaredMethods;
volatile Method[] publicMethods;
volatile Constructor<T>[] declaredConstructors;
volatile Constructor<T>[] publicConstructors;
volatile Field[] declaredPublicFields;
volatile Method[] declaredPublicMethods;
final int redefinedCount; ReflectionData(int redefinedCount) {
this.redefinedCount = redefinedCount;
}
} //注意这是个SoftReference,在内存资源紧张的时候可能会被回收。volatile保证多线程环境下的读写的正确性
private volatile transient SoftReference<ReflectionData<T>> reflectionData;
//J主要用于和ReflectionData中的redefinedCount进行比较,如果两个值不相等,说明ReflectionData缓存的数据已经过期了。
private volatile transient int classRedefinedCount = 0;

获取类的构造函数

在获取一个类的class后,我们可以通过反射获取一个类的所有构造函数,class类内部封装了如下方法用于提取构造函数:

    //publicOnly指示是否只获取public的构造函数,我们常用的getConstructors方法是只返回public的构造函数,而getDeclaredConstructors返回的是所有构造函数,由于java的构造函数不会继承,所以这里不包含父类的构造函数。
private Constructor<T>[] privateGetDeclaredConstructors(boolean publicOnly) {
checkInitted(); //主要是读取了sun.reflect.noCaches配置。
Constructor<T>[] res;
ReflectionData<T> rd = reflectionData();//这里缓存中读取reflectionData,如果还没有缓存,则创建一个reflectionData并设置到缓存。但是注意这个ReflectionData可能只是个空对象,里面并没有任何数据。
if (rd != null) {
res = publicOnly ? rd.publicConstructors : rd.declaredConstructors;
if (res != null) return res;//检查缓存中是否有数据
}
//没有缓存数据可用,从这里开始需要从jvm中去获取数据。
if (isInterface()) {
res = new Constructor[0];//接口没有构造函数
} else {
res = getDeclaredConstructors0(publicOnly);//native方法,从jvm中获取
}
//如果代码执行到了这里,说明需要更新缓存了,将之前从jvm中请求到的数据放置到缓存中。
if (rd != null) {
if (publicOnly) {
rd.publicConstructors = res;
} else {
rd.declaredConstructors = res;
}
}
return res;
}

上面的代码片段比较简单,主要就是读取配置,检查缓存中是否有有效数据,如果有,直接从缓存中返回,如果没有,调用native的方法从jvm中请求数据,然后设置到缓存。比较重要的是reflectionData()这个调用。这个方法主要是用于延迟创建并缓存ReflectionData对象,注意是对象,里面并没有保存反射数据,这些数据只有在第一次执行相应的反射操作后才会被填充。下面是这个方法的实现代码:

    private ReflectionData<T> reflectionData() {
SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
int classRedefinedCount = this.classRedefinedCount;
ReflectionData<T> rd;
//检查缓存是否有效,如果有效,从缓存中直接返回reflectionData。
if (useCaches &&
reflectionData != null &&
(rd = reflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;
}
//无法使用到缓存,创建新的reflectionData
return newReflectionData(reflectionData, classRedefinedCount);
}

下面看一下newReflectionData()的实现:

    private ReflectionData<T> newReflectionData(SoftReference<ReflectionData<T>> oldReflectionData,int classRedefinedCount) {
if (!useCaches) return null;
//这里使用while-cas模式更新reflectionData,主要是考虑多线程并发更新的问题,可能有另外一个线程已经更新了reflectionData,并且设置了有效的缓存数据,如果这里再次更新就把缓存数据覆盖了。
while (true) {
ReflectionData<T> rd = new ReflectionData<>(classRedefinedCount);
if (Atomic.casReflectionData(this, oldReflectionData, new SoftReference<>(rd))) {
return rd;//cas成功,那么我们swap的这个新对象是有效的。
}
//重新读取oldReflectionData,为下次重试cas做准备。
oldReflectionData = this.reflectionData;
classRedefinedCount = this.classRedefinedCount;
//先判断这个oldReflectionData是否有效。如果是无效的数据,需要去重试cas一个新的ReflectionData了。
if (oldReflectionData != null &&
(rd = oldReflectionData.get()) != null &&
rd.redefinedCount == classRedefinedCount) {
return rd;//reflectionData中已经是有效的缓存数据了,直接返回这个reflectionData
}
}
}

上面的几个代码片段是反射获取一个类的构造函数的主要方法调用。主要流程就是先从class内部的reflectionData缓存中读取数据,如果没有缓存数据,那么就从jvm中去请求数据,然后设置到缓存中,供下次使用。

执行构造函数

在通过反射获取到一个类的构造函数我们,一般我们会试图通过构造函数去实例化声明这个构造函数的类,如下:

MyClass myClass = (MyClass) constructor.newInstance();

下面来看看这个newInstance()到底发生了什么:

    public T newInstance(Object... initargs)
throws InstantiationException, IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
//首先判断语言级别的访问检查是否被覆盖了(通过setAccess(true)方法可以将private的成员变成public),如果没有被覆盖,需要进行访问权限检查。
if (!override) {
if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class<?> caller = Reflection.getCallerClass();
checkAccess(caller, clazz, null, modifiers);
}
}
//枚举无法通过反射创建实例
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
//创建ConstructorAccessor对象,并进行缓存
ConstructorAccessor ca = constructorAccessor;
if (ca == null) {
ca = acquireConstructorAccessor();
}
//通过ConstructorAccessor来执行newInstance
return (T) ca.newInstance(initargs);
}

上面代码主要就是做权限检查,如果权限通过,则通过执行acquireConstructorAccessor()获取一个ConstructorAccessor来真正执行newInstance,acquireConstructorAccessor()这个方法实现比较简单,真正工作的是ReflectionFactory.newConstructorAccessor(),这个方法的主要工作就是为一个Constructor动态生成针对ConstructorAccessor接口的实现ConstructorAccessorImpl,下面来看一下这个方法实现:

    public ConstructorAccessor newConstructorAccessor(Constructor c) {
checkInitted();
Class declaringClass = c.getDeclaringClass();
//如果声明这个构造函数的类部是抽象类,则返回一个InstantiationExceptionConstructorAccessorImpl,这其实也是个ConstructorAccessor,只是它对newInstace的实现是直接抛出一个InstantiationException异常
if (Modifier.isAbstract(declaringClass.getModifiers())) {
return new InstantiationExceptionConstructorAccessorImpl(null);
}
//如果是Class,同上
if (declaringClass == Class.class) {
return new InstantiationExceptionConstructorAccessorImpl
("Can not instantiate java.lang.Class");
}
//如果是ConstructorAccessorImpl子类,这里会造成无限循环,所以直接通过native方式实例化这个类
if (Reflection.isSubclassOf(declaringClass,
ConstructorAccessorImpl.class)) {
return new BootstrapConstructorAccessorImpl(c);
}
//判断是否启用了Inflation机制,默认是启用了。
//如果没有启用Inflation机制,那么通过asm操作字节码的方式来生成一个ConstructorAccessorImpl类。
//如果启用了,那么在执行inflationThreshold(默认15次)次数之前,是通过navite调用来执行newInstance,超过这个次数之后,才会通过asm来生成类。
//Inflation机制主要是在执行时间和启动时间上做一个平衡,native方式执行慢但是第一次执行不耗费任何时间,asm生成代码的方式执行快(20倍),但是第一次生成需要耗费大量的时间。
//可以通过sun.reflect.noInflation和sun.reflect.inflationThreshold配置型来进行动态配置
if (noInflation) {
return new MethodAccessorGenerator().
generateConstructor(c.getDeclaringClass(),
c.getParameterTypes(),
c.getExceptionTypes(),
c.getModifiers());
} else {
NativeConstructorAccessorImpl acc =
new NativeConstructorAccessorImpl(c);
DelegatingConstructorAccessorImpl res =
new DelegatingConstructorAccessorImpl(acc);
acc.setParent(res);
return res;
}
}

由上面代码可见,在生成ConstructorAccessorImpl的操作上,一共提供多种版本的实现:直接抛出异常的实现,native执行的实现,asm生成代码的实现。native的实现比较简单,asm 版本的主要就是字节码操作了,比较复杂,暂时不做深入了。

JAVA反射原理的更多相关文章

  1. [转]Java Spring的Ioc控制反转Java反射原理

    转自:http://www.kokojia.com/article/12598.html 学习一个东西的时候,如果想弄明白,最好想想框架内部是如何实现的,如果是我做我会怎么实现.下面我就写一个Ioc ...

  2. JAVA反射原理解读

    一.什么是JAVA的反射 1.在运行状态中,对于任意一个类,都能够知道这个类的属性和方法. 2.对于任意一个对象,都能够调用它的任何方法和属性. 这种动态获取信息以及动态调用对象的方法的功能称为JAV ...

  3. 深入理解java反射原理

    反射是java的一个特性,这一特性也使得它给了广大的第三方框架和开发过者很大的想像空间. 通过反射,java可以动态的加载未知的外部配置对象,临时生成字节码进行加载使用,从而使代码更灵活!可以极大地提 ...

  4. 【反射】利用java反射原理将xml文件中的字段封装成对应的Bean

    本例使用的xml解析方式为jdom ... <ROOT> <Consignment> ... </Consignment> </ROOT> 解析xml文 ...

  5. java 反射原理写了一个赋值和取值通用类

    首先了解一下反射的原理,什么是反射?所谓的反射就是指java 语言在运行时拥有一项自观的能力,反射能使你得到装载到 jvm 中的类的内部信息,它不需要你在编码的时候就知道所需类的内部信息,允许程序执行 ...

  6. java反射原理运用

    1.首先用Java反射机制的要做到的一个目的:我们都知道通过得到一个对象中的指定方法或者属性等,基于这个原理我们来做一个 通用的功能,让客户端可以通过传入的对象和一个标识去调用这个对象里自己想要的方法 ...

  7. java反射原理,应用

    java类的加载过程 调用java命令运行程序时,该命令会启动一条java虚拟机进程,该程序的所有线程都会运行在这个虚拟机进程里面.程序运行产生的线程.变量都处于这个进程,共同使用该JVM进程的内存区 ...

  8. Java 反射原理

    一.Java 反射的定义 反射机制是在运行状态中, 对于任意一个类, 都能够知道这个类的所有属性和方法: 对于任意一个对象,都能够调用它的任意一个方法或者属性: 二.反射提供的功能: 在运行时判断任意 ...

  9. Java反射原理和实际用法

    背景 反射在Java中非常重要,是Java区别于其他编程语言的一大特性.Java中的AOP切面.动态代理等看起来像黑魔法一样的技术,就离不开反射.字节码等.这些技术能在不侵入原有代码的情况下,做一些增 ...

随机推荐

  1. Python学习之路-Day2-Python基础3

    Python学习之路第三天 学习内容: 1.文件操作 2.字符转编码操作 3.函数介绍 4.递归 5.函数式编程 1.文件操作 打印到屏幕 最简单的输出方法是用print语句,你可以给它传递零个或多个 ...

  2. 堆和栈(java内存)

    栈内存: 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,java就在栈中为这个变量分配内存空间,当超过变量的作用域后,java会自动释放掉为 ...

  3. NSA Fuzzbunch分析与利用案例

    Shadow Brokers泄露出一份震惊世界的机密文档,其中包含了多个 Windows 远程漏洞利用工具.本文主要介绍了其中一款工具Fuzzbunch的分析与利用案例 1 整体目录介绍 解压EQGR ...

  4. call和apply的小结

    call和apply的区别: 1.call函数和apply方法的第一个参数都是要传入给当前对象的对象,即函数内部的this.后面的参数都是传递给当前对象的参数. 2.俩者的格式和参数定义: call的 ...

  5. jQ实现的一个轮播图

    众所周知,轮播图是被广泛的运用的. 轮播图我们在很多的网站上都可以看到,例如淘宝.京东这些网站都很常见. 下面开始我们的轮播之旅: 搭建我们的骨架: <!DOCTYPE html> < ...

  6. .Net程序员学用Oracle系列(10):系统函数(中)

    1.四大转换函数 1.1.TO_CHAR 1.2.TO_NUMBER 1.3.TO_DATE 1.4.CAST 2.两大近似值函数 2.1.ROUND 2.2.TRUNC 3.正则函数 3.1.正则函 ...

  7. Lists, Maps and Sets in Java

    ArrayList vs LinkedList vs Vector From the hierarchy diagram, they all implement List interface. The ...

  8. Handler线程间通信

    package com.hixin.appexplorer; import java.util.List; import android.app.Activity; import android.ap ...

  9. JDBC进阶

    PreparedStatement的使用: conn = DriverManager.getConnection("jdbc:mysql://localhost/mydata?" ...

  10. Django集成celery实战小项目

    上一篇已经介绍了celery的基本知识,本篇以一个小项目为例,详细说明django框架如何集成celery进行开发. 本系列文章的开发环境: window 7 + python2.7 + pychar ...