Java反射底层原理以及应用

您所在的位置:网站首页 java可以做底层吗 Java反射底层原理以及应用

Java反射底层原理以及应用

2024-07-16 17:10| 来源: 网络整理| 查看: 265

写在前面: Java反射, 这个东西百度就会出来相关概念:

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

字每一个都认识, 连起来的意思也不是那么的深奥, 但总给人感觉前面蒙着一层面纱看不清, 似懂非懂, 懂又不懂的感觉. 笔者就尝试从自己的理解对Java反射进行一些解释.

0. 类加载过程

要说反射就得从Java的类加载过程说起. 在这里插入图片描述     字丑见谅, Java类加载大体分为这么5个步骤, 有机会详细分析这五个步骤, 我们这里主要说的是第一个阶段–加载, 这个加载和前面的类加载是不一样的概念, 加载是类加载的一个阶段.加载是类加载子系统将类的Class对象加载到JVM的过程, 主要工作是:

通过全限定名来获取定义此类的二进制字节流.将二进制字节流的静态存储结构转化为方法区的运行时数据结构.在内存中生成这个类的java.lang.Class对象, 作为方法区这个类的数据访问入口.

    以上方法区之类的名称就不解释分析, 那属于另外一部分的内容. 重点看第三点的在内存中生成这个类的java.lang.Class对象. 反射的几乎所有东西都是围绕这个东西在做操作, 所以在讲反射之前我们有必要先对这个东西进行了解.在Java代码中很好获取这种类型的对象实例.

public class TestClass { public TestClass(){}; } public class Test{ public static void Main(String ... args) { // 1. 通过对象类型.class Class clazz1 = TestClass.class; // 2. 通过forName 这个名称熟悉吗, 就是JDBC连接其中加载驱动那一步 Class class2 = Class.forName("TestClass的权限定类型名"); // 3. 通过对象实例的getClass()方法, 这个方法是继承自Object类的方法 Class class3 = new TestClass().getClass(); // 4. 还有一种通过类加载器获取, 笔者就知道这4种 // 另外拓展其实java 8个基础数据类型和void的关键字也有Class类型. // void.class int.class ...读者可以自行尝试打印看看. } }

    万物皆对象, 这是面向对象的语言经常说的. Class是类的类, 这么说好绕, 是不是, 也可以把类的Class对象实例看做蓝图或者建筑图纸, 每当我们要去建房的时候我们就去拿图纸(图纸就是Class对象实例)根据图纸描述一步一步的建设房子(房子就是我们通过new 等方式创建的对象), 不管我们建多少套房子, 只要是房子类型一样的, 就是拿的同一张图纸, 就如上面代码中的TestClass类我们不管创建了多少个这个类型的对象实例, 使用都是TestClass.class这张图纸. 上面这一段主要解释的是无论某个对象在Jvm中被创建了多少实例, 都只有一个Class对象与之对应.     做个小结, 加载就是类加载子系统把类的Class加载到虚拟机内存, 并生成类的Class对象实例, 而Class对象实例则保存了要加载的类的类信息. 需要提示下在加载阶段完成后类本身还是不可用的, 类加载还要经历后面几个关键步骤.

2. 反射

    反射的概念上面其实已经讲了. 现在知道了Class对象是什么了, 以及每一个类都有个Class对象实例, 而类的创建的(上面的建房子)都是从Class对象实例获取相应的信息创建的. new TestClass()这种方式创建对象实例实际上Jvm内部自动去从TestClass的Class对象实例中获取了相应的信息进行创建, 而反射浅显理解就是把上面new的自动的过程变成我们程序员手动操作的过程, 手动去创建类的Class对象实例, 手动去获取构造函数, 手动去创建实例, 你可以这样理解反射, 但事实上还是有细微的差别. 写个例子初体验一下反射:

public class TestClass { private String param; public TestClass(String param) { this.param = param; } public TestClass() { } public void test(){ System.out.println("hello reflect... " + param); } private void test(String testParam){ System.out.println("test:hello reflect... " + testParam); } } public class Main { public static void main(String[] args) throws Exception { // 1. 类的加载的加载阶段, 创建类的Class对象实例 // 当然这里不止加载阶段, 还有其它阶段进行的. Class testClazz = TestClass.class; // 2. 从Class对象实例中获取TestClass的构造函数 // 参数String.class表示获取只有一个参数且为String类型的构造函数 // 不传参数默认为无参构造函数 Constructor testClazzDeclaredConstructor = testClazz.getDeclaredConstructor(String.class); // 3. 通过调用构造函数的newInstance方法创建实例 // newInstance的参数就是构造函数需要的参数 TestClass testClass = testClazzDeclaredConstructor.newInstance("param 111"); // 4. 调用实例的方法输出 hello reflect... param 111 testClass.test(); // 作为对比正常调用 new TestClass("param 111").test(); } }

    上面的代码案例就是反射, 可以看到相比正常的new TestClass("param 111").test()多了好几步更加繁琐了, 很多人就产生了疑问. 法拉第发明圆盘发电机的时候, 贵妇问他有什么用, 他说刚出生的婴儿有什么用呢? 况且反射之于Java可是壮汉而不是婴儿.     与反射相关的API大都在java.lang.reflect下, 比较核心的类有Method, Field, Constructor,Array还有代理用到Proxy等.在想需不需要介绍下相关API的基本操作. 介绍反射相关的操作:

反射-方法// 还是上面的TestClass类, 现在通过反射分别调用两个test方法 public class Main{ public static void main(String ... args) { // 为了简便这里就不通过反射创建对象了, 直接通过new创建 TestClass testClass = new TestClass("param 111"); // getMethod的方法可以通过方法名称获取到方法. // 方法签名getMethod(String name, Class... parameterTypes) // 后面的Class类型的可变长参数是指的需要调用的方法参数列表 // 必须严格按照顺序, java方法参数是方法签名的一部分,不同顺序的参数是不同方法 Method testNoargs = testClass.getClass().getMethod("test"); // 通过Method.invoke调用 // 要调用实例方法肯定需要对象实例 testNoargs.invoke(testClass); // getDeclaredMethod使用方式和getMethod一样, 参数含义也想通 // 只是getDeclaredMethod可以获取private方法 Method testArgString = testClass.getClass().getDeclaredMethod("test", String.class); // 设置方法的访问权限可以通过设置为true, 从而可以调用私有方法 testArgString.setAccessible(true); // 按照常规正常的操作我们是不可以调用私有方法的, 通过反射可以 // 这也是反射的应用之一 testArgString.invoke(testClass, "param 222"); } } 反射-字段 // 还是使用上面的TestClass对象 public class Main { public static void main(String ... args) { TestClass testClass = new TestClass("param 111"); Class testClassClass = testClass.getClass(); // 1. 通过getField获取字段, 和上面一样只能获取public Field publicField1 = testClassClass.getField("publicField1"); // 这里get传入的是对象实例 System.out.println(publicField1.get(testClass)); // 2. 通过getDeclareField获取字段, 可以获取私有字段 Field field1 = testClassClass.getDeclaredField("field1"); // 设置访问权限, 把private变成public field1.setAccessible(true); System.out.println(field1.get(testClass)); // 还可以修改它的值 field1.set(testClass, "field22"); System.out.println(field1.get(testClass)); // 3.这是静态字段的示例 Field staticField = testClassClass.getDeclaredField("staticField"); staticField.setAccessible(true); // 这一段这几个testClass传null也可以, 想想为什么 // 静态字段和非静态字段的区别 System.out.println(staticField.get(testClass)); staticField.set(testClass, "staticField2"); System.out.println(staticField.get(testClass)); // 4. 这是常量, 常量也是可以修改. 常量由final修饰 Field staticFinalField = testClassClass.getDeclaredField("staticFinalField"); staticFinalField.setAccessible(true); // 这句代码我也不明白, 测试发现打开下面的这句代码, 就没办法修改常量了. // System.out.println(staticFinalField.get(null)); // 把常量改为可修改. Field modifiers = staticFinalField.getClass().getDeclaredField("modifiers"); modifiers.setAccessible(true); modifiers.set(staticFinalField, staticFinalField.getModifiers() & ~Modifier.FINAL); staticFinalField.set(null, "waxxd22"); System.out.println(staticFinalField.get(TestClass.class)); } }

注意4的例子修改常量在高版本上可能有些问题, 高版本的jdk需要用别的方式修改常量, 高版本大概12以上吧, 并且这个东西了解即可, 一般还是不要去修改常量的好.另外在修改基本类型的常量比如int的时候应该会不起效果. 具体原因public static final int a = 100;定义这么一个变量, 在别的地方使用a的时候它可能直接把a替换为100, 所以你改了a, 别的地方依旧是100. 3. 反射-数组

public class Main { public static void main(String ... args) { // 1. 反射创建数组, 之前说过基础数据类型也有Class对象 Object intArr = Array.newInstance(int.class, 3); // 2. 反射赋值 Array.set(intArr, 1, 2); // 3. 反射取值 System.out.println(Array.get(intArr, 1)); // 4. 获取数组的Class类型 System.out.println(int[].class); // 如何通过forName获取int[]的Class呢 System.out.println(Class.forName("[I")); // boolean[] 是[Z long[] 是[J 对象类型[]是[L-> String[] -> [Ljava.lang.String] } }

关于数组那里的Class.forName为什么那么怪异的说明, 你认为不怪异那就算了. 我们声明的时候如字符串数组new java.lang.String[]但是经过编译器编译会变成Ljava.lang.String所以Class.forName里面的参数要写成那样的形式. 但是并不能通过这种方式生成一个数组, 这只表示把Ljava.lang.String这么一个Class对象加载到内存中去了, 想想生成一个数组是需要传入长度的, 我们又不知道Ljava.lang.String这个类型的构造函数长什么样.

3.0 反射应用

反射能做什么, 在工作中也有人问过我这样的问题, 反射能做什么. 上面的例子已经回答了这个问题, 当我告诉别人能调用方法, 能读取字段修改字段, 能创建对象的时候, 他们的头脑中的大都大大的疑惑, 这些我不反射也可以做并且更简单. 所以这里我试图解释一下反射到底能做什么

肉眼可见的一个作用, 读懂源码, 很多框架都大量应用到反射, 比如spring, 随处可见的反射.偶尔一些特殊需求做一些骚操作. 就像上面举例的访问私有方法私有字段, 甚至修改常量. 操作一些别人不让你操作的东西. 这里有一个示例:// java有个类Unsafe类, 听名字都很唬人, 不安全类 // 这个类是Java的黑魔法, 之前官方说在JDK8以后要遗弃它, 可是现在到了最新的15,16都还存在 // 很多优秀的框架或者功能都基于它完成, 比如NIO, Netty, CAS // 有人说这个类一半天使一半魔鬼 // 众所周知Java自动内存管理没有指针 // 那么Unsafe就可以解放你的天性, 随意申请内存, 指针都给你. 但这也是危险的 /* 这个类正常的方式肯定是没办法创建的, 因为构造函数是私有的. 然后有人说不是有个getUnsafe()方法吗, 你去调用就知道会报错. 它会检查调用者的类加载器不是Bootstrap就抛异常, 而我们自己编写的类肯定不是Bootstrap 这样做也是因为安全性限制我们调用 所以非要调用就只能通过反射生成这个类实例或者反射获取theUnsafe字段 */ public final class Unsafe { private static native void registerNatives(); static { registerNatives(); } private Unsafe() {} private static final Unsafe theUnsafe = new Unsafe(); public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } } // 删除了其它的代码 } /* 还有其它骚操作我在工作中实际用到的一个, Gson工具在反序列化时候默认把所有数字都 反序列化成double还弄些科学计数法之类的.改源码的话是个很简单的判断就搞定了 但是源码不好改啊,所以就通过反射修改相关属性. 替换成自己的实现类 */ 确确实实大量需要用到反射的地方, 写框架. 框架之所以为框架, 就因为它只搭了一个架子, 很多具体的功能就需要框架的使用者去自己做. 这个时候写框架的人员是不知道框架使用者的类叫什么但是他又要创建对象并调用你的方法.// 定义接口 public interface UserFunction { void userWork();} // 未来的流弊框架 public class FrameWaxxd { public FrameWaxxd(){}; public void frameDo() { // 1. do frame workd // code // 2. List userFunctions = 获取所有实现UserFunction接口的类; // 3. 遍历创建对象调用userWork(); // 4. do fram work } } 上面的示例代码就很好的说明了反射的作用, 首先作为框架开发人员, 并不能知道使用者实现了多少个UserFunction接口, 也不知道使用者UserFunction接口实现类的类名, 所以框架开发者没有办法在框架开发阶段通过new 构造函数()的方式去调用, 这些类都是需要运行时确定的, 所以这里几乎是必须通过反射去调用, 结合实际的工作实例, 我们在Springboot开发中那些Controller或者Service我们并没有进行创建, 但是我们知道对象肯定是被创建了, 就是通过反射的方式进行创建.Java的注解大概率会用到反射, 注解上面的值需要反射获取. 这里就不举例了.获取泛型也需要用到反射, Java的泛型实际不那么泛型, 是一种伪泛型, 只是在编译阶段有用, 可以用来检查代码, List和List是一样的类型, 因为在JVM里都是List, 而反射可以用来获取里面泛型类型. 有的时候也挺有用的.还记得前文吗, JDK动态代理的目标方法调用实际就是基于反射 最后

这次好像没有最后了, 最后说一个int.class == Integer.class 结果是true还是false, 这个本质就是在问基本类型和包装类型的Class对象是不是同一个, 其实很简单, 明显不是同一个, 如果你理解了上文说讲的Class到底是个什么东西, Class是类的元数据, 保存了类的相关属性信息, 面向对象把事物抽象为类, 而在Java中类的抽象就是Class, Integer对象有各种各样的方法, 而int就只是一个数据类型, 由此它们肯定不是以同一个Class对象为蓝本创建的. 其它基础类型包装类型也类似. 那么另外一个问题int.class == Integer.TYPE吗? true. Integer.TYPE就是保存的int.class.



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3