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
图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/