程序开发技术文首页投稿(暂停使用,暂停投稿)

实时发布-嵌入式OSGi的应用

2015-12-22  本文已影响837人  IvanEye

场景

单机应用已经越来越不能符合目前越来越复杂的产品需求了。即使是小型应用,至少也需要部署2台以上的服务器做集群。且应用必须24小时对外服务,可用性得达到n个9。这就对发布有了更高的要求。

也就催生了灰度发布这样的发布过程。而即使是这样,还是需要经历大致如下的发布过程:

而业界一直诟病JVM的启动速度,再加上如果项目比较大,编译过程比较长,发布机器比较多,那么做一次完整的发布可能需要几个小时。万一中途出了问题,要回退,又要几个小时。

是否可以解决这样的问题呢?而OSGi恰是一个不错的选择!

OSGi

OSGi是一个优雅、完整和动态的组件模型,提供了完整的模块化运行环境。

应用程序(称为bundle)无需重新引导可以被远程安装、启动、升级和卸载。

其主要应用在嵌入式开发中,而在JavaSE和JavaEE方面则少有建树。其最著名的使用就是eclipse了。究其原因主要有:

可以看出,OSGi的主要缺点是开发较繁琐。而针对前面所提到的问题,OSGi解决了如下几个问题:

OSGi是如何做到这些的呢?其实OSGi实现了一套自身的ClassLoader,具体可见此文

OSGi容器

目前OSGi容器主要有Knopflerfish, Apache Felix, Equinox, Spring DM。其具体比较请见此文

以及其上的一些应用,方便在OSGi上进行开发,比如Karaf,ServiceMix等。

OSGi的使用方式

OSGi容器有两种使用方式:

Felix安装

这里使用felix作为OSGi容器来演示嵌入式OSGi的使用!felix可到Apache网站下载!

felix目录结构如下:

-bin:felix.jar路径,其实felix只需要这个jar包就可以运行了
-bundle:部署的bundle目录,如果你有需要部署的bundle,将其拷贝到此目录下,启动felix时会自动部署
-cache:bundle缓存目录
-conf:配置文件目录
-doc:相关文档

在根目录运行如下命令即可启动

java -jar bin/felix.jar
g! lb
START LEVEL 1
   ID|State      |Level|Name
    0|Active     |    0|System Bundle (5.4.0)|5.4.0
    1|Active     |    1|Apache Felix Bundle Repository (2.0.6)|2.0.6
    2|Active     |    1|Apache Felix Gogo Command (0.16.0)|0.16.0
    3|Active     |    1|Apache Felix Gogo Runtime (0.16.2)|0.16.2
    4|Active     |    1|Apache Felix Gogo Shell (0.10.0)|0.10.0
g!

这是普通的使用felix的方式。不做过多介绍。主要介绍嵌入式Felix的应用。

嵌入启动Felix

创建Maven项目

<dependencies>
    <dependency>
        <groupId>org.apache.felix</groupId>
        <artifactId>org.apache.felix.main</artifactId>
        <version>5.4.0</version>
    </dependency>
</dependencies>

编写启动类

FrameworkFactory factory = getFrameworkFactory();
m_fwk = factory.newFramework(configProps);
m_fwk.init();
AutoProcessor.process(configProps, m_fwk.getBundleContext());
m_fwk.start();
m_fwk.waitForStop(0);
System.exit(0);
//getFrameworkFactory()方法实现代码
private static FrameworkFactory getFrameworkFactory() throws Exception {
    URL url = Main.class.getClassLoader().getResource(
                                                        "META-INF/services/org.osgi.framework.launch.FrameworkFactory");
    if (url != null) {
        BufferedReader br = new BufferedReader(new InputStreamReader(url.openStream()));
        try {
            for (String s = br.readLine(); s != null; s = br.readLine()) {
                s = s.trim();
                // Try to load first non-empty, non-commented line.
                if ((s.length() > 0) && (s.charAt(0) != '#')) {
                    return (FrameworkFactory) Class.forName(s).newInstance();
                }
            }
        } finally {
            if (br != null)
                br.close();
        }
    }
    throw new Exception("Could not find framework factory.");
}

Felix属性

框架属性

启动属性

与Felix交互

构建bundle

<packaging>bundle</packaging>

...

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.felix</groupId>
            <artifactId>maven-bundle-plugin</artifactId>
            <version>2.5.3</version>
            <extensions>true</extensions>
            <configuration>
                <instructions>
                    <Bundle-Name>demo</Bundle-Name>
                    <Bundle-SymbolicName>demo</Bundle-SymbolicName>
                    <Implementation-Title>demo</Implementation-Title>
                    <Implementation-Version>1.0.0</Implementation-Version>
                    <Export-Package></Export-Package>
                    <Import-Package></Import-Package>
                    <Bundle-Activator>org.embedosgi.activator.Activator</Bundle-Activator>
                </instructions>
            </configuration>
        </plugin>
    </plugins>
</build>
package org.embedosgi.activator;

import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;

import java.util.Dictionary;
import java.util.Hashtable;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class Activator implements BundleActivator {
    public void start(BundleContext bundleContext) throws Exception {
        Dictionary<String,String> dict = new Hashtable<String, String>();
        String version = bundleContext.getBundle().getVersion().toString();
        dict.put("version",version);
        Object bean = Class.forName("org.embedosgi.demo.impl.HelloImpl").newInstance();
        bundleContext.registerService("org.embedosgi.demo.Hello", bean, dict);
        System.out.println("Reg Hello Service End!" + version);
    }

    public void stop(BundleContext bundleContext) throws Exception {

    }
}

