OGSI 实例(转)

原文:https://blog.csdn.net/u013851082/article/details/70214881

OSGI(Open Services Gateway Initiative),或者通俗点说JAVA动态模块系统,定义了一套模块应用开发的框架。

OSGI提供以下优势:

  1. 你可以动态地安装、卸载、启动、停止不同的应用模块,而不需要重启容器。
  2. 你的应用可以在同一时刻跑多个同一个模块的实例。
  3. OSGI在SOA领域提供成熟的解决方案,包括嵌入式,移动设备和富客户端应用等。

HelloWorld 开发步骤
使用IDE:Eclipse

  1. 启动Eclipse,依次点 File --> New --> Project。
  2. 选择Plug-in Project,next。
  3. 输入Project Name项目名称,比如com.howard.sample.HelloWorld,Target Platform(目标平台)里的an OSGI framework,选择standard。
  4. 剩下的保持默认,next。
  5. 下个对话框也默认,next。
  6. 然后选择Hello OSGI Bundle作为模版。Finish。
    此时已创建好了hello world bundle的模板代码,其中包含两个主要文件即Activator.javaMANIFEST.MF
    BundleActivator接口是定义了bundle在启停状态下需要实现的代码,是bundle的入口。

注意:
1、Activator类需要有自己的无参构造器。
​ 2、容器启动bundle过程中负责调用你的Activator类的start方法。bundle可以在此初始化资源比如说初始化数据库连接。start方法需要一个参数,BundleContext对象。这个对象允许bundles以取得 OSGI容器相关信息的方式和框架交互。如果某一个bundle有异常抛出,容器将对该bundle标记为stopped并不将其纳入service列表。
​ 3、容器关闭的时候会调用你的Activator类方法stop(),你可以利用这个机会做一些清理的操作。

MANIFEST.MF 解读

Manifest-Version: 1.0 // OSGI规范的版本
Bundle-ManifestVersion: 2 // bundle支持OSGI规范的版本
Bundle-Name: HelloService // 给bundle定义一个短名,方便人员阅读
Bundle-SymbolicName: com.howard.sample.HelloService //给bundle定义一个唯一的非局部名
Bundle-Version: 1.0.0.qualifier 
Bundle-Activator:com.howard.sample.service.impl.HelloServiceActivator // 主类名
Bundle-Vendor: HOWARD
Bundle-RequiredExecutionEnvironment: JavaSE-1.8
Import-Package: org.osgi.framework;version="1.3.0" // 定义bundle的导入包
Export-Package: com.howard.sample.service,com.howard.sample.service.impl  // 定义bundle的导出包 

运行:

1.  `点击Run --> Run Configuration`
2.  `在左边的OSGI Framework选项里右键 new ,创建一个新的OSGI Run Configuration`
3.  `名字随便取好了,我们取个OSGi hello world。`
4.  `你会注意到中间的窗口里Workspace项目里有一子项 com.howard.sample.HelloWorld,将其勾选上,其他的不用管。这时的状态应该如下图。`
5.  `点击Run按钮。在控制台你应该可以看见点东西了。那是叫做OSGI控制台的东东。与子相伴,还有一个"Hello world"。`

注意
如果出现 org.osgi.framework.BundleException: Could not find bundle: org.eclipse.equinox.console 错误,请在run的时候选中下面四个
org.apache.felix.gogo.command
org.apache.felix.gogo.runtime
org.apache.felix.gogo.shell
org.eclipse.equinox.console

OSGI控制台
引入包和导出包
在 MANIFEST.MF 文件中,Import-Package 和 Export-Package 中填写内容,并用逗号隔开。
如果是向外暴露接口,可以在Export-Package 中填写。导入则在Import-Package中填写。(不支持通配符)
Eclipse 选中 MANIFEST.MF 后,给予了我们选择包的功能导入和导出的功能,非常方便。

具体操作步骤

1、创建名为com.howard.sample.HelloService的bundle,创建步骤和前面一样。
2、在这个bundle内,添加一个com.howard.sample.service.HelloService.java 接口,代码如下:

public interface HelloService {  
    public String sayHello();  
}  

3、创建一个com.howard.sample.service.impl.HelloServiceImpl.java类实现刚才的接口

