什么是反射?

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

类的加载

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

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

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

ReflectionData

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

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

获取类的构造函数

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

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

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

  1. private ReflectionData<T> reflectionData() {
  2. SoftReference<ReflectionData<T>> reflectionData = this.reflectionData;
  3. int classRedefinedCount = this.classRedefinedCount;
  4. ReflectionData<T> rd;
  5. //检查缓存是否有效,如果有效,从缓存中直接返回reflectionData。
  6. if (useCaches &&
  7. reflectionData != null &&
  8. (rd = reflectionData.get()) != null &&
  9. rd.redefinedCount == classRedefinedCount) {
  10. return rd;
  11. }
  12. //无法使用到缓存,创建新的reflectionData
  13. return newReflectionData(reflectionData, classRedefinedCount);
  14. }

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

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

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

执行构造函数

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

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

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

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

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

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

由上面代码可见,在生成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. 高性能linux服务器内核调优

    高性能linux服务器内核调优 首先,介绍一下两个命令1.dmesg 打印系统信息.有很多同学们服务器出现问题,看了程序日志,发现没啥有用信息,还是毫无解决头绪,这时候,你就需要查看系统内核抛出的异常 ...

  2. hdu1213 How Many Tables 并查集的简单应用

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1213 简单的并查集 代码: #include<iostream> #include< ...

  3. MFC基础程序设计VS2015 最新03

    视频教程地址观看:http://pan.baidu.com/s/1mhKQ6kK 一.数字(浮点数或整数)转为文字:a)如果转为窄字符字符串,sprintf函数很好用,浮点数都没问题:b)如果转为宽字 ...

  4. DelayQueue的原理和使用浅谈

    在谈到DelayQueue的使用和原理的时候,我们首先介绍一下DelayQueue,DelayQueue是一个无界阻塞队列,只有在延迟期满时才能从中提取元素.该队列的头部是延迟期满后保存时间最长的De ...

  5. vue2入坑随记(一)

    都说Vue2简单,上手容易,但小马过河,自己试了才晓得,除了ES6语法和webpack的配置让你感到陌生,重要的是思路的变换,以前随便拿全局变量和修改dom的锤子不能用了,变换到关注数据本身.vue的 ...

  6. Mysql 忘记root密码后修改root密码

    1.修改my.cnf: 在mysqld进程配置文件中添加skip-grant-tables,添加完成后记住保存. 2.重新启动MYSQL数据库: service mysqld restart 2.修改 ...

  7. MySQL锁(MyISAM和InnoDB)

    MySQL有三种级别的锁: 1. 页级别 BDB 2. 表级别 MyISAM 3. 行级别 InnoDB 就 总体而言MyISAM表的读和写是串行的.在一定条件下,MyISAM表也支持查询和插入操作的 ...

  8. JavaScript 闭包究竟是什么

    用JavaScript一年多了,闭包总是让人二丈和尚摸不着头脑.陆陆续续接触了一些闭包的知识,也犯过几次因为不理解闭包导致的错误,一年多了 资料也看了一些,但还是不是非常明白,最近偶然看了一下 jQu ...

  9. 解密Lazy<T>

    1.Lazy<T>的使用 无意间看到一段代码,在创建对象的时候使用了Lazy,顾名思义Lazy肯定是延迟加载,那么它具体是如何创建对象,什么时候创建对象了? 先看这段示列代码: publi ...

  10. Python教程(2.6)——list和tuple简介

    Python中内置的类型有list和tuple. List list类似于C/C++的数组,可以存储多个数字.例如你可能会需要存储一个班里所有人的名字.这时就可以用到list.list中存储的数据叫做 ...