1         反射

1.1            什么是反射

正射:指的是我们知道类的定义和类中的方法名称,直接先创建对象,然后通过对象去调用方法。例如:

Apple apple = new Apple(); //直接初始化,「正射」

apple.setPrice(4);

反射:指的是一开始不知道我要初始化的类对象是什么,自然也无法使用 new 关键字来创建对象,需要用JDK 提供的反射 API 进行反射调用。需要通过类的路径字符串创建对象,通过方法名称字符串调用方法。例如:

Class clz = Class.forName("com.chenshuyi.reflect.Apple");//通过字符串获取类Class

Constructor constructor = clz.getConstructor();//获取类的构造器

Object object = constructor.newInstance();//创建对象

Method method = clz.getMethod("setPrice", int.class);//通过方法名获取方法

method.invoke(object, 4);//调用方法;

第一段代码在未运行时就已经确定了要运行的类(Apple),而第二段代码则是在运行时通过字符串值才得知要运行的类(com.chenshuyi.reflect.Apple)。反射就是在运行时才知道要操作的类是什么,并且可以在运行时获取类的完整构造,并调用对应的方法。

1.2            反射获取类、构造器、方法、属性

(1)获取类

使用 Class.forName 静态方法。当你知道该类的全路径名时,你可以使用该方法获取 Class 类对象。

Class clz = Class.forName("java.lang.String");

(2)获取构造器

通过 Constructor 对象的 newInstance() 方法

Constructor constructor = clz.getConstructor();//获取类的构造器

Object object = constructor.newInstance();//创建对象

(1)   获取方法

获取方法的 Method 对象

Method setPriceMethod = clz.getMethod("setPrice", int.class);

利用 invoke 方法调用方法

setPriceMethod.invoke(appleObj, 14);

(2)   获取属性

过 Class 对象的 getFields() 方法可以获取 Class 类的属性,但无法获取私有属性。

Class clz = Apple.class;

Field[] fields = clz.getFields();

for (Field field : fields) {

System.out.println(field.getName());

}

输出结果是:

Price

(5)获取包含私有属性

而如果使用 Class 对象的 getDeclaredFields() 方法则可以获取包括私有属性在内的所有属性:

Class clz = Apple.class;

Field[] fields = clz.getDeclaredFields();

for (Field field : fields) {

System.out.println(field.getName());

}

输出结果是:

name

price

1.3           反射的源码解析

(1)Method的invoke方法

public Object invoke(Object obj, Object... args)

throws IllegalAccessException, IllegalArgumentException,

InvocationTargetException

{

if (!override) {

if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {

Class<?> caller = Reflection.getCallerClass();

checkAccess(caller, clazz, obj, modifiers);

}

}

MethodAccessor ma = methodAccessor;             // read volatile

if (ma == null) {

 ma = acquireMethodAccessor();

}

return ma.invoke(obj, args);

}

(2)acquireMethodAccessor 返回MethodAccessor对象

private MethodAccessor acquireMethodAccessor() {

// First check to see if one has been created yet, and take it

// if so

MethodAccessor tmp = null;

if (root != null) tmp = root.getMethodAccessor();

if (tmp != null) {

methodAccessor = tmp;

} else {

// Otherwise fabricate one and propagate it up to the root

 tmp = reflectionFactory.newMethodAccessor(this);

setMethodAccessor(tmp);

}

return tmp;

}

(3)反射工厂返回对象reflectionFactory.newMethodAccessor

public MethodAccessor newMethodAccessor(Method var1) {
    checkInitted();
    if (noInflation &&
!ReflectUtil.isVMAnonymousClass(var1.getDeclaringClass())) {
        return (new MethodAccessorGenerator()).generateMethod(var1.getDeclaringClass(),
var1.getName(), var1.getParameterTypes(), var1.getReturnType(), var1.getExceptionTypes(),
var1.getModifiers());
    } else {
        NativeMethodAccessorImpl var2 = new
NativeMethodAccessorImpl(var1);
        DelegatingMethodAccessorImpl var3
= new DelegatingMethodAccessorImpl(var2);
        var2.setParent(var3);
        return var3;// 最终返回的是DelegatingMethodAccessorImpl对象
    }
}

其中DelegatingMethodAccessorImpl var3 = new DelegatingMethodAccessorImpl(var2);
是将NativeMethodAccessorImpl var2对象作为入参,传入DelegatingMethodAccessorImpl的构造函数。那么构造函数内部做了什么?很关键!

DelegatingMethodAccessorImpl(MethodAccessorImpl
var1) {

this.setDelegate(var1);对象设置在Delegate属性中,后面会用到

}

delegate 属性的定义是private
MethodAccessorImpl delegate;

(4)步骤(3)最终返回的是DelegatingMethodAccessorImpl对象,所以步骤(1)中return ma.invoke(obj, args);函数invoke是DelegatingMethodAccessorImpl的方法。看看DelegatingMethodAccessorImpl的invoke方法定义如下:

