Java安全之 ClassLoader類加載器


Java安全之 ClassLoader類加載器

0x00 前言

前面這里拋出一個問題,Java到底是什么類型的編程語言?是編譯型?還是解釋型?在這個問題是其實一直都都有疑惑,如果說是解釋型語言的話,那么為什么需要編譯呢?如果說是編譯型語言的話,那么在編譯完成后,需要JVM去解析才能運行呢?其實兩種說法都對,也不對。下面來看看java的一個執行流程就知道是怎么回事了。

0x01 類加載機制

Java中的源碼.java后綴文件會在運行前被編譯成.class后綴文件,Java類初始化的時候會調用java.lang.ClassLoader加載字節碼,.class文件中保存着Java代碼經轉換后的虛擬機指令,當需要使用某個類時,虛擬機將會加載它的.class文件,並創建對應的class對象,將class文件加載到虛擬機的內存。

在其中其實包含了比較多的內容,下面來看看他的詳細執行流程。

具體的實現分為三大步驟:

第一步 加載:

類加載指的是將class文件讀入內存,並為之創建一個java.lang.Class對象,即程序中使用任何類時,系統都會為之建立一個java.lang.Class對象,系統中所有的類都是java.lang.Class的實例。
類的加載由類加載器完成,JVM提供的類加載器叫做系統類加載器,此外還可以通過繼承ClassLoader基類來自定義類加載器。

第二步 連接:

連接階段負責把類的二進制數據合並到JRE中

其又可分為如下三個階段:

驗證:確保加載的類信息符合JVM規范,無安全方面的問題。

准備:為類的靜態Field分配內存,並設置初始值。

解析:將類的二進制數據中的符號引用替換成直接引用。

第三步 初始化:

