1. 程式人生 > >由osgi引出的classLoader的大總結(整理理解ClassLoader)

由osgi引出的classLoader的大總結(整理理解ClassLoader)

轉載請註明出處(corey)

最近在研究osgi,在osgi裡面裡面有個很重要的東西,就是ClassLoader,所以,在網上搜集了一些資料,整理一下,
並加入了自己的一些理解;

(1)jvm的裝載過程以及裝載原理
所謂裝載就是尋找一個類或是一個介面的二進位制形式並用該二進位制形式來構造代表這個類或是這個介面的class物件的過程,
其中類或介面的名稱是給定了的。當然名稱也可以通過計算得到,但是更常見的是通過搜尋原始碼經過編譯器編譯後所得到
的二進位制形式來構造。 在Java中,類裝載器把一個類裝入Java虛擬機器中,要經過三個步驟來完成:裝載、連結和初始化,
其中連結又可以分成校驗、準備和解析三步,除了解析外,其它步驟是嚴格按照順序完成的,各個步驟的主要工作如下:

  裝載:查詢和匯入類或介面的二進位制資料;
  連結:執行下面的校驗、準備和解析步驟,其中解析步驟是可以選擇的;
  校驗:檢查匯入類或介面的二進位制資料的正確性;
  準備:給類的靜態變數分配並初始化儲存空間;
  解析:將符號引用轉成直接引用;
  初始化:啟用類的靜態變數的初始化Java程式碼和靜態Java程式碼塊。

