java中類加載器的使用方法

這篇文章運(yùn)用簡單易懂的例子給大家介紹java中類加載器的使用方法,代碼非常詳細(xì),感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

創(chuàng)新互聯(lián)-專業(yè)網(wǎng)站定制、快速模板網(wǎng)站建設(shè)、高性價(jià)比崇州網(wǎng)站開發(fā)、企業(yè)建站全套包干低至880元,成熟完善的模板庫,直接使用。一站式崇州網(wǎng)站制作公司更省心,省錢,快速模板網(wǎng)站建設(shè)找我們,業(yè)務(wù)覆蓋崇州地區(qū)。費(fèi)用合理售后完善,十多年實(shí)體公司更值得信賴。

概述

Java類加載器( 英語:Java Classloader)是Java運(yùn)行時(shí)環(huán)境(Java Runtime Environment)的一部分,負(fù)責(zé)動(dòng)態(tài)加載Java類到 Java虛擬機(jī)的內(nèi)存空間中。類通常是按需加載,即第一次使用該類時(shí)才加載。由于有了類加載器,Java運(yùn)行時(shí)系統(tǒng)不需要知道文件與文件系統(tǒng)。學(xué)習(xí)類加載器時(shí),掌握J(rèn)ava的委派概念很重要。

獲得ClassLoader的途徑

1. 獲得當(dāng)前類的ClassLoader

clazz.getClassLoader()

2. 獲得當(dāng)前線程上下文的ClassLoader

Thread.currentThread().getContextClassLoader();

3. 獲得系統(tǒng)的ClassLoader

ClassLoader.getSystemClassLoader()

4. 獲得調(diào)用者的ClassLoader

DriverManager.getCallerClassLoader

ClassLoader源碼解析

 

代碼一:

public class Test12 {
    public static void main(String[] args) {
        String[] strings = new String[6];
        System.out.println(strings.getClass().getClassLoader());
        // 運(yùn)行結(jié)果:null

        Test12[] test12s = new Test12[1];
        System.out.println(test12s.getClass().getClassLoader());
        // 運(yùn)行結(jié)果:sun.misc.Launcher$AppClassLoader@18b4aac2

        int[] ints = new int[2];
        System.out.println(ints.getClass().getClassLoader());
        // 運(yùn)行結(jié)果:null
    }
}

loadClass方法

loadClass的源碼如下, loadClass方法加載擁有指定的二進(jìn)制名稱的Class,默認(rèn)按照如下順序?qū)ふ翌悾?/p>

a)調(diào)用findLoadedClass(String)檢查這個(gè)類是否被加載

b)調(diào)用父類加載器的loadClass方法,如果父類加載器為null,就會(huì)調(diào)用啟動(dòng)類加載器

c)調(diào)用findClass(String)方法尋找

使用上述步驟如果類被找到且resolve為true,就會(huì)去調(diào)用resolveClass(Class)方法

protected Class<?> loadClass(String name, boolean resolve)
  throws ClassNotFoundException
{
  synchronized (getClassLoadingLock(name)) {
      // First, check if the class has already been loaded
      Class<?> c = findLoadedClass(name);
      if (c == null) {
          long t0 = System.nanoTime();
          try {
              if (parent != null) {
                  c = parent.loadClass(name, false);
              } else {
                  c = findBootstrapClassOrNull(name);
              }
          } catch (ClassNotFoundException e) {
              // ClassNotFoundException thrown if class not found
              // from the non-null parent class loader
          }

          if (c == null) {
              // If still not found, then invoke findClass in order
              // to find the class.
              long t1 = System.nanoTime();
              c = findClass(name);

              // this is the defining class loader; record the stats
              sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
              sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
              sun.misc.PerfCounter.getFindClasses().increment();
          }
      }
      if (resolve) {
          resolveClass(c);
      }
      return c;
  }
}

findClass方法

findClass的源碼如下,findClass尋找擁有指定二進(jìn)制名稱的類,JVM鼓勵(lì)我們重寫此方法,需要自定義加載器遵循雙親委托機(jī)制,該方法會(huì)在檢查完父類加載器之后被loadClass方法調(diào)用,默認(rèn)返回ClassNotFoundException異常。

protected Class<?> findClass(String name) throws ClassNotFoundException {
    throw new ClassNotFoundException(name);
}

defineClass方法

