1. 程式人生 > >為什麽要自定義ClassLoader進行類加載

為什麽要自定義ClassLoader進行類加載

加載 instance dex 虛擬 需要 通過 spa ray 都是

假如,你開發的java程序都需要從E:\classloader1目錄下的類文件中加載class,而不是系統指定的系統目錄或者classpath目錄下加載,則如何解決? 需要自定義classloader。

ClassLoader:加載各種class文件到JVM中。ClassLoader是抽象類。

類的加載過程分為加載階段、連接階段、初始化。

  1. 加載階段:尋找class文件。
  2. 連接階段:驗證class文件的正確性;為類的靜態變量準備內存,並為其初始化默認值;把類中符號引用轉換為直接引用。
  3. 初始化階段:為累的靜態變量賦予正確的初始值,也就是在代碼的值。

JVM目前對類的初始化時一次lazy。所以何時初始化就會根據不同情況而不同,如果主動使用,則會進行類的初始化。

類的6種主動使用情況:

  1. 通過調用靜態變量會導致類的初始化。 但是若是靜態常量,則不會。
  2. 調用靜態方法,會導致類的初始化過程。
  3. 調用class.forName。
  4. 調用子類會導致父類初始化,但是如果只是通過子類調用父內的靜態方法後者變量,則不會導致子類初始化。
  5. 調用main方法的類也會被初始化。
  6. 調用 new關鍵字,必然會進行類的初始化,但是數組除外。

類的被動使用:除了主動的以外,都是被動的。

類的加載過程中階段,常量是在javac編譯階段就替換了值,所以不會再類的連接階段進行默認初始化(給相應類變量一個相關類型在沒有被設置時候的默認值)。

類的初始化階段中,賦值動作和靜態語句塊的執行代碼,這個是按照順序執行。虛擬機會保證這些動作會優先執行,因此父類的靜態變量總是能夠得到優先賦值。

JVM默認的加載類去尋找特定的幾個目錄下的類進行加載。比如當前路徑,classpath等。若想自定義路徑,或者在加載過程中,有些個性化的需求,則需要自定義加載類進行加載。
自定義加載類,還有一個好處,可以根據需求,進行單獨卸載和重新加載,這樣就可以進行無需停機的熱部署。

下面時自定義加載類的使用方法:

  1. 先編寫一個簡單的類,用於測試被自定義類加載。
    ‘‘‘
    package classLoaderStudy;
    public class ClassLoaderHello {
    static
    {
    System.out.println("hello world");
    }
    public String welcome()
    {
    return "Hello world";
    }

}
‘‘‘

  1. 編寫自定義加載類,

其中findClass是必須重寫的,功能就是讀入class文件位字節流,然後調用ClassLoader的自帶方法defineClass方法對類在方法區進行定義,這樣,才能使用這個自定義類。
‘‘‘
package classLoaderStudy;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
public class MyClassLoader extends ClassLoader{

   private final static Path DEFAULT_CLASS_DIR = Paths.get("E:",  "classloader1");

   private final Path classDir;

   public MyClassLoader()
   {
          super();
          this.classDir = DEFAULT_CLASS_DIR;

   }
   public MyClassLoader(String classDir)
   {
          super();
          this.classDir = Paths.get(classDir);
   }

   public MyClassLoader(String classDir,ClassLoader parent)
   {
          super(parent);
          this.classDir = Paths.get(classDir);
   }

   private byte[] readClassBytes(String name)
   throws ClassNotFoundException
   {
          String classPath = name.replace(".", "/");
          Path classFullPath =  classDir.resolve(Paths.get(classPath+".class"));
          if(!classFullPath.toFile().exists())
          {
                 throw new ClassNotFoundException("The Class "+name+" not  found");
          }
          try(ByteArrayOutputStream baos = new ByteArrayOutputStream())
          {
                 Files.copy(classFullPath, baos);
                 return baos.toByteArray();
          }
          catch(IOException e)
          {
                 throw new ClassNotFoundException("Load the class "+  name +"  occur error.",e);
          }
   }

   @Override
   /*
    * (non-Javadoc)
    * @see java.lang.ClassLoader#findClass(java.lang.String)
    * 必須要重寫這個類
    */
   protected Class<?> findClass(String name) throws ClassNotFoundException
   {
          byte[] classBytes = this.readClassBytes(name);
          if(null == classBytes || classBytes.length == 0)
          {
                 throw new ClassNotFoundException("can not load the class ");
          }
          return this.defineClass(name, classBytes, 0,classBytes.length);
   }

   @Override
   public String toString()
   {
          return "My ClassLoader";
   }
   public static void main(String[] args) {
          MyClassLoader classLoader = new MyClassLoader();
          Class<?> aClass  =null;
          try {
              aClass =  classLoader.loadClass("classLoaderStudy.ClassLoaderHello");
          } catch (ClassNotFoundException e) {
                 e.printStackTrace();
          }
          System.out.println(aClass.getClassLoader());
          try {
                 Object helloWorld = aClass.newInstance();
                 System.out.println(helloWorld);
                 Method welcomeMethod = aClass.getMethod("welcome");
                 String result = (String) welcomeMethod.invoke(helloWorld);
                 System.out.println(result);

          } catch (InstantiationException e) {

                 e.printStackTrace();
          } catch (IllegalAccessException e) {

                 e.printStackTrace();
          }catch (Exception e)
          {
                 e.printStackTrace();
          }

   }

}
‘‘‘

  1. 測試

classLoaderStudy\\ClassLoaderHello.class文件拷貝到 E:\\classloader1目錄下,
E:\\classloader1\\classLoaderStudy\\ClassLoaderHello.class並將工程中的 ClassLoaderHello.java文件和ClassLoaderHello.class文件刪除,從而確保不會加載工程中的這個類,以便測試自定義加載類。
在Eclipse中運行MyClassLoader類,結果如下
‘‘‘
My ClassLoader
hello worldclassLoaderStudy.ClassLoaderHello@7d4991ad
br/>classLoaderStudy.ClassLoaderHello@7d4991ad
‘‘‘

為什麽要自定義ClassLoader進行類加載