安全矩阵

 找回密码
 立即注册
搜索
查看: 1726|回复: 0

Java反序列化基础篇-类加载器

[复制链接]

249

主题

299

帖子

1391

积分

金牌会员

Rank: 6Rank: 6

积分
1391
发表于 2022-6-7 08:33:02 | 显示全部楼层 |阅读模式
原文链接:Java反序列化基础篇-类加载器

0x01 前言
这篇文章/笔记的话,打算从类加载器,双亲委派到代码块的加载顺序这样来讲。最后才是加载字节码。
0x02 类加载器及双亲委派
    说类加载器有些师傅可能没听过,但是说 Java ClassLoader,相信大家耳熟能详。
1. 类加载器有什么用
    加载 Class 文件
以这段简单代码为例
Student student = new Student();
我们知道,Student 本身其实是一个抽象类,是通过 new 这个操作,将其实例化的,类加载器做的便是这个工作。
ClassLoader 的工作如图所示
加载器也分多种加载器,每个加载器负责不同的功能。
主要分为这四种加载器
        虚拟机自带的加载器
        启动类(根)加载器
        扩展类加载器
        应用程序加载器
2. 几种加载器
引导类加载器
    引导类加载器(BootstrapClassLoader),底层原生代码是 C++ 语言编写,属于 JVM 一部分。
不继承java.lang.ClassLoader类,也没有父加载器,主要负责加载核心 java (JVM 本身),存储在/jre/lib/rt.jar目录当中。(同时处于安全考虑,BootstrapClassLoader只加载包名为javajavaxsun等开头的类)
扩展类加载器(ExtensionsClassLoader
扩展类加载器(ExtensionsClassLoader),由sun.misc.Launcher$ExtClassLoader类实现,用来在/jre/lib/ext或者java.ext.dirs中指明的目录加载 java 的扩展库。Java 虚拟机会提供一个扩展库目录,此加载器在目录里面查找并加载 java 类。
App类加载器(AppClassLoader
App类加载器/系统类加载器(AppClassLoader),由sun.misc.Launcher$AppClassLoader实现,一般通过通过(java.class.path或者Classpath环境变量)来加载 Java 类,也就是我们常说的 classpath 路径。通常我们是使用这个加载类来加载 Java 应用类,可以使用ClassLoader.getSystemClassLoader()来获取它。
3. 双亲委派机制
    Java 开发当中,双亲委派机制是从安全角度出发的。
我们这里以代码先来感受一下,双亲委派机制确实牛逼。
从报错的角度感受双亲委派机制
    尽量别尝试,看看就好了。要不然整个文件夹挺乱的,如果想上手尝试一下的话,我建议是新建一个项目,不要把其他的文件放一起。
新建一个 java.lang的文件夹,在其中新建 String.java的文件。
String.java
package java.lang;  
  
// 双亲委派的错误代码  
public class String {  
  
    public String toString(){  
        return "hello";  
}  
  
    public static void main(String[] args) {  
        String s = new String();  
s.toString();  
}  
}
看着是不是没有问题,没有错误吧?
我们自己定义了一个java.lang的文件夹,并在文件夹中定义了 String.class,还定义了 String 这个类的 toString 方法。我们跑一下程序。(这里如果把 Stirng 类放到其他文件夹会直接报错,原因也是和下面一样的)
    结果居然报错了!而且非常离谱
我这不是已经定义了 main 方法吗??为什么还会报错,这里就提到双亲委派机制了,双亲委派机制是从安全角度出发的。
首先,我们要知道 Java 的类加载器是分很多层的,如图。
我们的类加载器在被调用时,也就是在 new class 的时候,它是以这么一个顺序去找的 BOOT ---> EXC ----> APP
如果 BOOT 当中没有,就去 EXC 里面找,如果 EXC 里面没有,就去 APP 里面找。
    所以我们之前报错的程序当中,定义的java.lang.StringBOOT 当中是有的,所以我们自定义 String 时,会报错,如果要修改的话,是需要去 rt.jar 里面修改的,这里就不展开了。
从正确的角度感受双亲委派机制
前文提到我们新建的java.lang.String报错了,是因为我们定义的 String BOOT 包下面的 String 冲突了,所以才会报错,我们这里定义一个 BOOT EXC 都没有的对象试一试。
在其他的文件夹下,新建 Student.java
Student.java
package src.DynamicClassLoader;  
  
// 双亲委派的正确代码  
public class Student {  
  
    public String toString(){  
        return "Hello";  
}  
  
    public static void main(String[] args) {  
        Student student = new Student();  
  
System.out.println(student.getClass().getClassLoader());  
System.out.println(student.toString());  
}  
}
并把加载器打印出来
我们定义的 Student 类在 APP 加载器中找到了。
0x03 各场景下代码块加载顺序
    这里的代码块主要指的是这四种
        静态代码块:static{}
        构造代码块:{}
        无参构造器:ClassName()
        有参构造器:ClassName(String name)
场景一、实例化对象
这里有两个文件,分别介绍一下用途:
    Person.java:一个普普通通的类,里面有静态代码块、构造代码块、无参构造器、有参构造器、静态成员变量、普通成员变量、静态方法。
    Main.java:启动类
Person.java
package src.DynamicClassLoader;  
  
// 存放代码块  
public class Person {  
    public static int staticVar;  
public int instanceVar;  
  
static {  
        System.out.println("静态代码块");  
}  
  
    {  
        System.out.println("构造代码块");  
}  
  
    Person(){  
        System.out.println("无参构造器");  
}  
    Person(int instanceVar){  
        System.out.println("有参构造器");  
}  
  
    public static void staticAction(){  
        System.out.println("静态方法");  
}  
}
Main.java
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) {  
        Person person = new Person();  
}  
}
运行结果如图
    结论:
通过new关键字实例化的对象,先调用静态代码块,然后调用构造代码块,最后根据实例化方式不同,调用不同的构造器。
场景二、调用静态方法
直接调用类的静态方法
Person.java 不变,修改 Main.java 启动器即可。
Main.java
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) {  
        Person.staticAction();  
}  
}
    结论:
不实例化对象直接调用静态方法,会先调用类中的静态代码块,然后调用静态方法
场景三、对类中的静态成员变量赋值
Main.java
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) {  
                Person.staticVar = 1;  
        }  
}
    结论:
在对静态成员变量赋值前,会调用静态代码块
场景四、使用 class 获取类
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) {  
                Class c = Person.class;  
        }  
}
// 空屁
    结论:
利用class关键字获取类,并不会加载类,也就是什么也不会输出。
场景五、使用 forName 获取类
    这里要抛出异常一下。
我们写三种forName的方法调用。
修改 Main.java
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) throws ClassNotFoundException{  
                Class.forName("src.DynamicClassLoader.Person");
        }  
}
// 静态代码块
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) throws ClassNotFoundException{   
        Class.forName("src.DynamicClassLoader.Person", true, ClassLoader.getSystemClassLoader());  
}  
}
// 静态代码块
package src.DynamicClassLoader;  
  
// 代码块的启动器  
public class Main {  
    public static void main(String[] args) throws ClassNotFoundException{   
        Class.forName("src.DynamicClassLoader.Person", false, ClassLoader.getSystemClassLoader());
}  
}
//没有输出
    结论:
Class.forName(className)Class.forName(className, true, ClassLoader.getSystemClassLoader())等价,这两个方法都会调用类中的静态代码块,如果将第二个参数设置为false,那么就不会调用静态代码块
场景六、使用 ClassLoader.loadClass() 获取类
Main.java
package com.xiinnn.i.test;
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        Class.forName("com.xiinnn.i.test.Person", false, ClassLoader.getSystemClassLoader());
    }
}
//没有输出
    结论:
ClassLoader.loadClass()方法不会进行类的初始化,当然,如果后面再使用newInstance()进行初始化,那么会和场景一、实例化对象一样的顺序加载对应的代码块。


本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|安全矩阵

GMT+8, 2024-4-16 17:33 , Processed in 0.016859 second(s), 19 queries .

Powered by Discuz! X4.0

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表