defineClass的源碼如下,defineClass方法將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換為Class的實(shí)例。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                     ProtectionDomain protectionDomain)
    throws ClassFormatError
{
    protectionDomain = preDefineClass(name, protectionDomain);
    String source = defineClassSourceLocation(protectionDomain);
    Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
    postDefineClass(c, protectionDomain);
    return c;
}

自定義類加載器

/**
 * 繼承了ClassLoader,這是一個(gè)自定義的類加載器
 * @author 夜的那種黑丶
 */
public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
       Class<?> clazz = loader.loadClass("classloader.Test01");
        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 類加載器名稱,標(biāo)識作用
     */
    private String classLoaderName;

    /**
     * 從磁盤讀物字節(jié)碼文件的擴(kuò)展名
     */
    private String fileExtension = ".class";

    /**
     * 創(chuàng)建一個(gè)類加載器對象,將系統(tǒng)類加載器當(dāng)做該類加載器的父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(String classLoaderName) {
        // 將系統(tǒng)類加載器當(dāng)做該類加載器的父加載器
        super();
        this.classLoaderName = classLoaderName;
    }

    /**
     * 創(chuàng)建一個(gè)類加載器對象,顯示指定該類加載器的父加載器
     * 前提是需要有一個(gè)類加載器作為父加載器
     * @param parent 父加載器
     * @param classLoaderName 類加載器名稱
     */
    private ClassLoaderTest(ClassLoader parent, String classLoaderName) {
        // 顯示指定該類加載器的父加載器
        super(parent);
        this.classLoaderName = classLoaderName;
    }

    /**
     * 尋找擁有指定二進(jìn)制名稱的類,重寫ClassLoader類的同名方法,需要自定義加載器遵循雙親委托機(jī)制
     * 該方法會(huì)在檢查完父類加載器之后被loadClass方法調(diào)用
     * 默認(rèn)返回ClassNotFoundException異常
     * @param className 類名
     * @return Class的實(shí)例
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        byte[] data = this.loadClassData(className);
        /*
         * 通過defineClass方法將字節(jié)數(shù)組轉(zhuǎn)換為Class
         * defineClass:將一個(gè)字節(jié)數(shù)組轉(zhuǎn)換為Class的實(shí)例,在使用這個(gè)Class之前必須要被解析
         */
        return this.defineClass(className, data, 0 , data.length);
    }

    /**
     * io操作,根據(jù)類名找到對應(yīng)文件,返回class文件的二進(jìn)制信息
     * @param className 類名
     * @return class文件的二進(jìn)制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }
}

以上是一段自定義類加載器的代碼,我們執(zhí)行這段代碼

classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

可以看見,這段代碼中進(jìn)行類加載的類加載器還是系統(tǒng)類加載器(AppClassLoader)。這是因?yàn)閖vm的雙親委托機(jī)制造成的,private ClassLoaderTest(String classLoaderName)將系統(tǒng)類加載器當(dāng)做我們自定義類加載器的父加載器,jvm的雙親委托機(jī)制使自定義類加載器委托系統(tǒng)類加載器完成加載。

改造以下代碼,添加一個(gè)path屬性用來指定類加載位置:

public class ClassLoaderTest extends ClassLoader {
    public static void main(String[] args) throws Exception {
        ClassLoaderTest loader = new ClassLoaderTest("loader");
        loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
        Class<?> clazz = loader.loadClass("classloader.Test01");
        System.out.println("class:" + clazz);

        Object object = clazz.newInstance();
        System.out.println(object);
        System.out.println(object.getClass().getClassLoader());
    }
    //------------------------------以上為測試代碼---------------------------------

    /**
     * 從指定路徑加載
     */
    private String path;

    ......
    
    /**
     * io操作,根據(jù)類名找到對應(yīng)文件,返回class文件的二進(jìn)制信息
     * @param className 類名
     * @return class文件的二進(jìn)制信息
     * @throws ClassNotFoundException 如果類不能被找到,拋出此異常
     */
    private byte[] loadClassData(String className) throws ClassNotFoundException {
        InputStream inputStream = null;
        byte[] data;
        ByteArrayOutputStream byteArrayOutputStream = null;

        className = className.replace(".", "/");

        try {
            this.classLoaderName = this.classLoaderName.replace(".", "/");
            inputStream = new FileInputStream(new File(this.path + className + this.fileExtension));
            byteArrayOutputStream = new ByteArrayOutputStream();

            int ch;
            while (-1 != (ch = inputStream.read())) {
                byteArrayOutputStream.write(ch);
            }

            data = byteArrayOutputStream.toByteArray();
        } catch (Exception e) {
            throw new ClassNotFoundException();
        } finally {
            try {
                if (inputStream != null) {
                    inputStream.close();
                }
                if (byteArrayOutputStream != null) {
                    byteArrayOutputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public void setPath(String path) {
        this.path = path;
    }
}

運(yùn)行一下

class:class classloader.Test01
classloader.Test01@7f31245a
sun.misc.Launcher$AppClassLoader@18b4aac2

修改一下測試代碼,并刪除工程下的Test01.class文件

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
   loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz);

    Object object = clazz.newInstance();
    System.out.println(object);
    System.out.println(object.getClass().getClassLoader());
}

運(yùn)行一下

class:class classloader.Test01
classloader.Test01@135fbaa4
classloader.ClassLoaderTest@7f31245a

分析

改造后的兩塊代碼,第一塊代碼中加載類的是系統(tǒng)類加載器AppClassLoader,第二塊代碼中加載類的是自定義類加載器ClassLoaderTest。是因?yàn)镃lassLoaderTest會(huì)委托他的父加載器AppClassLoader加載class,第一塊代碼的path直接是工程下,AppClassLoader可以加載到,而第二塊代碼的path在桌面目錄下,所以AppClassLoader無法加載到,然后ClassLoaderTest自身嘗試加載并成功加載到。如果第二塊代碼工程目錄下的Test01.class文件沒有被刪除,那么依然是AppClassLoader加載。