其主要作用就是将HelloImpl对象对外发布为Hello服务,并设置版本号为自身bundle的版本号,即在pom.xml中设置的Version

Hello和HelloImpl很简单

package org.embedosgi.demo;

/**
 * Created by wangyifan on 2015/11/9.
 */
public interface Hello {
    String say(String name);
}

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        return "Hello " + name;
    }
}

通过maven的package命令打包,即可打包成一个bundle

本地部署与debug

打包成bundle后,一般情况下你需要把bundle发布到OSGi容器内去部署,这里就是发布到felix中。而目前我们使用了内嵌式的felix,可直接在本地部署。方便调试。

<!--这是我在上面创建的Felix启动项目的依赖配置,请根据自己的项目做修改-->
<dependencies>
    <dependency>
        <groupId>com.ivan.osgi</groupId>
        <artifactId>osgi</artifactId>
        <version>1.0-SNAPSHOT</version>
    </dependency>
</dependencies>

这样的话,每次运行时都会打包这个bundle,然后自动将其部署到了Felix容器中了。且支持debug

外部类获取OSGi服务

这里测试如何在org.embedosgi.main.Main中调用bundle中发布的Hello服务。

其实很简单,OSGi通过BundleContext来管理bundle,在上面发布服务的时候你也看到了,也是通过BundleContext来发布服务的。

而Framework提供了获取BundleContext的方法getBundleContext(),只要获取到BundleContext就可以获取服务了。相关代码如下:

BundleContext context = m_fwk.getBundleContext();
String filter = "(&(objectClass=org.embedosgi.demo.Hello)(version=1.0.0))";
Filter f = context.createFilter(filter);
ServiceTracker serviceTracker = new ServiceTracker(context, f, null);
serviceTracker.open();
Object o = serviceTracker.getService();
Class clz = o.getClass();
System.out.println(this.getClass().getClassLoader() + " | " + clz.getClassLoader());
Method method = clz.getDeclaredMethod("say", String.class);
System.out.println(method.invoke(o, "Ivan"));

内部bundle调用外部类

我们在Felix中启动项目中新增一个类HostHello

package org.embedosgi.host;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HostHello {
    public String name(){
        return "HostName";
    }
}

只是简单的返回一个字符串

在Bundle项目中,修改HelloImpl类来获取这个类

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;
import org.embedosgi.host.HostHello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        return "Hello " + name + new HostHello().name();
    }
}

如何能使HelloImpl调用到HostHello的name方法呢?

其实有两个方法可以实现!

我们先看第一个方法:

只需要在conf/config.properties中配置

org.osgi.framework.system.packages=org.embedosgi.host
或者
org.osgi.framework.system.packages.extra=org.embedosgi.host

然后修改Bundle项目的pom.xml文件

<plugin>
    <groupId>org.apache.felix</groupId>
    <artifactId>maven-bundle-plugin</artifactId>
    <version>2.5.3</version>
    <extensions>true</extensions>
    <configuration>
        <instructions>
            <Bundle-Name>demo</Bundle-Name>
            <Bundle-SymbolicName>demo</Bundle-SymbolicName>
            <Implementation-Title>demo</Implementation-Title>
            <Implementation-Version>1.0.0</Implementation-Version>
            <Export-Package></Export-Package>
            <Import-Package>*</Import-Package>
            <Bundle-Activator>org.embedosgi.activator.Activator</Bundle-Activator>
        </instructions>
    </configuration>
</plugin>

*号表示自动生成需要的导入包,你也可以将Import-Package标签删除,默认就是自动导入

也就是说,通过系统Bundle导出了org.embedosgi.host这个包,然后在Bundle项目中导入了这个包。这样就可以在Bundle中调用了

第二种方法

配置

org.osgi.framework.bootdelegation=sun.*,com.sun.*,org.osgi.framework,org.osgi.framework.*,org.embedosgi.host
org.osgi.framework.bundle.parent=app

这里的意思是所有以sun,com.sun,org.osgi.framework和org.embedosgi开头的包都通过AppClassLoader加载,加载后对所有bundle可见。

Bundle项目不需要做任何导入导出!

ClassLoader结构图

第二种方式的ClassLoader结构图如下:

HelloImpl在遇到HostHello类时,发现配置了org.osgi.framework.bootdelegation,那么直接委托给AppClassLoader来加载。

可以稍微修改下代码,打印出ClassLaoder即可得到!

package org.embedosgi.demo.impl;
import org.embedosgi.demo.Hello;
import org.embedosgi.host.HostHello;

/**
 * Created by wangyifan on 2015/11/9.
 */
public class HelloImpl implements Hello {
    public String say(String name) {
        System.out.println("HostHello ClassLoader = " + HostHello.class.getClassLoader());
        return "Hello " + name + new HostHello().name();
    }
}

打印结果

HostHello ClassLoader = sun.misc.Launcher$AppClassLoader@610f7612

多版本

OSGi支持多版本发布,即可以在一个OSGi容器内发布多个不同版本的应用。比如这里我们有一个demo-1.0.0.jar的应用。

我们可以对HelloImpl稍做修改,发布一个demo-1.0.1.jar的版本。两个版本可以并存。调用时只需要通过LDAP过滤即可。

总结

本文介绍了

通过如上内容,我们可以将应用中基础的部分固化,而业务代码动态化,来加快代码的迭代速度。

同时也可实现如服务框架,结合MVVM模式,可实现易扩展的Web应用。想象空间还是很大的。

最后给出项目代码

上一篇下一篇

猜你喜欢

热点阅读