第八章:OSGi框架-Gemini Web

来源:互联网 发布:淘宝欺诈风险订单关闭 编辑:程序博客网 时间:2024/05/21 14:02

简介

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并座位EclipseRT下Gemini项目的子项目。

主要特性

  • 支持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容器:

@Overridepublic 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这个事件进行监控:

@Overridepublic 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:

@Overridepublic 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的呢?

@Overridepublic 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的代码了,也可以做为后续的工作来完成。

0 0