類加載最后階段,若該類具有超類,則對其進行初始化,執行靜態初始化器和靜態初始化成員變量(如前面只初始化了默認值的static變量將會在這個階段賦值,成員變量也將被初始化

雙親委托機制

當一個類加載器查找class和resource時,是通過“委托模式”進行的,它首先會判斷這個class是不是已經加載成功,如果沒有加載的話它並不是自己進行查找,而是先通過父加載器,然后遞歸下去,直到Bootstrap ClassLoader,如果Bootstrap classloader找到了,直接返回,如果沒有找到,則一級一級返回,最后是由自身去查找這些對象;這種機制就叫做雙親委托。

其他的方式也可以去加載類的二進制數據:

1.從本地文件系統加載class文件。

2.從JAR包中加載class文件,如JAR包的數據庫啟驅動類。

3.通過網絡加載class文件。

4.把一個Java源文件動態編譯並執行加載。

0x02 ClassLoader類加載器

前面提到過編譯成class字節碼后的文件,會使用類加載器加載字節碼。也就是說在java中所有的類都會通過加載器進行加載才能運行。在JVM類加載器中最頂層的是Bootstrap ClassLoader(引導類加載器)Extension ClassLoader(擴展類加載器)App ClassLoader(系統類加載器)AppClassLoader是默認的類加載器,如果類加載時我們不指定類加載器的情況下,默認會使用AppClassLoader加載類。

ClassLoader類 核心方法:

1.loadClass(String className),根據名字加載一個類。
2.defineClass(String name, byte[] b, int off, int len),將一個字節流定義為一個類。
3.findClass(String name),查找一個類。
4.findLoadedClass(String name),在已加載的類中,查找一個類。
5.resolveClass(鏈接指定的Java類)

0x03 自定義Classloader加載class文件

在ClassLoader中有四個很重要實用的方法loadClass()、findLoadedClass()、findClass()、defineClass(),可以用來創建屬於自己的類的加載方式;比如我們需要動態加載一些東西,或者從C盤某個特定的文件夾加載一個class文件,又或者從網絡上下載class主內容然后再進行加載等。分三步搞定:

1、編寫一個類繼承ClassLoader抽象類;

2、重寫findClass()方法;

3、在findClass()方法中調用defineClass()方法即可實現自定義ClassLoader;

下面來編寫一個test類

package com.test;

public class test {
    public String method(){
        return "hello,world";
    }

}

編寫完成后使用javac進行編譯為class字節碼文件

javac .\test.java

會發現多出一個class字節碼文件,那么我們需要再對其轉換為byte類型,方便后面使用類加載器進行加載執行。

import sun.misc.IOUtils;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;

public class ulits {

    public static void main(String[] args) throws IOException {
        InputStream fis = new FileInputStream("test.class");
        byte[] bytes = IOUtils.readFully(fis, -1, false);
        System.out.println(Arrays.toString(bytes));

    }
}

自定義加載器類:

package com.test;

import java.lang.reflect.Method;

public class classloadertest extends ClassLoader{
    private static String testclassname= "com.test.test";
    //轉換byte后的字節碼
    private static byte[] classbytes= new byte[]{-54, -2, -70, -66, 0, 0, 0, 52, 0, 29, 10, 0, 6, 0, 15, 9, 0, 16, 0, 17, 8, 0, 18, 10, 0, 19, 0, 20, 7, 0, 21, 7, 0, 22, 1, 0, 6, 60, 105, 110, 105, 116, 62, 1, 0, 3, 40, 41, 86, 1, 0, 4, 67, 111, 100, 101, 1, 0, 15, 76, 105, 110, 101, 78, 117, 109, 98, 101, 114, 84, 97, 98, 108, 101, 1, 0, 4, 109, 97, 105, 110, 1, 0, 22, 40, 91, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 1, 0, 10, 83, 111, 117, 114, 99, 101, 70, 105, 108, 101, 1, 0, 9, 116, 101, 115, 116, 46, 106, 97, 118, 97, 12, 0, 7, 0, 8, 7, 0, 23, 12, 0, 24, 0, 25, 1, 0, 17, -23, -114, -75, -47, -122, -18, -108, -111, -23, -114, -76, -26, -124, -84, -27, -89, -101, 7, 0, 26, 12, 0, 27, 0, 28, 1, 0, 13, 99, 111, 109, 47, 116, 101, 115, 116, 47, 116, 101, 115, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 79, 98, 106, 101, 99, 116, 1, 0, 16, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 121, 115, 116, 101, 109, 1, 0, 3, 111, 117, 116, 1, 0, 21, 76, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 59, 1, 0, 19, 106, 97, 118, 97, 47, 105, 111, 47, 80, 114, 105, 110, 116, 83, 116, 114, 101, 97, 109, 1, 0, 7, 112, 114, 105, 110, 116, 108, 110, 1, 0, 21, 40, 76, 106, 97, 118, 97, 47, 108, 97, 110, 103, 47, 83, 116, 114, 105, 110, 103, 59, 41, 86, 0, 33, 0, 5, 0, 6, 0, 0, 0, 0, 0, 2, 0, 1, 0, 7, 0, 8, 0, 1, 0, 9, 0, 0, 0, 29, 0, 1, 0, 1, 0, 0, 0, 5, 42, -73, 0, 1, -79, 0, 0, 0, 1, 0, 10, 0, 0, 0, 6, 0, 1, 0, 0, 0, 3, 0, 9, 0, 11, 0, 12, 0, 1, 0, 9, 0, 0, 0, 37, 0, 2, 0, 1, 0, 0, 0, 9, -78, 0, 2, 18, 3, -74, 0, 4, -79, 0, 0, 0, 1, 0, 10, 0, 0, 0, 10, 0, 2, 0, 0, 0, 5, 0, 8, 0, 6, 0, 1, 0, 13, 0, 0, 0, 2, 0, 14};


    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        //只處理com.test.test類
        if (name.equals(testclassname)) {
          //將一個字節流定義為一個類。
            return defineClass(testclassname, classbytes, 0, classbytes.length);
        }
        return super.findClass(name);
    }

    public static void main(String[] args) throws Exception {
        //創建加載器
        classloadertest classloadertest = new classloadertest();
        //使用我們自定義的類去加載testclassname
        Class aClass = classloadertest.loadClass(testclassname);
        //反射創建test類對象
        Object o = aClass.newInstance();
        //反射獲取method方法
        Method method = o.getClass().getMethod("method");
        //反射去調用執行method方法
        String str = (String) method.invoke(o);
        System.out.println(str);


    }
}

可以看到我們class中的method方法執行了。

總結

1、java文件編譯成class字節碼文件
2、轉換字節碼文件為byte類型,目的方便存儲,加載執行
3、自定義一個類加載器類,自定義處理流程(findClass),實現對象的調用。過程為創建對象,加載class,反射創建自定義class的對象,反射獲取需要執行的方法,執行方法。

參考文章

https://javasec.org/javase/ClassLoader/
https://blog.csdn.net/javazejian/article/details/73413292
https://blog.csdn.net/CNAHYZ/article/details/82219210
https://blog.csdn.net/briblue/article/details/54973413
https://blog.csdn.net/xyang81/article/details/7292380

0x04 結尾

對於這篇文章,對於沒了解過類加載器的來說還是比較吃力的,比如我。在文中有些地方寫的也比較模糊,所以貼了幾個不錯的文章在上面,作為參考。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM