天天看點

黑馬程式員——高新技術--類加載器

——Java教育訓練、Android教育訓練、iOS教育訓練、.Net教育訓練、期待與您交流! ——-

類加載器:

隻要你用到了某個類,就需要把該類的檔案加載進記憶體中,這就是類加載器的作用。

Java虛拟機中可以安裝多個類加載器,系統預設有三個主要的類加載器,每個類加載器負責加載特定位置的類:BootStrap,ExtClassLoader,AppClassLoader。

類加載器也是Java類,是以java類的類加載器本身也要被其他的類加載器加載,顯然必須有第一個類加載器不是java類,它就是BootStrap類加載器,JVM核心啟動的時候,BootStrap就已經被加載了,它是内嵌在JVM核心中的,是用C++語言編寫的二進制代碼,是以不需要其他類加載器加載。

普通類是由AppClassLoader類加載器加載的,System類是由BootStrap類加載器加載的。

Java虛拟機中的所有類裝載器采用了具有父子關系的樹形結構進行組織,AppClassLoader類加載器的父級别類加載器是ExtClassLoader類加載器,ExtClassLoader類加載器的父級别類加載器是BootStrap類加載器。

在執行個體化每個類加載器對象時,需要為其指定一個父級類裝載器對象或者預設采用系統類裝載器為其父級類加載。

黑馬程式員——高新技術--類加載器
類加載器之間的父子關系和管轄範圍圖
           

類加載器的委托機制:

當Java虛拟機要加載一個類時,到底派出哪個類加載器去加載呢?

首先目前線程的類加載器去加載線程中的第一個類。

如果類A中引用了類B,Java虛拟機将使用加載類A的類裝載器來加載類B。

還可以直接調用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。

每個類加載器加載類時,又先委托給其上級類加載器。

當所有上級類加載器沒有加載到類,回到發起者類加載器,還加載不了,則抛ClassNotFoundException,不是再去找發起者類加載器的兒子

注意:

  1. 每個ClassLoader本身隻能分别加載特定位置和目錄中的類,但它們可以委托其他的類裝載器去加載類,這就是類加載器的委托模式。類裝載器一級級委托到BootStrap類加載器,當BootStrap無法加載目前所要加載的類時,然後才一級級回退到子孫類裝載器去進行真正的加載。當回退到最初的類裝載器時,如果它自己也不能完成類的裝載,那就應報告ClassNotFoundException異常。
  2. 有一道面試題,能不能自己寫個類叫java.lang.System?

    答案是不能,即使寫了也不會被類加載器加載。為了不讓我們寫System類,類加載機制采用委托機制,這樣可以保證父級類加載器優先,也就是總是使用父級類加載器能找到的類,結果就是總是使用java系統自身提供的System類,而不會使用我們自己所寫的System類。

  3. 父級類加載器加載的類無法引用隻能被子級類加載器加載的類

自定義類加載器:

類加載器中的loadClass方法内部實作了父類委托機制,是以我們沒有必要自己覆寫loadClass,否則需要自己去實作父類委托機制。我們隻需要覆寫findClass方法。loadClass方法中調用了findClass方法,使用的是模闆設計模式。我們得到了Class檔案後,就可以通過defineClass方法将二進制資料轉換成位元組碼。這就是自定義類加載器的編寫原理。

API文檔中的例子:
           
黑馬程式員——高新技術--類加載器

編寫對class檔案進行加密的工具類:

程式設計步驟:

編寫一個對檔案内容進行簡單加密的程式。

編寫一個自己的類裝載器,可實作對加密過的類進行裝載和解密。

實驗步驟:

  1. 對不帶包名的class檔案進行加密,加密結果存放到另外一個目錄,例如:java MyClassLoader MyTest.class F:\itcast。
  2. 運作加載類的程式,結果能夠被正常加載,但列印出來的類裝載器名稱為AppClassLoader:java MyClassLoader MyTest F:\itcast。

用加密後的類檔案替換CLASSPATH環境下的類檔案,再執行上一步操作就出問題了,錯誤說明是AppClassLoader類裝載器裝載失敗。

import java.util.Date;  

