OSGI example based on idea+maven

foreword

 

In the current popular java development tools, Eclipse and Idea have a large market share. The plug-ins after Eclipse 3.0 are developed based on OSGI . In theory, using Eclipse to develop OSGI modules is the best choice. The biggest difference between OSGI module development and ordinary jar package development is the configuration module " metadata " , and Eclipse provides a visual tool for module metadata configuration. I am more accustomed to using Idea for java development, and this demo explanation is also based on Idea development tools.

 

The OSGI module runs in the OSGI framework. To build your own development environment, you must first choose an OSGI framework. In this example, the equinox of Eclipse is selected . About how to configure equinox in the Idea development tool, I will not explain it here, you can refer to this article http://blog.csdn.net/love_taylor/article/details/75194394 .

 

Example explanation

 

This example develops 4 Bundle modules : manager , server , client , api :



 

The relationship of these 4 modules:

The role of the api module is to define the service interface, and there is no specific implementation;

The server module implements the interface defined in the api and is a specific service implementation;

The client module is used as a service method, using the services provided by the server module;

The role of the manager module is only to start the server and client modules, and to simulate the timing to start or stop the server module, to test the disappearance and appearance of the service;

 

The overall purpose of this program is very simple, that is, the test service is hot-pluggable. Let's take a look at the implementation of each module separately:

 

api module

This Bundle only defines a HelloService interface. Since this module does not need to interact with the OSGI framework or provide external services, there is no need to create an activator. The HelloService interface is defined as follows:

public interface HelloService {
    void sayHello();
}
 

 

The metadata configuration is also very simple (the Maven plugin is not used for metadata configuration here), and the configuration interface is as follows:



 

Maybe you are wondering why you don't need to use Export-Package to export packages, otherwise other modules cannot use the HelloService interface through Import-Package . This is the advantage of using development tools. In multiple modules of the same project, Idea can automatically identify the import and export packages and automatically add them to the Bundle jar (there is no problem with automatic import after experiments, but sometimes the export needs to be handled manually).

 

server module

This module mainly implements the interface of the api module and registers a service to the registry, that is, the process of publishing the service. So in addition to the implementation class, the module also needs an activator to publish the service:

public class ServerAtivictor implements BundleActivator{
    private BundleContext context;
 
    @Override
    public void start(BundleContext context) throws Exception {
        this.context = context;
 
        //Metadata, the client can filter according to the metadata when querying the service
        Dictionary dictionary = new Properties();
        dictionary.put("test","test");
        // register service
        ServiceRegistration registration = context.registerService(HelloService.class.getName(),new HelloServiceImpl("小明"),dictionary);
        System.out.println("start server bundle");
 
    }
 
    @Override
    public void stop(BundleContext context) throws Exception {
        System.out.println("stop server bundle");
    }
}

The content of the implementation class is very simple, just print a sentence:

public class HelloServiceImpl implements HelloService {
 
    private String name;
 
    public HelloServiceImpl(String name) {
        this.name = name;
    }
 
    @Override
    public void sayHello() {
        System.out.println("Hello,"+name);
    }
}
 

 

再来看server模块的元数据配置,也很简单 比起api模块来说只是多了一个激活器配置:



 

理论上需要在Additional properties中添加Import-Package配置项 导入com.sky.osgi.api包(HelloService接口所在的包),但无需我们动手 IdeaBuild jar时会在元数据配置文件MANIFEST.MF中自动加入。最终server模块的元数据配置内容如下:

Manifest-Version: 1.0
Bnd-LastModified: 1516779536888
Bundle-Activator: com.sky.osgi.server.ServerAtivictor
Bundle-ManifestVersion: 2
Bundle-Name: com.sky.osgi.server
Bundle-SymbolicName: com.sky.osgi.server
Bundle-Version: 1.0.0
Created-By: 1.8.0_65 (Oracle Corporation)
Export-Package: com.sky.osgi.server;uses:="org.osgi.framework";version
 ="1.0.0"
Import-Package: com.sky.osgi.api,org.osgi.framework
Require-Capability: osgi.ee;filter:="(&(osgi.ee=JavaSE)(version=1.8))"
Tool: Bnd-3.3.0.201609221906
 

 

可见server模块自动导入了com.sky.osgi.api包和org.osgi.framework包(激活器在这个包中)。

 

client模块

该模块相比前两个模块稍微复杂些,主要作用是:模拟当server模块提供的服务可用时,使用该服务,当服务不可用时 提示服务不存在,但不影响client模块自己的业务运行。这里使用“服务追踪器”来感知服务的变化。首先看下激活器的实现内容:

public class ClientActivator implements BundleActivator {
 
    private ServiceTracker serviceTracker;
    private ExecutorService executorService;
 
    @Override
    public void start(BundleContext context) throws Exception {
        //创建一个带定制器的 追踪器
        serviceTracker = new ServiceTracker(context, HelloService.class.getName(), new MyServiceTrackerCustomizer(context));
        serviceTracker.open();//打开追踪器
 
        //新开一个线程,模拟调用服务
        executorService = Executors.newSingleThreadExecutor();
        executorService.submit(new TestTask(serviceTracker));
    }
 
    @Override
    public void stop(BundleContext context) throws Exception {
        serviceTracker.close();//关闭追踪器
        executorService.shutdown();
    }
}
 

 

再来看下模拟服务调用的任务类:

public class TestTask implements Runnable{
    public ServiceTracker serviceTracker;
 
