Java反射笔记2
反射的优点和缺点
- 优点:可以动态的创建和使用对象,使用灵活,没有反射机制,就无法正常使用框架技术
- 缺点:使用反射基本是解释执行,对执行速度有影响
构建代码查看时间差距:
首先一个Cat类,中间的输出语句可要可不要
1 | package reflection; |
然后构建代码
1 | package reflection; |
上面的前一段是传统方法调用,下面是反射来调用
因为我这里循环次数不是很大,差距也只是个位数倍数,将i调整为10亿,再运行
时间差距几乎是很大的
反射调整优化-关闭访问调查:
1. Method和Field.Constructor对象都有setAccessible()方法
2. setAccessible作用是启动和禁用访问安全检查的开关
3. 参数值为true表示反射的对象在使用时取消访问检查,提高反射的检查,参数值为false则表示反射的对象执行访问检查
1 | public static void m3() throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException { |
优化效果如图,但效果不是特别好,但聊胜于无
Class类:
- Class也是类,因此也继承Object类
- Class类对象不是new出来的,是系统创建(见笔记1的程序运行图解)
比如说:
1 | Cat cat = new Cat(); |
在这一步进行打断点debug,step进去首先进入ClassLoader.java里的loadClass方法,此时方法里的String name参数刚好指代上面的Cat方法。
如果使用反射方法进行创建对象
1 | Class cls = Class.forName("Cat"); |
经过多次方法的调动,最后也会调用一个loadClass方法加载一个Cat类的Class类对象
3.对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
4.每个类的实例都会记得自己是由哪个Class实例所生成
5.通过Class可以完整的得到一个类的完整结构,通过一系列API
6.Class对象是存放在堆里的
7.类的字节码二进制数据,是放在方法区的,有的地方称为类的元数据
获取Class对象:
- 代码阶段/编译阶段:
Class.forName()
前提:已知一个类的全类名,且该类在类路径下,可通过Class类的静态方法forName()获取,可能抛出ClassNotFoundException,实例:
1 | Class cls1 = Class.forName("java.lang.Cat"); |
多用于配置文件,读取类的全路径,加载类
2.Class类阶段(加载阶段):
类.class
前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高
实例:
1 | Class cls2 = Cat.class; |
多用于参数传递,比如通过反射得到对应构造器对象
3.Runtime运行阶段:
对象.getClass()
前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象
实例:
Class clazz = 对象.getClass();
通过创建好的对象,获取Class对象
4.通过类加载器来获取到类的Class对象
- 先得到类加载器 car
1
ClassLoader classLoader = car.getClass().getClassLoader();
- 通过类加载器得到Class对象以上获得的Class对象都是同一个(因为内存里对于一个类只允许有一个Class类对象)
1
Class cls = classLoader.loadClass(classAllPath)
5.基本数据类型按照如下方式得到Class类对象
Class cls = 基本数据类型.class
6.基本数据类型对应的包装类,可以通过.TYPE得到Class类对象
Class cls = 包装类.TYPE
到此为止,基本的反射语法都能看懂了,那我们学习某个知识肯定到最后也是为了Bypass也就是绕过。在安全中,使用反射的一大目的也是为bypass。
forName有两个函数重载:
1 | Class forName(String name) |
第一个就是我们常见获取class方式,其实也就是第一种的封装
可以理解为下面
1 | Class.forName(className) |
一般情况下,forName第一个参数是类名,第二个代表是否初始化,第三个参数就是类加载器
类加载器就是告诉JVM如何去加载这个类,在这里JAVA默认根据类名来加载类,这个类名是类的完整路径,比如:java.lang.Runtime
第二个参数是initialize,决定是否初始化
在反射中如果使用功能.class来创建Class对象的引用时,不会自动初始化,如果使用forName()会自动初始化Class对象
(代码中test.class对象的本质就是test.class文件加载到内存中的内容)
这里初始化,可以理解为原来在C语言的中给一个变量赋初值,将这个思想转换到这里,也就是给一个类赋一个初始状态
1 | public class TrainPrint { |
以上三个代码块,运行顺序分别是static,empty block以及最后的构造函数
static {} 就是在“类初始化”的时候调⽤的,⽽ {} 中的代码会放在构造函数的 super() 后⾯, 但在当前构造函数内容的前⾯。
所以说forName中第二个参数就是告诉JVM是否执行类初始化
假设有一个函数,其中参数name可控且会执行初始化:
1 | public void ref(String name) throws Exception { Class.forName(name); |
那么我们可以构造一个类,来执行我们的命令:
1 | import java.lang.Runtime; |
其中我们将特意设置的恶意代码放在static中