(2):java中的類是什麼?
一個類代表要執行的程式碼,而資料則表示其相關狀態。狀態時常改變,而程式碼則不會。當我們將一個特定的狀態與一個類相對應起來,也就意味著將一個類事例化。儘管相同的類對應的例項其狀態千差萬別,但其本質都對應著同一段程式碼。在JAVA中,一個類通常有著一個.class檔案,但也有例外。在JAVA的執行時環境中(Java runtime),每一個類都有一個以第一類(first-class)的Java物件所表現出現的程式碼,其是java.lang.Class的例項。我們編譯一個JAVA檔案,編譯器都會嵌入一個public, static, final修飾的型別為java.lang.Class,名稱為class的域變數在其位元組碼檔案中。因為使用了public修飾,我們可以採用如下的形式對其訪問:
java.lang.Class klass = Myclass.class;
一旦一個類被載入JVM中,同一個類就不會被再次載入了(切記,同一個類)。這裡存在一個問題就是什麼是“同一個類”?正如一個物件有一個具體的狀態,即標識,一個物件始終和其程式碼(類)相關聯。同理,載入JVM的類也有一個具體的標識,我們接下來看。
在Java中,一個類用其完全匹配類名(fully qualified class name)作為標識,這裡指的完全匹配類名包括包名和類名。但在JVM中一個類用其全名和一個載入類ClassLoader的例項作為唯一標識。因此,如果一個名為Pg的包中,有一個名為Cl的類,被類載入器KlassLoader的一個例項kl1載入,Cl的例項,即C1.class在JVM中表示為(Cl, Pg, kl1)。這意味著兩個類載入器的例項(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它們所載入的類也因此完全不同,互不相容的。那麼在JVM中到底有多少種類載入器的例項?下一節我們揭示答案。

(3):java的幾種ClassLoader:
在java中,我們可以取得這麼以下三個ClassLoader類:
一.    ClassLoader基本概念

1.ClassLoader分類

類裝載器是用來把類(class)裝載進JVM的。

JVM規範定義了兩種型別的類裝載器:啟動內裝載器(bootstrap)和使用者自定義裝載器(user-defined class loader)。

JVM在執行時會產生三個ClassLoader:Bootstrap ClassLoader、Extension ClassLoader和AppClassLoader。Bootstrap是用C++編寫的,我們在Java中看不到它,是null,是JVM自帶的類裝載器,用來裝載核心類庫,如java.lang.*等。

AppClassLoader的Parent是ExtClassLoader,而ExtClassLoader的Parent為Bootstrap ClassLoader。

Java提供了抽象類ClassLoader,所有使用者自定義類裝載器都例項化自ClassLoader的子類。 System Class Loader是一個特殊的使用者自定義類裝載器,由JVM的實現者提供,在程式設計者不特別指定裝載器的情況下預設裝載使用者類。系統類裝載器可以通過ClassLoader.getSystemClassLoader() 方法得到。

例1,測試你所使用的JVM的ClassLoader

/*LoaderSample1.java*/

public   class  LoaderSample1 {

     public   static   void  main(String[] args) {

        Class c;

        ClassLoader cl;

        cl  =  ClassLoader.getSystemClassLoader();

        System.out.println(cl);

         while  (cl  !=   null ) {

            cl  =  cl.getParent();

            System.out.println(cl);

        }

         try  {

            c  =  Class.forName( " java.lang.Object " );

            cl  =  c.getClassLoader();

            System.out.println( " java.lang.Object's loader is  "   +  cl);

            c  =  Class.forName( " LoaderSample1 " );

            cl  =  c.getClassLoader();

            System.out.println( " LoaderSample1's loader is  "   +  cl);

        }  catch  (Exception e) {

            e.printStackTrace();

        }

    }

}


在我的機器上(Sun Java 1.4.2)的執行結果

null

java.lang.Object's loader is null

第一行表示,系統類裝載器例項化自類sun.misc.Launcher$AppClassLoader

第二行表示,系統類裝載器的parent例項化自類sun.misc.Launcher$ExtClassLoader

第三行表示,系統類裝載器parent的parent為bootstrap

第四行表示,核心類java.lang.Object是由bootstrap裝載的

第五行表示,使用者類LoaderSample1是由系統類裝載器裝載的

注意,我們清晰的看見這個三個ClassLoader類之間的父子關係(不是繼承關係),父子關係在ClassLoader的實現中有一個ClassLoader型別的屬性,我們可以在自己實現自定義的ClassLoader的時候初始化定義,而這三個系統定義的ClassLoader的父子關係分別是

AppClassLoader——————》(Parent)ExtClassLoader——————————》(parent)BootClassLoader(null c++實現)

系統為什麼要分別指定這麼多的ClassLoader類呢?
答案在於因為java是動態載入類的,這樣的話,可以節省記憶體,用到什麼載入什麼,就是這個道理,然而系統在執行的時候並不知道我們這個應用與需要載入些什麼類,那麼,就採用這種逐級載入的方式
(1)首先載入核心API,讓系統最基本的執行起來
(2)載入擴充套件類
(3)載入使用者自定義的類


package org.corey.clsloader;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;

import sun.net.spi.nameservice.dns.DNSNameService;

public class ClsLoaderDemo {

 /**
  * @param args
  */
 public static void main(String[] args) {
  
  System.out.println(System.getProperty("sun.boot.class.path"));
  System.out.println(System.getProperty("java.ext.dirs"));
  System.out.println(System.getProperty("java.class.path"));
 }
}


程式結果為:
E:/MyEclipse 6.0/jre/lib/rt.jar;E:/MyEclipse 6.0/jre/lib/i18n.jar;E:/MyEclipse 6.0/jre/lib/sunrsasign.jar;E:/MyEclipse 6.0/jre/lib/jsse.jar;E:/MyEclipse 6.0/jre/lib/jce.jar;E:/MyEclipse 6.0/jre/lib/charsets.jar;E:/MyEclipse 6.0/jre/classes
E:/MyEclipse 6.0/jre/lib/ext
E:/workspace/ClassLoaderDemo/bin

在上面的結果中,你可以清晰看見三個ClassLoader分別載入類的路徑;也知道為什麼我們在編寫程式的時候,要把用到的jar包放在工程的classpath下面啦,也知道我們為什麼可以不載入java.lang.*包啦!其中java.lang.*就在rt.jar包中;

(4)ClassLoader的載入機制:
現在我們設計這種一下Demo:

package java.net;

public class URL {
 private String path;

 public URL(String path) {
  this.path = path;
 }

 public String toString() {
  return this.path + " new Path";
 }
}


package java.net;

import java.net.*;

public class TheSameClsDemo {

 /**
  * @param args
  */
 public static void main(String[] args) {
  URL url = new URL("http://www.baidu.com");
  System.out.println(url.toString());
 }

}
在這種情況下,系統會提示我們出現異常,因為我們有兩個相同的類,一個是真正的URL,一個是我在上面實現的偽類;出現異常是正常的,因為你想想,如果我們在執行一個applet的時候,程式自己實現了一個String的類覆蓋了我們虛擬機器上面的真正的String類,那麼在這個String裡面,不懷好意的人可以任意的實現一些功能;這就造成極不安全的隱患;所以java採用了一種名為“雙親委託”的載入模式;

以下是jdk原始碼:

protected synchronized Class<?> loadClass(String name, boolean resolve)
 throws ClassNotFoundException
    {
 // First, check if the class has already been loaded
 Class c = findLoadedClass(name);
 if (c == null) {
     try {
  if (parent != null) {
      c = parent.loadClass(name, false);
  } else {
      c = findBootstrapClass0(name);
  }
     } catch (ClassNotFoundException e) {
         // If still not found, then invoke findClass in order
         // to find the class.
         c = findClass(name);
     }
 }
 if (resolve) {
     resolveClass(c);
 }
 return c;
    }

在上面的程式碼中,我們可以清晰的看見,我們呼叫一個ClassLoader載入程式的時候,這個ClassLoader會先呼叫設定好的parent ClassLoader來載入這個類,如果parent是null的話,則預設為Boot ClassLoader類,只有在parent沒有找的情況下,自己才會載入,這就避免我們重寫一些系統類,來破壞系統的安全;

再來看一個明顯的例子:
package org.corey;

public class MyCls{
 private String name;
 
 public MyCls(){
 
 }

 public MyCls(String name){
 this.name=name;
 }
 
 public void say(){
 System.out.println(this.name);
 }
}
把上面這個MyCls類打成jar包,丟進ext classLoader的載入路徑;

然後寫出main類:
package org.corey.clsloader;

import org.corey.MyCls;

public class TheSameClsDemo {

 /**
  * @param args
  */
 public static void main(String[] args) {
  MyCls myClsOb=new MyCls("name");
     myClsOb.say(); 
     System.out.println(MyCls.class.getClassLoader());
     System.out.println(System.getProperty("java.class.path"));
     System.out.println(TheSameClsDemo.class.getClassLoader());
 }
}

從上面的例子可以清晰的看出ClassLoader之間的這種雙親委託載入模式;

再來看下一個例子(摘自http://bbs.cnw.com.cn/viewthread.php?tid=95389)
下面我們就來看一個綜合的例子。首先在eclipse中建立一個簡單的java應用工程,然後寫一個簡單的JavaBean如下:
package classloader.test.bean;

publicclass TestBean {


public TestBean() {}


}
在現有當前工程中另外建立一測試類(ClassLoaderTest.java)內容如下:
測試一:


publicclass ClassLoaderTest {


publicstaticvoid main(String[] args) {


try {


//檢視當前系統類路徑中包含的路徑條目

            System.out.println(System.getProperty("java.class.path"));

//呼叫載入當前類的類載入器(這裡即為系統類載入器)載入TestBean

Class typeLoaded = Class.forName("classloader.test.bean.TestBean");

//檢視被載入的TestBean型別是被那個類載入器載入的

            System.out.println(typeLoaded.getClassLoader());

        } catch (Exception e) {

            e.printStackTrace();

        }

    }


}
對應的輸出如下:


D:"DEMO"dev"Study"ClassLoaderTest"bin


[email protected]
(說明:當前類路徑預設的含有的一個條目就是工程的輸出目錄)
測試二:
將當前工程輸出目錄下的…/classloader/test/bean/TestBean.class打包進test.jar剪貼到< Java_Runtime_Home >/lib/ext目錄下(現在工程輸出目錄下和JRE擴充套件目錄下都有待載入型別的class檔案)。再執行測試一測試程式碼,結果如下:


D:"DEMO"dev"Study"ClassLoaderTest"bin


[email protected]
對比測試一和測試二,我們明顯可以驗證前面說的雙親委派機制,系統類載入器在接到載入classloader.test.bean.TestBean型別的請求時,首先將請求委派給父類載入器(標準擴充套件類載入器),標準擴充套件類載入器搶先完成了載入請求。

測試三:
將test.jar拷貝一份到< Java_Runtime_Home >/lib下,執行測試程式碼,輸出如下:


D:"DEMO"dev"Study"ClassLoaderTest"bin

測試三和測試二輸出結果一致。那就是說,放置到< Java_Runtime_Home >/lib目錄下的TestBean對應的class位元組碼並沒有被載入,這其實和前面講的雙親委派機制並不矛盾。虛擬機器出於安全等因素考慮,不會載入< Java_Runtime_Home >/lib存在的陌生類,開發者通過將要載入的非JDK自身的類放置到此目錄下期待啟動類載入器載入是不可能的。做個進一步驗證,刪除< Java_Runtime_Home >/lib/ext目錄下和工程輸出目錄下的TestBean對應的class檔案,然後再執行測試程式碼,則將會有ClassNotFoundException異常丟擲。有關這個問題,大家可以在java.lang.ClassLoader中的loadClass(String name, boolean resolve)方法中設定相應斷點執行測試三進行除錯,會發現findBootstrapClass0()會丟擲異常,然後在下面的findClass方法中被載入,當前執行的類載入器正是擴充套件類載入器(sun.misc.Launcher$ExtClassLoader),這一點可以通過JDT中變數檢視檢視驗證。

(5)被不同的ClassLoader載入的兩個類之間有什麼限制和不同?
現在我們來看一下一個現象:
在eclipse裡面我是這樣做的:
OneCls.java

package org.corey.one;

import org.corey.two.TwoCls;

public class OneCls {

 public OneCls() {
  System.out.println();
  TwoCls two = new TwoCls();
  two.say();
 }

}

TwoCls.java

package org.corey.two;

public class TwoCls {
 
 public void say() {
  System.out.println("i am two");
 }
}

Demo.java:

package org.corey.Demo;

import org.corey.one.OneCls;

public class Demo {

 /**
  * @param args
  */
 public static void main(String[] args) {
  OneCls one=new OneCls();
 }
}

在這裡,我們來仔細看下,one引用了two,demo引用了one,這是三個類都是由AppClassLoader載入的;執行正常;

把OneCls打成jar包,放在lib/ext路徑下面,然後在工程裡面引入這個jar包;執行:異常,這是因為:


Demo是由AppClassLoader載入,委託給雙親載入失敗後,由AppClassLoader載入,而載入OneCls的時候,委託給雙親,被ExtClassLoader載入成功,但是在載入OneCls的時候,同時引用了TwoCls,但是ExtClassLoader引用TwoCls失敗,但是他只會委託給雙親,而不會委託給AppClassLoader這個兒子,所以會出現異常;


3. 奇怪的隔離性

我們不難發現,圖2中的類裝載器AA和AB, AB和BB,AA和B等等位於不同分支下,他們之間沒有父子關係,我不知道如何定義這種關係,姑且稱他們位於不同分支下。兩個位於不同分支的類裝載器具有隔離性,這種隔離性使得在分別使用它們裝載同一個類,也會在記憶體中出現兩個Class類的例項。因為被具有隔離性的類裝載器裝載的類不會共享記憶體空間,使得使用一個類裝載器不可能完成的任務變得可以輕而易舉,例如類的靜態變數可能同時擁有多個值(雖然好像作用不大),因為就算是被裝載類的同一靜態變數,它們也將被儲存不同的記憶體空間,又例如程式需要使用某些包,但又不希望被程式另外一些包所使用,很簡單,編寫自定義的類裝載器。類裝載器的這種隔離性在許多大型的軟體應用和服務程式得到了很好的應用。下面是同一個類靜態變數為不同值的例子。

package test;
public class A {
  public static void main( String[] args ) {
    try {
      //定義兩個類裝載器
      MyClassLoader aa= new MyClassLoader();
      MyClassLoader bb = new MyClassLoader();

      //用類裝載器aa裝載testb.B類
      Class clazz=aa.loadClass("testb. B");
      Constructor constructor=
        clazz.getConstructor(new Class[]{Integer.class});
      Object object =
     constructor.newInstance(new Object[]{new Integer(1)});
      Method method =
     clazz.getDeclaredMethod("printB",new Class[0]);

      //用類裝載器bb裝載testb.B類
      Class clazz2=bb.loadClass("testb. B");
      Constructor constructor2 =
        clazz2.getConstructor(new Class[]{Integer.class});
      Object object2 =
     constructor2.newInstance(new Object[]{new Integer(2)});
      Method method2 =
     clazz2.getDeclaredMethod("printB",new Class[0]);

      //顯示test.B中的靜態變數的值
      method.invoke( object,new Object[0]);
      method2.invoke( object2,new Object[0]);
    } catch ( Exception e ) {
      e.printStackTrace();
    }
  }
}

//Class B 必須位於MyClassLoader的查詢範圍內,
//而不應該在MyClassLoader的父類裝載器的查詢範圍內。
package testb;
public class B {
    static int b ;

    public B(Integer testb) {
        b = testb.intValue();
    }

    public void printB() {
        System.out.print("my static field b is ", b);
    }
}

public class MyClassLoader extends URLClassLoader{
  private static File file = new File("c://classes ");
  //該路徑存放著class B,但是沒有class A

  public MyClassLoader() {
    super(getUrl());
  }

  public static URL[] getUrl() {
    try {
      return new URL[]{file.toURL()};
    } catch ( MalformedURLException e ) {
      return new URL[0];
    }
  }
}

程式的執行結果為:

my static field b is 1
my static field b is 2

程式的結果非常有意思,從程式設計者的角度,我們甚至可以把不在同一個分支的類裝載器看作不同的java虛擬機器,因為它們彼此覺察不到對方的存在。程式在使用具有分支的類裝載的體系結構時要非常小心,弄清楚每個類裝載器的類查詢範圍,儘量避免父類裝載器和子類裝載器的類查詢範圍中有相同類名的類(包括包名和類名),下面這個例子就是用來說明這種情況可能帶來的問題。

(6) 類如何被裝載及類被裝載的方式(轉自Java類裝載體系中的隔離性  作者:盛戈歆)

在java2中,JVM是如何裝載類的呢,可以分為兩種型別,一種是隱式的類裝載,一種式顯式的類裝載。

2.1 隱式的類裝載

隱式的類裝載是編碼中最常用得方式:

A b = new A();

如果程式執行到這段程式碼時還沒有A類,那麼JVM會請求裝載當前類的類裝器來裝載類。問題來了,我把程式碼弄得複雜一點點,但依舊沒有任何難度,請思考JVM得裝載次序:

package test;
Public class A{
    public void static main(String args[]){
        B b = new B();
    }
}

class B{C c;}

class C{}

揭曉答案,類裝載的次序為A->B,而類C根本不會被JVM理會,先不要驚訝,仔細想想,這不正是我們最需要得到的結果。我們仔細瞭解一下JVM裝載順序。當使用Java A命令執行A類時,JVM會首先要求類路徑類裝載器(AppClassLoader)裝載A類,但是這時只裝載A,不會裝載A中出現的其他類(B類),接著它會呼叫A中的main函式,直到執行語句b = new B()時,JVM發現必須裝載B類程式才能繼續執行,於是類路徑類裝載器會去裝載B類,雖然我們可以看到B中有有C類的宣告,但是並不是實際的執行語句,所以並不去裝載C類,也就是說JVM按照執行時的有效執行語句,來決定是否需要裝載新類,從而裝載儘可能少的類,這一點和編譯類是不相同的。

2.2 顯式的類裝載

使用顯示的類裝載方法很多,我們都裝載類test.A為例。

使用Class類的forName方法。它可以指定裝載器,也可以使用裝載當前類的裝載器。例如:

Class.forName("test.A");
它的效果和
Class.forName("test.A",true,this.getClass().getClassLoader());
是一樣的。

使用類路徑類裝載裝載.

ClassLoader.getSystemClassLoader().loadClass("test.A");

使用當前程序上下文的使用的類裝載器進行裝載,這種裝載類的方法常常被有著複雜類裝載體系結構的系統所使用。

Thread.currentThread().getContextClassLoader().loadClass("test.A")

使用自定義的類裝載器裝載類

public class MyClassLoader extends URLClassLoader{
public MyClassLoader() {
        super(new URL[0]);
    }
}
MyClassLoader myClassLoader = new MyClassLoader();
myClassLoader.loadClass("test.A");

MyClassLoader繼承了URLClassLoader類,這是JDK核心包中的類裝載器,在沒有指定父類裝載器的情況下,類路徑類裝載器就是它的父類裝載器,MyClassLoader並沒有增加類的查詢範圍,因此它和類路徑裝載器有相同的效果。

(7)ClassLoader的一些方法實現的功能:
方法 loadClass

ClassLoader.loadClass() 是 ClassLoader 的入口點。其特徵如下:


Class loadClass( String name, boolean resolve ); name 引數指定了 JVM 需要的類的名稱,該名稱以包表示法表示,如 Foo 或 java.lang.Object。

resolve 引數告訴方法是否需要解析類。在準備執行類之前,應考慮類解析。並不總是需要解析。如果 JVM 只需要知道該類是否存在或找出該類的超類,那麼就不需要解析。

在 Java 版本 1.1 和以前的版本中,loadClass 方法是建立定製的 ClassLoader 時唯一需要覆蓋的方法。(Java 2 中 ClassLoader 的變動提供了關於 Java 1.2 中 findClass() 方法的資訊。)


方法 defineClass


defineClass 方法是 ClassLoader 的主要訣竅。該方法接受由原始位元組組成的陣列並把它轉換成 Class 物件。原始陣列包含如從檔案系統或網路裝入的資料。

defineClass 管理 JVM 的許多複雜、神祕和倚賴於實現的方面 -- 它把位元組碼分析成執行時資料結構、校驗有效性等等。不必擔心,您無需親自編寫它。事實上,即使您想要這麼做也不能覆蓋它,因為它已被標記成最終的。

你可以看見native標記,知道defineClass是一個jni呼叫的方法,是由c++實現資料到記憶體的載入的;


方法 findSystemClass


findSystemClass 方法從本地檔案系統裝入檔案。它在本地檔案系統中尋找類檔案,如果存在,就使用 defineClass 將原始位元組轉換成 Class 物件,以將該檔案轉換成類。當執行 Java 應用程式時,這是 JVM 正常裝入類的預設機制。(Java 2 中 ClassLoader 的變動提供了關於 Java 版本 1.2 這個過程變動的詳細資訊。)

對於定製的 ClassLoader,只有在嘗試其它方法裝入類之後,再使用 findSystemClass。原因很簡單:ClassLoader 是負責執行裝入類的特殊步驟,不是負責所有類。例如,即使 ClassLoader 從遠端的 Web 站點裝入了某些類,仍然需要在本地機器上裝入大量的基本 Java 庫。而這些類不是我們所關心的,所以要 JVM 以預設方式裝入它們:從本地檔案系統。這就是 findSystemClass 的用途。

其工作流程如下:


請求定製的 ClassLoader 裝入類。
檢查遠端 Web 站點,檢視是否有所需要的類。
如果有,那麼好;抓取這個類,完成任務。
如果沒有,假定這個類是在基本 Java 庫中,那麼呼叫 findSystemClass,使它從檔案系統裝入該類。
在大多數定製 ClassLoaders 中,首先呼叫 findSystemClass 以節省在本地就可以裝入的許多 Java 庫類而要在遠端 Web 站點上查詢所花的時間。然而,正如,在下一章節所看到的,直到確信能自動編譯我們的應用程式程式碼時,才讓 JVM 從本地檔案系統裝入類。


方法 resolveClass

正如前面所提到的,可以不完全地(不帶解析)裝入類,也可以完全地(帶解析)裝入類。當編寫我們自己的 loadClass 時,可以呼叫 resolveClass,這取決於 loadClass 的 resolve 引數的值。

方法 findLoadedClass

findLoadedClass 充當一個快取:當請求 loadClass 裝入類時,它呼叫該方法來檢視ClassLoader 是否已裝入這個類,這樣可以避免重新裝入已存在類所造成的麻煩。應首先呼叫該方法。


三.名稱空間及其作用

每個類裝載器有自己的名稱空間,名稱空間由所有以此裝載器為創始類裝載器的類組成。不同名稱空間的兩個類是不可見的,但只要得到類所對應的Class物件的reference,還是可以訪問另一名稱空間的類。

例2演示了一個名稱空間的類如何使用另一名稱空間的類。在例子中,LoaderSample2由系統類裝載器裝載,LoaderSample3由自定義的裝載器loader負責裝載,兩個類不在同一名稱空間,但LoaderSample2得到了LoaderSample3所對應的Class物件的reference,所以它可以訪問LoaderSampl3中公共的成員(如age)。

例2不同名稱空間的類的訪問

/*LoaderSample2.java*/

import  java.net. * ;

import  java.lang.reflect. * ;

public   class  LoaderSample2 {

     public   static   void  main(String[] args) {

         try  {

            String path  =  System.getProperty( " user.dir " );

            URL[] us  =  { new  URL( " file:// "   +  path  +   " /sub/ " )};

            ClassLoader loader  =   new  URLClassLoader(us);

            Class c  =  loader.loadClass( " LoaderSample3 " );

            Object o  =  c.newInstance();

            Field f  =  c.getField( " age " );

             int  age  =  f.getInt(o);

            System.out.println( " age is  "   +  age);

        }  catch  (Exception e) {

            e.printStackTrace();

        }

    }

}


/*sub/Loadersample3.java*/

public   class  LoaderSample3 {

     static  {

        System.out.println( " LoaderSample3 loaded " );

    }

     public   int  age  =   30 ;

}

編譯:javac LoaderSample2.java; javac sub/LoaderSample3.java

執行:java LoaderSample2

LoaderSample3 loaded

age is 30

從執行結果中可以看出,在類LoaderSample2中可以建立處於另一名稱空間的類LoaderSample3中的物件並可以訪問其公共成員age。

執行時包(runtime package)

由同一類裝載器定義裝載的屬於相同包的類組成了執行時包,決定兩個類是不是屬於同一個執行時包,不僅要看它們的包名是否相同,還要看的定義類裝載器是否相同。只有屬於同一執行時包的類才能互相訪問包可見的類和成員。這樣的限制避免了使用者自己的程式碼冒充核心類庫的類訪問核心類庫包可見成員的情況。假設使用者自己定義了一個類java.lang.Yes,並用使用者自定義的類裝載器裝載,由於java.lang.Yes和核心類庫java.lang.*由不同的裝載器裝載,它們屬於不同的執行時包,所以java.lang.Yes不能訪問核心類庫java.lang中類的包可見的成員。


(7)有關ClassLoader的過載
  擴充套件ClassLoader方法

我們目的是從本地檔案系統使用我們實現的類裝載器裝載一個類。為了建立自己的類裝載器我們應該擴充套件ClassLoader類,這是一個抽象類。我們建立一個FileClassLoader extends ClassLoader。我們需要覆蓋ClassLoader中的findClass(String name)方法,這個方法通過類的名字而得到一個Class物件。

     public  Class findClass(String name)    {

         byte [] data  =  loadClassData(name);

         return  defineClass(name, data,  0 , data.length);

    }


   我們還應該提供一個方法loadClassData(String name),通過類的名稱返回class檔案的字

節陣列。然後使用ClassLoader提供的defineClass()方法我們就可以返回Class物件了。

     public   byte [] loadClassData(String name)    {

        FileInputStream fis  =   null ;

         byte [] data  =   null ;

         try  {

            fis  =   new  FileInputStream( new  File(drive  +  name  +  fileType));

            ByteArrayOutputStream baos  =   new  ByteArrayOutputStream();

             int  ch  =   0 ;

             while  ((ch  =  fis.read())  !=   - 1 )  {

                baos.write(ch);              

            }

            data  =  baos.toByteArray();

        }  catch  (IOException e)  {

            e.printStackTrace();

        }       

         return  data;

    }

相關推薦

osgi引出classLoader總結整理理解ClassLoader

轉載請註明出處(corey) 最近在研究osgi,在osgi裡面裡面有個很重要的東西,就是ClassLoader,所以,在網上搜集了一些資料,整理一下,並加入了自己的一些理解; (1)jvm的裝載過程以及裝載原理所謂裝載就是尋找一個類或是一個介面的二進位制形式並用該二進位制形

java 呼叫 matlab 問題總結優質文章總結

Java呼叫Matlab函式以及同時配置多版本JDK的方法 https://blog.csdn.net/jacksonary/article/details/78913656 生成Jar包各種常見問題: http://blog.sina.com.cn/s/blog_65cee699010

VC++ MFC中CString類完美總結整理

CString位於標頭檔案afx.h中。①、CString 類物件的初始化:CString str;CString str1(_T("abc"));CString str2 = _T("defg");TCHAR szBuf[] = _T("kkk");CString str3(szBuf);CString s

PS知識點總結——基礎操作

PS的介面: 選單欄、屬性欄、工具欄、編輯區、活動面板 PS的功能: 影象處理(影樓後期、人像修復美化);排版(書籍、雜誌、噴繪、廣告);網頁、APP版面設計。 PS版本: CS2 3 4 5 6       CC (creative cloud)       CC 201

Vue框架專案實戰整理:6、輸出檢視、bug除錯總結safari、chrome

宣告:本教程不收取任何費用,歡迎轉載,尊重作者勞動成果,不得用於商業用途,侵權必究!!! 文章目錄 一、輸出檢視 二、bug除錯總結-Safari 1、快速定位錯誤 2、檢視請求資訊、返回資訊 三、bug除錯總結-chrome 一、輸出檢視 console.l

Trie樹的常見應用總結面試+附程式碼實現

(一)Trie的簡介 Trie樹,又稱字典樹,單詞查詢樹或者字首樹,是一種用於快速檢索的多叉樹結構,如英文字母的字典樹是一個26叉樹,數字的字典樹是一個10叉樹。他的核心思想是空間換時間,空間消耗大但是插入和查詢有著很優秀的時間複雜度。(二)Trie的定義Trie樹的鍵不是

NTP的配置總結整理+轉載

一.NTP常見錯誤及解決辦法 最近按照網上查詢的資料進行ntp Server的配置後,使用ntp Client進行時間同步,報錯資訊“no server suitable for synchronization found” 。網上對於該問題的解釋的主流原因有 1.ntp

小知識點 總結常用,必會

x86 嵌套 參數 access 常用 標準輸出 mct 一個 bre 1、進入救援模式的幾種方法 centos7最小化安裝,在默認情況下,會出現如下界面: Install centos 7 Test this media & install centos

C++ set用法總結整理

順序容器包括vector、deque、list、forward_list、array、string,所有順序容器都提供了快速順序訪問元素的能力。 關聯容器包括set、map 關聯容器和順序容器有著根本的不同:關聯容器中的元素是按關鍵字來儲存和訪問的。與之相對,順序容器

設計模式總結:迭代器模式

前言 說到迭代器,所有的Java開發者都不陌生,例如HashSet,當我們需要遍歷HashSet的時候,通過iterator不停的next就可以迴圈遍歷集合中的所有元素。但是這麼做到底有什麼好處呢? 1、使用者不需要關心HashSet內部的實現,不關心

設計模式總結

內存 對象處理 avi 設計模式 簡化 添加 rgb 負責 .cn 版權聲明:本文為博主原創文章。轉載請註明出處:htt

乾貨 | 十經典排序演算法最強總結內含程式碼實現

乾貨 | 十大經典排序演算法最強總結(內含程式碼實現) 一、演算法分類 十種常見排序演算法可以分為兩大類: 比較類排序:通過比較來決定元素間的相對次序,由於其時間複雜度不能突破O(nlogn),因此也稱為非線性時間比較類排序。 非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基於比較排序的時間下界,

[51NOD1524] 可除圖的最組合,dp

鏈接 ble spa 組合 sin ons .html color 出現的次數 題目鏈接:https://www.51nod.com/onlineJudge/questionCode.html#!problemId=1524 題意:略。 這個題相當於是找出現最長的整除鏈。

HDU1253-勝利逃亡 三維BFS

魔王 panel 能夠 出差 成功 註意 define can p s 題目傳送門:http://acm.hdu.edu.cn/showproblem.php?pid=1253 勝利大逃亡 Time Limit:4000/2000MS(Java/Others)Memor

java中方法總結每周更新

實例 參數 創建 方法 get ack bject 子類 generate 1、URLEncoder.encode(username,"utf-8")將“utf-8”編碼的username先解碼,然後再采用URL編碼 2、URLDecoder.decode(autoLogi

樹狀數組求最RMQ with Shifts

art code else pan [1] int space -s article 代碼: #include <iostream> #include <stdio.h> #include <string.h> #include

數論總結 常用定理+ 模板

擴展歐幾裏德算法 二項式 rime spa 模板 phi 歐拉函數 noip prim 刷了好幾天的數論了 noip要考的幾乎都刷了一遍 看著公式有生無可戀的感覺啊 下面是一些總結 1.組合數 去年的noip考了組合數遞推公式 C(n, m) = C(n - 1, m -

關於Yii2中的MVC中的視圖總結持續更新中

gif pre lis frontend dbo register front reg open 一、首先在控制器中,將處理好的數據發送給前臺: $this->layout = ‘base‘; 這裏填寫視圖的模板文件(可以不寫這行代碼,如果不寫,默認為views/la

小a和uim之逃離luogu P1373 dp

math block pac clu 偶數 char sdi span lag 小a和uim之大逃離(luogu P1373 dp) 給你一個n*m的矩陣,其中元素的值在1~k內。限制只能往下和往右走,問從任意點出發,到任意點結束,且經過了偶數個元素的合法路徑有多少個。在

Array對象的方法總結ES5 與 ES6

判斷 否則 array 復制 indexof define 字符 red 三個參數 ES5 數組方法 1.Array.isArray() 方法用來判斷一個值是否為數組。它可以彌補typeof運算符的不足 2.valueOf() 方法返回數組本身 3.toString()