欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

OSGI框架:模块层、生命周期层、服务层

程序员文章站 2022-04-28 09:55:42
...

一、模块层

1. 什么是模块化:

模块层是OSGi框架中最基础的一部分,其中Java的模块化特性在这一层得到了很好的实现。但是这种实现与Java本身现有的一些模块化特性又有明显的不同。

模块化其实就是计算机科学中常见的一个概念: “将一个大型系统分解为多个较小的互相协作的逻辑单元,通过强制设定模块之间的逻辑边界来改善系统的维护性和封装性”。

OSGI框架:模块层、生命周期层、服务层

一个模块(module)定义了一个逻辑边界,这种模块本身精确的控制了哪些类是完全被封装起来的,而哪些类需要暴露出来作为外部使用。这样我们就可以轻松的将实现屏蔽在模块的内部,而将公共API暴露在外部。

2. OSGI中模块化与面向对象的联系与区别

在用Java编写面向对象程序的时候,一个了解面向对象概念的人是不会把所有功能都塞到同一个类里面去的,面向对象让你从问题域中发现多个事物,并且每个事物负责不同的功能,尽量做到高内聚和低耦合。在这里,我们可以说面向对象的模块化粒度是在“类”这个级别上。 
而OSGi的模块化,则是通过为JAR包添加metadata来定义哪些类应该暴露哪些类又隐藏在包中,其控制可见性的粒度是在bundle(JAR包)这一层面上的。

所以,它们所带来的能力都是通过控制可见性和可用性来保证高内聚和低耦合的,但是粒度不同,一个是对象层面上的,一个是模块层面上的。 既然负责的是不同的粒度,那么两者并不相互冲突,各有各的作用在里面。

3. OSGI模块层基础

3.1 Bundle的概念

这是模块层最核心的概念,也是模块(module)这个概念在OSGi中的具象表现。接下来你将会在OSGi的世界中创建和使用数不胜数的bundle。 

什么是bundle?——bundle是以jar包形式存在的一个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),并且jar包的物理边界也同时是运行时逻辑模块的封装边界。

OSGI框架:模块层、生命周期层、服务层

一个更为直观的说明:在标准的jar包的manifest文件中添加一些bundle的模块化特征(就是前面提到的metadata)后,这个jar包就变成了一个bundle。 
那么有了上面的描述,你大概也能想到,bundle和普通jar包最大的区别就在于元数据。

3.2 使用元数据来定义Bundle

Bundle元数据的目的在于准确描述模块化相关的bundle特征,这样才能让OSGi框架恰当的对bundle进行各种处理工作(比如依赖解析,强制封装等),这些元数据主要有这三个部分:

① 可读的信息(可选):帮助更好地理解和使用bundle

② bundle的标识符(必须):唯一的标识一个bundle

③ 代码可见性(必须):定义内部与外部代码

3.3 代码可见性

OSGi标准定义了如下的属性用于描述代码的可见性:

Bundle-ClassPath—它定义了形成这个bundle的所有代码所在的位置,和Java的classath的概念相近,不同点在于,Java中的classpath是定义的jar包的位置,而这个属性描述的是bundle内部的类在bundle中的路径。有例子如下:

Bundle-ClassPath:.,other-classes/,embedded.jar
Export-Package—显式暴露需要和其他bundle共享的代码,每个包之间用逗号分隔,每个包可用修饰词来修饰包的其他特征。

Export-Package: org.serc.hellworld; vendor=”SERC”,
org.serc.hellworld.impl; vendor=”Gou Rui”  
Import-Package—定义该bundle所依赖的外部代码,其格式和Export-Package相同,并且也可以使用修饰词来修饰包。不过这里的修饰词是用来限制所依赖包的范围的,像是一个过滤器,而不像Export-Package中用来声明包的特征。例如如下语句:

Import-Package: org.serc.helloworld; vendor=”SERC”

二、生命周期层

1. 简介