public class HelloServiceImpl implements HelloService{  
    public String sayHello() {  
        System.out.println("Inside HelloServiceImple.sayHello()");  
        return "Say Hello";  
   }  
}  

4、打开MANIFEST.MF,选择Runtime标签项,在Exported Packages选项栏,点击Add并且选择com.howard.sample.service这个包。此时导出栏应该多出了一个包。

注意,我们仅仅暴露了HelloService接口,而不是直接暴露HelloServiceImpl实现。
此时打开HelloWorld项目下的Activator.java文件,这时候你会发现可以使用HelloService这个接口了。但还是不能使用HelloServiceImpl实现类。Eclipse会告诉你:Access restriction(立入禁止)。

**Class级别可见域**

为什么OSGI容器可以做到让jar包中的一些classes可见而另一些又不可见呢。

答案其实就是OSGI容器自定义了java class loader来有选择的加载类。OSGI容器为每一个bundle都创建了不同的class loader。因此,bundle可以访问的classes包括

- Boot classpath:所有的java基础类。
- Framework classpath:OSGI框架级别的classloader加载的类
- Bundle classpath:Bundle本身引用的关系紧密的JAR的路径
- Imported packages:就是在MANIFEST.MF里声明的导入包,一旦声明,在bundle内就可见了。

bundle级别的可见域允许你可以随时放心的更改HelloServiceImpl实现类而不需要去担心依赖关系会被破坏。

继续操作:
修改HelloService文件
1、确保com.howard.sample.HelloService里的MANIFEST.MF导入org.osgi.framework包
2、创建com.howard.sample.service.impl.HelloServiceActivator.java,代码如下:

public class HelloServiceActivator implements BundleActivator {  
    ServiceRegistration helloServiceRegistration;  
    @Override  
    public void start(BundleContext context) throws Exception {  
        HelloService helloService = new HelloServiceImpl();  
        helloServiceRegistration = context.registerService(HelloService.class  
                .getName(), helloService, null);  
    }  

    @Override  
    public void stop(BundleContext context) throws Exception {  
        helloServiceRegistration.unregister();  
    }  
}  

我们就是用BundleContext的registerService方法注册service的。这个方法需要三个参数。

  • service的接口名。如果service实现了多个接口,那样你需要传入一个包含所有接口名的String数组。在这里我们传入的是HelloService这个接口。
  • 真正的service实现。在例子中我们传了一个HelloServiceImpl实现。
  • service属性。这个参数可以在有多个service实现同一个接口的情况下,消费者用来区分真正感兴趣的service。
context.registerService(HelloService.class.getName(), helloService, null);

第一个参数,如果实现了多个接口,那么可以传入一个String[] 数组进去;
第三个参数是区分用的,目前还未使用到。

3、修改HelloService的MANIFEST.MF文件,将Bundle-Activator改成 com.howard.sample.service.impl.HelloServiceActivator

HelloWorld导入服务

public class Activator implements BundleActivator {  
   ServiceReference helloServiceReference;  
   public void start(BundleContext context) throws Exception {  
       System.out.println("Hello World!!");  
       helloServiceReference=context.getServiceReference(HelloService.class.getName());  
       HelloService helloService=(HelloService)context.getService(helloServiceReference);  
       System.out.println(helloService.sayHello());  
   }  
   public void stop(BundleContext context) throws Exception {  
       System.out.println("Goodbye World!!");  
       context.ungetService(helloServiceReference);  
   } 
}  

在此之后可以启动两个服务了。

创建服务工厂
场景 : 某些servie是比较耗费资源,不希望它一直占用资源,最好是在真正用它的时候创建不用的时候销毁

做法就是使用ServiceFactory接口的实现来代替原先service具体的实现到OSGI容器去注册。这样,以后只有当其他bundle请求该服务时,才会由ServiceFactory实现类来处理请求并返回一个新的service实例。

实例步骤如下:
1、在HelloService bundle创建一个实现ServiceFactory接口的类HelloServiceFactory类,代码如下:

public class HelloServiceFactory implements ServiceFactory<Object> {  
 private int usageCounter = 0;  
 @Override  
 public Object getService(Bundle bundle, ServiceRegistration registration) {  
     System.out.println("Create object of HelloService for " + bundle.getSymbolicName());  
     usageCounter++;  
     System.out.println("Number of bundles using service " + usageCounter);  
     HelloService helloService = new HelloServiceImpl();  
     return helloService;  
 }  
 @Override  
 public void ungetService(Bundle bundle, ServiceRegistration registration, Object service) {  
     System.out.println("Release object of HelloService for " + bundle.getSymbolicName());  
     usageCounter--;  
     System.out.println("Number of bundles using service " + usageCounter);  
    }  
 }

ServiceFactory接口定义了两个方法:

  • getService方法:特定的bundle在第一次调用BundleContext的getService方法时由OSGI框架调用,在实例代码中,我们用这个方法来返回一个新的HelloService的实现。OSGI框架会缓存这个返回的对象,如果同一个bundle在未来再次调用BundleContext的getService方法的话,会直接返回这个缓存中的对象。

  • ungetService方法:bundle释放service的时候由OSGI容器调用。

2、修改HelloServiceActivator.java的start方法,将ServiceFactory作为服务注册,代码如下

public class HelloServiceActivator implements BundleActivator {  
    ServiceRegistration helloServiceRegistration;  
    @Override  
    public void start(BundleContext context) throws Exception {  
        HelloServiceFactory helloServiceFactory = new HelloServiceFactory();  
        helloServiceRegistration = context.registerService(HelloService.class  
            .getName(), helloServiceFactory, null);  
    }  
  
    @Override  
    public void stop(BundleContext context) throws Exception {  
        helloServiceRegistration.unregister();  
    } 
}

现在运行下试试看,你会发现HelloWorld bundle启动时才会初始化HelloService,控制台会打印出"Number of bundles using service 1",当HelloWorld bundle暂停时会打印出"Number of bundles using service 0"。

单独启动HelloService没有效果,启动了HelloWorld bundle才会调用HelloService的启动方法。

services跟踪
某种情形下,我们可能需要在某个特殊的接口有新的服务注册或取消注册时通知消费端。这时我们可以使用ServiceTracker类。如下步骤所示:
1、在HelloWorld bundle里的MANIFEST.MF导入org.osgi.util.tracker包。
2、创建HelloServiceTracker类,代码如下:

public class HelloServiceTracker extends ServiceTracker {  
  public HelloServiceTracker(BundleContext context) {  
    super(context, HelloService.class.getName(),null);  
  }  
  public Object addingService(ServiceReference reference) {  
    System.out.println("Inside HelloServiceTracker.addingService " + reference.getBundle());  
    return super.addingService(reference);  
  }  
  public void removedService(ServiceReference reference, Object service) {  
    System.out.println("Inside HelloServiceTracker.removedService " + reference.getBundle());  
    super.removedService(reference, service);  
  } 
}  

我们在HelloServiceTracker的构造函数里将HelloService接口名传进去,ServiceTracker会跟踪实现这个接口的所有的注册services。ServiceTracker主要有两个重要方法:

  • addingService方法:bundle注册一个基于给定接口的service时调用。
  • removeService方法:bundle取消注册一个基于给定接口的service时调用。

3、修改Activator类,使用刚刚创建的HelloServiceTracker来获取service:

public class Activator implements BundleActivator {  
    HelloServiceTracker helloServiceTracker;  
    public void start(BundleContext context) throws Exception {  
        System.out.println("Hello World!!");  
        helloServiceTracker= new HelloServiceTracker(context);  
        helloServiceTracker.open();  
        HelloService helloService=(HelloService)helloServiceTracker.getService();  
        System.out.println(helloService.sayHello());  
    }  
  
    public void stop(BundleContext context) throws Exception {  
        System.out.println("Goodbye World!!");  
        helloServiceTracker.close();  
    }
}

现在运行一下,可以发现只要HelloService bundle启动或是暂停都会导致HelloServiceTracker的对addingService或removedService方法的调用。

ServiceTracker不仅仅能跟踪Service的动向,它还能通过getService方法取得Service实例并返回。但是如果同一个接口下有多个service注册,这时返回哪个service呢?这时候就需要看service的等级哪个高了。这个等级是service注册时的property属性里的一项:SERVICE_RANKING。谁的SERVICE_RANKING高,就返回谁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容