首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 其他教程 > 操作系统 >

制造一个基于OSGi的Web Application

2012-08-15 
打造一个基于OSGi的Web Application动机和目标OSGi技术发展至今也有好几年了,然而除了在富客户端应用(以Ec

打造一个基于OSGi的Web Application
动机和目标

OSGi技术发展至今也有好几年了,然而除了在富客户端应用(以Eclipse为代表)和服务器应用(如大多数的应用服务器)方面大放光芒之外,在Web Application方面的应用和资料却少之又少。一方面,在OSGi规范中,对于Web应用方面的规划尚不成熟,即使在最新的4.2版中,也仅仅只有一个HttpService,这个简陋的service甚至不能覆盖任何一个现有的Servlet规范;另一方面,各个OSGi实现厂商对HttpService的实现也是不完全的,在开发实现一个常规的Web Application时,这些实现也是完全不够用的。本文章的目的,也就是为了探索OSGi在Web Application上的开发之路该如何走,从我的视角提出一些看法,做一些尝试,希望对大家有所帮助。

现在OSGi与Web Application的结合,大致有两个方向:

    OSGi包含Web Container:目前能完美嵌入OSGi的Web Container似乎只有jetty一个,tomcat的catalina似乎有希望能成为第二个。我们完全不能指望Websphere和Weblogic能在短期内具有能嵌入OSGi的能力,所以这个方向理所当然的被我放弃了。Web Container包含OSGi:这个方面目前只有equinox的Servlet Bridge这么一个著名的实现,equinox通过Servlet Bridge的方式来实现一个OSGi的HttpService服务,这个服务目前能做的事情还非常有限,还不足以覆盖Servlet规范。

我的目标是构建一个OSGi与Web Application结合的方式,它要能满足一下几点需求:

    基于OSGi的bundle和service。适合绝大对数支持Servlet 2.4和Jsp 2.0规范的Web服务器。适合现有的实现OSGi 4.2规范的OSGi Framework实现:equinox、felix和knopflerfish。支持大部分Servlet 2.4和Jsp 2.0规范中声明的功能。提供一个基于HttpService的服务实现,以此来兼容其他使用HttpService的service。

毫无疑问,我将采用Web Container中包含OSGi的方式来实现,具体的内容将在以后陆续提供。

?

搭建开发环境
环境整合:
1.运行Eclipse,指定Workspace路径为:D:/dbstar/workspaces/OSGi
2.设置Plug-in Development的Target Platform,增加equinox-SDK-3.6M5并设为默认,这样我们就可以使用equinox-SDK-3.6M5来作为我们开发bundle的基准库,而不是使用Eclipse自带的plugin开发环境。
制造一个基于OSGi的Web Application
3.在Server配置中增加Tomcat两个版本的服务器。
制造一个基于OSGi的Web Application
这个项目只作为部署Web Application使用,相关java代码放在另外一个Java Project中,因此我们再创建一个新的Java Project,名字为OSGi-Web-Launcher。然后在OSGi-Web项目的Java EE Module Dependencies中设置OSGi-Web-Launcher为关联,这样在部署的时候,OSGi-Web-Launcher项目中的java代码将为打包为jar存放到Web的WEB-INF/lib目录之中。

为了启动OSGi,我们在web中增加一个ServletContextListener监听器实现,并且通过这个监听器来控制OSGi容器的启动和终止。

在OSGi-Web-Launcher项目中增加一个java类,类名为FrameworkConfigListener,实现接口ServletContextListener,package为org.dbstar.osgi.web.launcher。在contextInitialized方法中,增加启动OSGi的代码,在contextDestroyed方法中,增加停止OSGi的代码,这样我们就可以使OSGi容器的生命周期与ServletContext的生命周期保持一致了。

启动OSGi容器:
感谢OSGi规范4.2给了我们一个简单统一的启动OSGi容器的方式,所有实现OSGi4.2规范的容器实力都应该实现这种启动方式,那就是通过org.osgi.framework.launch.FrameworkFactory,同时,还必须在其实现jar中放置一个文件:META-INF/services/org.osgi.framework.launch.FrameworkFactory,这个文件中设置了实际的FrameworkFactory实现类的类名。在equinox-SDK-3.6M5的org.eclipse.osgi_3.6.0.v20100128-1430.jar中,这个文件的内容是:org.eclipse.osgi.launch.EquinoxFactory。

我们先写一个工具类来载入这个配置文件中的内容:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1packageorg.dbstar.osgi.web.launcher;
2
3importjava.io.BufferedReader;
4importjava.io.IOException;
5importjava.io.InputStream;
6importjava.io.InputStreamReader;
7
8publicabstractclassServiceLoader{
9publicfinalstatic<E>Class<E>load(Class<E>clazz)throwsIOException,ClassNotFoundException{
10returnload(clazz,Thread.currentThread().getContextClassLoader());
11}
12
13@SuppressWarnings("unchecked")
14publicfinalstatic<E>Class<E>load(Class<E>clazz,ClassLoaderclassLoader)throwsIOException,
15ClassNotFoundException{
16Stringresource="META-INF/services/"+clazz.getName();
17InputStreamin=classLoader.getResourceAsStream(resource);
18if(in==null)returnnull;
19
20try{
21BufferedReaderreader=newBufferedReader(newInputStreamReader(in));
22StringserviceClassName=reader.readLine();
23return(Class<E>)classLoader.loadClass(serviceClassName);
24}finally{
25in.close();
26}
27}
28}


然后获取到FrameworkFactory的实例类:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1try{
2frameworkFactoryClass=ServiceLoader.load(FrameworkFactory.class);
3}catch(Exceptione){
4thrownewIllegalArgumentException("FrameworkFactoryserviceloaderror.",e);
5}
6if(frameworkFactoryClass==null){
7thrownewIllegalArgumentException("FrameworkFactoryservicenotfound.");
8}


实例化FrameworkFactory:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1FrameworkFactoryframeworkFactory;
2try{
3frameworkFactory=frameworkFactoryClass.newInstance();
4}catch(Exceptione){
5thrownewIllegalArgumentException("FrameworkFactoryinstantiationerror.",e);
6}


获取Framework的启动配置:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1Map<Object,Object>configuration;
2try{
3//载入Framework启动配置
4configuration=loadFrameworkConfig(event.getServletContext());
5if(logger.isInfoEnabled()){
6logger.info("LoadFrameworkconfiguration:[");
7for(Objectkey:configuration.keySet()){
8logger.info("/t"+key+"="+configuration.get(key));
9}
10logger.info("]");
11}
12}catch(Exceptione){
13thrownewIllegalArgumentException("LoadFrameworkconfigurationerror.",e);
14}


启动配置读取外部配置文件,可以在此配置文件中增加OSGi容器实现类相关的配置项,例如Equinox的osgi.console:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1//载入Framework启动配置
2privatestaticMap<Object,Object>loadFrameworkConfig(ServletContextcontext)throwsMalformedURLException{
3StringconfigLocation=context.getInitParameter(CONTEXT_PARAM_OSGI_CONFIG_LOCATION);
4if(configLocation==null)configLocation=DEFAULT_OSGI_CONFIG_LOCATION;
5elseif(!configLocation.startsWith("/"))configLocation="/".concat(configLocation);
6
7Propertiesconfig=newProperties();
8try{
9//载入配置项
10config.load(context.getResourceAsStream(configLocation));
11if(logger.isInfoEnabled())logger.info("LoadFrameworkconfigurationfrom:"+configLocation);
12}catch(IOExceptione){
13if(logger.isWarnEnabled())logger.warn("LoadFrameworkconfigurationerrorfrom:"+configLocation,e);
14}
15
16StringstorageDirectory=config.getProperty(PROPERTY_FRAMEWORK_STORAGE,DEFAULT_OSGI_STORAGE_DIRECTORY);
17//检查storageDirectory合法性
18if(storageDirectory.startsWith(WEB_ROOT)){
19//如果以WEB_ROOT常量字符串开头,那么相对于WEB_ROOT来定位
20storageDirectory=storageDirectory.substring(WEB_ROOT.length());
21storageDirectory=context.getRealPath(storageDirectory);
22}else{
23//如果是相对路径,那么相对于WEB_ROOT来定位
24if(!newFile(storageDirectory).isAbsolute()){
25storageDirectory=context.getRealPath(storageDirectory);
26}
27}
28storageDirectory=newFile(storageDirectory).toURL().toExternalForm();
29config.setProperty(PROPERTY_FRAMEWORK_STORAGE,storageDirectory);
30if(logger.isInfoEnabled())logger.info("UseFrameworkStorage:"+storageDirectory);
31
32returnconfig;
33}


然后,就可以获取framework实例了,通过framework来初始化,启动和停止OSGi容器:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1try{
2framework=frameworkFactory.newFramework(configuration);
3framework.init();
4
5//初始化Framework环境
6initFramework(framework,event);
7
8//启动Framework
9framework.start();
10
11succeed=true;
12}catch(BundleExceptione){
13thrownewOSGiStartException("StartOSGiFrameworkerror!",e);
14}catch(IOExceptione){
15thrownewOSGiStartException("InitOSGiFrameworkerror",e);
16}


在initFramework方法中,主要做两件事情,一是将当前的ServletContext作为一个service注册到OSGi容器中去:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1privatestaticvoidregisterContext(BundleContextbundleContext,ServletContextservletContext){
2Propertiesproperties=newProperties();
3properties.setProperty("ServerInfo",servletContext.getServerInfo());
4properties.setProperty("ServletContextName",servletContext.getServletContextName());
5properties.setProperty("MajorVersion",String.valueOf(servletContext.getMajorVersion()));
6properties.setProperty("MinorVersion",String.valueOf(servletContext.getMinorVersion()));
7bundleContext.registerService(ServletContext.class.getName(),servletContext,properties);
8}

第二件事就是:在第一次初始化容器时,加载并启动指定目录中的bundle:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1//初始化Framework环境
2privatestaticvoidinitFramework(Frameworkframework,ServletContextEventevent)throwsIOException{
3BundleContextbundleContext=framework.getBundleContext();
4ServletContextservletContext=event.getServletContext();
5
6//将ServletContext注册为服务
7registerContext(bundleContext,servletContext);
8
9Filefile=bundleContext.getDataFile(".init");
10if(!file.isFile()){//第一次初始化
11if(logger.isInfoEnabled())logger.info("InitFramework制造一个基于OSGi的Web Application");
12
13StringpluginLocation=servletContext.getInitParameter(CONTEXT_PARAM_OSGI_PLUGINS_LOCATION);
14if(pluginLocation==null)pluginLocation=DEFAULT_OSGI_PLUGINS_LOCATION;
15elseif(!pluginLocation.startsWith("/"))pluginLocation="/".concat(pluginLocation);
16
17//安装bundle
18FilebundleRoot=newFile(servletContext.getRealPath(pluginLocation));
19if(bundleRoot.isDirectory()){
20if(logger.isInfoEnabled())logger.info("LoadFrameworkbundlesfrom:"+pluginLocation);
21
22FilebundleFiles[]=bundleRoot.listFiles(newFilenameFilter(){
23publicbooleanaccept(Filedir,Stringname){
24returnname.endsWith(".jar");
25}
26});
27
28if(bundleFiles!=null&&bundleFiles.length>0){
29for(FilebundleFile:bundleFiles){
30try{
31bundleContext.installBundle(bundleFile.toURL().toExternalForm());
32if(logger.isInfoEnabled())logger.info("Installbundlesuccess:"+bundleFile.getName());
33}catch(Throwablee){
34if(logger.isWarnEnabled())logger.warn("Installbundleerror:"+bundleFile,e);
35}
36}
37}
38
39for(Bundlebundle:bundleContext.getBundles()){
40if(bundle.getState()==Bundle.INSTALLED||bundle.getState()==Bundle.RESOLVED){
41if(bundle.getHeaders().get(Constants.BUNDLE_ACTIVATOR)!=null){
42try{
43bundle.start(Bundle.START_ACTIVATION_POLICY);
44if(logger.isInfoEnabled())logger.info("Startbundle:"+bundle);
45}catch(Throwablee){
46if(logger.isWarnEnabled())logger.warn("Startbundleerror:"+bundle,e);
47}
48}
49}
50}
51}
52
53newFileWriter(file).close();
54if(logger.isInfoEnabled())logger.info("Frameworkinited.");
55}
56}


以上就是启动OSGi容器的过程,相比较而言,停止容器就简单多了:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1publicvoidcontextDestroyed(ServletContextEventevent){
2if(framework!=null){
3if(logger.isInfoEnabled())logger.info("StoppingOSGiFramework制造一个基于OSGi的Web Application");
4
5booleansucceed=false;
6try{
7if(framework.getState()==Framework.ACTIVE)framework.stop();
8framework.waitForStop(0);
9framework=null;
10
11succeed=true;
12}catch(BundleExceptione){
13thrownewOSGiStopException("StopOSGiFrameworkerror!",e);
14}catch(InterruptedExceptione){
15thrownewOSGiStopException("StopOSGiFrameworkerror!",e);
16}finally{
17if(logger.isInfoEnabled()){
18if(succeed)logger.info("OSGiFrameworkStopped!");
19elselogger.info("OSGiFrameworknotstop!");
20}
21}
22}
23}



最后,还有一件事情,就是将FrameworkConfigListener配置到web.xml中:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1<!--InitOSGiframework-->
2<listener>
3<listener-class>org.dbstar.osgi.web.launcher.FrameworkConfigListener</listener-class>
4</listener>


让我们来测试一下吧,在Eclipse中新建一个Server:
制造一个基于OSGi的Web Application
制造一个基于OSGi的Web Application

另外,在OSGi-Web-Launcher项目的classpath中增加org.eclipse.osgi_3.6.0.v20100128-1430.jar,并且在Java EE Module Dependencies中勾选这个jar,这样可以保证这个jar最终部署到Web Application的WEB-INF/lib目录下去。同样,还需要增加commons-logging.jar。

然后就可以启动这个Server查看效果了。

附上本文中提到的源代码。

?

为OSGi容器提供Web Application环境

本章叙述如何在OSGi容器中提供必要的Web Application环境,其中包括Servlet 2.4、Jsp 2.0和Commons-Logging相关的package,使得其他在OSGi容器中的bundle可以import。

为了在OSGi容器中提供export的package,一般有三种方式:

    一个常规的bundle,自身包含必要的class,同时在Export-Package中声明。一个Host为System Bundle的Fragment Bundle,同样也可以在Export-Package中声明导出的package,只要这个package中的class在System Bundle的ClassLoader中能load到。通过启动Framework的配置项:org.osgi.framework.system.packages和org.osgi.framework.system.packages.extra。OSGi 4.2规范中描述了这两个标准的配置项。在这两个配置项中描述的package都等同于在System Bundle中声明了export。


对于在Web Application中运行的OSGi容器,一些必要的环境是通过Web Container提供的,我们最好不要,也不应该用自己的类来替换,这包括了j2ee相关的jar,如servlet和jsp相关的jar等等。在一些WebServer的实现中,会自动屏蔽Web Application的classpath中的j2ee相关的jar。

除了j2ee相关的jar之外,还有一些使用非常普遍的jar,比如说Apache commons一类,其中最常用的大概就是commons-lang.jar、commons-io.jar和commons-logging.jar了,这些jar最好也有Web Container来提供,或者有必要的话,在Web Application中提供,而不是在OSGi容器中提供,这涉及到一些JVM层次的单例类,或者希望能由Web Application级别来统一实现和配置的环境,最常见的应用就是日志配置了。通过由Web Application提供的commons-logging来给OSGi容器中的环境使用,而commons-logging通过何种方式来实现,不需要让OSGi内部知道。

至于导出package到OSGi的方式中,是采用第二种还是第三种,主要区别在于:第三种方式是加载framework时指定的,在其后的生命周期中不可更改,而第二种方式则更符合OSGi动态加载的特性。

我采用第二种方式来给OSGi容器增加环境支持,具体操作很简单,以Servlet为例,首先编写一个文本文件,名字为:MANIFEST.MF,内容如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1Manifest-Version:1.0
2Bundle-ManifestVersion:2
3Bundle-Name:ServletExtensionFragment
4Bundle-SymbolicName:javax.servlet_extension;singleton:=true
5Bundle-Version:2.4.0
6Fragment-Host:system.bundle;extension:=framework
7Bundle-RequiredExecutionEnvironment:J2SE-1.5
8Export-Package:javax.servlet;version="2.4.0",
9javax.servlet.http;version="2.4.0",
10javax.servlet.resources;version="2.4.0"
11Bundle-Vendor:dbstar

注意其中关键的header属性,Fragment-Host: system.bundle; extension:=framework
这样写才能保证这个Fragment Bundle在各种OSGi Framework实现中都能兼容。
保存以后,将这个文件放置到一个名字为META-INF的目录中,然后用jar命令打包成一个jar即可(或者用winrar打包,记得选择压缩方式为zip,在打包后将zip后缀名改成jar,我通常都是这么干的)。

Jsp的MANIFEST.MF:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1Manifest-Version:1.0
2Bundle-ManifestVersion:2
3Bundle-Name:JspExtensionFragment
4Bundle-SymbolicName:javax.servlet.jsp_extension;singleton:=true
5Bundle-Version:2.0.0
6Bundle-Vendor:dbstar
7Fragment-Host:system.bundle;extension:=framework
8Bundle-RequiredExecutionEnvironment:J2SE-1.5
9Export-Package:javax.servlet.jsp;version="2.0.0",
10javax.servlet.jsp.el;version="2.0.0",
11javax.servlet.jsp.resources;version="2.0.0",
12javax.servlet.jsp.tagext;version="2.0.0"


commons-logging的MANIFEST.MF

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1Manifest-Version:1.0
2Bundle-ManifestVersion:2
3Bundle-Name:CommonsLoggingExtensionFragment
4Bundle-SymbolicName:org.apache.commons.logging_extension;singleton:=true
5Bundle-Version:1.1.1
6Bundle-Vendor:dbstar
7Fragment-Host:system.bundle;extension:=framework
8Bundle-RequiredExecutionEnvironment:J2SE-1.5
9Export-Package:org.apache.commons.logging;version="1.1.1",
10org.apache.commons.logging.impl;version="1.1.1"

因为我用的是commons-logging-1.1.1.jar,所以version写的是1.1.1,大家可以修改成自己所使用的jar的版本。


将上面生成的三个jar放到OSGi-Web项目的WEB-INF/osgi/plugins目录下面。还记得我在上一章创建的那个Tomcat Server么,clean一次,新的jar会部署到Tomcat中去,然后就可以运行Server了。
至于为什么是clean而不是publish,区别在于clean会清除所有OSGi容器创建出来的文件,这样下次启动OSGi时就会做一个install bundle的事情,而publish不会自动install新加进去的bundle。

如果你使用的是equinox,那么你可以在控制台中看到Syetem Bundle现在多了几个Fragments,查看一下Servlet Bundle,会显示下列信息,表示servlet 2.4的package在OSGi容器中已经可用了:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->osgi>bundle2
javax.servlet_extension_2.4.0[2]
Id=2,Status=RESOLVEDDataRoot=D:/dbstar/workspaces/OSGi/.metadata/.plugins/org.eclipse.wst.server.core/tmp0/wtpwebapps/OSGi-Web/WEB-INF/osgi/configuration/org.eclipse.osgi/bundles/2/data
Noregisteredservices.
Noservicesinuse.
Exportedpackages
javax.servlet;version="2.4.0"[exported]
javax.servlet.http;version="2.4.0"[exported]
javax.servlet.resources;version="2.4.0"[exported]
Noimportedpackages
Hostbundles
org.eclipse.osgi_3.6.0.v20100128-1430[0]
Nonamedclassspaces
Norequiredbundles
");
12
13StringpluginLocation=servletContext.getInitParameter(CONTEXT_PARAM_OSGI_PLUGINS_LOCATION);
14if(pluginLocation==null)pluginLocation=DEFAULT_OSGI_PLUGINS_LOCATION;
15elseif(!pluginLocation.startsWith("/"))pluginLocation="/".concat(pluginLocation);
16
17//安装bundle
18FilebundleRoot=newFile(servletContext.getRealPath(pluginLocation));
19if(bundleRoot.isDirectory()){
20if(logger.isInfoEnabled())logger.info("LoadFrameworkbundlesfrom:"+pluginLocation);
21
22//StartLevelService,用于设置bundle的startlevel
23ServiceReferenceslRef=bundleContext.getServiceReference(StartLevel.class.getName());
24StartLevelsl=slRef==null?null:(StartLevel)bundleContext.getService(slRef);
25//设置新bundle的初始startlevel为系统配置项:org.osgi.framework.startlevel.beginning的值
26Stringbsl=bundleContext.getProperty("org.osgi.framework.startlevel.beginning");
27if(bsl!=null&&isInteger(bsl))sl.setInitialBundleStartLevel(Integer.parseInt(bsl));
28
29//安装bundle并设置相应的startlevel
30FileslRoot=newFile(bundleRoot,"start");
31if(slRoot.isDirectory()){
32FileslDirs[]=slRoot.listFiles(newFileFilter(){
33publicbooleanaccept(Filefile){
34returnfile.isDirectory()&&isInteger(file.getName());
35}
36});
37
38for(FileslDir:slDirs){
39installBundles(bundleContext,slDir,sl,Integer.parseInt(slDir.getName()));
40}
41}
42
43//安装直属目录下面的bundle
44installBundles(bundleContext,bundleRoot,sl,0);
45
46for(Bundlebundle:bundleContext.getBundles()){
47if(bundle.getState()==Bundle.INSTALLED||bundle.getState()==Bundle.RESOLVED){
48if(bundle.getHeaders().get(Constants.BUNDLE_ACTIVATOR)!=null||bundle.getHeaders().get("Service-Component")!=null){
49try{
50bundle.start(Bundle.START_ACTIVATION_POLICY);
51if(logger.isInfoEnabled())logger.info("Startbundle:"+bundle);
52}catch(Throwablee){
53if(logger.isWarnEnabled())logger.warn("Startbundleerror:"+bundle,e);
54}
55}
56}
57}
58
59if(slRef!=null)bundleContext.ungetService(slRef);
60}
61
62newFileWriter(file).close();
63if(logger.isInfoEnabled())logger.info("Frameworkinited.");
64}
65}
使用Tomcat原生API来动态管理Web元素:原理