生命周期层在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的。生命周期层一个主要的功能就是让你能够从外部管理应用或者建立能够自我管理的应用(或者两者的结合),并且给了应用本身很大的动态性。 
这一章里,我们介绍生命周期层的基本特性和如何有效的使用这些特性。当然按照惯例,我们依然会先讲清楚什么是生命周期管理以及OSGi需要生命周期管理的原因,然后再讲解生命周期层的一些基本内容。

2. 什么是生命周期管理

一般来说,程序(或者程序的一部分)都一定服从某种生命周期。软件的生命周期有4个典型的阶段,如下图:

OSGI框架:模块层、生命周期层、服务层

如果你正在创建一个应用,首先你得安装(install)它;然后当这个应用的所有依赖都满足了,我们就可以执行(execute)这个应用;如果这个应用不需要了,我们可以停止(stop)它;过了一段时间,我们可能需要更新(update)这个应用的版本;最后,我们可能会移除(remove)这个应用,因为再也用不着了。

我们通过在外部或者内部对应用进行这些操作,完成对应用的“生命周期管理”过程。对于非模块化应用,这些操作就是以整个应用为对象的;如果是对于模块化应用,那么我们就可以有更细粒度(针对应用中的某个模块)的生命周期管理了。

一般情况下,要想管理应用的生命周期,就得通过框架提供的API来完成。一套清晰明确的生命周期API使得你的应用可以对一段代码进行配置、初始化和维护。而OSGi标准就定义了这样的一套API,使得你能在bundle生命周期的各种阶段对其进行多种外部或内部操作,从而达到对bundle进行功能控制的目的。

由于你构建的应用被模块化为若干个部分,并且有了这些API之后你能对其中任意一个部分的去留和动作进行精确和即时的控制,那么你所构建的这个应用的灵活性就大大的提升了。如果没有生命周期管理,别人给你什么样的应用,你就只能使用什么样的应用,可控制性很低;而一旦有了生命周期管理,无论别人给你什么样的应用,你也能通过生命周期管理对这个已经模块化应用的行为(无论是启动、更新还是停止等等)进行精确控制。

3. OSGI bundle的生命周期

OSGI框架:模块层、生命周期层、服务层

这个图清晰的展现了bundle在生命周期中的各个状态和状态间的转移条件。 
我们可以通过Bundle的getState方法来获得bundle的当前状态。 
在这里需要说明的是Starting和Stopping状态,这两个状态是暂态,也就是说这两个状态在持续一会以后就会自动转移到下一个状态,不需要转移条件。

4. 三个重要的接口

命周期层的API主要是由以下三个核心接口来组成的:BundleActivator,BundleContext和Bundle。

BundleActivator:让你能够捕捉bundle的start和stop事件,并对这两个事件作出自定义的反应。 

BundleContext:一个bundle在框架中的执行时上下文,这个上下文提供了和框架进行交互的方法。 

Bundle:在逻辑上表示了一个bundle,OSGi环境中的一个物理bundle对应了一个bundle对象。该对象中包含了bundle的基本信息和bundle声明周期的控制接口。

4.1 BundleActivator

BundleActivator的接口是如下定义的:

public interface BundleActivator {
public void start(BundleContext context) throws Exception;
public void stop(Bundlecontext context) throws Exception;
}
如果一个类实现了这个接口,那么这个类就成为了一个Activator。但是有实现是不够的,你要让OSGi框架知道这个Activator的存在。所以你还需要在MANIFEST文件中添加如下一项属性(假设你定义的activator的类叫做org.foo.Activator):

Bundle-Activator:org.foo.Activator
这样一来,当这个bundle启动(start)的时候,OSGi框架就会调用这个Activator的start方法,同样的也适用与stop方法。

需要注意的是,并不是每个bundle都需要一个activator,有时候你的bundle只是为了和其他bundle分享代码,而并不需要在启动和停止的时候做出多余的动作,所以是否使用这个借口,还得具体问题具体分析。

4.2 BundleContext

这个接口中的方法的功能主要分为两个部分: 

① 一部分是和部署与生命周期管理相关 

② 另一部分则是关于利用服务层进行bundle间交互的方法 

