随笔 - 19, 文章 - 1, 评论 - 21, 引用 - 0
数据加载中……

打造一个基于OSGi的Web Application——在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。

我们先写一个工具类来载入这个配置文件中的内容:
 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的实例类:
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:
1             FrameworkFactory frameworkFactory;
2             try {
3                 frameworkFactory = frameworkFactoryClass.newInstance();
4             } catch (Exception e) {
5                 throw new IllegalArgumentException("FrameworkFactory instantiation error.", e);
6             }

获取Framework的启动配置:
 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:
 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容器:
 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容器中去:
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:
 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容器的过程,相比较而言,停止容器就简单多了:
 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中:
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查看效果了。

附上本文中提到的源代码

posted on 2010-03-23 18:28 dbstar 阅读(6037) 评论(5)  编辑  收藏 所属分类: OSGi

评论

# re: 打造一个基于OSGi的Web Application——在WebApplication中启动OSGi  回复  更多评论   

实在是好文章,早就想做一些OSGi方面的扩展!
十分感谢博主!一定是高手,思路很清晰!
2010-03-24 13:16 | liucr

# re: 打造一个基于OSGi的Web Application——在WebApplication中启动OSGi  回复  更多评论   

楼主好厉害。写的很清晰。就是问一点,我用的myeclipse,怎么把java project关联到web项目,打成jar?
myeclipse没有java ee model 选项,如果能够给我说一声,感激不尽
2010-08-20 11:50 | 洗洗更健康

# re: 打造一个基于OSGi的Web Application——在WebApplication中启动OSGi[未登录]  回复  更多评论   

感谢您的分享。
不过我在操作的时候遇到一些问题:
“启动OSGi容器”那部分的介绍,提到“必须在其实现jar中放置一个文件”,这里需要我怎么操作呢?
如果可以共享OSGi-Web和OSGi-Web-Launcher这两份Eclipse工程就好了。
2011-11-10 17:32 | alex

# re: 打造一个基于OSGi的Web Application——在WebApplication中启动OSGi  回复  更多评论   

@alex
不需要你去放置那个文件的,任何OSGi的实现都会有这个文件。你可以把org.eclipse.osgi_3.6.0.v20100128-1430.jar解压开来,就能看到这个文件。

2011-11-18 10:55 | dbstar

# re: 打造一个基于OSGi的Web Application——在WebApplication中启动OSGi  回复  更多评论   

OSGi框架在Eclipse和MyEclipse上运行有哪些区别?
2014-09-03 11:05 | 宇峰

只有注册用户登录后才能发表评论。


网站导航: