今天遇到一個比較奇怪的問題,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的實例。兩個不同的類型,賦值自然會引發錯誤。