打造一个基于OSGi的Web Application——在WebApplication中启动OSGi

EdwardWong 2010-03-23

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

首先,在Eclipse中创建一个DynamicWebProject,名字为OSGi-Web,Contextroot为osgi。

这个项目只作为部署WebApplication使用,相关java代码放在另外一个JavaProject中,因此我们再创建一个新的JavaProject,名字为OSGi-Web-Launcher。然后在OSGi-Web项目的JavaEEModuleDependencies中设置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

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 />-->1             try {

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 />-->1             FrameworkFactory frameworkFactory;

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 />--> 1             Map<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 />--> 1             try {

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 />-->1     private static void registerContext(BundleContext bundleContext, ServletContext servletContext) {

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

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 />--> 1     public void contextDestroyed(ServletContextEvent event) {

2if(framework!=null){

3if(logger.isInfoEnabled())logger.info("StoppingOSGiFramework");

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     <!-- 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,并且在JavaEEModuleDependencies中勾选这个jar,这样可以保证这个jar最终部署到WebApplication的WEB-INF/lib目录下去。同样,还需要增加commons-logging.jar。

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

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

相关推荐