Eclipse插件Lazy Start實(shí)現(xiàn)原理分析
朱興(zhu_xing@live.cn)
每次提到有關(guān)Eclipse插件啟動(dòng)的問(wèn)題的時(shí)候,腦子中自然的反應(yīng)就是:可以設(shè)定為預(yù)先啟動(dòng)(org.eclipse.ui.startup),否則默認(rèn)的情況下是懶啟動(dòng)(Lazy Start),只有當(dāng)插件中的功能被真正調(diào)用的時(shí)候,插件才會(huì)被啟動(dòng)。可能是人也跟著變懶了,也一直沒(méi)有去留心Eclipse底層是怎么實(shí)現(xiàn)這種懶加載的,只是有個(gè)大致的猜測(cè),估計(jì)又是用hook機(jī)制了。昨天閑著具體看了一下實(shí)現(xiàn),果然是類似的實(shí)現(xiàn)。下面就大致和大家分享一下,說(shuō)的不一定準(zhǔn)確,僅供參考 ~_~。
直接進(jìn)入主題,我們的Eclipse實(shí)例啟動(dòng)肯定要構(gòu)造工作區(qū),那么ResourcesPlugin肯定會(huì)被啟動(dòng),我們就在ResourcesPlugin.startup方法設(shè)置一個(gè)斷點(diǎn),調(diào)試棧如下:

假設(shè)我們對(duì)插件類型加載細(xì)節(jié)不知道,猜測(cè)大致過(guò)程如下:
1、 DefaultClassLoader加載類型(org.eclipse.core.resources.IContainer)
2、EclipseLazyStarter.preFindLocalClass
3、 啟動(dòng)資源插件:ResourcesPlugin.startup
補(bǔ)充說(shuō)明:
1、 org.eclipse.osgi.internal.baseadaptor.DefaultClassLoader是Eclipse針對(duì)OSGI類加載實(shí)現(xiàn)的核心角色,也是eclipse插件默認(rèn)的類加載器類型,當(dāng)然,每個(gè)插件有自己獨(dú)立的類加載器實(shí)例來(lái)負(fù)責(zé)類型加載。
2、 DefaultClassLoader、BundleLoader、ClasspathManager三者協(xié)作,處理類型加載請(qǐng)求(為什么一個(gè)類加載過(guò)程要搞的這么復(fù)雜呢?Eclipse的考慮是什么呢? 大家思考吧~_~)
【EclipseLazyStarter調(diào)用分析】
我們先大致看一下EclipseLazyStarter.preFindLocalClass方法的代碼實(shí)現(xiàn):
1 public class EclipseLazyStarter implements ClassLoadingStatsHook, HookConfigurator {
2 public void preFindLocalClass(String name, ClasspathManager manager) throws ClassNotFoundException {
3 //首先判斷,如果不需要啟動(dòng)則返回
4
5 //如果插件正在啟動(dòng),則設(shè)定5000ms超時(shí)等待;如果超時(shí),直接報(bào)錯(cuò)返回
6
7 //啟動(dòng)插件
8 }
9 }
加載類型之前為什么要給回調(diào)一下EclipseLazyStarter. preFindLocalClass,又hook了?我們看了一下EclipseLazyStarter繼承了ClassLoadingStatsHook接口,ClassLoadingStatsHook接口的類型API文檔說(shuō)明了它的作用:
A ClassLoadingStatsHook hooks into the <code>ClasspathManager</code> class.
追蹤前面的調(diào)用棧,ClassLoadingStatsHook是在ClasspathManager.
findLocalClass中被調(diào)用的:
1 public Class findLocalClass(String classname) throws ClassNotFoundException {
2 Class result = null;
3 ClassLoadingStatsHook[] hooks = data.getAdaptor().getHookRegistry().getClassLoadingStatsHooks();
4 try {
5 for (int i = 0; i < hooks.length; i++)
6 hooks[i].preFindLocalClass(classname, this);
7 result = findLocalClassImpl(classname, hooks);
8 return result;
9 } finally {
10 for (int i = 0; i < hooks.length; i++)
11 hooks[i].postFindLocalClass(classname, result, this);
12 }
13 }
再接著往下看之前,我們大致已經(jīng)知道來(lái)的Eclipse的插件lazy start是怎么回事了:
EclipseLazyStarter hook到了插件類加載器的類型加載過(guò)程中了,在類型被加載之前會(huì)回調(diào)EclipseLazyStarter. preFindLocalClass方法:如果類型所在插件還沒(méi)啟動(dòng),啟動(dòng)它;如果正在啟動(dòng),則設(shè)置5000ms的超時(shí),限時(shí)不能完成啟動(dòng),則報(bào)錯(cuò)返回!
(附加說(shuō)明:頭一段時(shí)間在另外一篇隨筆中,寫了一些編寫插件啟動(dòng)類應(yīng)該注意的點(diǎn),其中有一條就是避免在插件啟動(dòng)方法中干耗時(shí)的事情。這里真正告訴我們了原因:如果超過(guò)5000ms不能完成啟動(dòng)--注意這其中還不包含所依賴插件的啟動(dòng)時(shí)間,那么肯定會(huì)出現(xiàn)類加載超時(shí)的錯(cuò)誤了:
While loading class "{1}", thread "{0}" timed out waiting ({4}ms) for thread "{2}" to finish starting bundle "{3}". To avoid deadlock, thread "{0}" is proceeding but "{1}" may not be fully initialized.
)
【EclipseLazyStarter是如何完成注冊(cè)過(guò)程的?】