public Object invoke(Object var1, Object[] var2)
throws IllegalArgumentException, InvocationTargetException {

 return this.delegate.invoke(var1, var2);//调用了属性delegateinvoke方法;也就是NativeMethodAccessorImpl对象的invoke方法;    }

(5)NativeMethodAccessorImpl对象的invoke实现

public Object invoke(Object var1, Object[] var2)
throws IllegalArgumentException, InvocationTargetException {//累计大于阈值,切换

if (++this.numInvocations
> ReflectionFactory.inflationThreshold()
&&
!ReflectUtil.isVMAnonymousClass(this.method.getDeclaringClass())) {

MethodAccessorImpl var3 = (MethodAccessorImpl)(new MethodAccessorGenerator()).generateMethod(this.method.getDeclaringClass(),
this.method.getName(), this.method.getParameterTypes(),
this.method.getReturnType(), this.method.getExceptionTypes(),
this.method.getModifiers());

this.parent.setDelegate(var3);

}

return invoke0(this.method, var1, var2);

}

每次NativeMethodAccessorImpl.invoke()方法被调用时,都会增加一个调用次数计数器numInvocations,看超过阈值 没有;一旦超过,则调用MethodAccessorGenerator.generateMethod()来生成Java版的 MethodAccessor的实现类,并且通过this.parent.setDelegate(var3);改变DelegatingMethodAccessorImpl所引用的MethodAccessor为 Java版。后续经由DelegatingMethodAccessorImpl.invoke()调用到的就是Java版的实现了。

注意到关键的invoke0()方法是个native方法。它在HotSpot VM里是由JVM_InvokeMethod()函数所支持的,不能在深入了:

JNIEXPORT
jobject JNICALL Java_sun_reflect_NativeMethodAccessorImpl_invoke0

(JNIEnv
*env, jclass unused, jobject m, jobject obj, jobjectArray args)

{

return JVM_InvokeMethod(env, m, obj, args);

}

为什么要这样先用native后又切换为java的呢?

就像注释里说的,实际的
MethodAccessor 实现有两个版本,一个是 Native 版本NativeMethodAccessorImpl,一个是 Java 版本MethodAccessorImpl。Java版本在第一次启动时,需要加载字节码实现Method.invoke() 和 Constructor.newInstance() ,比较耗时。所以Native 版本第一次启动比java版本快3-4倍,但是后面的调用java版本比Native快20倍。所以为了避免启动慢,第一次使用native版本快速启动。为了避免后续运行慢,在切换到java版本MethodAccessorImpl 对象去实现反射。

java反射使用和源码解析的更多相关文章

  1. Dubbo原理和源码解析之服务引用

    一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...

  2. Dubbo原理和源码解析之“微内核+插件”机制

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  3. Dubbo原理和源码解析之服务暴露

    github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...

  4. Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  5. Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析

    一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...

  6. java编译后字节码解析

    java编译后字节码解析 参考网摘: https://my.oschina.net/indestiny/blog/194260

  7. Go语言备忘录:net/http包的使用模式和源码解析

    本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录:net/http包的使用模式和源码解析,多谢!  目录: 一.http ...

  8. Dubbo原理和源码解析之标签解析

    一.Dubbo 配置方式 Dubbo 支持多种配置方式: XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现 属性配置:加载 classpath 根目录下的 dubbo.pr ...

  9. Go语言备忘录(3):net/http包的使用模式和源码解析

    本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢!  目录: 一.h ...

随机推荐

  1. MATLAB关系运算符和逻辑运算符

    1 关系运算符 关系运算符用来比较两个数之间的大小关系,在Matlab中的关系运算符包括: <  小于 <= 小于或等于 >        大于 >=      大于或等于 = ...

  2. 快速排序 之添加复合插入排序和原始序列取中值左pivot

    quicksort中,当n小于一定值时,排序效率就比直接插入排序底了,所以,此时就不要再递归下去了,直接插入排序好了:快速的原理就是因为折半递归,所以初始pivot应该有个好一点的选择,这里在原序列左 ...

  3. CentOS配置apache多站点设置

    配置文件目录: /etc/httpd/conf.d /etc/httpd/conf/httpd.conf 错误日志文件在哪里? 网站文件目录: /var/www/html (Ubuntu/Centos ...

  4. 关于11G DataGuard 日志传输的案例

    案例探讨 在归档和非归档模式下,配置参数log_archive_dest_2后,DG的备库是否传输日志. 案例环境描述 本次环境如下,一套RAC+单机DG,然后从DG还原出来一个单独的测试库A,测试库 ...

  5. vi / vim 命令集合

    vim的命令太多了,不常用就会忘记,所以我决定把vim的各种命令整理下来,包括vim的插入删除.光标移动.多窗口编辑.复制粘贴.查找替换.以及一些常用命令 删除操作 dd 删除当前行 ndd      ...

  6. SQL中sysname数据类型的含义(转)

    sysname SQL Server 实例包括用户定义的名为 sysname 的数据类型.sysname 用于表列.变量以及用于存储对象名的存储过程参数.sysname 的精确定义与标识符规则相关:因 ...

  7. you've successfully authenticated, but Gitee.com does not provide she access.

    如果都是正常的生成ssh的操作,还是会报这个错误,那么就是.... 你没更改文件夹的权限,这个坑跳了很久(汗...) sudo chmod 777 -r 文件夹

  8. DataGridView上下键事件获取到的是上次停留行的内容

    DataGridView上下键事件 在DataGridView中,通过上下键将选中行的内容返回, 问题: 通过上边的方法总是获取到上次停留行的内容,不是当前选中行的内容. winform的项目,使用C ...

  9. Ubuntu下载

    由于官网服务器在国外,下载速度奇慢,所以我们可以利用阿里云镜像下载ubuntuubuntu 14.04:http://mirrors.aliyun.com/ubuntu-releases/14.04/ ...

  10. java面试经常问到的计算机网络问题

    GET 和 POST 的区别 GET请注意,查询字符串(名称/值对)是在 GET 请求的 URL 中发送的:/test/demo_form.asp?name1=value1&name2=val ...