本文是之前寫(xiě)的Developing Equinox/Spring-osgi/Spring Framework Web Application系列的升級(jí)版,Tomcat-OSGi的基礎(chǔ)Demo之一,主要演示傳統(tǒng)web application到OSGi application的轉(zhuǎn)換,由于是升級(jí)版,所以本文的側(cè)重點(diǎn)不再是基礎(chǔ)配置的演示。
一、準(zhǔn)備工作
1,JDK 1.6
2,Eclipse 3.4-jee
3,Spring-framework-2.5.6
4,spring-osgi-1.2.0
5, org.eclipse.equinox源碼,可從 :pserver:anonymous@dev.eclipse.org:/cvsroot/rt 中獲得
二、顯示首頁(yè)中的幾個(gè)問(wèn)題
1. ClassNotFoundException: org.springframework.web.servlet.view.InternalResourceViewResolver
META-INF/dispatcher/petstore-servlet.xml中定義的bean:





在之前的版本中,這里是沒(méi)有問(wèn)題的,可是在spring-osgi-1.2.0中,卻會(huì)有這個(gè)問(wèn)題,這是因?yàn)槿鄙僖粋€(gè)

為何要使用動(dòng)態(tài)引入?
因?yàn)闊o(wú)法在spring-beans中import定義的bean,因此如果不使用動(dòng)態(tài)引入,那么spring-beans就無(wú)法load定義的bean,而下面統(tǒng)一使用spring-core中的ClassUtils.forName來(lái)查找bean class,是一個(gè)非常好的做法。
spring-beans中是這樣load一個(gè)bean的

Thread.currentThread().getContextClassLoader()中的ClassLoader是org.eclipse.core.runtime.internal.adaptor.ContextFinder,它是osgi framework的classloader,它通過(guò)查找類(lèi)調(diào)用堆中距離本次loadClass調(diào)用最近的DefaultClassLoader(bundle的classloader)去加載一個(gè)類(lèi)。
DefaultClassLoader中封裝了ClassLoaderDelegate(BundleLoader)查找類(lèi)的過(guò)程
spring-beans加載bean class的過(guò)程:
讀取配置文件 -> 發(fā)現(xiàn)一個(gè)bean配置 -> 通過(guò)ClassUtils加載 -> 使用Thread.currentThread().getContextClassLoader()加載bean class
ContextFinder加載bean class的過(guò)程:
從類(lèi)調(diào)用堆中找到距離最近DefaultClassLoader,使用ClassUtils的DefaultClassLoader來(lái)加載bean class -> 使用ClassUtils所在bundle的BundleLoader去查找一個(gè)類(lèi)
BundleLoader加載bean class的過(guò)程在OSGi規(guī)范中有比較詳細(xì)的介紹,這里主要看一下動(dòng)態(tài)引入

















現(xiàn)在,spring-bean是如何加載一個(gè)bean的過(guò)程就變得非常明了了,ClassUtils在spring-core中,當(dāng)使用spring-core的BundleLoader去加載一個(gè)bean class時(shí),如果沒(méi)有動(dòng)態(tài)引入,則會(huì)出現(xiàn)找不到class的情況。
很明顯,spring-osgi-1.2.0中的spring-core并沒(méi)有配置動(dòng)態(tài)引入,在這個(gè)版本中或許是通過(guò)操作classloader來(lái)實(shí)現(xiàn)bean的加載,這個(gè)沒(méi)有研究。
同理,對(duì)于數(shù)據(jù)庫(kù)驅(qū)動(dòng)找不到的問(wèn)題,也可以這樣來(lái)解決。
2. 找不到tld
index.jsp中包含了2個(gè)標(biāo)簽庫(kù),在上一個(gè)版本中,將其放入/web/WEB-INF目錄中就可以正常顯示,可是在新版本中卻不行。
當(dāng)一個(gè)對(duì)jsp的請(qǐng)求到達(dá)時(shí),先將jsp生稱(chēng)java文件,之后進(jìn)行編譯。而生稱(chēng)java文件時(shí),需要處理tld資源。
tld資源路徑的處理是由TldLocationsCache來(lái)完成的,當(dāng)它第一次初始化時(shí),會(huì)在 "/WEB-INF/",classpath中的jar包,web.xml中查找tld文件并緩存起來(lái)。












這里主要看一下為什么這個(gè)版本中直接將tld文件放入/web/WEB-INF目錄中會(huì)提示找不到tld
processWebDotXml()方法是處理web.xml的
scanJars()是處理classpath資源
processTldsInFileSystem("/WEB-INF/"); 是查找web-inf目錄中的tld (這個(gè)方法在equinox部分的實(shí)現(xiàn)上行不通)
在方法processTldsInFileSystem("/WEB-INF/")中使用的是當(dāng)前Servlet的ServletContext.getResourcePaths()方法來(lái)獲取web-inf目錄中的tld資源,注冊(cè)equinox-JspServlet的過(guò)程中,做了2層封裝,ServletRegistration和org.eclipse.equinox.jsp.jasper.JspServlet,分別生成了2個(gè)ServletConfig和ServletContext,大概過(guò)程如下:
ProxyServlet:















equinox-JspServlet:









那么processTldsInFileSystem("/WEB-INF/")方法中的ServletContext就是從equinox-JspServlet$ServletContextAdaptor開(kāi)始的













