Pushlets是在類名為Pushlet的servlet的init方法中進行初始化的。一般我們會在web.xml配置pushlet的時候,指定其servlet在Web應用啟動時就進行初始化,即便這樣,還是有可能初始化失敗,導致整個Pushlets失效。Pushlet.init代碼如下:

1 public void init() 2 throws ServletException 3 { 4 try 5 { 6 String webInfPath = getServletContext().getRealPath("/") + "/WEB-INF"; 7 Config.load(webInfPath); 8 9 Log.init(); 10 11 Log.info("init() Pushlet Webapp - version=" + Version.SOFTWARE_VERSION + " built=" + Version.BUILD_DATE); 12 13 SessionManager.getInstance().start(); 14 15 Dispatcher.getInstance().start(); 16 17 if (Config.getBoolProperty("sources.activate")) 18 EventSourceManager.start(webInfPath); 19 else 20 Log.info("Not starting local event sources"); 21 } 22 catch (Throwable t) { 23 throw new ServletException("Failed to initialize Pushlet framework " + t, t); 24 } 25 }
Pushlet類的初始化方法首先會加載Pushlets配置和日志打印組件。其首先調用的Config.load()方法會在class path及WEB-INF中查找pushlet.properties配置文件,並加載所有配置項。這一步至關重要,因為Pushlet會根據配置項決定如何實例化controller、dispatcher、session manager、session、subscriber、subscription這些核心類以及日志類,默認的配置指向這些類的默認實現。事實上,Pushlets正是通過pushlet配置文件來實現其擴展性和靈活性的。如果Pushlets的默認實現無法滿足你的業務要求,你可以添加自己的實現,只要繼承自Pushlet默認的實現,然后重寫其中的某個方法即可。
舉個例子,如果要實現Pushlet點對點推送消息,需要知道被推送的對象,因而需要“固定”住Pushlet產生的pushlet session id(有別於web的session id,是有pushlet自己產生和維護的,默認的實現是一串隨機字符串),在網上搜索的有些實現是直接修改Pushlets的源代碼直接改寫SessionManager的createSession來生成跟用戶ID強綁定的pushlet session id,例如直接使用用戶ID作為pushlet session id,實際上不需要修改Pushlets的源碼這么暴力,可以繼承SessionManager實現自己的MySessionManager並重寫createSessionId方法來返回用戶ID,然后在pushlet.properties配置文件中將sessionmanager.class=nl.justobjects.pushlet.core.SessionManager配置項改寫為我們自己的實現例如com.test.MySessionManager。這樣的實現方式可以比較好的將我們自己的業務代碼與開源組件的代碼分離,至少在Pushlets.jar版本升級的時候(為了使用新功能或修復bug),能夠省去不少合並代碼的麻煩。
回頭來說Pushlets的初始化陷阱,Dispatcher提供broadcast、multicast、unicast來進行消息的廣播、多播和單播,這都會調用到SessionManager類。而Dispatcher和SessionManager都是單例實現,通過getInstance來獲取自身對象的引用,它是采用一段靜態代碼來實現單例創建的,SessionManager的實例化代碼如下
static { try { instance = (SessionManager)Config.getClass("sessionmanager.class", "nl.justobjects.pushlet.core.SessionManager").newInstance(); Log.info("SessionManager created className=" + instance.getClass()); } catch (Throwable t) { Log.fatal("Cannot instantiate SessionManager from config", t); } }
這段代碼只會在程序第一次引用SessionManager時執行一次,如果我們的業務代碼在調用Dispatcher類的相應方法執行消息推送(不管是廣播、多播還是單播)的時候,系統尚未調用Pushlet.init方法初始化Pushlets,那么就會因為配置尚未加載而造成instance對象創建失敗,其后整個Pushlets都是癱瘓的,雖然應用程序能夠成功啟動,但消息推送的功能就是失效的。例如,我們采用Spring框架的時候,一般是配置org.springframework.web.context.ContextLoaderListener來裝載Spring框架,這樣一來Spring框架會在Pushlet servlet之前初始化完成。在這個空擋如果業務邏輯觸發了Pushlets消息推送,就將導致Dispatcher、SessionManager類的靜態代碼塊被執行。雖然隨后Pushlet.init方法會加載配置項,但關於如何創建核心類實例的配置項將不會再被使用。