现在的位置: 首页 > 综合 > 正文

打造一个基于OSGi的Web Application

2013年08月23日 ⁄ 综合 ⁄ 共 14700字 ⁄ 字号 评论关闭

动机和目标

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了。

以下是我的目录结构:

环境整合:
1.运行Eclipse,指定Workspace路径为:D:/dbstar/workspaces/OSGi
2.设置Plug-in Development的Target
Platform,增加equinox-SDK-3.6M5并设为默认,这样我们就可以使用equinox-SDK-3.6M5来作为我们开发
bundle的基准库,而不是使用Eclipse自带的plugin开发环境。

3.在Server配置中增加Tomcat两个版本的服务器。

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

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

 

在WebApplication中启动
OSGi

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

首先,在Eclipse中创建一个Dynamic Web Project,名字为OSGi-Web,Context root为osgi。

这个项目只作为部署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 
==
 
null

return
 
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
"
);

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
"
);

 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-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相关的j

抱歉!评论已关闭.