OSGi是Open Services Gateway initiative的缩写,叫做开放服务网关协议,由于OSGi 的诸多优秀特性,可动态改变系统行为,热插拔的插件体系结构,高可复用性,高效性等等,OSGi正在成为一种趋势,越来越多的项目采用了OSGi,越来越多的中间件都开始采取了OSGi的标准。OSGi体系包括OSGi联盟、OSGi标准和OSGi框架,一系列的OSGi标准由OSGi联盟维护,OSGi标准标准的实现通常则称为OSGi框架容器或者OSGi服务平台。
OSGi联盟的主要目标是促进OSGi技术在应用,同时提升OSGi技术的市场价值,形成一个跨行业的生态系统。OSGi联盟现在主要做着以下几件事情:提供标准、提供标准的参考实现、提供OSGi的测试套件以及OSGi的认证。
OSGi联盟(OSGiAlliance)于1999年3月开始制定OSGi规范,其主要目的就是要制定一套开放式标准,以便向局域网及其中的设备提供可管理的服务;其基本思路是,一旦在网络设备(如服务器和嵌入式设备)上使用了OSGi服务平台,可以在网络上的任何地方管理这些设备上运行的软件组件的生命周期,可以在后台对这些组件进行安装、升级或卸载,但不需要打断该设备的正常运行。
2004年6月发布的Eclipse3.0就是第一个基于OSGi平台的版本。由于Eclipse的成功,OSGi在企业开发中逐渐成为切实可行的、较有价值的一种技术,Spring也通过一个叫“OSGi服务平台上的Spring动态模型(亦称之为OSGi Spring)”的项目来支持OSGi。该项目提供OSGi基础架构,以便开发者在Spring的企业开发中更容易使用OSGi。
OSGi规范和Servlet规范及EJB规范类似,该规范定义了两种对象,一是容器对外提供的服务对象,另一个是容器和您的应用程序之间必须遵守的契约,其中,服务对象是容器要实现的,通过这些容器,把应用程序劈分为多个模块单元,开发者可以管理这些模块单元之间的交叉依赖关系。OSGi标准规范了一系列常用的功能集合,例如:生命周期管理、安全日志、配置文件、事件队列、Web开发、JPA&JDBC等,大部分支持OSGi标准的框架都提供了这些服务,这样一方面规范了项目的代码结构,一方面节约了项目开发的时间。
OSGi R1于2000年发布,现在最新的标准版本是R5,到现在为止应用最广泛的当属是2005年发布的R4。
OSGi最新标准分为两个部分,OSGi Core和OSGi
Enterpise。
OSGi Core顾名思义,就是OSGi的核心标准,正是这个标准定义了一种动态化模块化的应用架构,其中主要定义了OSGi框架。OSGi框架提供了一个通用安全可管理的Java框架,能够支持可扩展可下载的应用(即bundles)的部署。OSGi框架是OSGi技术最基础也是最核心的部分。OSGi框架分为安全层、模块层、生命周期层、服务层,如下图所示:
模块层定义了一个模块化Java模型,对Java部署模式的一些缺点进行了改进,并对bundle 之间包的共享有严格的规定。模块层独立于生命周期层和服务层,使用时可以不需要生命周期层和服务层。生命周期层提供了对模块层的bundle 进行管理的API,而服务层提供了bundle之间的通信模型。
安全层基于Java2的安全机制增加了一些限制,并且弥补了Java标准的一些不足。
生命周期层为bundle 提供了生命周期管理API,为bundle提供了一个运行时模型,定义了一个bundle 如何启动、停止、安装和卸载,生命周期层也提供全面的事件API,允许bundle去控制和操作服务平台。
服务层 为bundle开发者提供了一个动态、简明且并且统一的编程模型,通过解耦服务标准(即Java接口)和它的实现,能够简化服务bundle的开发和部署。这个模型允许bundle 开发者只使用他们自己的接口规范来绑定服务。框架通过使用服务层,为系统提供了一种扩展机制,成为hooks。
OSGi中统一的编程模型可以帮助bundle开发者应对很多情况下的扩展的问题,统一的接口使得软件组件能够匹配和组合,同时保证稳定的运行。OSGi框架中bundle 可以在运行时通过服务注册中心选择一个可用的实现,bundle 可以注册新服务、接收关于服务状态的通知或者查找服务区以适配当前的设备。这使得一个bundle在部署后仍然具有可扩展性,新的bundle可被安装,已存在的bundle可修改和更新,而无需重新启动系统。
OSGi Enterprise由OSGi联盟的EEG(Enterprise Expert Group ) 制定,主要通过裁剪或者扩展OSGi框架(即OSGi Core)来定义技术需求与标准,以满足企业环境下IT软件基础设施的用况。OSGi Enterprise主要包括组件模型、分布式服务、Web应用于HTTP Servlet、事件模型、管理与配置服务、名称与目录服务、数据访问、事务支持以及其它一些支持服务。
OSGi架构非常适合开发者实现面向服务的应用(SOA),由于OSGi具有隐藏真实的服务实现类的能力,所以它为面向服务的应用提供了良好的类与接口的组合。框架可以提供给Java项目一个模块化的底层环境,以及一系列通用的服务(Service)。和普通的JVM程序相比,OSGi的程序天生拥有动态模块的特点,不同的模块(OSGi里称之为Bundle)有着独立的生命周期,可以在不重启容器的情况下,动态地安装、卸载、启动和停止您的应用程序中的不同模块;模块间的依赖性管理也由OSGi提供。容器可以同时运行该模块的多个版本,OSGi为开发嵌入式应用、移动应用、富互联网应用(RIA)提供了非常优秀的基础架构。
从企业开发者的角度看,OSGi容器的要求很低,可以很容易地把它嵌入到企业应用中,比如在开发Web应用时,可以把这个Web应用分为多个模块,一个模块负责视图层,另一个模块负责DAO层,第三个模块负责数据访问层,如果使用OSGi容器来管理这些模块之间的交叉依赖,就可以在不用重启该Web应用的前提下,将DAO层从速度较慢的升级到速度较快的DAO。
只要您的应用和OSGi规范兼容,您的应用就应该可以运行在任何OSGi容器中,现在比较流行的开放源码的OSGi容器有以下三种:
(1)、Equinox容器是参照OSGi规范第4版实现的,它实现了OSGi规范4中规定的必须强制实现的功能,同时,它也实现了OSGi规范中大部分的可选功能;
(2)、 Knoflerfish是OSGi规范第3版和第4版的开源实现,它实现了OSGi规范规定的必须实现的功能及部分可选功能;
(3)、Apache的Felix是Apache软件基金会实现的OSGi开源容器,包含了一个OSGi R4服务平台(Service Platform)标准的实现,以及大量相关的OSGi功能与技术的实现。
从开发的角度来说,OSGi具有以下特点:
复杂性的降低:基于OSGi的组件模型bundle能够隐藏内部实现,bundle基于服务进行交互。
复用:很多第三方的组件可以以bundle的形式进行复用。
简单:核心的API总过包括不超过30个类和接口。
小巧:OSGi R4框架的实现仅需要300KB的JAR file就足够。在系统中引入OSGi几乎没有什么开销。
非侵入式:服务可以以POJO的形式实现,不需要关注特定的接口。
从部署和运行的角度来说,OSGi的特点就更多了,OSGi的动态化很大程度体现在系统的部署和运行时。这些特点包括:
切合真实运行环境:OSGi框架是动态的,bundle能够进行即时的更新,服务可以根据需要动态增加或者删除。比如一个服务可以是一个网络中的设备,如果一个设备被监测到,则服务可以动态注册;如果设备被移除,则服务能够动态注销。在这样的运行环境中编程将需要耗费大量的开销来处理动态性,但是OSGi帮助开发者处理了绝大多数动态性方面的工作。
易于部署:OSGi定义了组件是如何安装和管理的,标准化的管理API使得OSGi能够和现有和将来的各种系统有机的集成。
动态更新:这是OSGi被最经常提起的一个特性,即所谓的“热插拔”特性,bundle能够动态的安装、启动、停止、更新和卸载,而整个系统无需重启。
适配性:这主要得益于OSGi提供的服务机制、组件可以动态的注册、获取和监听服务,使得系统能够在OSGi环境调整自己的功能。
透明:提供了管理API来访问内部状态,因此通常无需去查看日志,能够通过命令行进行调试。
版本化:bundle可以版本化,多版本能够共存而不会影响系统功能,解决了JAR hell的问题。(这在开发时也提供了很大的帮助)
快速:这得益于OSGi的类加载机制,和JAR包的线性加载不同,bundle委托式的类加载机制,使得类的加载无需进行搜索,这又能有效的加快系统的启动速度。
懒加载:OSGi技术采用了很多懒加载机制。比如服务可以被注册,但是直到被使用时才创建。
此外OSGi还有一些其他的优势,比如:
安全:OSGi提供了一个安全层,基于Java的安全模型增加了可用性。
大公司的支持:OSGi联盟的成员里包含了很多业界有名的IT公司,比如Oracle, IBM, Samsung, Nokia,
Progress, Motorola, NTT, Siemens, Hitachi, Deutsche Telekom, Redhat, Ericsson等。
bundle是以jar包形式存在的一个模块化物理单元,里面包含了代码,资源文件和元数据(metadata),并且jar包的物理边界也同时是运行时逻辑模块的封装边界。
一个更为直观的描述:在标准的jar包的manifest文件中添加一些bundle的模块化特征metadata,这个jar包就变成了一个bundle,bundle和普通jar包最大的区别就在于元数据。
Bundle元数据的目的在于准确描述模块化相关的bundle特征,让OSGi框架恰当的对bundle进行各种处理工作(比如依赖解析,强制封装等),这些元数据主要有这三个部分:
可读的信息(可选):帮助更好地理解和使用bundle;bundle的标识符(必须):唯一的标识一个bundle;代码可见性(必须):定义内部与外部代码。
可读的信息
这些内容可以帮助人们直观的了解这个bundle是做什么的,从哪里来。OSGi标准定义了几个元数据条目来达到这个目的,但是所有的条目都不是必须的,并且也不对模块化特性产生任何的影响,OSGi框架会完全无视这些内容。 下面就是一个这类信息的例子:
Bundle-Name: SERC Helloworld
Bundle-Vendor: GR, SERC
Bundle-DocURL: http://elevenframework.org
Bundle-Category: example
Bundle-Copyright: SERC
bundle的标识符
OSGi R4标准“唯一bundle标识符”被用来标识一个bundle。为了向后兼容,Bundle-Name就不能用来作为标识符了,否则就会增加维护向后兼容的工作,新的manifest属性就诞生了:Bundle-SymbolicName
Bundle-SymbolicName:
org.serc.helloworld
两者相比,Bundle-Name是给用户读的,而Bundle-SymbolicName是给OSGi框架读的,让OSGi框架能够唯一标识一个bundle。
代码可见性
在JavaSE中的jar包如果放在了classpath里面,那么它对这个classpath下的所有程序都是可见的,并且这种可见性不能改变,而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”
Bundle生命周期
模块层的定义一个bundle,要使用bundle,就得使用生命周期层的API,和OSGi框架的生命周期层进行交互。OSGi框架的核心并没有强制使用任何特定的API交互机制(比如命令行,GUI,或者XML配置文件等),只是单纯的Java API,所以开发者可以任意创造出自己想要的交互模式,保证了框架的灵活性。
Bundle只有在被安装(install)到一个OSGi框架的运行实例中才能用起来,OSGi框架支持对这些bundle完整的生命周期管理,并且支持这些管理操作在应用执行完成。 Bundle的getState方法可获得bundle的当前状态,下图清晰的展现了bundle在生命周期中的各个状态和状态间的转移条件。
Bundle生命周期的状态转移图
框架提供的三个重要接口
BundleActivator
生命周期层的API主要是由以下三个核心接口来组成的:BundleActivator,BundleContext和Bundle。
• BundleActivator:让你能够捕捉bundle的start和stop事件,并对这两个事件作出自定义的反应。
• BundleContext:一个bundle在框架中的执行时上下文,这个上下文提供了和框架进行交互的方法。
• Bundle:在逻辑上表示了一个bundle,OSGi环境中的一个物理bundle对应了一个bundle对象。该对象中包含了bundle的基本信息和bundle声明周期的控制接口。
BundleActivator的接口是如下定义的:
public interface BundleActivator {
public void start(BundleContext context) throws Exception;
public void stop(Bundlecontext context) throws Exception;
}
让OSGi框架知道一个Activator的存在,还需要在MANIFEST文件中添加如下一项属性(假设定义的activator的类叫做org.foo.Activator):
Bundle-Activator:org.foo.Activator
当这个bundle启动(start)的时候,OSGi框架就会调用这个Activator的start方法,同样的也适用与stop方法。并不是每个bundle都需要一个activator,不需要启动停止等Action的bundle就不需要。
BundleContext
BundleActivator接口中start和stop两个方法中,传入的参数都是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之间传递。
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自己就会抛出异常。
服务层
OSGi的服务层是面向服务的编程模型,具有服务的完全动态性。OSGi框架有一个中心化的注册表,这个注册表遵从publish-find-bind模型:
一个提供服务的bundle可以发布POJO作为服务的实体;一个使用服务的bundle可以通过这个注册表找到和绑定服务。
可以通过BundleContext接口来完成上述的工作。
发布服务
为了让别的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的生命周期相互依。 代码中的参数new LSE()是一个POJO,这个对象不需要实现任何OSGi类型或者使用标注,只要满足服务约定(这里就是接口)就可以了。
此外,如果在删除发布的服务之前bundle停止了,框架会帮助你删除这些服务。
发现和绑定服务
描述和发布一个服务后,开发者可以根据服务约定从注册表中找到正确的服务。 下面是发现服务并获得其引用的接口:
ServiceReference reference
=
bundleContext.getServiceReference(StockListing.class.getName());
这是根据实现的接口名称获得的服务,也是最简单的方法。这个方法的返回类型是ServiceReference,它可以在bundle之间互享,因为它和使用服务的bundle的生命周期无关。
选择最适合你的服务
在getServiceReference这个方法中,选择service的默认优先级是先选择service.rank最高的,在rank相等的情况下选择最早在框架中注册的。除了这个默认的规则,还可以在
getServiceReferences中通过添加过滤参数(作为调用该方法的第二个参数)来做一些筛选。
ServiceReference[]
references =
bundleContext.getServiceReferences(StockListing.class.getName(), “(&(currency=GBP)(objectClass=org.example.StockChart))”);
在这里的匹配参数是一个字符串,这个字符串的格式属于LDAP查询格式,在RFC 1960标准中有完整的描述。 上面的字符串中等号左边的内容就是前面提到的元数据(Dictionary)中的左值,通过这个左值对应的右值来与服务所带有的元数据进行匹配
绑定和使用服务
在发现了服务之后,使用服务之前,需要从注册表中绑定实现的服务。
StockListing listing =
(StockListing)
bundleContext.getService(reference);
这个方法返回的POJO实例和之前在注册表中注册的实例是同一个。
每次使用getService方法的时候,注册表会将对应服务的使用次数加1,同时会记录谁在使用这个服务。所以不在使用这服务的时候,告诉注册表。
bundleContext.ungetService(reference);
listing = null;