java反射使用和源码解析
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);//调用了属性delegate的invoke方法;也就是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反射使用和源码解析的更多相关文章
- Dubbo原理和源码解析之服务引用
一.框架设计 在官方<Dubbo 开发指南>框架设计部分,给出了引用服务时序图: 另外,在官方<Dubbo 用户指南>集群容错部分,给出了服务引用的各功能组件关系图: 本文将根 ...
- Dubbo原理和源码解析之“微内核+插件”机制
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- Dubbo原理和源码解析之服务暴露
github新增仓库 "dubbo-read"(点此查看),集合所有<Dubbo原理和源码解析>系列文章,后续将继续补充该系列,同时将针对Dubbo所做的功能扩展也进行 ...
- Spring源码解析02:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- Spring源码解析 | 第二篇:Spring IOC容器之XmlBeanFactory启动流程分析和源码解析
一. 前言 Spring容器主要分为两类BeanFactory和ApplicationContext,后者是基于前者的功能扩展,也就是一个基础容器和一个高级容器的区别.本篇就以BeanFactory基 ...
- java编译后字节码解析
java编译后字节码解析 参考网摘: https://my.oschina.net/indestiny/blog/194260
- Go语言备忘录:net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录:net/http包的使用模式和源码解析,多谢! 目录: 一.http ...
- Dubbo原理和源码解析之标签解析
一.Dubbo 配置方式 Dubbo 支持多种配置方式: XML 配置:基于 Spring 的 Schema 和 XML 扩展机制实现 属性配置:加载 classpath 根目录下的 dubbo.pr ...
- Go语言备忘录(3):net/http包的使用模式和源码解析
本文是晚辈对net/http包的一点浅显的理解,文中如有错误的地方请前辈们指出,以免误导! 转摘本文也请注明出处:Go语言备忘录(3):net/http包的使用模式和源码解析,多谢! 目录: 一.h ...
随机推荐
- Oracle 12C CRS-5013
1.背景 OS:SUSE 12SP3 DB:12.2.0.1.190115 2节点RAC Q:crs alert日志一直刷如下报错 2019-02-12 12:46:18.163 [ORAAGENT( ...
- Oracle程序备份
--使用root用户备份 su - root mkdir -p /oracle/data_dump/backup_soft nohup tar -cvf /oracle/data_dump/backu ...
- JavaScript 当前URL取参返回字典
getParam : function(){ return (key, strURL = window.location.search) => new RegExp("(^|\\?|& ...
- Cocos Creator 动态改变sprite图片
首先将存放图片最外层文件夹命名为resources changeBj: function(){ var url = 'globalUI/video/gVideoPlayClick'; var _thi ...
- luogu P1578 奶牛浴场
很好的一道题 王知昆爷爷的论文(讲的特别清楚) https://wenku.baidu.com/view/bc8311f69e314332396893f7.html 先贴上AC代码 #include& ...
- 网络-03-端口号-linux端口详解大全--著名端口
端口号码 / 层 名称 注释 1 tcpmux TCP 端口服务多路复用 5 rje 远程作业入口 7 echo Echo 服务 9 discard 用于连接测试的空服务 11 systat 用于列举 ...
- 从Node到Go的心路之旅
我最近将一个系统从Node重构到了Go,花了大概两周多的时间,这个过程也是不得已而为之,因为公司开发的系统最终需要部署到客户的服务器,而又不想暴露源码. 但是NodeJs开发的系统却无法从根本上来保护 ...
- CCF CSP 201809-1 卖菜
题目链接:http://118.190.20.162/view.page?gpid=T79 问题描述 试题编号: 201809-1 试题名称: 卖菜 时间限制: 1.0s 内存限制: 256.0MB ...
- eclipse端口号冲突解决办法
解决办法如下: 1.打开cmd 2.输入 netstat -ano|findstr 8080 然后按回车键(8080对应你的端口号) 3.输入 taskkill /pid 6856 /f ...
- 数据库session立即生效(64---8192) SCOPE参数
SCOPE=MEMORY|SPFILE|BOTH 指示了修改参数时的“作用域”: SCOPE=MEMORY :只在实例中修改,重启数据库后此次修改失效. SCOPE=SPFILE :只修改SPFILE ...