Java Web应用集成OSGI

栏目: 服务器 · 发布时间: 6年前

内容简介:就像Java Web应用程序需要运行在Tomcat、Weblogic这样的容器中一样。程序员开发的OSGI程序包也需要运行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程序包在OSGI中称作

对OSGI的简单理解

就像Java Web应用程序需要运行在Tomcat、Weblogic这样的容器中一样。程序员开发的OSGI程序包也需要运行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程序包在OSGI中称作 Bundle

Bundle 的整个生命周期都交与OSGI容器进行管理。可以在不停止服务的情况下,对 Bundle 进行加载和卸载,实现热部署。

Bundle 对于外部程序来说就是一个黑盒。他只是向OSGI容器中注册了供外部调用的服务接口,至于实现则对外部不可见。不同的 Bundle 之间的调用,也需要通过OSGI容器来实现。

Bundle如何引入jar

刚才说到 Bundle 是一个黑盒,他所有实现都包装到了自己这个“盒子”中。在开发 Bundle 时,避免不了引用一些比如Spring、Apache commons等开源包。在为 Bundle 打包时,可以将当前 Bundle 依赖jar与 Bundle 的源码都打包成一个包(all-in-one)。这种打包结果就是打出的包过大,经常要几兆或者十几兆,这样当然我们是不可接受的。下面就介绍一种更优的做法。

Bundle与OSGI容器的契约

Bundle 可以在 MANIFEST.MF 配置文件中声明他要想运行起来所要的包以及这些包的版本 !!!而OSGI容器在加载 Bundle 时会为 Bundle 提供 Bundle 所需要的包 !!! 在启动OSGI容器时,需要在OSGI配置文件中定义 org.osgi.framework.system.packages.extra ,属性。这个属性定义了 OSGI容器能提供的包以及包的版本 。OSGI在加载 Bundle 时,会将他自己能提供的包以及版本与Bundle所需要的包以及版本列表进行匹配。如果匹配不成功则直接抛出异常:

Unable to execute command on bundle 248: Unresolved constraint in bundle
com.osgi.demo2 [248]: Unable to resolve 248.0: missing requirement [248.0] osgi
.wiring.package; (&(osgi.wiring.package=org.osgi.framework)(version>=1.8.0)(!(version>=2.0.0)))

也可能加载 Bundle 通过,但是运行 Bundle 时报 ClassNotFoundException 。这些异常都由于配置文件没配置造成的。理解了配置文件的配置方法,就能解决60%的异常。

Import-Package

BundleImport-Package 属性中通过以下格式配置:

<!--pom.xml-->
 <Import-Package>
javax.servlet,
javax.servlet.http,
org.xml.sax.*,
org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,
org.springframework.util.*;version="[2.5,5.0]"
</Import-Package>
  • 包与包之间通过逗号分隔
  • 可以使用*这类的通配符,表示这个包下的所有包。如果不想使用通配符,则同一个包下的其他包彼此之间可以使用 ; 分隔。
  • 如果需要指定包的版本则在包后面增加 ;version="[最低版本,最高版本]" 。其中 [ 表示大于等于、 ] 表示小于等于、 ) 表示小于。

org.osgi.framework.system.packages.extra

语法与 Impirt-Package 基本一致,只是 org.osgi.framework.system.packages.extra 不支持通配符。

  • 错误的方式

    org.springframework.beans.factory.*;version=4.1.1.RELEASE
  • 正确的方式:

    org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,

Class文件加载

在我们平时开发中有些情况下加载一个Class会使用 this.getClassLoader().loadClass 。但是通过这种方法加载 Bundle 中所书写的类的 class 会失败,会报 ClassNotFoundException 。在 Bundle 需要使用下面的方式来替换 classLoader.loadClass 方法

public void start(BundleContext context) throws Exception {
     Class classType = context.loadClass(name);
 }

Bundle中加载Spring配置文件时的问题

由于 Bundle 加载 Class 的特性,会导致在加载Spring配置文件时报错。所以需要将Spring启动所需要的ClassLoader进行更改,使其调用 BundleContext.loadClass 来加载Class。

String xmlPath = "";
ClassLoader classLoader = new ClassLoader(ClassUtils.getDefaultClassLoader()) {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return currentBundle.loadClass(name);
        } catch (ClassNotFoundException e) {
            return super.loadClass(name);
        }
    }
    };
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    beanFactory.setBeanClassLoader(classLoader);
    GenericApplicationContext ctx = new GenericApplicationContext(beanFactory);
    ctx.setClassLoader(classLoader);
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader) {
        @Override
        public void setClassLoader(ClassLoader classLoader) {
            if (this.getClassLoader() == null) {
                super.setClassLoader(classLoader);
            }
        }
    };
    ctx.setResourceLoader(resourceLoader);
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
    reader.loadBeanDefinitions(xmlPath);
    ctx.refresh();

Web应用集成OSGI

这里选用了 Apache Felix 来开发,主要是因为 Apache Felix 是Apache的顶级项目。社区活跃,对OSGI功能支持比较完备,并且文档例子比较全面。

其实OSGI支持两种方式来部署 Bundle

  • 单独部署OSGI容器,通过OSGI自带的Web中间件(目前只有jetty)来对外提供Web服务
  • 将OSGI容器嵌入到Web应用中,然后就可以使用Weblogic等中间件来运行Web���用

从项目的整体考虑,我们选用了第二种方案。

BundleActivator开发

开发 Bundle 时,首先需要开发一个 BundleActivator 。OSGI在加载 Bundle 时,首先调用 BundleActivatorstart 方法,对 Bundle 进行初始化。在卸载 Bundle 时,会调用 stop 方法来对资源进行释放。

public void start(BundleContext context) throws Exception;
public void stop(BundleContext context) throws Exception;