Tomcat的org.apache.catalina.Context接口提供了动态管理注入到Catalina Web Container中的Web元素的API。在基于OSGi的Web Application中,可以利用这个接口来实现在OSGi容器中动态管理Web元素的目的。为了达到这个目的,我们还需要做一些额外的配置。请注意,以下方法仅适用于Tomcat,并非通用的实现,而且只针对5.5.28版和6.0.24版的Tomcat做过简单的测试。


首先我们要做的事情,就是将Tomcat的org.apache.catalina.Context实现类作为Service注入到OSGi容器中去。在OSGi-Web工程的WebContent/META-INF目录中,增加一个context.xml文件,内容如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1<?xmlversion="1.0"encoding="UTF-8"?>
2<Contextprivileged="true"/>

这样我们就可以使用org.apache.catalina.ContainerServlet这个接口类了,通过它可以访问Catalina的内部功能,它有Catalina被类加载器加载,而不是我们的WebApplication类加载器。它的 Setter方法在这个Servlet的新的实例被放进Service时被执行。
接下来我们写一个Servlet,这个Servlet将实现ContainerServlet接口,请注意它是怎么工作的:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1packageorg.dbstar.osgi.web.launcher.tomcat;
2
3importjava.util.Properties;
4
5importjavax.servlet.ServletException;
6importjavax.servlet.UnavailableException;
7importjavax.servlet.http.HttpServlet;
8
9importorg.apache.catalina.ContainerServlet;
10importorg.apache.catalina.Context;
11importorg.apache.catalina.Wrapper;
12importorg.dbstar.osgi.web.launcher.FrameworkConfigListener;
13importorg.osgi.framework.BundleContext;
14importorg.osgi.framework.ServiceRegistration;
15importorg.osgi.framework.launch.Framework;
16
17publicfinalclassTomcatContextServletextendsHttpServletimplementsContainerServlet{
18privatestaticfinallongserialVersionUID=-3977062987005392657L;
19
20privateWrapperwrapper;
21privateContextcontext;
22
23privateServiceRegistrationregistration;
24
25publicWrappergetWrapper(){
26returnwrapper;
27}
28
29publicvoidsetWrapper(Wrapperwrapper){
30this.wrapper=wrapper;
31if(wrapper==null)context=null;
32elsecontext=(Context)wrapper.getParent();
33}
34
35@Override
36publicvoidinit()throwsServletException{
37//EnsurethatourContainerServletpropertieshavebeenset
38if((wrapper==null)||(context==null))thrownewUnavailableException("Wrappernotset.");
39
40//EnsurethatFrameworkhavebeenset
41Frameworkframework=FrameworkConfigListener.getFramework();
42if(framework==null)thrownewUnavailableException("Frameworknotset.");
43
44//将context注册为服务
45registration=registerContext(framework.getBundleContext(),context);
46}
47
48privatestaticServiceRegistrationregisterContext(BundleContextbundleContext,Contextcontext){
49Propertiesproperties=newProperties();
50properties.setProperty("DisplayName",context.getDisplayName());
51properties.setProperty("ContextPath",context.getPath());
52returnbundleContext.registerService(Context.class.getName(),context,properties);
53}
54
55@Override
56publicvoiddestroy(){
57if(registration==null)return;
58
59Frameworkframework=FrameworkConfigListener.getFramework();
60if(framework==null)return;
61
62if(framework.getState()==Framework.ACTIVE)registration.unregister();
63registration=null;
64}
65}

