Java安全笔记(1)-反射机制
0x1. 类加载器: ClassLoader
Java是一个依赖于JVM(Java虚拟机)
实现的跨平台的开发语言,Java
会先通过编译器将源代码转换为Java二进制字节码,一般是保存在.class
文件中,之后通过JVM
解释器执行这段代码。字节码文件会包含很多Class信息,在JVM解释器运行的过程中,ClassLoader
就是用来加载类的,它会将Java字节码中的Class加载到内存中,而每个Class
对象内部都有一个ClassLoader
属性标识由哪个ClassLoader
加载。
常见的ClassLoader
一切的Java
类都必须经过JVM加载之后才可以运行,最常见的ClassLoader
: BootstrapClassLoader
、ExtensionClassLoader
、AppClassLoader
、URLClassLoader
、ContextClassLoader
BootstrapClassLoader
JVM内置的默认classLoader
,负责加载JVM运行时的核心类,位于JAVA_HOME/lib/rt.jar/
文件夹中,由C代码实现,Bootstrap ClassLoader
类加载器所加载的类的ClassLoader时候都会返回null
ExtClassLoader
扩展类加载器,负责加载 JVM 扩展类,扩展 jar 包位于 JAVA_HOME/lib/ext/*.jar
中,库名通常以 javax 开头
AppClassLoader
应用类加载器/系统类加载器,直接提供给用户使用的ClassLoader,它会加载ClASSPATH
环境变量或者java.class.path
属性里定义的路径中的jar包和目录,我们自己编写和使用的第三方Jar包通常都是由它来加载
URLClassLoader
ClassLoader抽象类的一种实现,它可以根据URL搜索类或资源,并进行远程加载。
package demo1;
public class JavaClassLoader {
public static void main(String[] args){
ClassLoader classLoader = ClassLoader.getSystemClassLoader();
ClassLoader extensionClassLoader = classLoader.getParent();
System.out.println("App Classloader: " + classLoader);
System.out.println("parent Classloader: " + extensionClassLoader);
System.out.println("The parent of parent Classloader: " + extensionClassLoader.getParent());
}
}
执行结果:
App Classloader: sun.misc.Launcher$AppClassLoader@18b4aac2
parent Classloader: sun.misc.Launcher$ExtClassLoader@5cad8086
The parent of parent Classloader: null
ClassLoader
类有如下常见的方法:
loadClass
:参数为需要加载的全限定类名,该方法会先查看目标类是否已经被加载,查看父级加载器并递归调用loadClass()
,如果都没找到则调用findClass()
。这种寻找类的方式称为双亲委派机制(delegation model),主要是为了安全性,避免用户自己编写的类动态替换Java的一些核心类,同时也避免了重复加载。
双亲委派:findClass
: 搜索类的位置,一般会根据名称或位置加载.class字节码文件,获取字节码数组,然后调用defineClass()。findloadedClass
: 查找JVM已经加载过的类defineClass
: 将字节码转换为JVM的java.lang.Class
对象
代码中关于defineClass
还是比较值得一看,loadClass
的作用是加载Class文件,转换为字节码,当class不是在文件里面,而是从其它来源的时候,比如网络请求,这个时候就轮到defineClass
上场了,defineClass
负责把byte[]直接转换为Class,也就是说defineClass
是对类加载方式的扩展:
However, some classes may not originate from a file; they may originate from other sources, such as the network, or they could be constructed by an application. The method {@link #defineClass(String, byte[], int, int) defineClass} converts an array of bytes into an instance of class Class. Instances of this newly defined class can be created using {@link Class#newInstance Class.newInstance}.
Java反射
获取Class对象的四种方法
反射的主要作用是通过Class对象来对类的属性和方法进行获取和调用,包括类的私有方法(protected和private),一般来说有两种方法可以获取类:
obj.getClass()
: 如果上下⽂中存在某个类的实例 obj ,那么我们可以直接通过obj.getClass()
来获取它的类java.lang.Runtime obj = java.lang.Runtime.getRuntime(); Class c = obj.getClass();
Class.forName
:Class c= Class.forName("java.lang.Runtime");
ClassLoader
:Class clazz = Classloader.getSystemClassLoader().loadClass("java.lang.Runtime");
- 原生类.class:
Class clazz = java.lang.Runtime.class;
forName
有两个函数重载:
Class<?> forName(String name)
Class<?> forName(String name, **boolean** initialize, ClassLoader loader)
第⼀个就是我们最常⻅的获取class的⽅式,其实可以理解为第⼆种⽅式的⼀个封装:Class.forName(className) // 等于 Class.forName(className, true, currentLoader)
默认情况下,forName
的第⼀个参数是类名;第⼆个参数表示是否初始化;第三个参数就是ClassLoader
。
反射调用函数
package student;
public class Student {
int age;
String name;
public Student() {
this.age = 18;
this.name = "D";
}
public void sayHello(){
System.out.println("My name is " + name + ", My Age is " + age);
}
}
class Test1{
public static void main(String[] args) throws Exception{
Student student = new Student();
student.sayHello();
System.out.println(student.getClass().getClassLoader());
//反射调用Student
String class_name = "student.Student";
String func = "sayHello";
Class stu_class = Class.forName(class_name);
Student stu2 = (Student) stu_class.newInstance();
Class.forName(class_name).getMethod(func).invoke(stu2);
}
}
getDeclaredMethod()
: Returns a Method object that reflects the specified declared method of the class or interface represented by this Class object.getMethod()
: returns a Method object that reflects the specified public member method of the class or interface represented by this Class object.
getDeclaredMethod()
可以获取类里面任何方法,getMethod()
只可以获取public
属性的方法。另外几个比较用的多是是:
getField(String name)
: 根据字段名获取对应的字段,只能获取public类型的字段,可以获取父类的字段。getFields()
: 获取类所有的字段,只能获取public类型的字段,可以获取父类的字段。getDeclaredField(String name)
: 根据字段名获取对应的字段,可以获取public、protected和private类型的字段,不能获取父类的字段。getDeclaredFields()
: 获取类所有的字段,包括public、protected和private。不能获取父类的字段。
通过反射获取修改私有变量
如果字段是staic修饰的时候,在获取和修改字段的时候,可以使用null代替具体对象的stu
通过反射调用私有方法
如果调用的方法有多个参数,需要以数组的形式传入:
- 使用
getDeclaredMethod
获取多个参数的方法,第二个参数为new Class[]{}类型的数组,数组中每一个值对应参数的class对象。这是一种标准的传参方式,建议即使方法没有参数或者只有一个参数也按照这种方式传参 - 使用
method.invoke
方法对方法进行调用,传递的第二个参数表示实际调用时传递的参数值,类型是Object数组。
对于static类型的方法,与字段的使用方法相似,在执行方法时,同样可以把obj对象换成null
反射获取构造函数
构造函数是一种特殊的方法,很多情况下需要通过反射获取构造函数,然后通过构造函数生成类的实例。
getConstructor(Class... parameterTypes)
根据参数类型获取对应的构造函数,只能获取public类型的构造函数,不能获取父类的构造函数。getConstructors()
获取类所有的构造函数,只能获取public类型的字段,不能获取父类的构造函数。getDeclaredConstructor (Class... parameterTypes)
根据参数类型获取对应的构造函数,可以获取public、protected和private类型的构造函数,不能获取父类的构造函数。getDeclaredConstructors()
获取类所有的构造函数,包括public、protected和private。不能获取父类的构造函数。
在获取到构造函数之后,需要通过newInstance函数来生成类对象。关于newInstance的使用如下所示:
newInstance(Object ... initargs)
: newInstance函数接受可变的参数个数,构造函数实际有几个传输,这里就传递几个参数值。newInstance返回的数据类型是Object,一般需要强制转换类型。