代碼中的bundleResourcePath,就是注冊(cè)這個(gè)Servlet填寫(xiě)的alias——/web/jsp
現(xiàn)在,已經(jīng)了解如何查找tld了,只要將/WEB-INF目錄放入/web/jsp中就可以了或者注冊(cè)servlet的時(shí)候這樣寫(xiě):


為何上一個(gè)版本可以呢?時(shí)間距離太遠(yuǎn),也不太好找源碼,所以沒(méi)有研究這部份,我猜測(cè)應(yīng)該是在scanJars()方法中,通過(guò)classloader的URLs遍歷來(lái)獲取的,
equinox在處理相同HttpContext的ServletContext時(shí),只是將attributes共享,而并沒(méi)有共享資源訪問(wèn),在這個(gè)例子中,應(yīng)該是將相同HttpContext中的資源遍歷,在Tomcat-OSGi中,使用的是naming.DirContext去處理資源的查找。
3. SpringMVC中的Controller 的問(wèn)題
在上一個(gè)版本中,對(duì)于無(wú)法找到Controller的問(wèn)題,是通過(guò)BundleContextAware來(lái)解決的,因?yàn)樗窃赟pring-OSGi中完成,因此其本質(zhì)就是修改ClassLoader來(lái)解決的。
而更好的解決辦法其實(shí)是export controller所在的package,使用動(dòng)態(tài)引入功能,因?yàn)镾pring-bean都是通過(guò)Spring-core中的ClassUtils.forName來(lái)查找的。
4. DispatcherServlet中的URI-Bean與osgi-bean引用的問(wèn)題

dispatcherServlet.setContextConfigLocation("META-INF/dispatcher/petstore-servlet.xml");
DispatcherServlet讀取配置文件中的bean,存放于DispatcherServlet的ApplicationContext的BeanFactory中,某個(gè)bean需要使用到OSGi的bean 引用時(shí),例如:






可以看到/shop/viewCategory.do是一個(gè)bean,它被保存在DispatcherServlet的ApplicationContext中,
osgi bean引用petStoreOsgi是存放在bundle的ApplicationContext中,這2個(gè)ApplicationContext并沒(méi)有關(guān)聯(lián),因此無(wú)法找到。
這個(gè)兩個(gè)ApplicationContext的創(chuàng)建順序是這樣的:
1. 程序中注冊(cè)DispatcherServlet后,它被初始化時(shí)創(chuàng)建ApplicationContext,加載contextConfigLocation中定義的bean
2. Spring-OSGi當(dāng)監(jiān)聽(tīng)到bundle started的事件時(shí),為該bundle創(chuàng)建ApplicationContext,加載bundle中/META-INF/spring/*.xml中定義的bean
因此,這個(gè)問(wèn)題的解決辦法就是,讓DispatcherServlet的ApplicationContext在bundle的ApplicationContext之后創(chuàng)建,并設(shè)置成parent-child關(guān)系
實(shí)際上這個(gè)問(wèn)題應(yīng)該是由mvc框架去考慮的,在Spring-OSGi的文檔中有解決方法,它是通過(guò)OsgiBundleXmlWebApplicationContext來(lái)實(shí)現(xiàn)的,也就是說(shuō)它無(wú)法在本例中使用,因?yàn)楫?dāng)DispatcherServlet被初始化時(shí),使用的Equinox的ServletConfig。
> 如何讓bundle的ApplicationContext成為DispatcherServlet的ApplicationContext的parent?
在DispatcherServlet的ApplicationContext在創(chuàng)建時(shí)的部分代碼如下:
FrameworkServlet.java

















WebApplicationContextUtils.java













可以看到,使用的是ServletContext的屬性來(lái)存放ApplicationContext,因此在ApplicationContextAware.setApplicationContext(ApplicationContext bundleApplicationContext)中,可以通過(guò)下面的代碼來(lái)設(shè)置:




















讓tmpHttpServlet和DispatcherServlet具有相同的HttpContext,那么DispatcherServlet就可以得到parent-ApplicationContext了。
>如何讓DispatcherServlet的ApplicationContext在bundle的ApplicationContext之后創(chuàng)建?
按照上一個(gè)版本中的方法,使用spring中的ApplicationContextAware接口,在這個(gè)接口的setParentApplicationContext方法之后,進(jìn)行資源注冊(cè)。
這里需要注意的是在bundle停止時(shí)注銷(xiāo)注冊(cè)的資源
以上幾點(diǎn)基本就是在新版中遇到的問(wèn)題。
Demo下載: http://extwind.googlecode.com/svn/JPetStoreOSGi_Workspace.rar
如何使用這個(gè)Demo:
建議新建一個(gè)workspace,java編譯器需要6.0版本
將所有的bundles導(dǎo)入后,需要再將 org.extwind.osgi.demo.jpetstoreosgi.launcher 導(dǎo)入
本例中不包含DB數(shù)據(jù),因此還需要準(zhǔn)備Spring 2.5.6中的jpetstore
運(yùn)行 spring-framework-2.5.6\samples\jpetstore\db\hsqldb\server.bat
在Eclipse中運(yùn)行 org.extwind.osgi.demo.jpetstoreosgi.launcher.Launcher
訪問(wèn)首頁(yè)地址:http://localhost/shop/index.do
-----------------------------------------------------------------------------------------------------------
稍后將介紹如何在Tomcat-OSGi中使用JPetStoreOSGi