我们在这里主要关注第一部分的方法,其中主要的方法列表如下:

public interface BundleContext {
...
String getProperty(String key);
Bundle getBundle();
Bundle installBundle(String location, InputStream input) throws BundleException;
Bundle installBundle(String location) throws BundleException;
Bundle getBundle(long id);
Bundle[] getBundles();
void addBundleListener(BundleListener listener);
void removeBundleListener(BundleListener listener);
void addFrameworkListener(FrameworkListener listener);
void removeFrameworkListener(FrameworkListener listener);
...
}
bundle context对于与其相关的bundle来说都是唯一的执行上下文,并且只有在该bundle是属于active状态的时候执行时上下文才是有意义的,对这个时段准确的描述就是在start方法被调用和stop方法被调用的两个时间点之间。所以如果一个bundle并没有处于这个时间段里面,但是他的bundlecontext对象却被使用了,那么框架就会抛出异常。

框架使用这个上下文对象还有一个目的就是为了bundle的安全和资源分配,所以BundleContext对象应该被当做私有对象,不应该被随意在bundle之间传递。

4.3 Bundle

在BundleContext接口中,我们发现有名为getBundle的方法,我们可以从中得到Bundle对象。

对于每个被安装到框架中的bundle,框架都创建了一个Bundle对象在逻辑上表达之。这个接口中定义了bundle生命周期管理的方法,下面是这个接口的片段,这个接口的方法所带来的功能都是显而易见的:

public interface Bundle {
...
BundleContext getBundleContext();
long getBundleId();
Dictionary getHeaders();
Dictionary getHeaders(String locale);
String getLocation();
int getState();
String getSymbolicName();
Version getVersion();
void start(int options) throws BundleException;
void start() throws BundleException;
void stop(int options) throws BundleException;
void stop() throws BundleException;
void update(InputStream input) throws BundleException;
void update() throws BundleException;
void uninstall() throws BundleException;
...
}
稍微需要说明一下的是getLocation方法,大部分OSGi框架的实现都是将locatioin解释为指向OSGi bundle的一个URL,在需要的时候就会通过URL将bundle下载到框架中来安装使用。但是OSGi标准没有规定location的形式必须是URL,而且URL也并不是非要不可的,因为我们还可以通过输入流(Input Stream)来安装bundle。

此外,bundle不能自己改变自己的状态,比如说一个active的bundle不能stop自己,stop自己就会抛出异常。


三、服务层

1. 简介

作为OSGi框架中最上面的一层,服务层带给了我们更多的动态性,并且使用了大家或多或少都曾了解过的面向服务编程模型,其好处是显而易见的。

这里我们依然会讲解什么是服务层和服务层对于OSGi框架的意义,此外,还将告诉大家什么时候应该使用服务,什么时候不应该使用。最后将会是OSGi服务层的一些基础,包括如何定义、注册和发现服务。

2. 为什么要使用服务

服务(更准确的说是面向服务的编程模型)给予了我们一种即插即用的软件开发方法,意味着更强的灵活性。这种灵活性是如何体现的呢?

① 低耦合,利于组件复用:通过服务我们能够清晰的定义组件的边界,从而将服务的使用者和提供者之间的耦合度降到很低。

② 更加强调接口而不是在具体的实现:Java的interface提供了一种形式的契约,在OSGi的服务层中充分利用了接口特性的优势,这样使得无论有多少个类实现了这个接口,只要满足对这个接口的功能需要,就可以被使用者使用。

③ 对于依赖有比较清晰地描述:单是接口本身只包含服务的名称和参数类型,并不足以清楚的描述服务的所有特征,而面向服务的编程模型中要求了更加清晰的描述使得这些特征能够唯一标识一个服务。

④ 支持对多个竞争实现(多个实现同一个接口的类)的筛选:服务框架会帮助你记录服务的元数据,可以据此帮助使用者查询和筛选服务,使用者更加的主动,这一点和传统的依赖注入框架不同。

3. OSGI服务层基础

