關於類加載時LinkageError的問題記錄

今天遇到一個比較奇怪的問題,MultiPageEditor所使用的類和子Page使用了同一個類,因爲我使用的是同一個jar包裏的類,兩個工程各自使用自己的jar包,但是是一個相同的jar包,這樣在運行時,就出現了這個錯誤:

LinkageError之loader (instance of xxx) previously initiated loading for

a different type with name “"

鏈接過來一個問題說明:

一下內容轉載自:

 (轉載請說明)

http://www.cnblogs.com/deepnighttwo/archive/2011/08/31/2160990.html

 

LinkageError包括其子類,是Java中比較不應該出現的Error。出現這些問題,大概有幾個問題:ClassLoader沒有嚴格遵守Java中默認的雙親委派模式;全限定名相同的兩個類在不同的CL中有重複;程序運行時使用的類的版本與開發時候不一樣(類有變化,比如改了方法的可見性等)。

而LinkageError本身則更少見。當遇到LinkageError loader (instance of xxx) previously initiated loading for a different type with name "lib/MyData"時,可以肯定系統中有ClassLoader違背了Parent Delegate的規則。

問題的根源就是,當一個類已經被解析過之後,它用到的其它類也就已經確定並被解析好了。這時候,如果另一個CL也加載了同一個類(類名相同),並嘗試將這個類的實例給前面的引用賦值,因爲實際上兩個類是由不同的類加載器加載的,既在JVM看來是不同的類,所以就會出現這個錯誤。

下面是一個簡單的例子。沒有實際意義,只爲了展示問題。

系統中的幾個角色類:

IFac - 接口,提供一個getMyData方法。

MyData - 數據類,沒有實際意義,但是在系統中,這個類的加載則是引發問題的關鍵

IFacImpl - IFac接口的實現。

系統中還有一個不可或缺的角色——那個不遵守規則的ClassLoader。我們需要自己寫一個ClassLoader來違法Parent Delegate的規則。

 package webcl;
 2 
 3 import java.net.URL;
 4 import java.net.URLClassLoader;
 5 import java.net.URLStreamHandlerFactory;
 6 
 7 public class WebAppCL extends URLClassLoader {
 8 
 9     public WebAppCL(URL[] urls, ClassLoader parent,
10             URLStreamHandlerFactory factory) {
11         super(urls, parent, factory);
12     }
13 
14     public WebAppCL(URL[] urls, ClassLoader parent) {
15         super(urls, parent);
16     }
17 
18     public WebAppCL(URL[] urls) {
19         super(urls);
20     }
21 
22     @Override
23     protected synchronized Class<?> loadClass(String name, boolean resolve)
24             throws ClassNotFoundException {
25         try {
26             return findClass(name);
27         } catch (ClassNotFoundException ex) {
28             return super.loadClass(name, resolve);
29         }
30     }
31 
32 }

 

這個ClassLoader唯一的作用就是違反PD的規則。

然後是App程序:

 package test;
 2 
 3 import java.net.URL;
 4 
 5 import lib.IFac;
 6 import lib.MyData;
 7 import webcl.WebAppCL;
 8 public class App2 { // 應用程序的classpath上有IFac類和MyData類。
 9     public static void main(String[] args) throws Exception {
10         MyData resolved = new MyData();
11         WebAppCL cl = new WebAppCL(new URL[] {
12                 new URL("file:\\C:\\Users\\zangmeng\\Desktop\\data.jar"),        // 包含MyData類
13                 new URL("file:\\C:\\Users\\zangmeng\\Desktop\\faclib.jar") });   // 包含FacImpl類,不包含IFac類。
14         IFac fac = (IFac) cl.loadClass("faclib.FacImpl").newInstance();
15         MyData data = fac.getData();
16     }
17 }

 

程序在運行時,類加載情況如下所示:

程序很簡單,四行代碼而已。

第一行是創建一個MyData的實例。目的是讓MyData類被加載和解析。

第二行是創建一個WebAppCL,這個不聽話的CL會優先加載自己classpath下的類,如果失敗了再去問parent cl要。現在這個CL可以加載MyData類和FacImpl類。

第三行,首先是IFac fac這段。JVM需要使用當前類加載器,也就是AppClassLoader,加載並解析IFac類,解析的過程中,同時鏈接到AppCL加載的MyData類。等號的另一邊,通過WebCl加載並創建一個FacImpl的實例。這個時候,因爲WebAppCL會首先從自己的CP里加載類,所以在解析FacImpl的時候,加載MyData的請求並沒有被委派到AppCL,而是自己自己消化了,這時候JVM裏面就有兩個MyData類了。但是程序到這裏並沒有錯誤,因爲WebAppCL裏面並沒有IFac接口,這個接口還是AppCL的,但是這時候隱患已經埋下了——

FacImpl類在解析的時候,需要MyData類,而這個MyData類是被WebCL加載的。

IFac則不同,它的MyData類是被AppCL加載的。

FacImpl實現了IFac接口,按說getMyData方法應該返回相同的類型。而在runti的時候,這兩個MyData卻是不同的類型,因爲它們是被不同的CL加載的。

第四行是引起錯誤的地方。等號左邊的MyData實際上是第一行中那個被AppCL加載的類的一個引用。等號右邊返回的MyData實例是被WebCL加載的MyData的實例。兩個不同的類型,賦值自然會引發錯誤。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章