start 方法中调用 context.registerService 来完成对外服务的注册。

Hashtable props = new Hashtable();
props.put("servlet-pattern", new String[]{"/login","/logout"})
ServiceRegistration servlet = context.registerService(Servlet.class, new DispatcherServlet(), props);
  • context.registerService方法的第一个参数表示服务的类型,由于我们提供的是Web请求服务,所以这里的服务类型是一个 javax.servlet.Servlet ,所以需要将 javax.servlet.Servlet 传入到方法中
  • 第二个参数为服务处理类,这里配置了一个路由Servlet,其后会有相应的程序来处理具体的请求。
  • 第三个参数为 Bundle 对外提供服务的属性。在例子中,在 Hashtable 中定义了 Bundle 所支持的 servlet-pattern 。OSGI容器所在Web应用通过 Bundle 定义的 servlet-pattern 判断是否将客户请求分发到这个 Bundleservlet-pattern 这个名称是随意起的,并不是OSGI框架要求的名称。

应用服务集成OSGI容器

  • 首先工程需要添加如下依赖
<dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.framework</artifactId>
            <version>5.6.10</version>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.bundle</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.bridge</artifactId>
            <version>3.0.18</version>
        </dependency>
        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.proxy</artifactId>
            <version>3.0.0</version>
        </dependency>
  • 然后在 web.xml 中添加
<listener>
        <listener-class>org.apache.felix.http.proxy.ProxyListener</listener-class>
    </listener>
  • 开发 ServletContextListener 用以初始化并启动OSGI容器

    请参考 Apache Felix 提供的 例子程序 。例子中提供的 ProvisionActivator 会扫描 /WEB-INF/bundles/ ,加载其中的 Bundle 包。(当然例子中提供的ProvisionActivator并不带有 Bundle 自动发现注册等机制,这些逻辑需要自行增加。请参照后续的Bundle自动加载章节)

路由开发

通过上面的配置,只是将OSGI容器加载到了Web应用中。还需要修改Web应用程序路由的代码。

  • Bundle 加载到OSGI容器中后,可以通过 bundleContext.getBundles() 方法获取到OSGI容器中的所有已经加载的 Bundle
  • 可以调用 Bundlebundle.getRegisteredServices() 方法获取到该 Bundle 对外提供的所有服务服务。 getRegisteredServices 方法返回 ServiceReference 的数组。前文中我们调用 context.registerService(Servlet.class, new DispatcherServlet(), props) 我们已经注册了一个服务, getRegisteredServices 返回的数据只有一个 ServiceReference 对象。
  • 获取 Bundle 所能提供的服务
    可以通过 ServiceReference 对象的 getProperty 方法获取 context.registerService 中传入的 props 中的值。这样我们就能通过调用 ServiceReference.getProperty 方法获取到该 Bundle 所能提供的服务。
  • 通过上面提供的接口,我们可以将 Bundle 对应 ServiceReference 以及 Bundle 对应的 servlet-pattern 进行缓存。当用户请求进入到应用服务器后,通过缓存的 servlet-pattern 可以判断 Bundle 是否能提供用户所请求的服务,如果可以提供通过下面的方式,来调用 Bundle 所提供的服务。
ServiceReference sr = cache.get(bundleName);
 HttpServlet servlet = (HttpServlet) this.bundleContext.getService(sr);
 servlet.service(request, response);

Bundle自动加载

Apache Felix 例子中提供的 ProvisionActivator ,只会在系统启动时加载 /WEB-INF/bundles/ 目录下的 Bundle 。当文件夹下的 Bundle 文件有更新时,并不会自动更新OSGI容器中的 Bundle 。所以 Bundle 自动加载的逻辑,需要我们自己增加。下面提供实现的思路:

  • 在第一次加载文件夹下的 Bundle 时,记录 Bundle 包所对应的最后的更新时间。
  • 在程序中创建一个独立线程,用以扫描 /WEB-INF/bundles/ 目录,逐个的比较 Bundle 的更新时间。如果与内存中的不相符合,则从OSGI中获取 Bundle 对象然后调用其 stop 以及 uninstall 方法,将其从OSGI容器中卸载。
  • 卸载后,再调用 bundleContext.installBundle 以及 bundle.start 将最新的 Bundle 加载到OSGI容器中

BundleListener

最后一个问题,通过上面的方式,可以实现 Bundle 的自动加载。但是刚才我们介绍了,在路由程序中,我们会缓存OSGI容器中所有的 Bundle 所对应的 ServiceReference 以及所有 Bundle 所对应的 servlet-pattern 。所以 Bundle 自动更新后,我们还需要将路由程序中的缓存同步的进行更新。

可以通过向 bundleContext 中注册 BundleListener ,当OSGI容器中的 Bundle 状态更新后,会调用 BundleListenerbundleChanged 回调方法。然后我们可以在 bundleChanged 回调方法中书写更新路由缓存的逻辑

this.bundleContext.addBundleListener(new BundleListener() {
    @Override
    public void bundleChanged(BundleEvent event) {
        if (event.getType() == BundleEvent.STARTED) {
            initBundle(event.getBundle());
        } else if (event.getType() == BundleEvent.UNINSTALLED) {
            String name = event.getBundle().getSymbolicName();
            indexes.remove(name);
        }
     }
 });

本文永久更新链接地址 http://www.linuxidc.com/Linux/2018-01/150479.htm


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Single Page Web Applications

Single Page Web Applications

Michael Mikowski、Josh Powell / Manning Publications / 2013-9-30 / USD 44.99

Code for most web sites mostly runs on the server. When a user clicks on a link, the site reacts slowly because the browser sends information to the server and the server sends it back again before di......一起来看看 《Single Page Web Applications》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码