通过ContainerServlet接口提供的setWrapper方法,我们获得了一个Wrapper实例,这个实例对应于TomcatContextServlet部署到Tomcat中的封装类,通过其getParent方法我们就可以获得Servlet所在的Context了。
接下来在init方法中,我们将获得的Context实例,通过Framework注册到OSGi容器中去。在destroy方法中,注销Context的注册,这样形成了一个完整的生命周期。
然后,将这个TomcatContextServlet部署到web.xml中去:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1<!--registeraorg.apache.catalina.ContexttoOSGiContainer-->
2<servlet>
3<servlet-name>TomcatContextServlet</servlet-name>
4<servlet-class>org.dbstar.osgi.web.launcher.tomcat.TomcatContextServlet</servlet-class>
5<load-on-startup>1</load-on-startup>
6</servlet>

设置<load-on-startup>使这个Servlet在WebContainer初始化时加载,否则它将没有加载的机会,因为我们在应用中不会直接使用到这个Servlet。

最后还有一件事情不要忘记了,我们需要将org.apache.catalina及其相关的package export到OSGi容器中去,这样才能在OSGi容器中供给bundle来import。参照《打造一个基于OSGi的Web Application——为OSGi容器提供Web Application环境》一文中提到的方式,我们将catalina.jar作为extension Fragment的方式,引入到OSGi容器中去。
Catalina的MANIFEST.MF:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1Manifest-Version:1.0
2Bundle-ManifestVersion:2
3Bundle-Name:CatalinaExtensionFragment
4Bundle-SymbolicName:org.apache.catalina_extension;singleton:=true
5Bundle-Version:5.5.28
6Bundle-Vendor:dbstar
7Fragment-Host:system.bundle;extension:=framework
8Bundle-RequiredExecutionEnvironment:J2SE-1.5
9Export-Package:org.apache.catalina,org.apache.catalina.authenticator,
10org.apache.catalina.connector,org.apache.catalina.core,org.apache.cat
11alina.deploy,org.apache.catalina.loader,org.apache.catalina.mbeans,or
12g.apache.catalina.realm,org.apache.catalina.security,org.apache.catal
13ina.session,org.apache.catalina.startup,org.apache.catalina.users,org
14.apache.catalina.util,org.apache.catalina.valves

在接下来的章节中,我会逐一描述如何在基于Tomcat的OSGi容器中,如何实现各种Web元素的动态管理,尽请期待哦:)

最后提供几个本章提到的bundle给大家下载,大家就不用自己再起生成一个了。
org.apache.catalina_extension_5.5.28.jar

热点排行