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. mysql-5.7.25 源码 安装

    mysql-5.7.25 源码 安装 编译 export INSTALL_PREFIX="/data/services" export MYSQL_INSTALL_PATH=&qu ...

  2. (转)jmeter接口测试--获取token

    Jmeter进行接口测试-提取token 项目一般都需要进行登陆才能进行后续的操作,登陆有时发送的请求会带有token,因此, 需要使用后置处理器中的正则表达式提取token,然后用BeanShell ...

  3. Lintcode: Nuts & Bolts Problem

    Given a set of n nuts of different sizes and n bolts of different sizes. There is a one-one mapping ...

  4. java中进程与线程的区别

    进程是一个正在运行的应用程序.一个进程包含一个或多个线程.它可以是一段完整的代码或部分程序的动态执行.系统资源分配与调度的基本单位.而线程是CPU调度与运行的基本单位,它是一组指令的集合或是程序的特殊 ...

  5. keytool 错误: java.lang.Exception: 密钥库文件不存在: keystore

    通过Android Studio编译器获取SHA1 第一步.打开Android Studio的Terminal工具 第二步.输入命令:keytool -v -list -keystore keysto ...

  6. 介绍Kubernetes监控Heapster

    什么是Heapster? Heapster是容器集群监控和性能分析工具,天然的支持Kubernetes和CoreOS,Kubernetes有个出名的监控agent—cAdvisor.在每个kubern ...

  7. python习题二

    1.输入1-127的ascii码并输出对应的字符 for i in range(1,128):    print(chr(i)) 2.输入a,b,c,d,4个整数,计算a+b-c*d的结果 a = i ...

  8. php高精度计算问题

    从事金融行业,资金运算频繁,这里说下我遇到的坑....稍不留神,用户资金可能损失几十万,甚至更可怕......直接上实例吧: javascript 0.1 + 0.2 为啥不等于 0.3 ? (正确结 ...

  9. Linux性能优化-理解平均负载

    1 平均负载含义 当系统变慢的时候,我们一般使用 top 或 uptime 命令来查看系统平均负载情况. 正确定义:单位时间内,系统中处于可运行状态和不可中断状态的平均进程数.错误定义:单位时间内的c ...

  10. 猴子吃桃问题(Java递归实现)

    猴子吃桃问题:猴子第一天摘下若干个桃子,当即吃了一半,还不过瘾,又多吃了一个,第二天早上又将剩下的桃子吃掉一半,又多吃了一个.以后每天早上都吃了前一天剩下的一半零一个.到第10天早上想再吃时,见只剩下 ...