web container与osgi container集成方案实践

来源:互联网 发布:设置网络共享文件夹 编辑:程序博客网 时间:2024/05/22 15:08

一、目的:
    目前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/

原创粉丝点击