作者: Jakob Jenkov 譯者:葉文海(yewenhai@gmail.com)
Java 允許你在運行期動態(tài)加載和重載類,但是這個功能并沒有像人們希望的那么簡單直接。這篇文章將闡述在 Java 中如何加載以及重載類。 你可能會質疑為什么 Java 動態(tài)類加載特性是 Java 反射機制的一部分而不是 Java 核心平臺的一部分。不管怎樣,這篇文章被放到了 Java 反射系列里面而且也沒有更好的系列來包含它了。
所有 Java 應用中的類都是被 java.lang.ClassLoader 類的一系列子類加載的。因此要想動態(tài)加載類的話也必須使用 java.lang.ClassLoader 的子類。
一個類一旦被加載時,這個類引用的所有類也同時會被加載。類加載過程是一個遞歸的模式,所有相關的類都會被加載。但并不一定是一個應用里面所有類都會被加載,與這個被加載類的引用鏈無關的類是不會被加載的,直到有引用關系的時候它們才會被加載。
在 Java 中類加載是一個有序的體系。當你新創(chuàng)建一個標準的 Java 類加載器時你必須提供它的父加載器。當一個類加載器被調(diào)用來加載一個類的時候,首先會調(diào)用這個加載器的父加載器來加載。如果父加載器無法找到這個類,這時候這個加載器才會嘗試去加載這個類。
類加載器加載類的順序如下: 1、檢查這個類是否已經(jīng)被加載。 2、如果沒有被加載,則首先調(diào)用父加載器加載。 3、如果父加載器不能加載這個類,則嘗試加載這個類。
當你實現(xiàn)一個有重載類功能的類加載器,它的順序與上述會有些不同。類重載不會請求的他的父加載器來進行加載。在后面的段落會進行講解。
動態(tài)加載一個類十分簡單。你要做的就是獲取一個類加載器然后調(diào)用它的 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();
}
}
動態(tài)類重載有一點復雜。Java 內(nèi)置的類加載器在加載一個類之前會檢查它是否已經(jīng)被加載。因此重載一個類是無法使用 Java 內(nèi)置的類加載器的,如果想要重載一個類你需要手動繼承 ClassLoader。
在你定制 ClassLoader 的子類之后,你還有一些事需要做。所有被加載的類都需要被鏈接。這個過程是通過 ClassLoader.resolve()方法來完成的。由于這是一個 final 方法,因此這個方法在 ClassLoader 的子類中是無法被重寫的。resolve()方法是不會允許給定的 ClassLoader 實例鏈接一個類兩次。所以每當你想要重載一個類的時候你都需要使用一個新的 ClassLoader 的子類。你在設計類重載功能的時候這是必要的條件。
在前面已經(jīng)說過你不能使用已經(jīng)加載過類的類加載器來重載一個類。因此你需要其他的 ClassLoader 實例來重載這個類。但是這又帶來了一些新的挑戰(zhàn)。
所有被加載到 Java 應用中的類都以類的全名(包名 + 類名)作為一個唯一標識來讓 ClassLoader 實例來加載。這意味著,類 MyObject 被類加載器 A 加載,如果類加載器 B 又加載了 MyObject 類,那么兩個加載器加載出來的類是不同的。看看下面的代碼:
MyObject object = (MyObject)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObject 類在上面那段代碼中被引用,它的變量名是 object。這就導致了 MyObject 這個類會被這段代碼所在類的類加載器所加載。
如果 myClassReloadingFactory 工廠對象使用不同的類加載器重載 MyObject 類,你不能把重載的 MyObjec t類的實例轉換(cast)到類型為 MyObject 的對象變量。一旦 MyObject 類分別被兩個類加載器加載,那么它就會被認為是兩個不同的類,盡管它們的類的全名是完全一樣的。你如果嘗試把這兩個類的實例進行轉換就會報 ClassCastException。 你可以解決這個限制,不過你需要從以下兩個方面修改你的代碼: 1、標記這個變量類型為一個接口,然后只重載這個接口的實現(xiàn)類。 2、標記這個變量類型為一個超類,然后只重載這個超類的子類。
請看下面這兩個例子:
MyObjectInterface object = (MyObjectInterface)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");
MyObjectSuperclass object = (MyObjectSuperclass)
myClassReloadingFactory.newInstance("com.jenkov.MyObject");
只要保證變量的類型是超類或者接口,這兩個方法就可以正常運行,當它們的子類或是實現(xiàn)類被重載的時候超類跟接口是不會被重載的。
為了保證這種方式可以運行你需要手動實現(xiàn)類加載器然后使得這些接口或超類可以被它的父加載器加載。當你的類加載器加載 MyObject 類時,超類 MyObjectSuperclass 或者接口 MyObjectSuperclass 也會被加載,因為它們是 MyObject 的依賴。你的類加載器必須要代理這些類的加載到同一個類加載器,這個類加載器加載這個包括接口或者超類的類。
光說不練假把式。讓我們看看一個簡單的例子。下面這個例子是一個類加載器的子類。注意在這個類不想被重載的情況下它是如何把對一個類的加載代理到它的父加載器上的。如果一個類被它的父加載器加載,這個類以后將不能被重載。記住,一個類只能被同一個 ClassLoade r實例加載一次。 就像我之前說的那樣,這僅僅是一個簡單的例子,通過這個例子會向你展示類加載器的基本行為。這并不是一個可以讓你直接用于設計你項目中類加載器的模板。你自己設計的類加載器應該不僅僅只有一個,如果你想用來重載類的話你可能會設計很多加載器。并且你也不會像下面這樣將需要加載的類的路徑硬編碼(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 類。注意它既繼承了一個超類并且也實現(xiàn)了一個接口。這樣做僅僅是為了通過例子演示這個特性。在你自定義的情況下你可能僅會實現(xiàn)一個類或者繼承一兩個接口。
public class MyObject extends MyObjectSuperClass implements AnInterface2{
//... body of class ... override superclass methods
// or implement interface methods
}