過(guò)程簡(jiǎn)要解釋如下:
1、啟動(dòng)osgi framework,兩種啟動(dòng)方式:如果想利用Eclipse的一些特性,則就以EclipseStarter為入口點(diǎn)啟動(dòng);否則,可以用命令行的方式,以Laucher.main為入口點(diǎn)啟動(dòng)
2、初始化FrameworkAdaptor(對(duì)應(yīng)eclipse實(shí)現(xiàn)是BaseAdaptor)看一下接口說(shuō)明:
/**
* FrameworkAdaptor interface to the osgi framework. This class is used to provide
* platform specific support for the osgi framework.
*
* <p>The OSGi framework will call this class to perform platform specific functions.
*
* Classes that implement FrameworkAdaptor MUST provide a constructor that takes as a
* parameter an array of Strings. This array will contain arguments to be
* handled by the FrameworkAdaptor. The FrameworkAdaptor implementation may define the format
* and content of its arguments.
*
* The constructor should parse the arguments passed to it and remember them.
* The initialize method should perform the actual processing of the adaptor
* arguments.
* <p>
* Clients may implement this interface.
* </p>
* @since 3.1
*/
顯而易見(jiàn),F(xiàn)rameworkAdaptor其實(shí)是osgi framework的后門,提供平臺(tái)附加支持。
看一下BaseAdaptor的構(gòu)造函數(shù):
1 /**
2 * Constructs a BaseAdaptor.
3 * @param args arguments passed to the adaptor by the framework.
4 */
5 public BaseAdaptor(String[] args) {
6 if (LocationManager.getConfigurationLocation() == null)
7 LocationManager.initializeLocations();
8 hookRegistry = new HookRegistry(this);
9 FrameworkLogEntry[] errors = hookRegistry.initialize();
10 if (errors.length > 0)
11 for (int i = 0; i < errors.length; i++)
12 getFrameworkLog().log(errors[i]);
13 // get the storage after the registry has been initialized
14 storage = getStorage();
15 // TODO consider passing args to BaseAdaptorHooks
16 }
我們看到,調(diào)用了HookRegistry.initialize進(jìn)行初始化
3、初始化HookRegistry,我們直接看一下HookRegistry.initialize方法實(shí)現(xiàn)
1 /**
2 * Initializes the hook configurators. The following steps are used to initialize the hook configurators. <p>
3 * 1. Get a list of hook configurators from all hook configurators properties files on the classpath,
4 * add this list to the overall list of hook configurators, remove duplicates. <p>
5 * 2. Get a list of hook configurators from the ("osgi.hook.configurators.include") system property
6 * and add this list to the overall list of hook configurators, remove duplicates. <p>
7 * 3. Get a list of hook configurators from the ("osgi.hook.configurators.exclude") system property
8 * and remove this list from the overall list of hook configurators. <p>
9 * 4. Load each hook configurator class, create a new instance, then call the {@link HookConfigurator#addHooks(HookRegistry)} method <p>
10 * 5. Set this HookRegistry object to read only to prevent any other hooks from being added. <p>
11 * @return an array of error log entries that occurred while initializing the hooks
12 */
13 public FrameworkLogEntry[] initialize() {
14 ArrayList configurators = new ArrayList(5);
15 ArrayList errors = new ArrayList(0); // optimistic that no errors will occur
16 mergeFileHookConfigurators(configurators, errors);
17 mergePropertyHookConfigurators(configurators);
18 loadConfigurators(configurators, errors);
19 // set to read-only
20 readonly = true;
21 return (FrameworkLogEntry[]) errors.toArray(new FrameworkLogEntry[errors.size()]);
22 }
其中的mergeFileHookConfigurators方法調(diào)用,讀取了一個(gè)名為hookconfigurators.properties的屬性配置文件,在org.eclipse.osgi插件中??匆幌吕锩娴膬?nèi)容:
1 ###############################################################################
2 # Copyright (c) 2005, 2006 IBM Corporation and others.
3 # All rights reserved. This program and the accompanying materials
4 # are made available under the terms of the Eclipse Public License v1.0
5 # which accompanies this distribution, and is available at
6 # http://www.eclipse.org/legal/epl-v10.html
7 #
8 # Contributors:
9 # IBM Corporation - initial API and implementation
10 ###############################################################################
11 hook.configurators= \
12 org.eclipse.osgi.internal.baseadaptor.BaseHookConfigurator,\
13 org.eclipse.osgi.internal.baseadaptor.DevClassLoadingHook,\
14 org.eclipse.core.runtime.internal.adaptor.EclipseStorageHook,\
15 org.eclipse.core.runtime.internal.adaptor.EclipseLogHook,\
16 org.eclipse.core.runtime.internal.adaptor.EclipseErrorHandler,\
17 org.eclipse.core.runtime.internal.adaptor.EclipseAdaptorHook,\
18 org.eclipse.core.runtime.internal.adaptor.EclipseClassLoadingHook,\
19 org.eclipse.core.runtime.internal.adaptor.EclipseLazyStarter,\
20 org.eclipse.core.runtime.internal.stats.StatsManager,\
21 org.eclipse.osgi.internal.verifier.SignedBundleHook
22
^_^,我們的EclipseLazyStarter赫然在列?。?!
回過(guò)頭來(lái)看一下EclipseLazyStarter(繼承ClassLoadingStatsHook)的使用方式:
BaseAdaptor.getHookRegistry().getClassLoadingStatsHooks()
前面已經(jīng)看了ClasspathManager中findLocalClass方法的代碼,就是這么調(diào)用ClassLoadingStatsHook policy的(我們的EclipseLazyStarter...)
【總結(jié)】
hook了,osgi framework留了個(gè)后門,Eclipse好好的利用了這個(gè)后門~_~
【附加說(shuō)明】
1、EclipseLazyStarter只是ClassLoadingStatsHook policy的實(shí)現(xiàn),其實(shí)HookRegsitry中還有其他的hook policy,例如:
org.eclipse.osgi.baseadaptor.hooks.ClassLoadingHook
org.eclipse.osgi.baseadaptor.hooks.BundleFileWrapperFactoryHook
org.eclipse.osgi.baseadaptor.hooks.BundleFileFactoryHook
org.eclipse.osgi.baseadaptor.hooks.StorageHook
org.eclipse.osgi.baseadaptor.hooks.AdaptorHook
2、大家可以順帶詳細(xì)的看一下HookRegistry、HookConfigurator、BaseAdaptor等
3、hook這種手法在Eclipse的資源管理中也有比較成功的應(yīng)用,可以看一下
org.eclipse.core.resources.team.IMoveDeleteHook
例如cvs、ClearCase等團(tuán)隊(duì)開(kāi)發(fā)管理工具中,都實(shí)現(xiàn)了這種hook,通過(guò)擴(kuò)展點(diǎn)org.eclipse.core.resources.moveDeleteHook動(dòng)態(tài)掛入。大家有興趣可以深入看看,看過(guò)之后應(yīng)該就明白了為什么cvs、ClearCase等一些團(tuán)隊(duì)開(kāi)發(fā)管理工具功能有一些不同了~_~
4、對(duì)osgi感興趣的同學(xué),可以看一下org.eclipse.osgi插件中的代碼,質(zhì)量很高~_~
亂轟轟的,湊合著看吧
本博客中的所有文章、隨筆除了標(biāo)題中含有引用或者轉(zhuǎn)載字樣的,其他均為原創(chuàng)。轉(zhuǎn)載請(qǐng)注明出處,謝謝!