当前位置:首页 > Java技术 > 【夯实Java基础06】这可能是把Java反射写的最详细的一篇文章

【夯实Java基础06】这可能是把Java反射写的最详细的一篇文章

2022年11月07日 20:48:34Java技术6

本文已收录至我的GitHub:Java开发宝典,欢迎大家给个Star:https://github.com/eson15/javaAll
我会持续更新,欢迎star!
微信搜索:武哥聊编程,回复“笔记”,可以领取一份我亲自写的10万字Springboot经典学习笔记一份



反射是框架设计的灵魂要素!

1. 反射是什么?

所谓的反射就是java语言在运行时拥有的一种自观的能力,反射使您的程序代码能够得到装载到JVM中的类的内部信息,允许您执行程序时才得到需要类的内部信息,而不是在编写代码的时候就必须要知道所需类的内部信息;也可以通俗的将这种动态获取信息以及动态调用对象的方法称为Java的反射机制.

通过Java的反射机制,程序猿们可以更深入的控制程序的运行过程,如在程序运行时对用户输入的信息进行验证,还可以逆向控制程序的执行过程,这也使反射成为构建灵活的应用的主要工具。

2. 反射原理大解析

2.1 反射的常用类和函数

Java反射机制的实现要借助于4个类:

  • Class 类对象
  • Constructor 类的构造器对象
  • Field 类的属性对象
  • Method 类的方法对象

2.2 Class 类包含的方法

通过这四个对象我们可以粗略的看到一个类的各个组成部分。其中最核心的就是Class类,它是实现反射的基础,Class类包含的方法主要有:

序号 名称 功能
1 getName() 获取此类型的全限定名
2 getSuperClass() 得到此类型的直接超类的全限定名
3 isInterface() 判断此类型是类类型还是接口类型
4 getTypeParamters() 获取这个类型的访问修饰符
5 getInterfaces() 获取任何直接超接口的全限定名的有序列表
6 getFields() 获取字段信息
7 getMethods() 获取方法信息

2.3 反射的主要方法

应用反射时我们最关心的一般是一个类的构造器、属性和方法,下面我们主要介绍Class类中针对这三个元素的方法:

2.3.1 得到构造器的方法

语法 功能
Constructor getConstructor(Class[] params) 获得使用特殊的参数类型的公共构造函数
Constructor[] getConstructors() 获得类的所有公共构造函数
Constructor getDeclaredConstructor(Class[] params) 获得使用特定参数类型的构造函数(与接入级别无关)
Constructor[] getDeclaredConstructors() 获得类的所有构造函数(与接入级别无关)

2.3.2 获得字段信息的方法

语法 功能
Field getField(String name) 获得命名的公共字段
Field[] getFields() 获得类的所有公共字段
Field getDeclaredField(String name) 获得类声明的命名的字段
Field[] getDeclaredFields() 获得类声明的所有字段

2.3.3 获得方法信息的方法

语法 功能
Method getMethod(String name, Class[] params) 使用特定的参数类型,获得命名的公共方法
Method[] getMethods() 获得类的所有公共方法
Method getDeclaredMethod(String name, Class[] params) 使用特写的参数类型,获得类声明的命名的方法
Method[] getDeclaredMethods() 获得类声明的所有方法

2.4 反射实战的基本步骤

Class actionClass=Class.forName(“MyClass”);
Object action=actionClass.newInstance();
Method method = actionClass.getMethod(“myMethod”,null);
method.invoke(action,null);

上面就是最常见的反射使用的例子,前两行实现了类的装载、链接和初始化(newInstance方法实际上也是使用反射调用了<init>方法),后两行实现了从class对象中获取到method对象然后执行反射调用。下面简单分析一下该代码的具体原理。

2.4.1 获得类的Class对象

通常有三种不同的方法:
1)Class c = Class.forName(“java.lang.String”)
2)Class c = MyClass.class
3)对于基本数据类型可以用Class c = int.class 或 Class c = Integer.TYPE这样的语句.

举个小栗子:先通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例

PS:反射的原理之一其实就是动态的生成类似于上述的字节码,加载到jvm中运行。

设想一下,上面的代码中,如果想要实现method.invoke(action,null)调用action对象的myMethod方法,只需要实现这样一个Method类即可:

    Class Method{
     

        public Object invoke(Object obj,Object[] param){
     
    
            MyClass myClass=(MyClass)obj;
    
            return myClass.myMethod();
    
        }
    
    }

2.4.2 获取 Method 对象

首先来看一下Method对象是如何生成的:
- 使用Method m =myclass.getMethod("myMethod")获得了一个Class对象
- 接着对其进行判断,如果没有对应的cache,那么JVM就会为其创建一个并放入缓冲空间
- 处理器再判断Cache中是否存在"myMethod"
- 如果没有则返回NoSuchMethodException
- 如果存在那么就Copy一份"myMethod"对象并返回

