前言
在目前流行的java开发工具中,Eclipse和Idea都占有很大的市场份额。Eclipse 3.0以后的插件就是基于OSGI开发的,理论上使用Eclipse开发OSGI模块是最佳的选择。OSGI模块开发与普通的jar包开发最大的不同就是配置模块“元数据”, 而Eclipse中提供了可视化工具进行模块元数据配置。本人比较习惯使用Idea做java开发,本次demo讲解也是基于Idea开发工具进行。
OSGI模块是在OSGI框架中运行的,要搭建自己的开发环境首先就要选择一个OSGI框架,本示例选择的是Eclipse的equinox。关于如何在Idea开发工具中配置equinox,这里不进行讲解,可以参考http://blog.csdn.net/love_taylor/article/details/75194394 这篇文章。
示例讲解
本示例开发了4个Bundle模块:manager、server、client、api:
这4个模块的关系:
api模块的作用是定义服务接口,没有具体实现;
server模块实现了api中定义的接口,是具体的服务实现;
client模块作为服务使用方法,使用server模块提供的服务;
manager模块的作用仅仅是用来启动server和client模块,以及模拟定时启动或者停止server模块,用于测试服务的消失和出现;
这个程序整体目的很简单,就是用于测试服务的是可以热插拔的。下面分别来看下各个模块的实现:
api模块
这个Bundle只定义了一个HelloService接口,由于这个模块不需要与OSGI框架交互,也不需要对外提供服务,所有不需要创建激活器。HelloService接口定义如下:
public interface HelloService { void sayHello(); }
元数据配置也很简单(这里没有使用Maven插件进行元数据配置),配置界面如下:
也许你会疑惑为什么不需要使用Export-Package导出包呢,否则其他模块不能通过Import-Package使用HelloService接口啊。这就是使用开发工具的好处,在同一个工程的多个模块中,Idea可以自动识别导入导出包,并自动添加到Bundle jar中(经过实验自动导入没有问题,导出有时候需要手动处理)。
server模块
该模块主要就是对api模块的接口进行实现,并注册一个服务到注册中心,即发布服务的过程。所以除了实现类以为,该模块还需要一个激活器 才能发布服务:
public class ServerAtivictor implements BundleActivator{ private BundleContext context; @Override public void start(BundleContext context) throws Exception { this.context = context; //元数据,客户端在查询服务是可以根据元数据过滤 Dictionary dictionary = new Properties(); dictionary.put("test","test"); //注册服务 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"); } }
实现类的内容很简单,就是打印一句话:
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接口所在的包),但无需我们动手 Idea在Build 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 MapbundleMap = 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中配置:
org.apache.maven.plugins maven-jar-plugin 2.3.1 2 server com.sky.osgi.server 1.0.0 sky com.sky.osgi.server.ServerAtivictor org.osgi.framework
可见manifestEntries节点里的配置项跟Bundle的元数据配置项是一一对应的。
另外本示例中,使用Maven引入包的方式和平时开发完全一样,就不再贴出pom.xml文件的配置内容,上述代码已上传GitHub: https://github.com/gantianxing/osgi_demo1.git