web container与osgi container集成方案实践

14 篇文章 0 订阅
7 篇文章 0 订阅

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值