Java Web应用集成OSGI

栏目: Java · 发布时间: 6年前

内容简介:Java Web应用集成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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

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

Programming Ruby

Programming Ruby

Dave Thomas、Chad Fowler、Andy Hunt / Pragmatic Bookshelf / 2004-10-8 / USD 44.95

Ruby is an increasingly popular, fully object-oriented dynamic programming language, hailed by many practitioners as the finest and most useful language available today. When Ruby first burst onto the......一起来看看 《Programming Ruby》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具