equinox在建立bundle的classloader時,首先擷取bundle的classpath,然後執行createbclprevileged方法,此方法最後轉交由basedata來建立classloader。
basedate建立classloader的關鍵代碼片段為:
classloadinghook[] hooks = adaptor.gethookregistry().getclassloadinghooks();
classloader parent = adaptor.getbundleclassloaderparent();
baseclassloader cl = null;
for (int i = 0; i < hooks.length && cl == null; i++)
cl = hooks[i].createclassloader(parent, delegate, domain, this, bundleclasspath);
if (cl == null)
cl = new defaultclassloader(parent, delegate, domain, this, bundleclasspath);
return cl;
在equinox中,預設的情況下adaptor.getbundleclassloaderparent傳回的為bootstrap classloader,可通過修改啟動的osgi.parentclassloader 來改變這個parent classloader,
osgi.parentclassloader 的可選值有四個,分别是:
● boot:預設
● app:systemclassloader
● ext:systemclassloader的parent
● fwk:啟動equinox的classloader
classloadinghook在createclassloader的時候都沒有做動作,是以最後classloader都是通過建立defaultclassloader對象來建構的,其中parent參數為null,delegate參數為bundleloader執行個體,bundleclasspath參數為bundle的classpath。
經過以上步驟後,完成了classloader的建立,可以開始加載class了,根據上面上述,bundle的class就由defaultclassloader來完成了。
檢視defaultclassloader的loadclass代碼,發現真正的加載class的過程是轉為調用了delegate 的findclass來完成的,delegate參數對應的為bundleloader執行個體,轉為跟蹤bundleloader的findclass方法。
bundleloader的findclass方法的代碼片段:
if (checkparent && parentcl != null && name.startswith(java_package))
return parentcl.loadclass(name);
如果不是java.開頭的類,則交由findclassinternal方法來完成加載。
findclassinternal方法遵循的為osgi規範中定義的class的加載順序,不過仍然稍有改動:
1)判斷是否交由parent classloader去完成加載
在啟動equinox時,equinox會讀取org.osgi.framework.bootdelegation屬性,該屬性對應配置的為需要從parent classloader中加載的package,如值配置的為*,說明所有的都從parent classloader中加載 ,如值配置的為具體的package,那麼則放入bootdelegation集合;如配置的為帶通配符的package,那麼則放入bootdelegationstems集合。
判斷時equinox首先判斷是否所有的都從parent classloader中加載,如是則從parent classloader中加載;
如需要加載的類的package位于bootdelegation或bootdelegationstems集合中,那麼同樣從parent classloader中加載。
如不從parent classloader中加載,則進入下面的步驟。
2)嘗試調用equinox提供的classloaderdelegatehook的擴充來加載
equinox對外提供了classloaderdelegatehook的接口擴充,可編寫classloaderdelegatehook的實作,注冊到framework中,那麼當有class需要加載等動作時都會得到通知。
在預設情況下,equinox中沒有classloaderdelegatehook的實作,是以繼續下面的步驟。
3)判斷是否在import-package中,如在則交由相應的packagesource去加載
根據bundle配置的import-package,判斷目前需要加載的類是否在import-package中,如在則交由對應的packagesource進行加載,packagesource在加載時即直接交由對應的bundle的classloader去加載,如加載的類的package在import-package中,但加載後仍然沒有找到class,則直接抛出classnotfoundexception,如加載到,則直接傳回。
如所需要加載的類的package不在import-package中,則繼續下面的步驟。
4)嘗試從require-bundle中加載
嘗試使用require-bundle來加載,如加載到,則直接傳回,如加載不到,則繼續下面的步驟。
5)嘗試從目前bundle中加載
直到經過以上步驟的嘗試,才嘗試由目前bundle中加載,目前bundle加載的方法即從bundle-classpath或目前bundle的fragment中查找相應名稱的class檔案,并讀取該檔案進行加載,如class檔案已加載,則進行緩存,再次加載時則不需要查找和解析class檔案。
如從目前bundle中仍然未找到所需的類,則繼續下面的步驟。
6)嘗試從dynamicimport-package中加載
判斷需要找的類的package是否在dynamicimport-package中,如果在,則交由相應的packagesource進行加載,如packagesource中加載不到,則抛出classnotfoundexception;如不在dynamicimport-package中,則繼續下面的步驟。
7)再次嘗試調用equinox提供的classloaderdelegatehook的擴充來加載
這步和第2)步相同,是以在預設情況下繼續下面的步驟。
8)嘗試使用eclipse的buddy機制來加載
buddy機制是eclipse的擴充,并不符合osgi規範,是以在此不做深入分析。
9)判斷一定的條件,如符合則從parent classloader中加載
判斷的條件為:parent classloader不為null、不從parent classloader中加載、equinox的向後相容屬性(osgi.compatibility.bootdelegation)為true以及jvm的bug class,如滿足以上條件,則嘗試從parent classloader中加載。
如經過以上所有步驟後,仍然未找到需要加載的class,則抛出classnotfoundexception。
從上面的代碼分析中,在equinox中可以通過osgi.parentclassloader、org.osgi.framework.bootdelegation來控制從bundle classloader外來加載class,這對于內建equinox其他容器而言,非常有用,另外,還可以通過實作classloaderdelegatehook來改變class的加載。
本文出自seven的測試人生公衆号最新内容請見作者的github頁:http://qaseven.github.io/