首先,需要说明的是,OSGi的服务层除开前面提到的面向服务的编程模型,还有一个区别于其他很多类似模型的特性,那就是服务的完全动态性。也就是说,当一个bundle发现并开始使用OSGi中的一个服务了以后,这个服务可能在任何的时候改变或者是消失。这方面的内容将在以后更加深入的讲解。 

OSGi框架有一个中心化的注册表,这个注册表遵从publish-find-bind模型:

OSGI框架:模块层、生命周期层、服务层

一个提供服务的bundle可以发布POJO作为服务的实体;一个使用服务的bundle可以通过这个注册表找到和绑定服务。 

我们可以通过BundleContext接口来完成上述的工作,下面就是含有这方面功能的接口列表:

public interface BundleContext {  
...  
void addServiceListener(ServiceListener listener, String filter) throws InvalidSyntaxException;  
void addServiceListener(ServiceListener listener);  
void removeServiceListener(ServiceListener listener);  
ServiceRegistration registerService(String[] clazzes, Object service, Dictionary properties);   
ServiceRegistration registerService(String clazz, Object service, Dictionary properties);  
ServiceRegistration[] getServiceReferences(String clazz, String filter) throws InvalidSyntaxException;  
ServiceRegistration[] getAllServiceReferences(String clazz, String filter) throws InvalidSyntaxException;  
ServiceReference getServiceReference(String clazz);  
Object getService(ServiceReference reference);  
boolean ungetService(ServiceReference reference);  
...  
}
3.1 发布服务

为了让别的bundle能发现这个服务,你必须在发布它之前对其进行特征描述。这些特征包括接口的名字(可以是名字的数组),接口的实现,和一个可选的java.util.Dictionary类型的元数据信息。下面是一个例子:

String[] interfaces =  new String[]{StockListing.class.getName(), StockChart.class.getname()};  
Dictionary metadata =  new Properties();  
metadata.setProperty(“name”, “LSE”);  
metadata.setProperty(“currency”, Currency.getInstance(“GBP”));  
metadata.setProperty(“country”, “GB”);  
ServiceRegistration registration = bundleContext.registerService(interfaces, new LSE(), metadata);

在上面的代码中,我们得到了ServiceRegistration对象,我们可以用这个对象来更新服务的元数据: 
registration.setProperties(newMetadata);

也可以直接就把这个服务移除: 
registration.unregister();

需要注意的是这个对象不能和其他Bundles共享,因为它和发布服务的bundle的生命周期相互依存,也就是说,如果这个bundle已经不在框架执行环境中存在,那么这个对象也不应该存在了,“皮之不存毛将焉附”就是这个道理。

试想如果这个ServiceRegistration共享给了其他的bundle(具体的说就是其他bundle中存在对这个对象的引用),那么发布服务的那个bundle即使被移除了,由于其他bundle中的引用依然存在,那么垃圾处理机制不会抹去这个对象,这样不但于理不合,而且实际上这个对象也是不可用的,因为这个对象所依存的bundle已经不在了。

代码中的参数new LSE()是一个POJO,这个对象不需要实现任何OSGi类型或者使用标注,只要满足服务约定(这里就是接口)就可以了。

此外,如果在删除发布的服务之前bundle停止了,框架会帮助你删除这些服务。

3.2 发现和绑定服务

上一小节我们说明了如何描述和发布一个服务,那么现在我们可以根据服务约定从注册表中找到正确的服务。

下面是发现服务并获得其引用的接口:

ServiceReference reference =  
bundleContext.getServiceReference(StockListing.class.getName());

这是根据实现的接口名称获得的服务,也是最简单的方法。

注意这里的reference是服务对象的间接引用,可是为什么要用间接引用而不直接返回那个实际的服务对象呢?实际上是为了将服务的使用和服务的实现进行解耦,将服务注册表作为两者的中间人,达到跟踪和控制服务的目的,同时还可以在服务消失了以后通知使用者。

这个方法的返回类型是ServiceReference,它可以在bundle之间互享,因为它和使用服务的bundle的生命周期无关。



相关标签: osgi