xiao1star2025-03-14文章来源:SecHub网络安全社区
类加载的基本流程
java在类初始化的时候会调用静态代码块,而其他的像构造方法等需要在实例化也就是使用的时候才会被调用
User类
有构造方法、静态代码块、静态方法
public class User {
public String name;
public static int age;
public User() {
System.out.println("这是无参构造");
}
{
System.out.println("这是构造代码块");
}
static{
System.out.println("这是静态代码块");
}
public static void People(){
System.out.println("这是静态方法");
}
}
在Main类中进行类加载的测试
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
User user = new User();//创建User类对象
//结果为
//这是静态代码块
//这是构造代码块
//这是无参构造
User.age=88;//调用静态成员变量,结果为这是静态代码块
User.People();//调用静态方法,结果为这是静态代码块、这是静态方法
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class<User> userClass = User.class;
//结果为无,User.class 只是加载类并创建 Class 对象,
//而不会执行类的初始化代码
}
}
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
Class.forName("User");//结果为这是静态代码块
ClassLoader sc = ClassLoader.getSystemClassLoader();//获取当前系统的类加载器
Class.forName("User",false,sc);//结果为无
}
}
原因:跟进forName方法可以发现,该方法会调用一个forName0的方法而其第二个参数是默认情况下是true(其表示默认情况下会进行初始化),进而完成初始化操作,调用静态代码块。当然可以将其设置为false来实现不初始化
对ClassLoader的思考
public class Main {
public static void main(String[] args) throws ClassNotFoundException {
ClassLoader sc = ClassLoader.getSystemClassLoader();
System.out.println(sc);//结果sun.misc.Launcher$AppClassLoader@18b4aac2
}
}
得到的结果是Launcher类中的一个内部类AppClassLoader,这就引出了一个双亲委派的类加载机制
双亲委派模型是指,要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器。Java平台通过委派模型去加载类。每个类加载器都有一个父加载器。当需要加载类时,会优先委派当前所在的类的加载器的父加载器去加载这个类。如果父加载器无法加载到这个类时,再尝试在当前所在的类的加载器中加载这个类。
file协议
创建一个Hello类,在其中创建一个静态代码块进而实现计算机的弹出
import java.io.IOException;
public class Hello {
static{
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
在idea中编译生成一个.class文件,将其移动到E盘,并将创建的Hello.java文件删除否则会直接去找.java文件实现类的加载
在测试类Main中通过URLclassLoader类来进行磁盘中.class文件的获取与加载,之后通过newInstance方法来实现初始化
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, MalformedURLException {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("file:///E:\\")});
//注意要在file:///E:后再加两个反斜杠,否则会去找E盘中的xxx.jar文件而不是.class文件
Class<?> hello = urlClassLoader.loadClass("Hello");
hello.newInstance();
}
}
若直接是file:///E:会产生NotFound的报错
http协议
使用file协议只能进行加载本地的类,最重要的还是使用http协议
在E盘中开启一个http服务
python -m http.server 8888
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("http://127.0.0.1:8888/")});
Class<?> hello = urlClassLoader.loadClass("Hello");
hello.newInstance();
}
}
成功接受到访问的请求
将实例化URLClassLoader对象时的参数修改为jar:file:///E:\\Hello.jar!/
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class Main {
public static void main(String[] args) throws MalformedURLException, ClassNotFoundException, InstantiationException, IllegalAccessException {
URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:file:///E:\\Hello.jar!/")});
//URLClassLoader urlClassLoader = new URLClassLoader(new URL[]{new URL("jar:http://127.0.0.1:8888/Hello.jar!/")});
Class<?> hello = urlClassLoader.loadClass("Hello");
hello.newInstance();
}
}
因为ClassLoader是一个抽象类,无法直接实例化这个类,利用了反射来获取其中的defineClass方法
public class Main {
public static void main(String[] args) throws Exception {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Method defineClass = ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class);
defineClass.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("E:\\Hello.class"));//读取Hello.class的字节码长度
Class test =(Class) defineClass.invoke(systemClassLoader, "Hello", code, 0, code.length);
test.newInstance();
}
}
缺点:因为在ClassLoader中的defineClass是方法是一个protect修饰符,无法直接使用反射来进行调用
在Unsafe中有一个defineClass方法其是共有的
Unsafe的构造方法是私有的无法直接实例化Unsafe对象,但是其有一个getUnsafe的静态方法可以实例化一个Unsafe对象,但问题是该方法必须通过Security校验直接使用会报错
但是有一个成员变量可以实现Unsafe的实例化
但是该成员变量是私有的
那么我们可以利用反射getDeclaredFiled来获取这个成员变量,进而实现defineClass方法的调用
public class Main {
public static void main(String[] args) throws Exception {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
Class<Unsafe> unsafeClass = Unsafe.class;
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");//获取theUnsafe成员变量
theUnsafe.setAccessible(true);
Unsafe unsafe= (Unsafe) theUnsafe.get(null);
byte[] code= Files.readAllBytes(Paths.get("E:\\Hello.class"));//获取字节码
Class<?> hello = unsafe.defineClass("Hello", code, 0, code.length, systemClassLoader, null);
hello.newInstance();
}
}