打造一个基于OSGi的Web Application

               

动机和目标

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

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

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

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

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

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

 

搭建开发环境

工欲善其事必先利其器,在正式开发之前,花一点时间来构建开发环境还是有必要的。本章介绍一下我的开发环境。

我使用的开发环境如下:

  1. Eclipse:当然了,最新版3.52,其中包含了最新版的WTP(Eclipse Web Tools Platform),个人感觉,不比MyEclipse差,而且最重要的是,它是free的。
  2. equinox-SDK:版本为3.6M5,实现了OSGi R4 core framework specification 4.2。
  3. Tomcat:作为第一个实现的Web Container,我采用了Tomcat,从中抽取几个特定版本作为测试对象:5.5.28和6.0.26这两个版本,因为他们支持Java5和Servlet2.4/Jsp2.0。
  4. JDK:当然Java5以上的,谁叫Equinox只支持Java5以上的呢,我采用的是jdk1.5.0.22。基于Websphere和Weblogic的缓慢的JDK升级历程,我还是决定不采用Java6或者是7了。

以下是我的目录结构:
打造一个基于OSGi的Web Application
环境整合:
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

自此,我的开发环境就已经设置好了,当然了,还有一些其他的个人习惯设置,比如说字体,默认编码设为UTF-8,Code Template和Formatter等等,就不一一赘述了。

在下面一篇中,将介绍如何在Web Application中启动OSGi。

 

在WebApplication中启动OSGi

本章将创建一个Web Application项目,并描述如何在此应用中启动OSGi。