    public TestTask(ServiceTracker serviceTracker) {
        this.serviceTracker = serviceTracker;
    }
 
    @Override
    public void run() {
            boolean flag = true;
            while (flag){
                try {
                    HelloService helloService = (HelloService)serviceTracker.getService();
                    if (helloService!= null) {
                        helloService.sayHello();
                    } else {
                        System.out.println("没有可用的服务");
                    }
                }catch (Exception e){
                    System.out.println("业务异常");
                }
 
                //睡5秒重试
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("线程池关闭");
                    flag = false;
                }
            }
    }
}

 

定制器MyServiceTrackerCustomizer的代码就不贴了,本文中所有的代码详见文章末尾提供的github地址。

 

由于idea自动配置导入导出,client模块的元数据配置界面也很简单:




manager模块

这个模块的作用很简单,就是控制server模块和client模块的启动和停止,该模块主要使用的是OSGI生命周期层的api。在OSGI框架启动是,把所有的4个模块加载到OSGI框架,并只启动manager模块,再由manager模块的激活器启动server模块和client模块。manager模块的激活器实现内容如下:

public class ManagerActivator implements BundleActivator{
    private Map<String,Bundle> bundleMap = new HashMap<>();
    private Thread thread;
 
    @Override
    public void start(BundleContext bundleContext) throws Exception {
        System.out.println("开始启动Bundle");
 
        //获取OSGI框架中已经所有Bundle
        Bundle[] bundles = bundleContext.getBundles();
        for (Bundle bundle:bundles){
            //启动server Bundle
            if(bundle.getSymbolicName().equals("com.sky.osgi.server")){
                bundle.start();
                bundleMap.put("com.sky.osgi.server",bundle);
            }
            //启动client Bundle
            if(bundle.getSymbolicName().equals("com.sky.osgi.client")){
                bundle.start();
                bundleMap.put("com.sky.osgi.client",bundle);
            }
            //为什么不启动api bundle呢?
            // 因为它不需要提供服务,也不需要与OSGI框架交互,只是作为导出api接口使用
        }
 
        System.out.println("所需Bundle启动结束");
 
        //启动线程修改server Bundle状态
        thread = new Thread(new UpdateTask());
        thread.start();
 
    }
 
    @Override
    public void stop(BundleContext bundleContext) throws Exception {
        thread.interrupt();//停止线程
        bundleMap.clear();
    }
 
    class UpdateTask implements Runnable{
        @Override
        public void run() {
            Bundle server = bundleMap.get("com.sky.osgi.server");
            boolean flag = true;
            while(flag){
                try {
                    if (server.getState() != Bundle.ACTIVE){
                        server.start();
                    }else{
                        server.stop();
                    }
                } catch (BundleException e) {
                    e.printStackTrace();
                    System.out.println("Bundle启动停止出现异常");
                }
 
                try {
                    //模拟每隔10秒交替启动和停止Bundle
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    flag = false;
                    System.out.println("管理Bundle线程停止");
                }
            }
 
        }
    }
}
 

 

在该激活器中,创建了一个新的线程,模拟每隔10秒 启动或者停止server模块。client模块会自动感知服务的变化,并及时做出正确的相应,元数据配置方式以上面类似就不上图了。至此整个demo内容讲解完毕。

 

demo测试运行

如要正确的运行本示例代码,需要注意idea运行配置界面的配置:



 

注意“Start after insall”,意思是说框架加载模块完成后 就启动模块。这里只需要启动manager模块,在该模块中再来控制其他模块的启动。最后先build整个工程打包,点击运行或者debug运行,即可启动框架并执行整个工程代码。



 



 

 

测试结果表面 client模块可以实时的感知到server模块中服务的变化,并且彼此运行互不干扰,感兴趣的朋友可以自己运行试试。

 

总结

 

本次示例模拟内容虽然简单,但已经覆盖OSGI的模块层、生命周期层、服务层。这里再提下模块层元数据配置,本示例中主要使用的是idea自动的导入和导出机制。在开发一个大型的Bundle时,最繁琐的就是确定导入导出包,使用Idea开发工具可以减少这部分工作量。如果一定要手动配置元数据,可以使用Maven插件在pom.xml中配置:

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-jar-plugin</artifactId>
                <version>2.3.1</version>
                <configuration>
                    <archive>
                        <manifestEntries>
                            <Bundle-ManifestVersion>2</Bundle-ManifestVersion>
                            <Bundle-Name>server</Bundle-Name>
                            <Bundle-SymbolicName>com.sky.osgi.server</Bundle-SymbolicName>
                            <Bundle-Version>1.0.0</Bundle-Version>
                            <Bundle-Vendor>sky</Bundle-Vendor>
                            <Bundle-Activator>com.sky.osgi.server.ServerAtivictor</Bundle-Activator>
                            <!--<Export-Package>-->
                                <!--com.sky.osgi.server-->
                            <!--</Export-Package>-->
                            <Import-Package>
                                org.osgi.framework
                            </Import-Package>
                        </manifestEntries>
                    </archive>
                </configuration>
            </plugin>
        </plugins>
    </build>

 

可见manifestEntries节点里的配置项跟Bundle的元数据配置项是一一对应的。

 

 

另外本示例中,使用Maven引入包的方式和平时开发完全一样,就不再贴出pom.xml文件的配置内容,上述代码已上传GitHub: https://github.com/gantianxing/osgi_demo1.git

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326296757&siteId=291194637