2007-10-11<轉> 剖析 Eclipse 類裝入器
關鍵字: classloader Eclipse 提供了一個強大的開發平台,越來越多的應用基於 Eclipse 來開發。但是由於 Eclipse 作為一個靈活的平台,其類裝入器具有一定的特殊性,在開發 Eclipse 外掛程式時我們經常遇到類找不到的問題,尤其是當我們開發的應用使用了第三方的軟體包時。本文深入剖析了 Java 的類裝入器機制以及 Eclipse 的類裝入器的原理與模型,並總結了 Eclipse 外掛程式應用開發常見的與類裝載器相關的問題,同時給出了相應的解決方案。
Java 類裝入器原理
類裝入器是 JVM 用來裝入類的類,它對於 Java 編程是非常重要的一個概念。一般情況下,程式員在編寫程式的時候都可以忽略類裝入器的存在性。但是對於伺服器端編程或者是一些特殊情況下時候,深入瞭解類裝入器的機制以及其在不同情況下的實現還是非常必要的。
首先,當一個 JVM 啟動的時候,Java 預設開始使用三個類裝入器。它們分別是:
引導(Bootstrap)類裝入器;
擴充(Extension)類裝入器;
系統(System)類裝入器;
它們分別實現如下的功能:
引導類裝入器是用本地代碼實現的類裝入器。它負責將 <Java_Runtime_Home>/lib 下面的類庫載入到記憶體中。
擴充類裝入器是由 Sun 的 ExtClassLoader 實現的。它負責將 < Java_Runtime_Home >/lib/ext 或者由系統變數 java.ext.dir 指定位置中的類庫載入到記憶體中。
系統類別裝入器又叫應用程式類裝入器,是由 Sun 的 AppClassLoader 實現的。它負責將系統類別路徑(CLASSPATH)中指定的類庫載入到記憶體中。
當應用程式需要載入某個類到記憶體中的時候,類裝入器是如何工作的呢?這就設計到類裝入器的一個重要方 面:代理機制。每一個類裝入器,除了引導類裝入器以外,都有一個父類裝入器。對於系統預設定義的三個類裝入器,引導類裝入器是擴充類裝入器的父類裝入器, 而擴充類裝入器是系統類別裝入器的父類裝入器。當然,應用程式也可以使用自己的類裝入器來使用特定的方法來裝載類,因此,整個系統中的類裝入器就形成一個樹 狀結構。
當使用某個類裝入器來試圖裝載某個類的時候,該類裝入器會首先使用其父類裝入器來試圖裝載該類。對於每 一個裝載進來的類,JVM 都會給其分配一個唯一的 ID。因此,不同類裝入器可以裝載同一個類到 JVM 中。例如,對於如結構的 ClassLoaderA 和 ClassLoaderB:
圖 1 類裝入器的結構
假設類 C 在系統類別裝入器指定的類路徑中,則無論是使用 ClassLoaderA 還是使用 ClassLoaderB,都只會得到同樣一個類 C。
但是如果類 C 分別在 ClassLoaderA 以及 ClassLoaderB 指定的類庫中,則使用 ClassLoaderA 得到到類 C 執行個體會不同於 ClassLoaderB 得到的類 C 執行個體。儘管兩個類裝入器在同一個 JVM 中。
上面的類裝入器的向上代理結構看上去很完美了,但是,當系統變得複雜的時候,就還是顯得不夠用了。
例如,當 Java 引入了 JNDI 以後,JNDI 核心部分是通過引導 類裝入器在 JVM 啟動的時候裝載進入 JVM 的。而 JDNI 核心部分是通過配置資訊來在運行時候裝載定義在使用者的類路徑中的特定類來完成特定需要。而這是上面定義的類裝入器的向上代理模式所不能支援的。
為瞭解決這個問題,Java 2 中引入了線程上下文(Thread Content)類裝入器的概念,每一個線程有一個 Context 類裝入器。這個 Context 類裝入器是通過方法 Thread.setContextClassLoader() 設定的,如果當前線程在建立後沒有調用這個方法設定 Context 類裝入器,則當前線程從他的父線程繼承 Context 類裝入器。如果整個應用都沒有設定 Context 類裝入器,則系統類別裝入器被設定為所有線程的 Context 類裝入器。
對於我們上面所說 JNDI 的情況,引導 類裝入器裝載進入的 JNDI 核心類會使用 Context 類裝入器來裝載其所需要的 JNDI 實作類別,而不是將該裝載任務代理給其父類裝入器來完成。這樣,就解決了上面的問題。可以認為 Context 類裝入器在傳統的 Java 向上代理機制上開啟了一個後門。Context 類裝入器在 J2EE 中使用的很廣泛,比如 Java 命名服務(JNDI),Java API for XML Parsing(JAXP)(註:在 Java1.4 中 JAXP 才作為 Java 的核心類的一部分,它才開始使用 Context 類裝入器來載入不同的實作類別)等。
簡單而言,Java 中的類裝入器就是上面幾種,但是,在具體使用中,還是有很多變化,我們下面分別對於一些情況進行說明。
Eclipse 類裝入器原理
在典型的 Java 應用程式中,我們要載入一個類或資源時,通常可以使用三種類裝入器:
當前類的類裝入器;
當前線程的 Context 類裝入器;
系統類別裝入器;
在實際的應用開發中比較常用到前兩個類裝入器是,下邊重點介紹在 Eclipse 外掛程式運行環境中前兩種類裝入器的結構和原理以及和典型的 Java 應用的不同之處。
當前外掛程式類的類裝入器
是 Eclipse 外掛程式在運行時當前外掛程式類的類裝入器的體繫結構圖。
圖 2 Eclipse 外掛程式類裝入器的結構
其中 EclipseClassloader 類裝入器實現了 OSGi 規範的 BundleClassLoader,它用來裝載 Bundle (也就是外掛程式)中的類 OSGi ParentClassloader 類裝入器是 Eclipse 外掛程式類裝入器的父類裝入器,可以通過在啟動 Eclipse 時設定系統屬性 osgi.parentClassloader 來改變它,這個改變類會影響所有的 Eclipse 外掛程式。如果在啟動時沒有設定系統屬性 osgi.parentClassloader, Eclipse 使用一個預設的空的類裝入器。
BundleLoader 是 EclipseClassloader 類裝入器的代理,它是用來載入外掛程式相關的資源的。
Eclipse 外掛程式類裝入器載入類或資源的過程如下:
首先試圖從父類裝入器載入類,其過程是先從 OSGi ParentClassloader 類裝入器載入類,OSGi ParentClassloader 類裝入器使用傳統的 Java 裝入器的委託模式依次從父類裝入器載入類。
如果無法從 OSGi ParentClassloader 類裝入器載入類,則試圖通過代理 BundleLoader 來載入類。
BundleLoader 首先試圖從此外掛程式的需求依賴外掛程式("require"指定的外掛程式)中去載入需要的類,如果找不到,則通過 EclipseClassloader 類裝入器來從外掛程式本地載入需要的類。
如果還是找不到要載入的類,就會拋出類找不到異常。
當前線程的 Context 類裝入器
在 Eclipse 中並沒有設定 Context 類裝入器,所以預設情況下當前線程的 Context 類裝入器為系統的類裝入器,其體繫結構如 圖 3 所示。
圖 3 Eclipse 外掛程式當前線程 Context 類裝入器的結構
在 Eclipse 中,每個外掛程式都有自己的類裝入器,每個線程有自己的類裝入器。外掛程式和線程之間沒有統一的映射關係,所以 Eclipse 架構沒有將線程的 Context 類裝入器設定成有意義類裝入器。
Eclipse 外掛程式開發常見類裝入問題及解決方案
Eclipse 已經越來越多的被用來作為一個平台使用,基於 Eclipse 的外掛程式應用開發也越來越多,尤其是使用 Eclipse 外掛程式作為富Client Access Server的應用。其中有兩種比較典型的應用:
在 Eclipse 外掛程式中調用 EJB
在 Eclipse 外掛程式中調用 Web 服務
在以上這些典型的 Eclipse 外掛程式的開發過程中,通常會遇到兩中類找不到的問題:
在 JNDI 中尋找命名服務時拋出類找不到異常
在使用 HTTPS/SSL 調用 Web 服務時拋出類找不到異常
在 JNDI 中尋找命名服務時拋出類找不到異常
問題
在 Eclipse 中使用 "Hello,World" 模版開發一個簡單外掛程式應用,然後再 Action 的實現中添加下面的方法,並在 run 方法中調用它。
清單 1 尋找 JNDI 服務的方法
- public Object ejbLookup(String ejbJNDIName) throws NamingException{
- Properties props = new Properties();
- props.put(Context.INITIAL_CONTEXT_FACTORY,
- "com.ibm.websphere.naming.WsnInitialContextFactory");
- props.put(Context.PROVIDER_URL,"iiop://" + WAS_HOST + ":" + WAS_IIOP_PORT);
- System.out.println(" *** Creating initial context ...");
- Context ctx = new InitialContext(props);
- System.out.println(" *** Looking up EJB:"+ejbJNDIName);
- Object obj = ctx.lookup(ejbJNDIName);
- System.out.println(" *** Got EJB object["+obj.toString()+"]");
- return obj;
- }
以上方法用來在 JNDI 中根據傳入的 JNDI 名字尋找 EJB 對象,在找到 EJB 對象後列印這個對象。
在運行以上簡單的 Eclipse 外掛程式應用前,需要確保所有作為獨立的 Java 應用調用 EJB 時需要的 WebSphere 庫檔案到外掛程式根目錄下,同時修改外掛程式的 plugin.xml 把這些庫檔案加入到 "Run-time libraries" 列表中.
同時還要確保尋找的 EJB 已經部署到 WebSphere 上並且已經啟動。
註:在本例中筆者使用了 WebSphere 6.0 內建的 Plants by WebSphere 範例應用程式中的 EJB 對象。這個範例應用程式可以通過以下命令來安裝:
- WAS_install_root/samples/bin/install -samples PlantsByWebSphere
運行我們剛剛開發的 Eclipse 外掛程式應用,點擊 "Hello,Eclipse world" 按鈕來調用以上方法,我們會在控制台上看到以下異常資訊:
清單 2 尋找 JNDI 對象的方法運行結果
- *** Creating initial context ...
- javax.naming.NoInitialContextException: Cannot instantiate class:
- com.ibm.websphere.naming.WsnInitialContextFactory. Root exception is
- java.lang.ClassNotFoundException:
- com.ibm.websphere.naming.WsnInitialContextFactory
- at java.net.URLClassLoader.findClass(URLClassLoader.java:374)
- at java.lang.ClassLoader.loadClass(ClassLoader.java(Compiled Code))
- at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:441)
- at java.lang.ClassLoader.loadClass(ClassLoader.java(Compiled Code))
- at java.lang.Class.forName0(Native Method)
- at java.lang.Class.forName(Class.java:259)
- at com.sun.naming.internal.VersionHelper12.loadClass(VersionHelper12.java:59)
- at javax.naming.spi.NamingManager.getInitialContext(NamingManager.java:661)
- at javax.naming.InitialContext.getDefaultInitCtx(InitialContext.java:256)
- at javax.naming.InitialContext.init(InitialContext.java:232)
- at javax.naming.InitialContext.<init>(InitialContext.java:208)
- at com.test.actions.Invoker.ejbLookup(Invoker.java:73)
從以上的異常可以看到 Eclipse 沒有找到類 com.ibm.websphere.naming.WsnInitialContextFactory,這個類位於 WebSphere 庫檔案 naming.jar 中,事實上這個庫檔案已經作為執行階段程式庫加入到了外掛程式的 plguin.xml 檔案的 runtime 節了,但是 Eclipse 外掛程式在運行時卻找不到位於這個庫檔案中的類。下面分析其原因。
原因
以上異常是在初始化 JNDI 的初始上下文(Initial Context)時出現的,我們知道 Java 類的載入使用委託的機制,它總是會先載入父類裝入器中的類。核心的 JNDI 類是在引導(Bootstrap)類裝入器裝入的,而具體的 JNDI 類在廠商提供的庫檔案中(這裡是 IBM 提供的 naming.jar 檔案)。當 JNDI 初始化的時候,位於引導類裝入器中的核心 JNDI 類會使用當前線程的 Context 類裝入器來裝入 JNDI 具體的實作類別,因為這些 JNDI 具體的實作類別位於 naming.jar 庫檔案中,而這個庫檔案位於外掛程式的執行階段程式庫檔案清單中,從 圖 3 Eclipse 外掛程式類當前線程 Context 類裝入器的結構中我們不難看出,在類裝入器的樹狀結構中並沒有包含載入 Eclipse 外掛程式本地庫檔案的類轉入器,所以 Eclipse 外掛程式是找不到庫檔案 naming.jar 的。
解決方案
我們可以通過在外掛程式程式中動態設定線程的 Context 類裝入器來解決以上問題。在調用 JNDI 尋找方法之前,我們可以把當前線程的 Context 類裝入器設定為當前類的類裝入器,這樣在當前線程的類裝入器的體繫結構中就包含了載入 Eclipse 外掛程式本地庫檔案的類裝入器了。
修改調用方法 ejbLookup 的代碼如下:
清單 3 在調用 ejbLookup 前設定 Context 類裝入器
- ClassLoader currentClassLoader = this.getClass().getClassLoader();
- Thread currThread = Thread.currentThread();
- ClassLoader originalContextCL = currThread.getContextClassLoader();
- try {
- currThread.setContextClassLoader(currentClassLoader);
- Invoker.ejbLookup("plantsby/ShoppingCartHome");
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- currThread.setContextClassLoader(originalContextCL);
- }
首先儲存當前線程的 Context 類裝入器,然後設定當前線程的 Context 類裝入器為 Eclipse 外掛程式的類裝入器,接著調用 ejbLookup 方法,最後要確保將當前線程的類裝入器還原為原來的類裝入器,將還原語句放到 finally 塊中是個不錯的方法。
再一次運行 Eclipse 外掛程式應用,異常不會出現了,EJB 的尋找成功。結果如下:
清單 4 尋找 JNDI Name 成功運行
- *** Creating initial context ...
- *** Looking up EJB:plantsby/ShoppingCartHome
- *** Got EJB object[IOR:00bdbdbd00000055524d … 000407e]
小結
當我們在 Eclipse 外掛程式中調用第三方的庫檔案,而這個庫檔案又使用了線程的 Context 類裝入器來載入類,這時,儘管我們把這些庫檔案放在了外掛程式的根目錄下,並在 plugin.xml 中將其聲明為執行階段程式庫檔案,外掛程式在運行時仍然可能會找不到這些庫檔案中的類。這種情況通常發生於使用 factory 機制的庫檔案。
在這種情況下,我們可以在 Eclipse 外掛程式程式中通過手工設定當前線程的 Context 類裝入器為 Eclipse 外掛程式的類裝入器來解決這個問題。
值得一提的是,在Eclipse的最新版本 3.2 中解決了這個問題,在 Eclipse 3.2 的外掛程式程式中我們不需要再在程式中手工設定當前線程的 Context 類裝入器。
在使用 HTTPS/SSL 調用 Web 服務時拋出類找不到異常
問題
在 Eclipse 中開發一個外掛程式作為 Web 服務的用戶端,當我們使用 HTTP 協議調用 Web 服務時,Eclipse 外掛程式工作很正常,但是當 Web 服務要求用戶端必須使用基於 HTTPS 傳輸協議(SSL)來調用 Web 服務時,我們通常會遇到和安全相關的類檔案找不到的問題。
首先我們使用 HTTP 的方式來調用 Web 服務。
1.調用 Web 服務的代碼如下:
清單 5 調用 Web 服務的方法
- public void callWebSerivce(URL wsdlUrl,String endpoint){
- try {
- ServiceFactory factory = ServiceFactory.newInstance();
- QName qName = new
- QName("http://addr.webservices.samples.websphere.ibm.com",
- "AddressBookService");
- Service service = factory.createService(wsdlUrl,qName);
- AddressBook addrBook = (AddressBook) service.getPort(new
- QName("http://addr.webservices.samples.websphere.ibm.com",
- "AddressBook"),
- AddressBook.class);
- ((javax.xml.rpc.Stub)
- addrBook)._setProperty("javax.xml.rpc.service.endpoint.address", endpoint);
- System.out.println (" *** Getting address from address book ...");
- Address addr = addrBook.getAddressFromName ("Purdue Boilermaker");
- System.out.println (" *** City of the address is:"+addr.getCity());
- } catch (ServiceException e) {
- e.printStackTrace();
- } catch (RemoteException e) {
- e.printStackTrace();
- }
- }
2.調用以上方法的代碼如下:
清單 6 調用callWebSerivce方法
- ClassLoader currentClassLoader = this.getClass().getClassLoader();
- Thread currThread = Thread.currentThread();
- ClassLoader originalContextCL = currThread.getContextClassLoader();
- try {
- URL wsdlUrl = new
- URL("http://localhost:9080/AddressBookW2JE/services/AddressBook?wsdl");
- Invoker.callWebSerivce(wsdlUrl,"http://localhost:9080/AddressBookW2JE
- /services/AddressBook");
- } catch (Exception e) {
- e.printStackTrace();
- }finally{
- currThread.setContextClassLoader(originalContextCL);
- }
註:本文代碼使用了 WebSphere 6.0 內建的 WebService 範例應用程式。
你可以通過以下命令來安裝這個範例程式:
- install_root/samples/bin/install -samples WebServicesSamples
注意,在調用方法 callWebSerivce 時必須設定當前線程的 Context 類裝入器為 Eclipse 外掛程式的類裝入器,否則會遇到類找不到的問題,其原因和上文講到的相同。
3.運行 Eclipse 外掛程式並觸發以上方法的調用,如果正常的話會得到以下輸出。
清單 7 使用 HTTP 調用 Web 服務的結果
- *** Getting address from address book ...
- *** City of the address is:West Lafayette
下面我們修改以上程式,通過 SSL 來調用 Web 服務。
1.修改調用方法 callWebSerivce 的代碼中的 Web 服務的端點地址。
清單 8 修改 Web 服務的端點地址
- …
- Invoker.callWebSerivce(wsdlUrl,
- "https://localhost:9443/AddressBookW2JE/services/AddressBook");
2.然後重新運行 Eclipse 外掛程式應用,在運行之前,確保在 run-time 工作台啟動配置中添加下列和安全相關的 JAVA 虛擬機器參數。
清單 9 調用 Web 服務的 JAVA 虛擬機器參數
- -Djavax.net.ssl.trustStore=<WAS_HOME>/profiles/default/etc/DummyClientTrustFile.jks
- -Djavax.net.ssl.trustStorePassword=WebAS
- -Djavax.net.ssl.keyStore=<WAS_HOME>/profiles/default/etc/DummyClientKeyFile.jks
- -Djavax.net.ssl.keyStorePassword=WebAS
3.運行結果如下。
清單 10 使用 HTTPS 調用 Web 服務的結果
- *** Getting address from address book ...
- 2006-12-29 16:15:50
- com.ibm.ws.webservices.engine.PivotHandlerWrapper bindExceptionToResponse
- SEVERE: WSWS3400I: 資訊:意外異常。
- java.lang.NoClassDefFoundError: com/ibm/crypto/fips/provider/IBMJCEFIPS
- at com.ibm.ws.ssl.SSLConfig.<init>(SSLConfig.java:279)
- at com.ibm.ws.ssl.SSLConfig.<clinit>(SSLConfig.java:206)
- at com.ibm.ws.webservices.engine.components.net.
- SSLConfiguration.<init>(SSLConfiguration.java:87)
- at com.ibm.ws.webservices.engine.transport.http.
- HttpChannelAddress.keyValueforPool(HttpChannelAddress.java:325)
- at com.ibm.ws.webservices.engine.transport.channel.OutboundConnectionCache.
- findGroupAndGetConnection(OutboundConnectionCache.java:224)
- at com.ibm.ws.webservices.engine.transport.http.HTTPSender.invoke(HTTPSender.java:391)
- at com.ibm.ws.webservices.engine.PivotHandlerWrapper.invoke(PivotHandlerWrapper.java:226)
- at com.ibm.ws.webservices.engine.PivotHandlerWrapper.invoke(PivotHandlerWrapper.java:226)
- at com.ibm.ws.webservices.engine.PivotHandlerWrapper.invoke(PivotHandlerWrapper.java:226)
- at com.ibm.ws.webservices.engine.WebServicesEngine.invoke(WebServicesEngine.java:279)
- at com.ibm.ws.webservices.engine.client.Connection.invokeEngine(Connection.java:798)
- at com.ibm.ws.webservices.engine.client.Connection.invoke(Connection.java:693)
- at com.ibm.ws.webservices.engine.client.Connection.invoke(Connection.java:644)
- at com.ibm.ws.webservices.engine.client.Connection.invoke(Connection.java:472)
- at com.ibm.ws.webservices.engine.client.Stub$Invoke.invoke(Stub.java:818)
- ……
應用程式找不到類 com.ibm.crypto.fips.provider.IBMJCEFIPS,這個類位於擴充目錄 (jre/lib/ext) 下庫檔案 ibmjcefips.jar 中,這個庫檔案是用來提供 Java 加密擴充功能的,屬於 JAVA 虛擬機器實現提供的類。
原因
從 圖 2 Eclipse 外掛程式類裝入器的體繫結構中我們可以看到 Eclipse 外掛程式在載入類時不能從預設擴充 (jre/lib/ext目錄中的庫檔案) 中尋找類,預設情況下,Eclipse 外掛程式在載入類時其尋找順序如下:
Java核心引導庫檔案(rt.jar)
需求依賴外掛程式
當前外掛程式中的類
位於當前外掛程式運行時節的執行階段程式庫檔案
Java 的預設擴充目錄(通常位於 jre/lib/ext 目錄)通常存放一些 Java 公用的擴充,例如 Java Cryptography Extensions(JCE)。不同的安全擴充通常放在擴充目錄下,這樣應用就不需要修改使用者的類路徑即可實現安全擴充的功能。
但是有時我們的外掛程式應用會用到預設擴充目錄下的庫檔案,例如當我們在 Eclipse 外掛程式中使用 SSL 傳輸協議調用 Web 服務時,就需要使用安全擴充相關的庫檔案。各廠商實現的方式是不同的,比如在 IBM 的 JDK 運行環境中會使用到位於擴充目錄下的 IBMJCE provider 庫檔案,以上問題正是因為當我們使用 SSL 傳輸協議來調用 Web 服務,使用到了 IBM 提供的 JCE 實作類別引起的。
解決方案
有兩個方法可以解決這個問題:
1. 在啟動 Eclipse 時設定 JAVA 虛擬機器參數 osgi.parentClassloader 為 "ext",如 圖 4 所示.
圖 4 Eclipse 外掛程式運行時 JAVA 虛擬機器參數設定
Eclipse 支援在啟動的時候設定系統參數 osgi.parentClassloader 為下列類裝入器:
boot:設定為 Java 的引導(Bootstrap)類裝入器;
app:設定為 Java 的系統(System)類裝入器;
ext:設定為 Java 的擴充(Extension)類裝入器;
fwk:設定為 OSGi framework 的類裝入器;
當我們將值設為 "ext" 時,Eclipse 外掛程式在載入類時其尋找順序更改為:
Java 核心引導庫檔案(rt.jar);
JRE 預設擴充目錄下的庫檔案;
需求依賴外掛程式;
當前外掛程式中的類;
位於當前外掛程式運行時部分的庫檔案;
這樣當我們運行以上程式時,Eclipse 外掛程式會從預設擴充目錄的庫檔案中尋找類,從而成功調用 Web 服務。
但是這樣做的一個問題是:類載入順序的改變會影響所有的外掛程式,並且由於 Java 擴充機制會載入所有位於擴充目錄下的庫檔案,這樣可能會潛在的引起庫檔案的衝突,因為擴充目錄是優先載入的,如果某個 Eclipse 外掛程式執行階段程式庫檔案清單中和擴充目錄下包含一個相同的庫檔案,Eclipse 將使用擴張目錄下的庫檔案,這也是 Eclipse 為什麼沒有預設把擴充目錄放在類載入路徑中,Eclipse 鼓勵每個外掛程式使用自己的類路徑來載入類同時避免庫檔案之間衝突。
2.把 IBMJCE provider 庫檔案拷貝到 Eclipse 外掛程式目錄下,然後把他們添加到執行階段程式庫檔案清單中。這種方法是把這些 JVM 的庫檔案看成第三方的庫檔案,這樣在運行時不會影響到其它的 Eclipse 外掛程式。
小結
當我們在外掛程式中使用的類存在於 Java 的擴充目錄(jre/lib/ext 目錄)下時,這些類預設情況下沒有位於外掛程式的類載入路徑中,比較常見的一種情況是基於 SSL 傳輸協議調用 Web 服務時,一些需要的安全加密擴充庫檔案會位於 Java 擴充目錄下。
在這種情況下,我們可以通過設定系統參數 osgi.parentClassloader 或拷貝需要的庫檔案到 Eclipse 外掛程式目錄下並加入到執行階段程式庫檔案清單中來解決這個問題。