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. caffe-ssd需要安装opencv

    https://blog.csdn.net/xiaxiazls/article/details/52039473

  2. empty和isset区别

    isset判断变量是否已存在(配置) unset把变量删除(释放)掉 empty 判断变量是否为空 is_null 判断变量是否为NULL is_null,我们可以把它看成是!isset,是isset ...

  3. 记录心得-IntelliJ iDea 创建一个maven管理的的javaweb项目

    熟能生巧,还是记录一下吧~ 开始! 第一步:File--New--Project--Maven--Create from archetype--maven-archetype-webapp 第二步:解 ...

  4. 通过安全网闸访问MongoDB

    前景描述: 在10.235的内网3台虚拟机上部署了mongodb的副本集,同网段中的虚拟机上部署的Springboot工程可正常访问.spring配置文件: data: mongodb: uri: m ...

  5. react组件实现扩展知识

    react-redux redux-thunk 实现异步action, ajax,定时器 redux-gen 利用生成器,实现middleware react-router-redux react-r ...

  6. Jmeter二次开发

    Jmater函数扩展的步骤1. 导入Jmeter源码,或使用maven项目,引入依赖的jar包 2. 继承AbstractFunction,实现自定义Function 3. 继承JMeterTestC ...

  7. C博客作业06--结构体&文件

    1.本章学习总结 1.1思维导图 1.2本章学习体会 学习了结构和文件,又是懵懵的课了,我的天啊.结构还好,题目集一出就做了,不是很难,感觉掌握的还可以,不过这只是感觉而已,等到真正来写大作业的时候又 ...

  8. sai u 2016

    再过20分钟,我就要结束2016年的工作回家过春节了.真是难过的一天啊,从来没有今天那么感受深刻,那么嫌弃时间太慢,没有归家心似箭,没有近乡情怯,只是好想,呵呵,来个午睡,来场电影,来点小说,哈哈哈. ...

  9. deepin 安装scrapy安装出错:fatal error: Python.h 的解决办法

    环境deepin Linux 15.7 Python3.6.5 今天重新装了deepin系统,需要重新安装scrapy,直接pip了一下,没成. 报错:fatal error: Python.h: 没 ...

  10. MySQL常用dos命令

    MySQL的基本目录 登陆MySQL 查看数据库 Show databases; 创建数据库 Create database 数据库的名字; 标准创建语句: Create database if no ...