OSGI 基础概述
概述:OSGI是Open Services Gateway initiative的缩写,叫做开放服务网关协议,通常可能指OSGi联盟、OSGi标准或者OSGi框架。
OSGI:OSGI联盟现在将OSGI定义为一种技术,该技术是指一系列用于定义Java动态化组件系统的标准。这些标准通过为大型分布式系统以及嵌入式系统提供一种模块化架构减少了软件的复杂度。
OSGI是OSGI联盟提出基于JVM的一系列用于定义Java动态化组件系统的规范标准;
通过这些标准提供一种“模块化”降低大型软件架构的复杂度及耦合性;
实现动态可插拔的动态组件服务;简而言之就是一堆协议栈,规范标准。
目的:实现软件架构模块化,将一个大的系统拆分成多个独立的模块(Bundle),通过OSGI规范使得各个模块间更好的达到高内聚、松耦合、可复用、热插拔。
更多详细内容请看OSGI官方规范文档
优点
-
开发
- 复杂性的降低:基于OSGi的组件模型bundle能够隐藏内部实现,bundle基于服务进行交互。
- 复用:很多第三方的组件可以以bundle的形式进行复用。
- 简单:核心的API总过包括不超过30个类和接口。
- 小巧:OSGi R4框架的实现仅需要300KB的JAR file就足够。在系统中引入OSGI几乎没有什么开销。
- 非侵入式:服务可以以POJO的形式实现,不需要关注特定的接口。
-
部署管理
- 易于部署:OSGi定义了组件是如何安装和管理的,标准化的管理API使得OSGi能够和现有和将来的各种系统有机的集成。
- 动态更新:这是OSGi被最经常提起的一个特性,即所谓的“热插拔”特性,bundle能够动态的安装、启动、停止、更新和卸载,而整个系统无需重启。
- 适配性:这主要得益于OSGi提供的服务机制、组件可以动态的注册、获取和监听服务,使得系统能够在OSGi环境调整自己的功能。
- 透明:提供了管理API来访问内部状态,因此通常无需去查看日志,能够通过命令行进行调试。
- 版本化:bundle可以版本化,多版本能够共存而不会影响系统功能,解决了JAR hell的问题。(这在开发时也提供了很大的帮助)
- 快速:这得益于OSGi的类加载机制,和JAR包的线性加载不同,bundle委托式的类加载机制,使得类的加载无需进行搜索,这又能有效的加快系统的启动速度。
- 懒加载:OSGi技术采用了很多懒加载机制。比如服务可以被注册,但是直到被使用时才创建。
OSGI分层模型
- Services(服务层):服务层为Bundle的java开发者提供了一个动态、简单的编程一致性模型,并使得Bundle服务开发和部署简化,而且Bundle服务接口与Bundle服务实现无耦合。这个模型允许开发者使用Bundle服务接口来绑定具体的服务,具体的服务实现将会在运行期获得。
- Life Cycle(生命周期层):生命周期层提供了管理Bundle生命周期的API,而且这个API为Bundle提供了运行期模型,定义了Bundle如何被启动、停止、安装、更新以及卸载。另外,还提供了一个完善的事件驱动API,用于在OSGi framework中管理和控制Bundle。Life Cycle层强依赖于Module层,但是不一定需要Security层依赖。
- Modules(模块层):模块层为Java定义了模块化模型,并且克服了Java部署模型中的一些缺点。在模块化模型中为Bundle间的Java package共享和隔离定义了严格的规则。这个Module层能够独立于Life Cycle层和Service层使用,在Life Cycle层中提供了管理模块化Bundle的API,另外Service层提供了Bundle间的通信模型。
- Security(安全层):安全层基于Java2 security,但是在这基础上添加了大量的限制,并补充了相应的Java标准。在这个层次中定义了安全包格式,以及运行时如何与java2 security层进行交互的方式。
- Execution Environment(底层执行环境,目前主要是java运行环境): 定义在特定平台中可用的方法和类。
-
Bundles:Bundles不属于OSGI系统底层框架,而是由开发人员制作的OSGI组件(最小模块化单元)。
综上所诉,其实OSGI最核心的就是三个模块,模块层、生命周期层和服务层。
OSGI层间的相互作用
概述:通过对OSGI的分层实现各个层的责任划分从而降低系统的复杂度和耦合度,各层各司其职(分而治之)。各层通过暴露的API实现层间的交互,实现系统的聚合。
osgi层间的相互作用
OSGI核心规范
概述:该部分主要介绍OSGI核心底层基础规范。
模块层
概述:无论何种设计的出现都是为了更好的提高代码或系统的扩展性、高效性、复杂性。从面向对象思想开始,它是对不同类型事物的分类,将每个事物的功能行为尽
可能的做到高内聚低耦合,它主要是强调对类的抽象划分其实也是一种模块化。而OSGI模块化则是通过对系统模块间的划分,将每个模块尽可能的做到高内聚低耦合。
两种思想目的是一致只是粒度不一样。
Bundle
概述:Framework定义了模块化单元,这个模块化单元称为Bundle。一个Bundle由Java类和其他资源文件组成,并且可以为终端用户功能。Bundle通过良好的定义,Bundle之间可以通过导入(import)和导出(export)来共享Java package。在OSGi Framework中,只有Bundle是部署Java应用的实体对象。
个人理解:Bundle是一个包含了代码,资源和元数据并且符合OSGI规范的以jar形式存在的一种模块化单元。因此一个符合OSGI规范的Bundle一定首先是符合JAR规范的jar文件。
区别:Bundle的本质也是jar,只是比jar多了一些OSGI规范特定的元信息数据(META-INFO/MANIFEST.MF)。
bundle
Bundle元数据常用头信息
Bundle-ActivationPolicy: lazy
Bundle-ActivationPolicy指定了framework第1次如何启动Bundle。
Bundle-Activator: com.acme.fw.Activator
Bundle-Activator指定了如何启动和停止Bundle的类。
Bundle-Category: osgi, test, nursery
Bundle-Category表示逗号分隔的分类名称。
Bundle-ClassPath: /jar/http.jar
Bundle-ClassPath定义了逗号分隔的路径,内容为Jar文件路径和类路径(Bundle内部)。点号(’.’ \u002E)表示Jar的根目录,而且也是默认的。
Bundle-ContactAddress: 2400 Oswego Road, Austin, TX 74563
Bundle-ContactAddress提供发行者的联系地址。
Bundle-Copyright: OSGi (c) 2002
Bundle-Copyright指Bundle的版权信息。
Bundle-Description: Network Firewall
Bundle-Description定义了Bundle的简短描述信息。
Bundle-DocURL: http://www.example.com/Firewall/doc
Bundle-DocURL指Bundle的文档链接地址。
Bundle-License: http://www.opensource.org/licenses/jabberpl.php
Bundle-License提供了一个机器可读的许可证信息(license),这个是可选项。提供这个头信息的目的是多个组织自动化处理许可证,例如Bundle被使用前的许可证验证。而且这个头结构提供了唯一的license命名,license信息是被读者可以阅读的,但是这个头信息只是纯粹的信息管理代理,OSGi Framework并不会处理。
Bundle-Localization: OSGI-INF/l10n/bundle
Bundle-Localization包含Bundle的本地化文件地址,默认值是OSGI-INF/l10n/bundle。其他翻译文件如OSGI-INF/l10n/bundle_de.properties, OSGI-INF/l10n/bundle_nl.properties。
Bundle-ManifestVersion: 2
Bundle-ManifestVersion定义了Bundle需要遵守本规范的规则,默认值是1表示第3个版本的Bundle,2表示第4个版本的或者更后发布的版本,也可以为OSGi Framework定义更高的数字。
Bundle-Name: Firewall
Bundle-Name定义了一个有可读性的名字来表示Bundle○ UNINSTALLED(未安装):状态值为整型数1。此时Bundle中的资源是不可用的。
Bundle-RequiredExecutionEnvironment: CDC-1.0/Foundation-1.0
Bundle-RequiredExecutionEnvironment指示在OSGI framekwork上必须支持的可执行环境,用逗号分隔。
Bundle-SymbolicName: com.acme.daffy
Bundle-SymbolicName为Bundle提供了一个全局唯一的名字。在framework中多次安装Bundle时,Bundle SymbolicName和version必须唯一。SymbolicName是基于反域名解析的,这部分头是必须的。
Bundle-Vendor: OSGi Alliance
Bundle-Vendor描述Bundle的发行者信息。
Bundle-Version: 1.1
Bundle-Version表示Bundle的版本信息,默认值是0.0.0 。
DynamicImport-Package: com.acme.plugin.
DynamicImport-Package包含了一个逗号分隔的动态导入包清单,也可以写动态导入所有依赖。(动态导入包)
Export-Package: org.osgi.util.tracker;version=1.3
Export-Package包含导出package声明信息。
Import-Package: org.osgi.util.tracker,org.osgi.service.io;version=1.4
Import-Package声明Bundle导入的包。
Bundle-Blueprint:OSGI-INF/blueprint/blueprint.xml,OSGI-INF/blueprint/blueprint-cli.xml
Bundle-Blueprint描述了Bundle加载的Blueprint配置文件路径。
Fragment-Host: org.eclipse.swt; bundle-version=”[3.0.0,4.0.0)”
Fragment-Host定义了本片段中的主Bundle。
Provide-Capability: com.acme.dict; from=nl; to=de; version:Version=1.2
Provide-Capability描述了Bundle提供的一组Capability。
Require-Bundle: com.acme.chess
Require-Bundle描述了该Bundle import了其他Bundle export的内容。
更多详细信息可查看OSGI官方规范文档
BundleActivator(Bundle激活器)
概述:用于对Bundle的start和stop事件的监听,当Bundle启动或停止时会回调start和stop方法。
实现
package com.duan2ping; /** * 根据需求选择,该类可有可无 */ public class Activator implements BundleActivator{ /** * Bundle启动时执行的方法 * 用于对资源的初始化 * @param bundleContext */ public void start(BundleContext bundleContext) { logger.info("Bundle start"); } /** * Bundle停止时执行的方法 * 用于释放资源 * @param bundleContext */ public void stop(BundleContext bundleContext) { logger.info("Bundle stop"); } }
BundleContext(Bundle上下文)
概述:Bundle在Framework中的执行上下文环境。当启动或者停止一个Bundle的时候,Framework将它发送到一个bundle的激活器(Bundle Activator)。
该接口提供了Bundle和底层框架的交互方法。主要分为两部分,一部分是和部署与生命周期管理相关,另一部分则是关于利用服务层进行Bundle间交互的方法。
Bundle间的隔离
概念:“模块化”的模块是一些相对独立的实体,OSGI中一个模块就是一个Bundle,它们都是独立,隔离的。OSGI对每一个Bundle都分别用一个Classloader来加载实现隔离,所以默认情况下Bundle间的类是相互隔离且不可见的。
Bundle间的依赖(包的可见性)
引子:在实际开发中各模块间必然会有依赖和交互,但Bundle间又是隔离的,如何能访问其他Bundle的类呢?
概念:传统java开发中对jar的引用很少去关注jar包的隔离性且jar之间的依赖并不清晰(各种jar冲突),而OSGI规范中Bundle被有计划的依赖,显示的声明bundle间的依赖关系。
Import-Package/Export-Package
概述:OSGI在Bundle的元信息清单(META-INF/MANIFEST.MF)添加了两项标记Import-Package/Export-Package,用于声明Bundle导入和导出的包。
实现:我们的目标是在MANIFEST.MF中声明导入和导出的包信息,我们可以自己编写MANIFEST.MF,但实际开发中不可能手动编写,容易出错不易维护,一般都是通过插件生成(插件下面再详述)。
Export package:指定Bundle需要暴露的包——>Export package:com.test.api;version=”1.0” (当前包中的类,子包的需单独导出或使用*)
Import package:指定Bundle依赖的包——>Import package:com.test.api;version=” [1.0,2.0)”(引用这个包中的类且版本在指定version区间中,子包中类需要单独引用或使用*)。
- 依赖解析规则:当有多个包提供者时
- 1.已解析的Bundle优先级高于未解析的
- 2.优先级相同优先版本高的
- 3.版本相同优先最先安装的Bundle
注意:因为bundle间是隔离的,所以Import package时,bundle类加载器会委托给Export package这个包的类加载器去加载这个包的类
import的包找不到时Bundle无法正常启动。
Bundle的依赖
- java.开头的包:由JDK提供了,代码中直接import。
- org.osgi开头(包括core、compendium等):由OSGI规范提供的,已经包含在OSGI框架(Felix)中,开发时需要导入(编译环境)但是发布程序中不需要
包含(运行环境),由Felix提供,因此需要在maven中添加scope为provided。 - 第三方jar包(多Bundle共享,公共Bundle):直接OSGI化放入到容器指定目录下或在容器中安装。
- 第三方jar包(单Bundle依赖):使用maven的Bundle插件指定Embed-Dependency过滤OSGI化后嵌入Bundle的Bundle-ClassPath中。
- 使用自己开发Bundle的依赖:直接Export-Package后,Import-Package即可。
- 为非OSGi第三方依赖项创建包
- wrap方式:bundles:install wrap:mvn:commons-lang/commons-lang/2.4
- 包含依赖再导出:添加第三方依赖再将依赖的包导出
Bundle编译环境和运行时环境
-
编译环境:配置dependencies是为了解决编译环境的依赖(防止编译出错)【第三方jar使用Maven-Bundle-plugin插件的Embed-Dependency添加到Bundle的cClasspath】
-
运行时环境:配置Import-Package才是为了真正的OSGI容器中程序的依赖【查询系统提供的类使用命令package:exports,找到需要的直接Import-Package即可】
Bundle类加载机制
引子:我们知道在Java中类的加载机制默认并且也是Java开发推荐的模型是双亲委派模型。这样保证了类的唯一性防止系统类被篡改确保系统的安全性和可靠性。对于一个底层系统环境
来说我觉得是必要的。但是做为实际业务需求它是有缺陷的,优点总是与缺点并存。正是这种安全可靠束缚了类的多样性、动态性,而OSGi实现模块化的关键就在于多样性和动态可插拔。这种
类加载机制肯定不可能满足Bundle类的隔离以及多个同类(双亲委派便是一山不容二虎,而我们愿意看到的是Love And Peace)共存,热更新等等现代化的开放思想(底层需要封建(稳定),上层需要开放(多样))。
封建的社会终究打破,自由的灵魂终得安所。势必这种机制会被新的需求打破,当然这也不是第一次了(第一次是ClassLoader本身的设计早于双亲委派因此为了兼容当然只能妥协。第二次是SPI设计,因双亲委派模型
自身的缺陷所导致不得不引入线程上下文类加载器。第三次便是OSGI了,为了满足模块化间的隔离和动态插拔)。那么打破后OSGI的类加载模型又是如何的呢?如下图所示。
概述:通过上面的OSGI类加载模型可以看到OSGI类加载模型不再是双亲委派模型中的树状结构,而是进一步发展为更加复杂的网状结构。因此搞懂了OSGI类加载模型,对于理解Java的类加载模型就算不能上天入地,
也能呼风唤雨了。
OSGI类加载模型
Bundle类加载器
-
父类加载器:由Java平台直接提供,最典型的场景包括启动类加载器(Bootstrap ClassLoader )扩展类加载器(Extension ClassLoader)和应用程序类
加载器(Application ClassLoader)。在一些特殊场景中(如将OSGi内嵌入一个Web中间件)还会有更多的加载器组成。它们用于加载以“java.*”开头的类以及
在父类委派清单中声明为要委派给父类加载器加载的类。 -
Bundle类加载器:每个Bundle都有自己独立的类加载器,用于加载本Bundle中的类和资源。当一个Bundle去请求加载另一个Bundle导出的Package中的类时
,要把加载请求委派给导出类的那个Bundle的加载器处理,而无法自己去加载其他Bundle的类。 -
其他加载器:譬如线程上下文类加载器、框架类加载器等。它们并非OSGi规范中专门定义的,但是为了实现方便,在许多OSGi框架中都会使用。例如框架类加载器
,OSGi框架实现一般会将这个独立的框架类加载器用于加载框架实现的类和关键的服。
务接口类 。
Bundle类加载顺序
概述:如果是java.开头的包(jdk核心包)就委托给父类加载器加载,如果该包是导入包则委托给导出该包的类加载器加载,否则在自身bundle的类路径下查找。
-
①如果类或资源在以java.*开头的Package中,那么这个请求需要委派给父类加载器加载否则,继续下一个步骤搜索。如果将这个请求委派给父类加载器后发现类或资源不存在,
那么搜索终止并宣告这次类加载请求失败。 -
②如果类或资源在父类委派清单(org.osgi.framework. bootdelegation)所列明的Package中,那么这个请求也将委派给父类加载器。如果将这个请求委派
给父类加载器后,发现类或资源不存在,那么搜索将跳转到一个步骤。 -
③如果类或资源在Import-Package标记描述的Package中,那么请求将委派给导出这个包的Bundle的类加载器,否则搜索过程将跳转到下一个步骤。如果将这个请求委派给
Bundle类加载器后,发现类或资源不存在,那么搜索终止并宣告这次类加载请求失败。 -
④如果类或资源在Require-Bundle导入的一个或多个Bundle 的包中,这个请求将按照Require-Bundle指定的Bundle清单顺序逐一委派给对应Bundle的类加载器 , 由于被委派的
加载器也会按照这里描述的搜索过程查找类,因此整个搜索过程就构成了深度优先的搜索策略。如果所有被委派的Bundle类加载器都没有找到类或资源,那么搜索将转到下一个步骤。 -
⑤搜索Bundle内部的Classpath。如果类或资源没有找到,那么这个搜索将转到下一个步骤。
-
⑥搜索每个附加的Fragment Bundle的Classpath。搜索顺序将按这些Fragment Bundle的ID升序搜索。如果这个类或资源没有找到,那么搜索转到下一个步骤。
-
⑦如果类或资源在某个Bundle已声明导出的Package中,或者包含在已声明导入(Import-Package或Require-Bundle)的Package中,那么这次搜索过程将以
没有找到指定的类或资源而终止。 -
⑧如果类或资源在某个使用DynamicImport-Package声明导入的Package中,那么将尝试在运行时动态导入这个Package。如果在某个导出该Package的Bundle
中找到需要加载的类,那么后面的类加载过程将按照步骤③处理。 -
⑨如果可以确定找到一个合适的完成动态导入的Bundle,那么这个请求将委派给该Bundle的类加载器。如果无法找到任何合适的Bundle来完成动态导入,那么搜索终止并宣
告此次类加载请求失败。当将动态导入委派给另一个Bundle类加载器时,类加载请求将按照步骤③处理。
Bundle类加载机制
生命周期层
概述:生命周期层提供API来控制包的安全性和Bundle生命周期操作。该层基于模块和安全层。
注释:该层主要是实现OSGI规范的框架底层维护。
Bundle对象
概述:OSGI框架中对每一个安装的Bundle都会关联一个Bundle对象,用于管理Bundle的生命周期。
Bundle标识
- Bundle标识(Identifier):一个长整型整数,在一个Bundle整个生命周期中由OSGI框架赋值的唯一标识。该ID为自增长,可通过Bundle对象的getBundleId()方法获取。
- Bundle位置(Location):Bundle的位置信息,可通过Bundle对象的getLocation()获取。
- Bundle特征名称(Symbolic Name):由开发人员指定。Bundle版本和特征名称是Bundle全局唯一标识。可通过Bundle对象的getSymbolicName()获取。
Bundle的状态
- UNINSTALLED(未安装):状态值为整型数1。此时Bundle中的资源是不可用的。
- INSTALLED(已安装):状态值为整型数2。此时Bundle已经通过了OSGI框架的有效性校验并分配了Bundle ID,本地资源已加载,但尚未对其依赖关系进行解析处理。
- RESOLVED(已解析):状态值为整数4。此时Bundle已经完成了依赖关系解析并已经找到所有依赖包,而且自身导出的Package已可以被其它Bundle导入使用。在此种状态的Bundle要么是已经准备好运行,要么就是被停止了。
- STARTING(启动中):状态值为整型数8。此时Bundle的BundleActivator的start()方法已经被调用但是尚未返回。如果start()方法正常执行结束,Bundle将自动转换到ACTIVE状态; 否则如果start()方法抛出了异常,Bundle将退回到RESOLVED状态。
- STOPPING(停止中):状态值为整型数16。此时Bundle的BundleActivator的stop()方法已经被调用但是尚未返回。无论stop()是正常结束还是抛出了异常,在这个方法退出之后,Bundle的状态都将转为RESOLVED。
- ACTIVE(已激活):状态值为整型数32。Bundle处于激活状态,说明BundleActivator的start()方法已经执行完毕,如果没有其他动作,Bundle将继续维持ACTIVE状态。
Bundle生命周期
概述:Bundle在OSIG框架中的状态流转。
bundle生命周期
服务层
概述:OSGi服务层定义了与生命周期层高度集成的动态协作模型。该服务模型是发布,查找和绑定模型。服务是在服务注册表的一个或多个Java接口下注册的普通
Java对象。Bundle可以注册服务,查找服务,或在注册状态发生变化时接收通知。
个人理解:该层主要是提提供了Bundle间服务实现的模型和对服务的管理维护。
- 优点
- 松耦合:服务通过面向接口编程,将业务接口和实现分离。服务调用者不关心服务的实现细节,服务提供者不关心调用的方式。
- 动态性:服务在程序运行中可注册和注销
- 可重用:服务创建后能用于多个模块的使用。
- 多样性:接口解决了java类的多继承问题。通过对接口的多种实现,使服务变得更加多样化。
Bundle间更松散的耦合
概述:在实际开发中Bundle间必然是会有很多依赖和交互的。前面我们提到了一种Import-Package/Export-Package,这种对包的依赖是强耦合。在当下微服务
分布式横行的架构下,面向服务编程模型显得更加灵活动态,松耦合。在实现项目架构中我们通常是这样设计的。模块A会将自己对外提供的API封装成一个单独的
Bundle并将包export。模块A的其他Bundle负责实现这些接口(需要Import Bundle API暴露的package)并将具体实现服务注册到OSIG框架中。而模块B中的
Bundle如果想调用模块A中的服务,则可Import模块A的Bundle API导出的包,并从OSGI框架获取到对应接口的服务实现。我们通过面向接口编程实现的服务模型
降低了模块间的耦合。
osgi模块交互模型
OSGI服务模型
概述:OSGi框架提供了一个中心化的注册表,这个注册表遵从publish-find-bind模型。
注意:因为服务注册表是在OSGI框架中的,因此这些服务都是本地服务。“本地”意味着它只是在osgi framework内有效,不可跨osgi framework调用更不可
跨JVM调用。
osgi服务模型
服务组成
- 服务
- 服务引用
- 服务接口
- 注册服务
- 服务属性
- 查询服务
- 服务事件
- 服务监听
- 服务追踪器