//被加密的class類源代碼
public class ClassLoaderAttachment extends Date {
       public String toString(){
             return "hello itheima" ;
      }
}
           
import java.io.*;

public class MyClassLoader {

       public static void main(String[] args) throws Exception {
            //要加密的檔案路徑
            String srcPath = args[];

            String destDir = args[];

            FileInputStream fis = new FileInputStream(srcPath);

            //儲存的檔案路徑及名字
            String destFileName = srcPath.substring(srcPath.lastIndexOf("\\" ) + );
            String destPath = destDir + "\\" + destFileName;

            FileOutputStream fos = new FileOutputStream(destPath);
            cypher(fis, fos);
            fis.close();
            fos.close();
      }

      //對輸入流資料進行加密
       private static void cypher(InputStream ips,OutputStream ops) throws Exception{
             int b = -;
             while((b = ips.read())!=-){
                  ops.write(b ^ );
            }
      }
}
public class ClassLoaderTest {

       public static void main(String[] args) {

            //這時會出錯
            System.out.println(new ClassLoaderAttachment().toString());
      }
}
           

用此加密工具加密class檔案之後,再用AppClassLoader執行該class類就會報錯,因為AppClassLoader已經無法正确加載編碼後的class檔案了

編寫和測試自己編寫的解密類加載器:

import java.io.*;

//自定義類加載器要繼承ClassLoader
public class MyClassLoader extends ClassLoader {

      private String classDir ;

      public MyClassLoader() {
      }

      //通過構造函數傳進已加密的檔案路徑
      public MyClassLoader(String classDir) {
             this.classDir = classDir;
      }

       @Override
       protected Class<?> findClass(String name) throws ClassNotFoundException {
            String classFileName = classDir + "\\" + name + ".class";
             try {
                  FileInputStream fis = new FileInputStream(classFileName);
                  ByteArrayOutputStream bos = new ByteArrayOutputStream();
                  cypher(fis, bos);
                  fis.close();
                  byte[] bytes = bos.toByteArray();

                  //将byte數組轉換為Class類執行個體
                  return defineClass(bytes, , bytes.length );

            } catch (Exception e) {
                  e.printStackTrace();
            }
            return super.findClass(name);
      }

       //對輸入流進行解密
       private static void cypher(InputStream ips,OutputStream ops) throws Exception{
             int b = -;
             while((b = ips.read())!=-){
                  ops.write(b ^ );
            }
      }
}
public class ClassLoaderTest {

       public static void main(String[] args) throws Exception {

            //使用自定義加載器加載經過加密的class檔案
            Class clazz = new MyClassLoader("itheimalib" ).loadClass("ClassLoaderAttachment");

            //從Class對象中擷取類的執行個體對象并列印
            Date d1 = (Date)clazz.newInstance();
            System. out.println(d1);
      }
}
           

注意:

  1. 之是以讓ClassLoaderAttachment類繼承Date是因為,如果直接寫ClassLoaderAttachment d1 = (ClassLoaderAttachment)clazz.newInstance();這條語句,編譯的時候就會報錯,因為此時的ClassLoaderAttachment在AppClassLoader加載進記憶體後就無法識别。是以需要通過借助一個父類對象繞過編譯器。也就是:Date d1 = (Date)clazz.newInstance();。
  2. 如果想讓父類加載器AppClassLoader加載ClassLoaderAttachment類,則需要執行下面的語句:

    Class clazz = new MyClassLoader(“itheimalib” ).loadClass( “com.itheima.day2.ClassLoaderAttachment”);

    類加載器的一個進階問題的實驗分析:

在tomcat伺服器上寫一個類,往網頁上列印出加載該類的所有類加載器:

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

public class MyServlet extends HttpServlet {

       public void doGet(HttpServletRequest request, HttpServletResponse response)
                   throws ServletException, IOException {
            response.setContentType( "text/html");
            PrintWriter out = response.getWriter();
            ClassLoader loader = this.getClass().getClassLoader();
             while(loader != null){
                  out.println(loader.getClass().getName() + "<br/>");
                  loader = loader.getParent();
            }
            out.close();
      }
}
           
黑馬程式員——高新技術--類加載器