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. Linux给MySQL创建用户,并分配权限

    //登录MYSQL 使用root账号登录 @>mysql -u root -p @>密码 //创建用户 mysql> insert into mysql.user(Host,User ...

  2. 2018-2019 20175232 实验二《Java面向对象程序设计》实验报告

    一.实验内容及步骤 1熟练掌握Junit和TDD: TDD(Test Driven Devlopment, 测试驱动开发)我们是该“先写产品代码,然后再写测试代码,通过测试发现了一些Bugs,修改代码 ...

  3. qt 画多边形(实现鼠标拖动节点)

    ---恢复内容开始--- 2018-01-06 这个小例子实现了移动鼠标,鼠标的坐标信息跟随鼠标移动,多边形的实现,鼠标点击可以拖动多边形点的位置,(其中有个问题?我在QMainWindow下,用mo ...

  4. Ajax请求二进制流并在页面展示

    后端代码: public void getIntegralQrcode(HttpServletResponse response, String token) throws BizException, ...

  5. Mac下安装SecureCRT并激活

    今天花了好长的时间终于把SecureCRT安装成功了 现在分享给大家 安装的步骤, 希望对大家用帮助 Mac下的SecureCRT需要破解才能使用 所以有些费劲的.. 先下载SecureCRT和破解文 ...

  6. 【论文速读】Pan He_ICCV2017_Single Shot Text Detector With Regional Attention

    Pan He_ICCV2017_Single Shot Text Detector With Regional Attention 作者和代码 caffe代码 关键词 文字检测.多方向.SSD.$$x ...

  7. 选择排序java实现

    package text.algorithm; /** * 选择排序 * O(n^2);空间复杂度O(1); */public class SelectionSort { public static ...

  8. jQuery的deferred对象使用详解

    转自: https://www.cnblogs.com/PengLee/p/5657101.html jQuery的开发速度很快,几乎每半年一个大版本,每两个月一个小版本.每个版本都会引入一些新功能. ...

  9. JS动态添加行列

    <%@ Page Language="C#" AutoEventWireup="true" CodeFile="Add-Delete Row.a ...

  10. freeswitch reloadxml 出错

    1.修改fs配置文件后,reloadxml控制台上报错误. 2,控制台日志显示 error near line 7301]:missing>] 查询 log 下的 freeswitch.xml. ...