再來測試一塊代碼

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader");
    loader2.setPath("/home/fanxuan/Study/java/jvmStudy/out/production/jvmStudy/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

結(jié)果顯而易見,類由系統(tǒng)類加載器加載,并且clazz和clazz2是相同的。

class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2
class:2133927002
sun.misc.Launcher$AppClassLoader@18b4aac2

再改造一下

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest("loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運(yùn)行結(jié)果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:621009875
classloader.ClassLoaderTest@45ee12a7

ClassLoaderTest是顯而易見,但是clazz和clazz2是不同的,這是因?yàn)轭惣虞d器的命名空間的原因。

我們可以通過設(shè)置父類加載器來讓loader和loader2處于同一命名空間

public static void main(String[] args) throws Exception {
    ClassLoaderTest loader = new ClassLoaderTest("loader");
    loader.setPath("/home/fanxuan/桌面/");
    Class<?> clazz = loader.loadClass("classloader.Test01");
    System.out.println("class:" + clazz.hashCode());

    Object object = clazz.newInstance();
    System.out.println(object.getClass().getClassLoader());

    ClassLoaderTest loader2 = new ClassLoaderTest(loader, "loader2");
    loader2.setPath("/home/fanxuan/桌面/");
    Class<?> clazz2 = loader2.loadClass("classloader.Test01");
    System.out.println("class:" + clazz2.hashCode());

    Object object2 = clazz2.newInstance();
    System.out.println(object2.getClass().getClassLoader());
}

運(yùn)行結(jié)果

class:325040804
classloader.ClassLoaderTest@7f31245a
class:325040804
classloader.ClassLoaderTest@7f31245a

擴(kuò)展:命名空間

1. 每個(gè)類加載器都有自己的命名空間,命名空間由該加載器及所有的父加載器所加載的類組成

2. 在同一命名空間中,不會(huì)出現(xiàn)類的完整名字(包括類的包名)相同的兩個(gè)類

3. 在不同的命名空間中,有可能會(huì)出現(xiàn)類的完整名字(包括類的包名)相同的兩個(gè)類

關(guān)于java中類加載器的使用方法就分享到這里了,希望以上內(nèi)容可以對大家有一定的幫助,可以學(xué)到更多知識。如果覺得文章不錯(cuò),可以把它分享出去讓更多的人看到。

分享標(biāo)題:java中類加載器的使用方法
本文路徑:http://muchs.cn/article0/iiooio.html

成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供網(wǎng)站排名、關(guān)鍵詞優(yōu)化、微信小程序、網(wǎng)站制作、服務(wù)器托管、網(wǎng)站內(nèi)鏈

廣告

聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會(huì)在第一時(shí)間刪除。文章觀點(diǎn)不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時(shí)需注明來源: 創(chuàng)新互聯(lián)

綿陽服務(wù)器托管