<mark>Gemini</mark> Web介绍

 由  Ruici 发布

简介

Gemini Web是OSGi Web Application Specification的一个参考实现(Reference Implementation)。

OSGi Web Application规范

这个标准的目的在于在OSGi环境下更好的支持Java EE中的Servlet模型,具体来说可以认为是提供一种Java EE web应用(已有的或者全新的)无缝(seamless)部署到处于OSGi环境下的Servlet容器中的方法。在OSGi标准中,HTTP Service是唯一支持Servlet编程模型的部分。但是它存在一些局限,HTTP Service主要关注于运行时(runtime),也就是构造应用的上下文(Servlet Context),但是不支持标准的Servlet打包、部署模型——WAR格式。将符合Java EE Servlet编程模型的应用部署到OSGi标准环境中的困难之大,可想而知。

Web Application规范支持复合Servlet 2.5(及以上)和JSP 2.1(及以上)标准的web应用,它规定了WAR包应该怎样部署到OSGi环境中,以及和OSGi服务的交互方式。

同时规范还定义了Web Application Bundle(WAB),它与WAR在Java EE中扮演的角色是相同的,可以认为是OSGi环境下web应用的打包、部署模型。WAB实际上也是一种标准的OSGi bundle,只不过它可以和OSGi环境及生命周期模型交互,而不是标准的Java EE环境。

Gemini Web历史

Gemini Web项目起源于Spring dm Server项目中的Web部分(Spring dm Server现在已经捐献给Eclipse,改名为Virgo)。由于OSGi联盟提出RFC66标准(Web Container),SpringSource为与标准保持一致,dm Server中的web container部分也就单独独立出来做为标准的参考实现,后来被捐献给Eclipse并座位EclipseRTGemini项目的子项目。

主要特性

  • 支持WAR包。标准WAR包可以直接被部署而不需要修改任何已有的代码(JNDI相关代码除外),在控制台中输入: install webbundle:file:xxx.war 即可(前提是OSGi环境中已经安装Web Application规范的参考实现,例如Gemni Web)。Gemini Web项目中是通过重写(rewrite)WAR包的元信息(MANIFEST.MF)来实现这一功能的。
  • 支持WAB——Web Application Bundle。和在OSGi环境下安装普通bundle一样。与安装war相比,忽略webbundle前缀即可
  • 通过extender模型对应用生命周期进行控制。当应用对应的bundle启动(start)后,container会启动相应的web app;stop时也是一样。这样,只需要OSGi控制台中原有的install/uninstall命令即可实现对web app的控制。
  • 可以通过url参数对web应用进行配置。例如:

    install webbundle:file:xxx.war?Web-ContextPath=xxx
    install webbundle:file:xxx.war?Bundle-SymbolicName=xxx
    

我个人猜测这个配置是专门针对WAR来设计的,因为WAR经过MANIFEST.MF重写后,这些信息都是由Web Container中的Transformer来生成的,这里就提供了一种控制的机制。

Gemini Web参考实现

Gemini Web Container由3个bundle构成:

  • core bundle——核心部分,主要包括Web Container,WebApplication的创建、启动、停止;事件管理;WAR Transformer等等。这一部分代码是Servlet容器无关的。
  • tomcat bundle——Gemini Web使用了tomcat作为Servlet容器的实现,这一部分代码主要是对tomcat容器的操作,例如创建servlet上下文,部署资源等等,它实际上依赖于tomcat的api
  • extender bundle——实现extender对web app的生命周期进行控制

接下来从源码的角度,对容器的功能、流程进行概要性的分析

Core Bundle

我们从Activater入手,core bundle的activator是WebContainerActivator,从名字很容易想到,这里要做的工作就是初始化web容器:

@Override
public void start(BundleContext context) throws Exception {
    WebBundleManifestTransformer transformer = registerWebBundleManifestTransformer(context);

    registerUrlStreamHandler(context, transformer);

    this.eventManager = new EventManager(context);
    this.eventManager.start();

    this.serviceTracker = new ServiceTracker<ServletContainer, WebContainer>(context, ServletContainer.class, new ServletContainerTracker(
       context, this.eventManager));
    this.serviceTracker.open();
}

可以看到,web容器的初始化实际上做了以下几件事情:注册Transformer,这里的transformer用来将war转换为wab(通过代码树我们可以发现有internal内有很多种transformer,transformer的具体实现本文不详述,下次可以单独写一篇片文章);获取ServletContainer的服务(通过ServiceTracker方式)。

接下来我们看另外一个重要组成部分Web Application,在core代码中,Web Application相关的部分主要包括start和stop,以启动时为例,web app在启动时主要完成以下几件事情(代码见StandardWebApplication.java,是WebApplication.java接口的实现):

this.container.startWebApplication(this.handle);
startOK = true;

