原文:https://blog.csdn.net/u013851082/article/details/70214881
OSGI(Open Services Gateway Initiative),或者通俗点说JAVA动态模块系统,定义了一套模块应用开发的框架。
OSGI提供以下优势:
- 你可以动态地安装、卸载、启动、停止不同的应用模块,而不需要重启容器。
- 你的应用可以在同一时刻跑多个同一个模块的实例。
- OSGI在SOA领域提供成熟的解决方案,包括嵌入式,移动设备和富客户端应用等。
HelloWorld 开发步骤
使用IDE:Eclipse
启动Eclipse,依次点 File --> New --> Project。
选择Plug-in Project,next。
输入Project Name项目名称,比如com.howard.sample.HelloWorld,Target Platform(目标平台)里的an OSGI framework,选择standard。
剩下的保持默认,next。
下个对话框也默认,next。
-
然后选择Hello OSGI Bundle作为模版。Finish。
此时已创建好了hello world bundle的模板代码,其中包含两个主要文件即Activator.java和MANIFEST.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高,就返回谁。