閱讀276 返回首頁    go 阿裏雲 go 技術社區[雲棲]


Java Reflection(十二):動態類加載與重載

內容索引
類加載器
類加載體係
類加載
動態類加載
動態類重載
自定義類重載
類加載/重載示例
Java允許你在運行期動態加載和重載類,但是這個功能並沒有像人們希望的那麼簡單直接。這篇文章將闡述在Java中如何加載以及重載類。
你可能會質疑為什麼Java動態類加載特性是Java反射機製的一部分而不是Java核心平台的一部分。不管怎樣,這篇文章被放到了Java反射係列裏麵而且也沒有更好的係列來包含它了。

類加載器

所有Java應用中的類都是被java.lang.ClassLoader類的一係列子類加載的。因此要想動態加載類的話也必須使用java.lang.ClassLoader的子類。

一個類一旦被加載時,這個類引用的所有類也同時會被加載。類加載過程是一個遞歸的模式,所有相關的類都會被加載。但並不一定是一個應用裏麵所有類都會被加載,與這個被加載類的引用鏈無關的類是不會被加載的,直到有引用關係的時候它們才會被加載。

類加載體係

在Java中類加載是一個有序的體係。當你新創建一個標準的Java類加載器時你必須提供它的父加載器。當一個類加載器被調用來加載一個類的時候,首先會調用這個加載器的父加載器來加載。如果父加載器無法找到這個類,這時候這個加載器才會嚐試去加載這個類。

類加載

類加載器加載類的順序如下:
1、檢查這個類是否已經被加載。
2、如果沒有被加載,則首先調用父加載器加載。
3、如果父加載器不能加載這個類,則嚐試加載這個類。

當你實現一個有重載類功能的類加載器,它的順序與上述會有些不同。類重載不會請求的他的父加載器來進行加載。在後麵的段落會進行講解。

動態類加載

動態加載一個類十分簡單。你要做的就是獲取一個類加載器然後調用它的loadClass()方法。下麵是個例子:

01 public class MainClass {
02  
03   public static void main(String[] args){
04  
05     ClassLoader classLoader = MainClass.class.getClassLoader();
06  
07     try {
08         Class aClass = classLoader.loadClass("com.jenkov.MyClass");
09         System.out.println("aClass.getName() = " + aClass.getName());
10     catch (ClassNotFoundException e) {
11         e.printStackTrace();
12     }
13  
14 }

動態類重載

動態類重載有一點複雜。Java內置的類加載器在加載一個類之前會檢查它是否已經被加載。因此重載一個類是無法使用Java內置的類加載器的,如果想要重載一個類你需要手動繼承ClassLoader。

在你定製ClassLoader的子類之後,你還有一些事需要做。所有被加載的類都需要被鏈接。這個過程是通過ClassLoader.resolve()方法來完成的。由於這是一個final方法,因此這個方法在ClassLoader的子類中是無法被重寫的。resolve()方法是不會允許給定的ClassLoader實例鏈接一個類兩次。所以每當你想要重載一個類的時候你都需要使用一個新的ClassLoader的子類。你在設計類重載功能的時候這是必要的條件。

自定義類重載

在前麵已經說過你不能使用已經加載過類的類加載器來重載一個類。因此你需要其他的ClassLoader實例來重載這個類。但是這又帶來了一些新的挑戰。

所有被加載到Java應用中的類都以類的全名(包名 + 類名)作為一個唯一標識來讓ClassLoader實例來加載。這意味著,類MyObject被類加載器A加載,如果類加載器B又加載了MyObject類,那麼兩個加載器加載出來的類是不同的。看看下麵的代碼:

1 MyObject object = (MyObject)
2     myClassReloadingFactory.newInstance("com.jenkov.MyObject");

MyObject類在上麵那段代碼中被引用,它的變量名是object。這就導致了MyObject這個類會被這段代碼所在類的類加載器所加載。

如果myClassReloadingFactory工廠對象使用不同的類加載器重載MyObject類,你不能把重載的MyObject類的實例轉換(cast)到類型為MyObject的對象變量。一旦MyObject類分別被兩個類加載器加載,那麼它就會被認為是兩個不同的類,盡管它們的類的全名是完全一樣的。你如果嚐試把這兩個類的實例進行轉換就會報ClassCastException。
你可以解決這個限製,不過你需要從以下兩個方麵修改你的代碼:
1、標記這個變量類型為一個接口,然後隻重載這個接口的實現類。
2、標記這個變量類型為一個超類,然後隻重載這個超類的子類。

請看下麵這兩個例子:

1 MyObjectInterface object = (MyObjectInterface)
2     myClassReloadingFactory.newInstance("com.jenkov.MyObject");

 

1 MyObjectSuperclass object = (MyObjectSuperclass)
2     myClassReloadingFactory.newInstance("com.jenkov.MyObject");

隻要保證變量的類型是超類或者接口,這兩個方法就可以正常運行,當它們的子類或是實現類被重載的時候超類跟接口是不會被重載的。

為了保證這種方式可以運行你需要手動實現類加載器然後使得這些接口或超類可以被它的父加載器加載。當你的類加載器加載MyObject類時,超類MyObjectSuperclass或者接口MyObjectSuperclass也會被加載,因為它們是MyObject的依賴。你的類加載器必須要代理這些類的加載到同一個類加載器,這個類加載器加載這個包括接口或者超類的類。

類加載/重載示例

光說不練假把式。讓我們看看一個簡單的例子。下麵這個例子是一個類加載器的子類。注意在這個類不想被重載的情況下它是如何把對一個類的加載代理到它的父加載器上的。如果一個類被它的父加載器加載,這個類以後將不能被重載。記住,一個類隻能被同一個ClassLoader實例加載一次。
就像我之前說的那樣,這僅僅是一個簡單的例子,通過這個例子會向你展示類加載器的基本行為。這並不是一個可以讓你直接用於設計你項目中類加載器的模板。你自己設計的類加載器應該不僅僅隻有一個,如果你想用來重載類的話你可能會設計很多加載器。並且你也不會像下麵這樣將需要加載的類的路徑硬編碼(hardcore)到你的代碼中。

01 public class MyClassLoader extends ClassLoader{
02  
03     public MyClassLoader(ClassLoader parent) {
04         super(parent);
05     }
06  
07     public Class loadClass(String name) throws ClassNotFoundException {
08         if(!"reflection.MyObject".equals(name))
09                 return super.loadClass(name);
10  
11         try {
12             String url = "file:C:/data/projects/tutorials/web/WEB-INF/" +
13                             "classes/reflection/MyObject.class";
14             URL myUrl = new URL(url);
15             URLConnection connection = myUrl.openConnection();
16             InputStream input = connection.getInputStream();
17             ByteArrayOutputStream buffer = new ByteArrayOutputStream();
18             int data = input.read();
19  
20             while(data != -1){
21                 buffer.write(data);
22                 data = input.read();
23             }
24  
25             input.close();
26  
27             byte[] classData = buffer.toByteArray();
28  
29             return defineClass("reflection.MyObject",
30                     classData, 0, classData.length);
31  
32         catch (MalformedURLException e) {
33             e.printStackTrace();
34         catch (IOException e) {
35             e.printStackTrace();
36         }
37  
38         return null;
39     }
40  
41 }

下麵是使用MyClassLoader的例子:

01 public static void main(String[] args) throws
02     ClassNotFoundException,
03     IllegalAccessException,
04     InstantiationException {
05  
06     ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
07     MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
08     Class myObjectClass = classLoader.loadClass("reflection.MyObject");
09  
10     AnInterface2       object1 =
11             (AnInterface2) myObjectClass.newInstance();
12  
13     MyObjectSuperClass object2 =
14             (MyObjectSuperClass) myObjectClass.newInstance();
15  
16     //create new class loader so classes can be reloaded.
17     classLoader = new MyClassLoader(parentClassLoader);
18     myObjectClass = classLoader.loadClass("reflection.MyObject");
19  
20     object1 = (AnInterface2)       myObjectClass.newInstance();
21     object2 = (MyObjectSuperClass) myObjectClass.newInstance();
22  
23 }

下麵這個就是被加載的reflection.MyObject類。注意它既繼承了一個超類並且也實現了一個接口。這樣做僅僅是為了通過例子演示這個特性。在你自定義的情況下你可能僅會實現一個類或者繼承一兩個接口。

1 public class MyObject extends MyObjectSuperClass implements AnInterface2{
2     //... body of class ... override superclass methods
3     //    or implement interface methods
4 }

最後更新:2017-05-23 11:31:44

  上一篇:go  Java Reflection教程
  下一篇:go  JAVA中的備忘錄模式實例教程