publishServletContext();

synchronized (this.monitor) {
    this.started = true;
    localStarted = this.started;
}

this.eventManager.sendDeployed(getBundle(), this.extender, webContextPath);

可以看到,首先告知servlet container(例如tomcat)启动一个app,这里有一个handle对象实际上就是servlet container在创建app时返回的上下文对象,然后将Servlet Context做为Service发部出来,其它代码主要是保证线程安全的操作以及事件处理。

Extender Bundle

在上一部分里,主要提到的是Web容器初始化以及Web应用的启动、停止。那么,一旦一个web bundle被添加至OSGi环境中,它是如何被创建为一个web应用并且部署到Servlet容器中呢?答案就是Extender。

Extender的作用,实际上就是一旦有web bundle安装到环境中,那么来完成上述的这些工作。这样做的好处显而易见,web app的开发者只需要关注自己的app是否复合Servlet规范即可,而不需要考虑实际的容器环境及部署细节,因为这些事情extender bundle都已经做好了,本质上这也是一种复用。

接下来我们看看extender bundle是如何实现的。实际上,extender的功能实现是非常简单的,它的java文件只有两个,每一个都不超过100行。首先我们来看看Activator的实现:

public final class ExtenderActivator implements BundleActivator {

    private ServiceTracker<WebContainer, String> serviceTracker;

    @Override
    public void start(BundleContext context) {
        this.serviceTracker = new ServiceTracker<WebContainer, String>(context, WebContainer.class, new ExtendedWebContainerTracker(context));
        this.serviceTracker.open();
    }

    @Override
    public void stop(BundleContext context) {
        this.serviceTracker.close();
    }

    private static final class ExtendedWebContainerTracker implements ServiceTrackerCustomizer<WebContainer, String> {

        private final BundleContext context;

        private BundleTracker<Object> bundleTracker;

        public ExtendedWebContainerTracker(BundleContext context) {
            this.context = context;
        }

        @Override
        public String addingService(ServiceReference<WebContainer> reference) {
            if (this.bundleTracker == null) {
                this.bundleTracker = new BundleTracker<Object>(this.context, Bundle.ACTIVE, new WebContainerBundleCustomizer(
                    this.context.getService(reference), this.context.getBundle()));
            }
            this.bundleTracker.open();
            return reference.getBundle().getSymbolicName();
        }

        @Override
        public void modifiedService(ServiceReference<WebContainer> reference, String service) {
        }

        @Override
        public void removedService(ServiceReference<WebContainer> reference, String service) {
            this.bundleTracker.close();
            this.bundleTracker = null;
        }

    }

}

他的start方法只干了一件事情,利用ServiceTrack跟踪WebContainer服务,使得接下来能够使用之。那么关键就在于ExtendedWebContainerTracker这个类了。可以看到通过实现OSGi Service Layer中的ServiceTrackerCustomizer来实现对web容器的跟踪。那么每当添加一个web container后,它会做什么呢?答案就是利用bundleTracker对环境中新增bundle这个事件进行监控:

@Override
public Object addingBundle(Bundle bundle, BundleEvent event) {
    Object handle = null;
    if (this.container.isWebBundle(bundle)) {
        try {
            WebApplication webApplication = this.container.createWebApplication(bundle, this.extenderBundle);
            handle = webApplication;
            webApplication.start();
        } catch (BundleException e) {
            logger.error("Exception occurred during web application startup.", e);
        } catch (WebApplicationStartFailedException _) {
            // ignore in order to track this bundle
            if (logger.isDebugEnabled()) {
                logger.debug("", _);
            }
        }
    }
    return handle;
}

可以看到,一旦环境中新增一个bundle并且是web bundle,那么就需要通过container来创建一个新的web app,并且启动之。看上去好像很复杂,我们来理一理:首先extender在启动时去获取web container的服务,并跟踪环境中的bundle,发现有新的web bundle后才创建web app。

于是有如下两个问题:

  • 为什么不能直接使用bundle tracker?非要这么复杂。我想原因是因为必须保证环境中web container是存在的,接下来的操作才能够继续下去。
  • 会不会出现多个web container存在的情况?从上一节分析来看,不会,因为core bundle activator一定只启动了一个实例。但是,如果存在多个container,extender代码会不会存在问题?

然后看web container是如何创建web app的(见StandardWebContainer.java):

WebApplicationHandle handle = this.servletContainer.createWebApplication(WebContainerUtils.getContextPath(bundle), bundle);
handle.getServletContext().setAttribute(ATTRIBUTE_BUNDLE_CONTEXT, bundle.getBundleContext());
return new StandardWebApplication(bundle, extender, handle, this.servletContainer, this.eventManager, this.retryController, FrameworkUtil.getBundle(this.getClass()).getBundleContext());

