打造一个基于OSGi的Web Application——在WebApplication中启动OSGi
本章将创建一个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。
我们先写一个工具类来载入这个配置文件中的内容: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的实例类:
2frameworkFactoryClass=ServiceLoader.load(FrameworkFactory.class);
3}catch(Exceptione){
4thrownewIllegalArgumentException("FrameworkFactoryserviceloaderror.",e);
5}
6if(frameworkFactoryClass==null){
7thrownewIllegalArgumentException("FrameworkFactoryservicenotfound.");
8 }实例化FrameworkFactory:
2try{
3frameworkFactory=frameworkFactoryClass.newInstance();
4}catch(Exceptione){
5thrownewIllegalArgumentException("FrameworkFactoryinstantiationerror.",e);
6 }获取Framework的启动配置:
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:
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容器:
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容器中去:
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:
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容器的过程,相比较而言,停止容器就简单多了:
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中:
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查看效果了。
附上本文中提到的源代码。