打造一个基于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。
我们先写一个工具类来载入这个配置文件中的内容:
然后获取到FrameworkFactory的实例类:
实例化FrameworkFactory:
获取Framework的启动配置:
启动配置读取外部配置文件,可以在此配置文件中增加OSGi容器实现类相关的配置项,例如Equinox的osgi.console:
然后,就可以获取framework实例了,通过framework来初始化,启动和停止OSGi容器:
在initFramework方法中,主要做两件事情,一是将当前的ServletContext作为一个service注册到OSGi容器中去:
以上就是启动OSGi容器的过程,相比较而言,停止容器就简单多了:
最后,还有一件事情,就是将FrameworkConfigListener配置到web.xml中:
让我们来测试一下吧,在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查看效果了。
附上本文中提到的 源代码。
首先,在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 == 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 }
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的实例类:
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 }
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 }
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 }
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 }
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 }
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:
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 }
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 }
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 }
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 >
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查看效果了。
附上本文中提到的 源代码。