上面的Class对象是在加载类时由JVM构造的,JVM为每个类管理一个独一无二的Class对象,这份Class对象里维护着该类的所有Method,Field,Constructor的cache,这份cache也可以被称作根对象。每次getMethod获取到的Method对象都持有对根对象的引用,因为一些重量级的Method的成员变量(主要是MethodAccessor),我们不希望每次创建Method对象都要重新初始化,于是所有代表同一个方法的Method对象都共享着根对象的MethodAccessor,每一次创建都会调用根对象的copy方法复制一份:

Method copy() {
      
    Method res = new Method(clazz, name, parameterTypes, returnType,

                            exceptionTypes, modifiers, slot, signature,

                            annotations, parameterAnnotations, annotationDefault);
    res.root = this;
    res.methodAccessor = methodAccessor;
    return res;
}

2.4.3 调用invoke()方法

获取到Method对象之后,调用invoke方法的流程如下:

m.invoke(obj,param);

- 首先调用MethodAccess.invoke
- 如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImp
- 如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl

我们可以看到,调用Method.invoke之后,会直接去调MethodAccessor.invoke。MethodAccessor就是上面提到的所有同名method共享的一个实例,由ReflectionFactory创建。创建机制采用了一种名为inflation的方式(JDK1.4之后):如果该方法的累计调用次数<=15,会创建出NativeMethodAccessorImpl,它的实现就是直接调用native方法实现反射;如果该方法的累计调用次数>15,会由java代码创建出字节码组装而成的MethodAccessorImpl。(是否采用inflation和15这个数字都可以在jvm参数中调整)

以调用MyClass.myMethod(String s)为例,生成出的MethodAccessorImpl字节码翻译成Java代码大致如下:

public class GeneratedMethodAccessor1 extends MethodAccessorImpl {
         
    public Object invoke(Object obj, Object[] args)  throws Exception {
     
        try {
     
            MyClass target = (MyClass) obj;
            String arg0 = (String) args[0];
            target.myMethod(arg0);
        } catch (Throwable t) {
     
            throw new InvocationTargetException(t);
        }
    }
}

通过对java运行过程的详细分析,我们可以发现其中第1次和第16次调用是最耗时的(初始化NativeMethodAccessorImpl和字节码拼装MethodAccessorImpl)。初始化不可避免,因而native方式的初始化会更快,所以前几次的调用会采用native方法。

随着调用次数的增加,每次反射都使用JNI跨越native边界会对优化有阻碍作用,相对来说使用拼装出的字节码可以直接以Java调用的形式实现反射,发挥了JIT优化的作用,避免了JNI为了维护OopMap(HotSpot用来实现准确式GC的数据结构)进行封装/解封装的性能损耗。

在已经创建了MethodAccessor的情况下,使用Java版本的实现会比native版本更快。所以当调用次数到达一定次数(15次)后,会切换成Java实现的版本,来优化未来可能的更频繁的反射调用。

3. Java反射的应用(Hibernate框架)

前面我们已经知道,Java 反射机制提供了一种动态链接程序组件的多功能方法,它允许程序创建和控制任何类的对象(根据安全性限制)之前,无需提前硬编码目标类。这些特性使得反射特别适用于创建以非常普通的方式与对象协作的库。例如,反射经常在持续存储对象为数据库、XML或其它外部格式的框架中使用。下面就已Hibernate框架为例像大家阐述一下反射的重要意义。

Hibernate是一个屏蔽了JDBC,实现了ORM的java框架,利用该框架我们可以抛弃掉繁琐的sql语句而是利用Hibernate中Session类的save()方法直接将某个类的对象存到数据库当中,也就是所涉及到sql语句的那些代码Hibernate帮我们做了。这时候就出现了一个问题,Hibernate怎样知道他要存的某个对象都有什么属性呢?这些属性都是什么类型呢?想一想,它在向数据库中存储该对象属性时的sql语句该怎么构造呢?OK,反射的作用此刻就体现出来了!

下面我们以一个例子来进行阐述,比如我们定义了一个User类,这个User类中有20个属性和这些属性的get和set方法,相应的在数据库中有一个User表,这个User表中对应着20个字段。假设我们从User表中提取了一条记录,现在需要将这条记录的20个字段的内容分别赋给一个User对象myUser的20个属性,而Hibernate框架在编译的时候并不知道这个User类,他无法直接调用myUser.getXXX或者myUser.setXXX方法,此时就用到了反射,具体处理过程如下:

  1. 根据查询条件构造PreparedStament语句,该语句返回20个字段的值;

  2. Hibernate通过读取配置文件得到User类的属性列表list(是一个String数组)以及这些属性的类型;

  3. 创建myUser所属类的Class对象c;c = myUser.getClass();

  4. 构造一个for循环,循环的次数为list列表的长度;

    • 读取list[i]的值,然后构造对应该属性的set方法;

    • 判断list[i]的类型XXX,调用PreparedStament语句中的getXXX(i),进而得到i出字段的值;

    • 将4.2中得到的值作为4.1中得到的set方法的参数,这样就完成了一个字段像一个属性的赋值,如此循环直至程序运行结束;

如果没有反射难以想象实现这么复杂的功能将会有多么难!

话说回来,反射给我们带来便利的同时也有它自身的缺点,比如性能较低、安全性较低、过程比较复杂等等,感兴趣的读者也可以在实际工作中再深入研究哦!

作者info

【作者】:武哥
【公众号】:武哥聊编程。欢迎大家关注~
【作者简介】:同济大学,硕士。先后在华为、科大讯飞、拼多多采坑。一个自学 Java 的菜鸟,期待你的关注。

点赞是对我最大的鼓励
↓↓↓↓↓↓

作者:武哥聊编程
来源链接:https://blog.csdn.net/eson_15/article/details/110749618

版权声明:
1、JavaClub(https://www.javaclub.cn)以学习交流为目的,由作者投稿、网友推荐和小编整理收藏优秀的IT技术及相关内容,包括但不限于文字、图片、音频、视频、软件、程序等,其均来自互联网,本站不享有版权,版权归原作者所有。

2、本站提供的内容仅用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律的规定,不得侵犯相关权利人及本网站的合法权利。
3、本网站内容原作者如不愿意在本网站刊登内容,请及时通知本站(javaclubcn@163.com),我们将第一时间核实后及时予以删除。


本文链接:https://www.javaclub.cn/java/68415.html

分享给朋友:

“【夯实Java基础06】这可能是把Java反射写的最详细的一篇文章” 的相关文章

Java中四种访问修饰符的区别

在java中共有4种访问级别,按访问权限由高到低为:public(公有的)、protected(受保护的)、友好的(没有任何访问权限关键字修饰)和private(私有的)。 类型 类内部 同一个包其...

在JAVA 中将堆与栈分开的原因

栈是运行时的单位,而堆是存储的单位。 栈解决程序的运行问题,即程序如何执行,或者说如何处理数据;堆解决的是数据存储的问题,即数据怎么 放、放在哪儿。 注意:在Java中一个线程就会相应有一个线程栈与之对应 栈因为是运行单位,因此里面存储的信息都是跟...

Java Web 工作技巧总结 16.8

Java Web 工作技巧总结 16.8

摘要: 原创出处:www.bysocket.com 泥瓦匠BYSocket 希望转载,保留摘要,谢谢! 四时不谢之兰,百节长青之竹,万古不败之石,千秋不变之人。 1. AOP – LOG 项目中,一个请求过来,一个响应回去。...

Java 基础:hashCode方法

Java 基础:hashCode方法

Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 一、前言     泥瓦匠最近被项目搞的天昏地暗。发现有些要给自己一些目标,关于技术的目标: 专注...

JavaWeb(一)之细说Servlet

JavaWeb(一)之细说Servlet

前言   其实javaWeb的知识早就学过了,可是因为现在在搞大数据开发,所以web的知识都忘记了。准备开始慢慢的把Web的知识一点一点的回忆起来,多学一点没有关系,就怕到时候要用的话,什么都不会了。 一、Servlet概述 1.1、Servlet简介   Se...

Java Calendar类的使用总结

  在实际项目当中,我们经常会涉及到对时间的处理,例如登陆网站,我们会看到网站首页显示XXX,欢迎您!今天是XXXX年。。。。某些网站会记录下用户登陆的时间,比如银行的一些网站,对于这些经常需要处理的问题,Java中提供了Calendar这个专门用于对日期进行操作的类,那么这个类有什么...

Java反射机制详解

Java反射机制详解

     Java反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。 1、关于Class &n...

Java Web系列:Java Web 项目基础

Java Web系列:Java Web 项目基础

1.Java Web 模块结构 JSP文件和AXPX文件类似,路径和URL一一对应,都会被动态编译为单独class。Java Web和ASP.NET的核心是分别是Servlet和IHttpHandler接口,因此无论是基础的Page文件(JSP、ASPX)方式还是后来发展的MVC...

Java内存管理:深入Java内存区域

Java内存管理:深入Java内存区域

对于从事C和C++程序开发的开发人员来说,在内存管理领域,他们既是拥有最高权力的皇帝,又是从事最基础工作的劳动人民—既拥有每 一个对象的“所有权”,又担负着每一个对象生命开始到终结的维护责任。   对于Java程序员来说,在虚拟机的自动内存管理机制的帮助下,不再需要为每一个new操作去...

浅谈Java中的hashcode方法

 浅谈Java中的hashcode方法   哈希表这个数据结构想必大多数人都不陌生,而且在很多地方都会利用到hash表来提高查找效率。在Java的Object类中有一个方法: public native int hashCode();   根据这个...

发表评论

访客

◎欢迎参与讨论,请在这里发表您的看法和观点。