標籤:
轉載自並發編程網 – ifeve.com
內容索引
類載入器
類載入體系
類載入
動態類載入
動態類重載
自訂類重載
類載入/重載樣本
Java允許你在運行期動態載入和重載類,但是這個功能並沒有像人們希望的那麼簡單直接。這篇文章將闡述在Java中如何載入以及重載類。
你可能會質疑為什麼Java動態類載入特性是Java反射機制的一部分而不是Java核心平台的一部分。不管怎樣,這篇文章被放到了Java反射系列裡面而且也沒有更好的系列來包含它了。
類載入器
所有Java應用中的類都是被java.lang.ClassLoader類的一系列子類載入的。因此要想動態載入類的話也必須使用java.lang.ClassLoader的子類。
一個類一旦被載入時,這個類引用的所有類也同時會被載入。類載入過程是一個遞迴的模式,所有相關的類都會被載入。但並不一定是一個應用裡面所有類都會被載入,與這個被載入類的引用鏈無關的類是不會被載入的,直到有參考關聯性的時候它們才會被載入。
類載入體系
在Java中類載入是一個有序的體系。當你新建立一個標準的Java類載入器時你必須提供它的父載入器。當一個類載入器被調用來載入一個類的時候,首先會調用這個載入器的父載入器來載入。如果父載入器無法找到這個類,這時候這個載入器才會嘗試去載入這個類。
類載入
類載入器載入類的順序如下:
1、檢查這個類是否已經被載入。
2、如果沒有被載入,則首先調用父載入器載入。
3、如果父載入器不能載入這個類,則嘗試載入這個類。
當你實現一個有重載類功能的類載入器,它的順序與上述會有些不同。類重載不會請求的他的父載入器來進行載入。在後面的段落會進行講解。
動態類載入
動態載入一個類十分簡單。你要做的就是擷取一個類載入器然後調用它的loadClass()方法。下面是個例子:
public class MainClass { public static void main(String[] args){ ClassLoader classLoader = MainClass.class.getClassLoader(); try { Class aClass = classLoader.loadClass("com.jenkov.MyClass"); System.out.println("aClass.getName() = " + aClass.getName()); } catch (ClassNotFoundException e) { e.printStackTrace(); }}動態類重載
動態類重載有一點複雜。Java內建的類載入器在載入一個類之前會檢查它是否已經被載入。因此重載一個類是無法使用Java內建的類載入器的,如果想要重載一個類你需要手動繼承ClassLoader。
在你定製ClassLoader的子類之後,你還有一些事需要做。所有被載入的類都需要被連結。這個過程是通過ClassLoader.resolve()方法來完成的。由於這是一個final方法,因此這個方法在ClassLoader的子類中是無法被重寫的。resolve()方法是不會允許給定的ClassLoader執行個體連結一個類兩次。所以每當你想要重載一個類的時候你都需要使用一個新的ClassLoader的子類。你在設計類重載功能的時候這是必要的條件。
自訂類重載
在前面已經說過你不能使用已經載入過類的類載入器來重載一個類。因此你需要其他的ClassLoader執行個體來重載這個類。但是這又帶來了一些新的挑戰。
所有被載入到Java應用中的類都以類的全名(包名 + 類名)作為一個唯一標識來讓ClassLoader執行個體來載入。這意味著,類MyObject被類載入器A載入,如果類載入器B又載入了MyObject類,那麼兩個載入器載入出來的類是不同的。看看下面的代碼:
MyObject object = (MyObject)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObject類在上面那段代碼中被引用,它的變數名是object。這就導致了MyObject這個類會被這段代碼所在類的類載入器所載入。
如果myClassReloadingFactory工廠對象使用不同的類載入器重載MyObject類,你不能把重載的MyObject類的執行個體轉換(cast)到類型為MyObject的物件變數。一旦MyObject類分別被兩個類載入器載入,那麼它就會被認為是兩個不同的類,儘管它們的類的全名是完全一樣的。你如果嘗試把這兩個類的執行個體進行轉換就會報ClassCastException。
你可以解決這個限制,不過你需要從以下兩個方面修改你的代碼:
1、標記這個變數類型為一個介面,然後只重載這個介面的實作類別。
2、標記這個變數類型為一個超類,然後只重載這個超類的子類。
請看下面這兩個例子:
MyObjectInterface object = (MyObjectInterface)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
//------------------------------------------------------------
MyObjectSuperclass object = (MyObjectSuperclass)myClassReloadingFactory.newInstance("com.jenkov.MyObject");
只要保證變數的類型是超類或者介面,這兩個方法就可以正常運行,當它們的子類或是實作類別被重載的時候超類跟介面是不會被重載的。
為了保證這種方式可以運行你需要手動實作類別載入器然後使得這些介面或超類可以被它的父載入器載入。當你的類載入器載入MyObject類時,超類MyObjectSuperclass或者介面MyObjectSuperclass也會被載入,因為它們是MyObject的依賴。你的類載入器必須要代理這些類的載入到同一個類載入器,這個類載入器載入這個包括介面或者超類的類。
類載入/重載樣本
光說不練假把式。讓我們看看一個簡單的例子。下面這個例子是一個類載入器的子類。注意在這個類不想被重載的情況下它是如何把對一個類的載入代理到它的父載入器上的。如果一個類被它的父載入器載入,這個類以後將不能被重載。記住,一個類只能被同一個ClassLoader執行個體載入一次。
就像我之前說的那樣,這僅僅是一個簡單的例子,通過這個例子會向你展示類載入器的基本行為。這並不是一個可以讓你直接用於設計你項目中類載入器的模板。你自己設計的類載入器應該不僅僅只有一個,如果你想用來重載類的話你可能會設計很多載入器。並且你也不會像下面這樣將需要載入的類的路徑寫入程式碼(hardcore)到你的代碼中。
public class MyClassLoader extends ClassLoader{ public MyClassLoader(ClassLoader parent) { super(parent); } public Class loadClass(String name) throws ClassNotFoundException { if(!"reflection.MyObject".equals(name)) return super.loadClass(name); try { String url = "file:C:/data/projects/tutorials/web/WEB-INF/" + "classes/reflection/MyObject.class"; URL myUrl = new URL(url); URLConnection connection = myUrl.openConnection(); InputStream input = connection.getInputStream(); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); int data = input.read(); while(data != -1){ buffer.write(data); data = input.read(); } input.close(); byte[] classData = buffer.toByteArray(); return defineClass("reflection.MyObject", classData, 0, classData.length); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; }}
下面是使用MyClassLoader的例子:
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException { ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader(); MyClassLoader classLoader = new MyClassLoader(parentClassLoader); Class myObjectClass = classLoader.loadClass("reflection.MyObject"); AnInterface2 object1 = (AnInterface2) myObjectClass.newInstance(); MyObjectSuperClass object2 = (MyObjectSuperClass) myObjectClass.newInstance(); //create new class loader so classes can be reloaded. classLoader = new MyClassLoader(parentClassLoader); myObjectClass = classLoader.loadClass("reflection.MyObject"); object1 = (AnInterface2) myObjectClass.newInstance(); object2 = (MyObjectSuperClass) myObjectClass.newInstance();}
下面這個就是被載入的reflection.MyObject類。注意它既繼承了一個超類並且也實現了一個介面。這樣做僅僅是為了通過例子示範這個特性。在你自訂的情況下你可能僅會實現一個類或者繼承一兩個介面。
查看原始碼 列印協助
public class MyObject extends MyObjectSuperClass implements AnInterface2{ //... body of class ... override superclass methods // or implement interface methods} 原文地址 作者: Jakob Jenkov 譯者:葉文海([email protected])
Java Reflection(十二):動態類載入與重載