首先,在Eclipse中创建一个Dynamic Web Project,名字为OSGi-Web,Context root为osgi。
打造一个基于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 />--> 1 package org.dbstar.osgi.web.launcher;
 2 
 3 import java.io.BufferedReader;
 4 import java.io.IOException;
 5 import java.io.InputStream;
 6 import java.io.InputStreamReader;
 7 
 8 public abstract class ServiceLoader {
 9     public final static <E> Class<E> load(Class<E> clazz) throws IOException, ClassNotFoundException {
10         return load(clazz, Thread.currentThread().getContextClassLoader());
11     }
12 
13     @SuppressWarnings("unchecked")
14     public final static <E> Class<E> load(Class<E> clazz, ClassLoader classLoader) throws IOException,
15             ClassNotFoundException {
16         String resource = "META-INF/services/" + clazz.getName();
17         InputStream in = classLoader.getResourceAsStream(resource);
18         if (in == nullreturn null;
19 
20         try {
21             BufferedReader reader = new BufferedReader(new InputStreamReader(in));
22             String serviceClassName = reader.readLine();
23             return (Class<E>) classLoader.loadClass(serviceClassName);
24         } finally {
25             in.close();
26         }
27     }
28 }


然后获取到FrameworkFactory的实例类:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1             try {
2                 frameworkFactoryClass = ServiceLoader.load(FrameworkFactory.class);
3             } catch (Exception e) {
4                 throw new IllegalArgumentException("FrameworkFactory service load error.", e);
5             }
6             if (frameworkFactoryClass == null) {
7                 throw new IllegalArgumentException("FrameworkFactory service not found.");
8             }


实例化FrameworkFactory:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1             FrameworkFactory frameworkFactory;
2             try {
3                 frameworkFactory = frameworkFactoryClass.newInstance();
4             } catch (Exception e) {
5                 throw new IllegalArgumentException("FrameworkFactory instantiation error.", e);
6             }


获取Framework的启动配置:

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


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

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


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

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


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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1     private static void registerContext(BundleContext bundleContext, ServletContext servletContext) {
2         Properties properties = new Properties();
3         properties.setProperty("ServerInfo", servletContext.getServerInfo());
4         properties.setProperty("ServletContextName", servletContext.getServletContextName());
5         properties.setProperty("MajorVersion", String.valueOf(servletContext.getMajorVersion()));
6         properties.setProperty("MinorVersion", String.valueOf(servletContext.getMinorVersion()));
7         bundleContext.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环境
 2     private static void initFramework(Framework framework, ServletContextEvent event) throws IOException {
 3         BundleContext bundleContext = framework.getBundleContext();
 4         ServletContext servletContext = event.getServletContext();
 5 
 6         // 将ServletContext注册为服务
 7         registerContext(bundleContext, servletContext);
 8 
 9         File file = bundleContext.getDataFile(".init");
10         if (!file.isFile()) { // 第一次初始化
11             if (logger.isInfoEnabled()) logger.info("Init Framework打造一个基于OSGi的Web Application");
12 
13             String pluginLocation = servletContext.getInitParameter(CONTEXT_PARAM_OSGI_PLUGINS_LOCATION);
14             if (pluginLocation == null) pluginLocation = DEFAULT_OSGI_PLUGINS_LOCATION;
15             else if (!pluginLocation.startsWith("/")) pluginLocation = "/".concat(pluginLocation);
16 
17             // 安装bundle
18             File bundleRoot = new File(servletContext.getRealPath(pluginLocation));
19             if (bundleRoot.isDirectory()) {
20                 if (logger.isInfoEnabled()) logger.info("Load Framework bundles from: " + pluginLocation);
21 
22                 File bundleFiles[] = bundleRoot.listFiles(new FilenameFilter() {
23                     public boolean accept(File dir, String name) {
24                         return name.endsWith(".jar");
25                     }
26                 });
27 
28                 if (bundleFiles != null && bundleFiles.length > 0) {
29                     for (File bundleFile : bundleFiles) {
30                         try {
31                             bundleContext.installBundle(bundleFile.toURL().toExternalForm());
32                             if (logger.isInfoEnabled()) logger.info("Install bundle success: " + bundleFile.getName());
33                         } catch (Throwable e) {
34                             if (logger.isWarnEnabled()) logger.warn("Install bundle error: " + bundleFile, e);
35                         }
36                     }
37                 }
38 
39                 for (Bundle bundle : bundleContext.getBundles()) {
40                     if (bundle.getState() == Bundle.INSTALLED || bundle.getState() == Bundle.RESOLVED) {
41                         if (bundle.getHeaders().get(Constants.BUNDLE_ACTIVATOR) != null) {
42                             try {
43                                 bundle.start(Bundle.START_ACTIVATION_POLICY);
44                                 if (logger.isInfoEnabled()) logger.info("Start bundle: " + bundle);
45                             } catch (Throwable e) {
46                                 if (logger.isWarnEnabled()) logger.warn("Start bundle error: " + bundle, e);
47                             }
48                         }
49                     }
50                 }
51             }
52 
53             new FileWriter(file).close();
54             if (logger.isInfoEnabled()) logger.info("Framework inited.");
55         }
56     }


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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> 1     public void contextDestroyed(ServletContextEvent event) {
 2         if (framework != null) {
 3             if (logger.isInfoEnabled()) logger.info("Stopping OSGi Framework打造一个基于OSGi的Web Application");
 4 
 5             boolean succeed = false;
 6             try {
 7                 if (framework.getState() == Framework.ACTIVE) framework.stop();
 8                 framework.waitForStop(0);
 9                 framework = null;
10 
11                 succeed = true;
12             } catch (BundleException e) {
13                 throw new OSGiStopException("Stop OSGi Framework error!", e);
14             } catch (InterruptedException e) {
15                 throw new OSGiStopException("Stop OSGi Framework error!", e);
16             } finally {
17                 if (logger.isInfoEnabled()) {
18                     if (succeed) logger.info("OSGi Framework Stopped!");
19                     else logger.info("OSGi Framework not stop!");
20                 }
21             }
22         }
23     }



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

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />-->1     <!-- Init OSGi framework -->
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,一般有三种方式:

  1. 一个常规的bundle,自身包含必要的class,同时在Export-Package中声明。
  2. 一个Host为System Bundle的Fragment Bundle,同样也可以在Export-Package中声明导出的package,只要这个package中的class在System Bundle的ClassLoader中能load到。
  3. 通过启动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 />--> 1 Manifest-Version: 1.0
 2 Bundle-ManifestVersion: 2
 3 Bundle-Name: Servlet Extension Fragment
 4 Bundle-SymbolicName: javax.servlet_extension;singleton:=true
 5 Bundle-Version: 2.4.0
 6 Fragment-Host: system.bundle; extension:=framework
 7 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 8 Export-Package: javax.servlet;version="2.4.0",
 9  javax.servlet.http;version="2.4.0",
10  javax.servlet.resources;version="2.4.0"
11 Bundle-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 />--> 1 Manifest-Version: 1.0
 2 Bundle-ManifestVersion: 2
 3 Bundle-Name: Jsp Extension Fragment
 4 Bundle-SymbolicName: javax.servlet.jsp_extension;singleton:=true
 5 Bundle-Version: 2.0.0
 6 Bundle-Vendor: dbstar
 7 Fragment-Host: system.bundle; extension:=framework
 8 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 9 Export-Package: javax.servlet.jsp;version="2.0.0",
10  javax.servlet.jsp.el;version="2.0.0",
11  javax.servlet.jsp.resources;version="2.0.0",
12  javax.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 />--> 1 Manifest-Version: 1.0
 2 Bundle-ManifestVersion: 2
 3 Bundle-Name: Commons Logging Extension Fragment
 4 Bundle-SymbolicName: org.apache.commons.logging_extension;singleton:=true
 5 Bundle-Version: 1.1.1
 6 Bundle-Vendor: dbstar
 7 Fragment-Host: system.bundle; extension:=framework
 8 Bundle-RequiredExecutionEnvironment: J2SE-1.5
 9 Export-Package: org.apache.commons.logging;version="1.1.1",
10  org.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> bundle 2
javax.servlet_extension_2
.4.0 [2]
  Id
=2, Status=RESOLVED    Data Root=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
  No registered services.
  No services in use.
  Exported packages
    javax.servlet
; version="2.4.0"[exported]
    javax.servlet.http; version="2.4.0"[exported]
    javax.servlet.resources; version="2.4.0"[exported]
  No imported packages
  Host bundles
    org.eclipse.osgi_3
.6.0.v20100128-1430 [0]
  No named class spaces
  No required bundles

最后提供几个本章提到的bundle给大家下载,大家就不用自己再起生成一个了。
javax.servlet_extension_2.4.0.jar
javax.servlet.jsp_extension_2.0.0.jar
org.apache.commons.logging_extension_1.1.1.jar
系统不让传扩展名为.jar的文件,大家下载后把扩展名改改吧,阿门。

在OSGi容器中管理Web元素的实现思路

要在OSGi容器中动态的管理Web相关的元素,目前有两种方式。

一种是通过类似于bridge的方式,现在外层的Web Application中配置一个对应的元素,然后通过对这个元素将外部的访问桥接到OSGi容器内部。在OSGi容器内部,会有一个总控的service,通过这个service来提供Web元素的动态管理,以及对外界请求的响应和分发。Equinox的Servlet Bridge就是采用的这种方式。

另一种方式就是通过使用Web Container提供的原生的API来实现Web元素的动态管理,将Web Container的部分功能作为Service注册到OSGi中,然后在OSGi中通过这个Service来动态的管理Web元素。

两种方式各有利弊:

对于bridge方式来说,优点是与Web Container实现无关,在多种Web Container中均能正常使用;缺点也很明显,无法使用Web Container提供的各种特性,并且相当于自己要实现一部分Web Server的功能,不仅是重复造*,而且还是个不怎么好的*,能否满足企业级应用的需求,还只是个未知数。

对于使用原生API的方式来说,优点就是完全使用Web Server自身的机制,可以保证性能和功能需求;缺点同样也很明显,现在流行的Web Server开源的并不多,而且就算是开源的,也不一定提供了可以动态管理Web元素的API,就算是有相应的API,也不一定适合OSGi环境,而且同一个Web Server的不同版本,API也很有可能会变化,这直接导致了原生方式实现的多样性和不兼容性。

幸运的是,在Tomcat 5.5.x和6.0.x中都提供了这样的API,在以后的章节中,我会着重针对Tomcat 5.5.x、6.0.x版本以及对bridge方式这三个场景,来实现对Web元素的动态管理。

Servlet 2.4中描述了如下可以在web.xml中配置的元素,这也是我要实现动态管理的目标:

  1. Context Parameter
  2. Error Page
  3. Filter
  4. Filter Mapping
  5. Listener
  6. Security role
  7. Servlet
  8. Servlet Mapping
  9. Welcome Page

增加日志输出功能

到目前为止,我们的基于OSGi内核的Web Application还没有任何的日志输出功能,本章将介绍如何在这个Web应用中配置和输出日志。

在前面的配置中,我们的应用中只含有commons-logging.jar,而OSGi容器之外的代码中,均是通过配置commons logging的Log对象来输出日志的,在默认的配置下,系统将采用Jdk14Logger来作为输出日志的实现,这对我们来说是远远不够的。我们下一步将配置更加常用的Log4j在作为我们的日志输出实现,通过以下几个步骤:

一、为Web Application配置Log4j:
  1.在OSGi-Web项目的Java EE Module Dependencies中,增加对log4j.jar的依赖关系。
  2.在WEB-INF/config目录中,增加一个log4j.properties文件,内容如下:

<!--<br /><br />Code highlighting produced by Actipro CodeHighlighter (freeware)<br />http://www.CodeHighlighter.com/<br /><br />--> 1 ### direct log messages to stdout ###
 2 log4j.appender.stdout=org.apache.log4j.ConsoleAppender
 3 log4j.appender.stdout.Target=System.out
 4 log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
 5 log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%- %m%n
 6 
 7 #Default Log File Configuration For OSGi
 8 log4j.appender.OSGiLog=org.apache.log4j.DailyRollingFileAppender
 9 log4j.appender.OSGiLog.DatePattern='.'yyyy-MM-dd
10 log4j.appender.OSGiLog.File=${osgi.root}/logs/OSGi.log
11 log4j.appender.OSGiLog.layout=org.apache.log4j.PatternLayout
12 log4j.appender.OSGiLog.layout.ConversionPattern=%d [%t] %-5p %- %m