web container与osgi container集成方案实践

一、目的:
目前Osgi Web开发仅有HttpService,Virgo的可以将WAR应用转换成Bundle,对我而言有些“重”,为了力求简洁,自行尝试使用Web Container来集成Osgi Container,便于定制客户化的管理功能,监控功能。


二、环境准备:

框架:
spring3.1 RC1
osgi 3.6.2
gemini-blueprint 1.0
servlet3

Server: tomcat7


三、架构方案:

选择spring3是在bundle内使用DI(Depedency Injection),通过Spring remote service 模块可以方便暴露服务,也可以暴露为Restful风格的服务;

选择gemini-blueprint是在模块之间的DS(Declarative Services)进行封装,更好的与Spring框架集成。gemini-blueprint实现注入DS的原理是:实现Bundle Lisetener 和Service Lisetener来监听事件来进行服务暴露和卸载,扫描META-INF/spring/*.xml来注入服务。

可以考虑了类似Apache CXF-DOSGI来注入和管理分布式的服务,在这里不进行讨论。


1.servlet2下架构:

图1

图1说明:

servlet2下的架构图和servlet3下的区别,是请求接入的Servlet部署所在WAR/JAR的位置不同。此图中的请求接入Servlet和管理Servlet部署在WAR中,在管理Servlet中启动OSGI Container,获得BundleContext,保存在ServletContext的Attribute中,并将ServletContext在Osgi中注册为一个服务。

样例代码:

/* (non-Javadoc) * @see javax.servlet.ServletContextListener#contextInitialized(javax.servlet.ServletContextEvent) */ public void start() { Class<FrameworkFactory> frameworkFactoryClass = null; //boolean succeed = false; try { frameworkFactoryClass = ServiceLoader.load(FrameworkFactory.class); } catch (Exception e) { throw new IllegalArgumentException("FrameworkFactory service load error.", e); } if (frameworkFactoryClass == null) { throw new IllegalArgumentException("FrameworkFactory service not found."); } FrameworkFactory frameworkFactory; try { frameworkFactory = frameworkFactoryClass.newInstance(); } catch (Exception e) { throw new IllegalArgumentException("FrameworkFactory instantiation error.", e); } try { // 载入Framework启动配置 configuration = loadFrameworkConfig(); if (logger.isInfoEnabled()) { logger.info("Load Framework configuration: ["); for (Object key : configuration.keySet()) { logger.info("\t" + key + " = " + configuration.get(key)); } logger.info("]"); } } catch (Exception e) { throw new IllegalArgumentException("Load Framework configuration error.", e); } try { framework = frameworkFactory.newFramework(configuration); framework.init(); beforeInitial(framework.getBundleContext()); File file = framework.getBundleContext().getDataFile(".init"); logger.info("init file:" + file); if (!file.exists() || !file.isFile()) { // 第一次初始化 // 初始化Framework环境 initFramework(framework.getBundleContext(), configuration);//, this.getInstalledBundleMap().keySet()); new FileWriter(file).close(); if (logger.isInfoEnabled()) logger.info("Framework inited."); } else { } afterInitial(framework.getBundleContext()); // 启动Framework framework.start(); logger.info("=========osgi container started!!============="); } catch (BundleException e) { throw new OSGiStartException("Start OSGi Framework error!", e); } catch (IOException e) { throw new OSGiStartException("Init OSGi Framework error", e); } }
private void registerContext(BundleContext bundleContext, ServletContext sctx) { Properties properties = new Properties(); properties.setProperty("ServerInfo", sctx.getServerInfo()); properties.setProperty("ServletContextName", sctx.getServletContextName()); properties.setProperty("MajorVersion", String.valueOf(sctx.getMajorVersion())); properties.setProperty("MinorVersion", String.valueOf(sctx.getMinorVersion())); bundleContext.registerService(ServletContext.class.getName(), sctx, properties); sctx.setAttribute("Bundle_Context", bundleContext); }
这样,Web Container接入请求后,可以通过BundleContext与Osgi Container交互,获取Service了。

但是这样还是需要将Spring web相关jar包,第三方包,和服务接口包放到 war/web-inf/lib下,这里有个技巧,在war/web-inf/lib下的包,如何在osgi container中变为可见的bundle。需要做两步:

步骤1:将需要暴露为bundle的jar包copy成一个新的名字的jar包,仅包含META-INF/MANIFEST.MF ,MANIFEST.MF中不要import-package.

例如 servlet-api.jar 对应servlet-api_extension-3.0.0.jar.

步骤2:设置Osgi的参数

org.osgi.framework.bootdelegation=javax.servlet,javax.servlet.* 说明这些类从指定的父类classloader中加载 osgi.parentClassloader=fwk 说明从framework 的classloadr,本文中的架构是从web container中启动,所以是webcontext classloader。


servlet2架构下要大量的做这样的extend jar来实现暴露成osgi中的bundle。

2.servlet3下架构:


图2

图2说明:

servlet3引入了模块fragment和动态注入的规范,针对图1进行架构优化,实现以bundle的形式开发,可以暴露为Servlet Http Service。大量使用的jar包就不需要和管理OSGI的WAR放到一起,也不需要做大量的extend jar包了,统一使用bundle来开发应用即可。

样例代码:

在gemini-blueprint 的xml 中 配置一个启动类:

public class ServletInitializer{ private ServletContext servletContext; private AnnotationConfigWebApplicationContext ctx;//= new AnnotationConfigWebApplicationContext(); public ServletContext getServletContext() { return servletContext; } public void setServletContext(ServletContext servletContext) { this.servletContext = servletContext; } public void start() { final ServletContext scx = this.servletContext; CountDownLatch doneSignal = (CountDownLatch) scx.getAttribute("Listener_ContinueFlag"); ServletRegistration servletRegistration = scx.getServletRegistration("/*"); if (servletRegistration == null) { final BundleContext bundleContext = (BundleContext) scx.getAttribute("Bundle_Context"); ctx = new AnnotationConfigWebApplicationContext(); ctx.register(MvcConfig.class); DispatcherServlet dispatcherServlet = new DispatcherServlet(ctx); ServletRegistration.Dynamic sr = servletContext.addServlet("dispatcherServlet", dispatcherServlet); sr.setLoadOnStartup(1); sr.addMapping("/*"); System.out.println("注册DispatcherServlet到web container"); ctx.refresh(); MyBundleContextWrapper bean = ctx.getBean(MyBundleContextWrapper.class); bean.setBundleContext(bundleContext); } else { System.out.println("已经存在/*对应的servlet"); } System.out.println("激活web listener 继续!"); doneSignal.countDown(); } public void stop() { if (ctx != null) { ctx.close(); ctx = null; } servletContext = null; System.out.println("stop!"); } }

1.通过MvcConfig来配置scan的package,通过Annotationi注入@Controller类。

2.把BundleContext通过MyBundleContextWrapper注入到web context中,MyBundleContextWrapper为scope=singleton的。

3.doneSignal信号量为管理的Servlet启动Osgi时放入的,需要等Bundle中的Servlet注入ServletContext完成后,才能继续,否则会报Servlet Context已经初始化的异常,因为Gemini-blueprint加载配置文件是异步的,所以需要做这个特殊处理。


这样整个应用就可以运行起来了。


四、问题/实践总结

1.spring中大量出现的 catch(ClassnotfoundException e) {} catch classnotfound异常,但不处理,也不记录日志的地方比比皆是,造成调试非常困难和耗时。

2.将引用的第三方包分类打包成一个大的bundle,好处是不需要管这些 bundle之间的依赖问题,但需要手工合并,体力活,注意spring3相关的jar达成一个bundle jar时,需要将META-INF/spring.schemas等文件也合并,否则解析xsd时,找不到映射到jar中的位置而报错。

3.虽然步骤2中做了合并,但是在开发中,还是需要单个import 这些plugin-project来,这样才知道import-package是否足够。

4.公共包尽量export service给其他bundle,而不是直接export class。

5.Bundle中注意资源的初始化和关闭,避免资源泄露。例如通过Lisetener中的初始化事件来初始化资源,通过停止事件来释放资源。

6.对资源进行管理的bundle应该单独打包,对外提供服务,例如Connection Pool,Thread Pool等bundle

7.对资源进行操作的bundle应该和资源(例如DB的版本)一起下发版本。

8.eclipse export的jar,spring 在做annotation scan时无法识别jar包的entry,需要用jar来打包。



五、参考

http://www.blogjava.net/dbstar/
http://www.blogjava.net/BlueDavy/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值