之前陆陆续续学过一点Java安全,笔记一直都没没有系统的写过,现在重新深入学一下之前的知识,会把笔记持续更新过来
Java反射
反射是java得一个重要特性,它可以获取一个类的所有信息,还可以执行类中的方法
反射赋予Java动态特性
我个人感觉静态语言的安全性是比较高的,因为一个供给使用的静态语言的程序的结构时固定的,能给攻击者能操控影响应用程序的点就十分稀少。而相对应的动态语言的灵活性好,结构也好改变,攻击者的攻击手段也就更多。所以初步感觉Java的安全问题或多或少都受反射影响
静态语言与动态语言
-
动态语言:是一类在 运行时可以改变自身结构或者变量类型的语言,或者说这一类 语言的结构是在 运行时才被决定的。其中的结构包括但不限于:函数、对象、代码块
代表语言:Javascript C# PHP Python
-
静态语言:是一类在 运行时不能改变自身结构或者变量类型的语言,也就是说这种 语言的结构在 运行之前就被决定了。
代表语言:C C++
Java作为一种静态语言,之所以拥有动态特性,就是因为有反射机制的存在
Java可以通过反射来不定化一段代码
public void execute(String className, String methodName) throws Exception {
Class clazz = Class.forName(className);
clazz.getMethod(methodName).invoke(clazz.newInstance());
}
这段代码在没有确定传入的参数是什么的时候,它的功能是未知的。这就是说在编译完成时,它的功能并没完全固定,它的功能会随着不同的情况产生不定的变化。这就是我所理解的反射带给Java的动态特性。
Class类
反射是如何赋予Java动态特性的呢?、
先来了解一下Java的类加载机制
类的加载过程
一个Java源文件的执行流程主要包括两个过程
- 编译
使用Javac 将 .java
文件编译成 .class
字节码文件
- 运行
JVM来运行.class
字节码文件
而类加载过程指的就是 JVM 把.class
字节码文件里的信息加载到内存中,并生成对象的过程
类加载又分成三个阶段:加载、连接、初始化
了解类加载过程之前,先简单了解一下Java内存模型
Java内存模型
Java内存中可以简单了解三个关键分区
- 方法区
线程共享,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码等数据
- 堆
堆的作用是存放对象实例和数组
- 方法栈
每个线程会有一个私有的栈。每个线程中方法的调用又会在本栈中创建一个栈帧。在方法栈中会存放编译期可知的各种基本数据类型(存具体数值)、对象引用(可以理解成指针,存对象在堆里面的具体地址)
它们之间的关系可以简单理解成如下图所示
现在来了解一下类加载的详细过程
- 加载:将字节码加载到内存中,并将静态数据转换成成方法区的运行时数据结构,然后生成唯一一个代表这个类的
java.lang.Class
对象在堆中,之后就可以通过这个Class对象来访问方法区的静态数据
- 连接:将Java类的二进制代码合并到JVM运行状态之中的过程
- 验证:确保加载的类信息符合JVM规范,没有安全问题
- 准备:为类中的静态字段分配内存,并设置默认的初始值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了
- 解析:将常量池内的符号引用转换为直接引用的过程
- 初始化:初始化就是执行类的构造器方法
init()
的过程。这个方法不需要定义,是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的。若该类具有父类,jvm
会保证父类的init
先执行,然后再执行子类的init
。
现在,已经大致了解了Java的类加载过程,继续来看看Java是通过什么东西来实现类加载的。
类加载器
Java 中类加载是通过类加载器实现的,在Java中类加载器可以分为4种
- 引导类加载器 Bootstap Classloader:使用C++编写,没有父类加载器,是JVM自带的类加载器,负责加载Java平台核心库,用来装载核心类库。无法直接过去
- 扩展类加载器 Extension Classloader:使用Java编写,父类加载器是引导类加载器,java.ext.dirs目录中加载类库,或者从JDK安装目录:jre/lib/ext目录下加载类库。我们就可以将我们自己的包放在以上目录下,就会自动加载进来了
- 应用程序类加载器 Application Classloader:使用Java编写,父类加载器是扩展类加载器 ,它负责加载环境变量classpath或者系统属性java.class.path指定路径下的类库
- 自定义加载器:自己定义的加载器
双亲委派机制
- 如果一个类加载器接收到了类加载的请求,它自己先检查是否加载过,如果没有加载过而且它有父类加载器,会把这个请求委托给父类加载器去执行,一直委托到启动类加载器:Bootstrap ClassLoader
- 如果父类加载器可以完成加载任务,就返回成功结果,如果父类加载不了,就由子类自己去尝试加载,如果子类加载失败就会抛出ClassNotFoundException异常,这就是双亲委派模式
通过Class对象获取类的信息
Class提供了一些函数来获取类的信息
- getName():获得包名+类名 ,返回 String
- getSimpleName():获得类名,返回 String
- getFeields():获得public标识的属性,返回 Field[]
- getDeclaredFields():获得所有属性 ,返回Field[]
- getMethod():获得本类及其父类的public方法,返回Menthod[]
- getDeclaredFields():获得本类所有方法,返回Menthod[]
...
获取Class类方法
- obj.getClass() 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过 obj.getClass() 来获取它的类
- Test.class 如果你已经加载了某个类,只是想获取到它的 java.lang.Class 对象,那么就直接 拿它的 class 属性即可。这个⽅法其实不属于反射。
- Class.forName 如果你知道某个类的名字,想获取到这个类,就可以使⽤ forName 来获取