很简单,将工作交给servlet container,同时在servlet上下文里放入bundle上下文,这可能是为了方便web app于OSGi环境进行交互。这段代码虽然属于core,但是由于和extender关系密切,所以也放在本节。

Tomcat Bundle

经过刚才的分析,我们完全可以肯定,tomcat bundle就是用来提供servlet container的。它的内部实现比较复杂,需要特定的tomcat api支持。而实际上,将OSGi化的bundle部署到tomcat,需要的细节工作肯定还有很多,我准备在另外的文章中单独讨论。本文只是简单的看一看tomcat bundle的功能以及与其他bundle和OSGi环境的联系。继续看Activator:

@Override
public void start(BundleContext context) throws Exception {
    this.oldExpressionFactory = System.setProperty(EXPRESSION_FACTORY, EXPRESSION_FACTORY_IMPL);

    registerURLStreamHandler(context);
    registerConnectorDescriptors(context);

    TomcatServletContainer container = createContainer(context);
    container.start();

    ServiceRegistration<ServletContainer> sr = context.registerService(ServletContainer.class, container, null);
    this.tracker.track(sr);

    synchronized (this.monitor) {
        this.container = container;
    }
}

简单来说,就是创建一个Servlet容器然后将其发布到OSGi环境中。然后,container是如何创建web app的呢?

@Override
public WebApplicationHandle createWebApplication(String contextPath, Bundle bundle) {
    contextPath = formatContextPath(contextPath);

    try {
        String docBase = determineDocBase(bundle);

        StandardContext context = (StandardContext) this.tomcat.addWebapp(contextPath, docBase, bundle);

        BundleWebappLoader loader = new BundleWebappLoader(bundle, this.classLoaderCustomizer);
        context.setLoader(loader);
        context.setResources(new BundleDirContext(bundle));

        ServletContext servletContext = context.getServletContext();

        return new TomcatWebApplicationHandle(servletContext, context, loader);
    } catch (Exception ex) {
        throw new ServletContainerException("Unablo te create web application for context path '" + contextPath + "'", ex);
    }
}

首先将其add到tomcat环境中,这中间就涉及到tomcat api的调用了.然后设置好Servlet上下文并返回,还记得上一节提到的将Servlet上下文注册到OSGi环境中吗?就是这个~

其实我们可以发现,tomcat bundle实际上就是对底层tomcat api进行了一层封装,提供一些公共接口(如Servlet Container)的实现。这样做还有一个好处就是,我同样可以对Jetty进行类似的封装,就能把Gemini Web中的servlet容器替换成Jetty了。

和Virgo的关系

官方文档中提到,Spring dm Server(即现在的Virgo),将Gemini Web做为其Web Container模块。但是,我在Gemini Web项目的依赖中,也发现了Virgo的jar包,难道这就是传说中Sister project?而在virgo的依赖中,我只找到了Gemini Web项目中的两个bundle——core和tomcat,那么virgo中是如何实现类似extender的方法呢?这个就需要研究virgo的代码了,也可以做为后续的工作来完成。


wmz 2015-06-08 19:56

http://osgi.jxtech.net 平台在OSGI方面运用相当灵活,把前端代码、后台业务逻辑、甚至建表SQL语句都集成在一个Bundle中,实在是太方便了。

顶(0) 踩(0) 回复

罗俊杰 2013-07-25 21:50

回复Lifan: 周志明老师的访谈里说到了这个问题,你可以看一下http://osgi.com.cn/article/7289397

顶(0) 踩(0) 回复

Lifan 2013-07-24 15:43

听说 SpringSource 想弃 OSGi 而去,这对 Virgo 的发展会产生什么影响

顶(0) 踩(0) 回复

Lifan 2013-07-24 15:43

听说 SpringSource 想弃 OSGi 而去,这对 Virgo 的发展会产生什么影响

顶(0) 踩(0) 回复

罗俊杰 2013-07-02 22:07

可以直接用virgo吧,Virgo 3.0以上的版本,自动就是集成了tomcat7,也自然而然支持servlet3.0了。使用virgo,同样天然就已经集成gemini了。<br><br>至于eclipse4.2,最新的virgo tool插件能在4.2下正常跑就没问题。

顶(0) 踩(0) 回复

拓荒者 2013-07-02 17:36

能不能做一个eclipse4.2+gemini+tomcat7,并支持servlet3.0规范的实例,要能够跑起来的,其实只要能够跑起来,大家学习起来就很容易了。

顶(0) 踩(0) 回复

Ruici 2013-01-29 15:01

Gemini Web已经是Virgo的一部分了,建议使用Virgo作为企业级应用的环境。

顶(0) 踩(0) 回复

罗俊杰 2013-01-27 21:07

Gemini Web其实最初是从Spring DM的Web extender发展而来的

顶(0) 踩(0) 回复
查看评论