深入理解OSGI的模块化

Posted 窗边冷月光

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了深入理解OSGI的模块化相关的知识,希望对你有一定的参考价值。

 

定义

OSGIOpen Service Gateway Initiative)技术是面向Java的动态模型系统。

OSGI框架实现了一个优雅、完整和动态地组件模型。应用程序(bundle)无需重新引导可以被远程安装、启动、升级和卸载。

OSGi技术提供允许应用程序使用精炼、可重用和可协作的组件构建的标准化原语。 这些组件能够组装进一个应用和部署中。

OSGi服务平台提供在多种网络设备上无需重启的动态改变构造的功能。

为了最小化耦合度和促使这些耦合度可管理,OSGi技术提供一种面向服务的架构,它能使这些组件动态地发现对方。   

OSGi联盟已经开发了例如像HTTP服务器、配置、日志、安全、用户管理、XML等很多公共功能标准组件接口。这些组件的兼容性插件实现可以从进行了不同优化和使用代价的不同计算机服务提供商得到。然而,服务接口能够基于专有权基础上开发。

OSGi的主要职责就是为了让开发者能够创建动态化、模块化的Java系统。

OSGI框架

 

模块和模块化

模块(module):定义了一个逻辑边界,这种模块本身精确的控制了哪些类是完全被封装起来的,而哪些类需要暴出来作为外部使用。

模块化(modularity):将一个大型系统分解为多个较小的互相协作的逻辑单元,通过强制设置模块之间的逻辑边界来改善系统的维护性和封装性。

构成

OSGi框架从概念上可以分为三层:模块层、生命周期层和服务层。

Module Layer:模块层关注代码的打包和共享;

Lifecycle Layer:生命周期层提供运行时管理以及对OSGI框架的访问接口;

Service Layer:服务层关注模块之间的交互和通信。

 

模块层

模块层是 OSGi 框架中最基础的部分。

OSGi 的模块化,是通过为 Jar 包添加metadata 来定义哪些类该暴露,哪些类该隐藏,其控制单元叫做 Bundlejar 包)。

首先,必须先了解一个基本概念——什么是Bundle

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

 

如何定义Bundle

 

Bundle OSGi 中的基本组件,其表现形式仍然为 Java 概念中传统的 Jar 包。

通过 META-INF 目录下的 MANIFEST.MF 文件对其予以进一步的定义。

通常一个 MANIFEST.MF 文件的内容如下:

 

Manifest-Version: 1.0

Bundle-ManifestVersion: 2

Bundle-Name: Util

Bundle-SymbolicName: com.ibm.director.la.util

Bundle-Version: 1.0.0

Bundle-RequiredExecutionEnvironment: J2SE-1.5

Import-Package: org.osgi.framework;version="1.3.0"

Export-Package: com.ibm.director.la.util;uses:="org.osgi.framework"

Bundle-ClassPath: lib/junit.jar,

 

MANIFEST.MF 文件存储的实际上是 Bundle 的元数据。

元数据的内容可以精确的定义 Bundle 的各种特征,同时能更好的对 Bundle 进行标识同时帮助用户对Bundle进行理解。

 

MANIFEST.MF文件格式

1.         属性声明的一般格式:namevalue

2.         一行不超过72个字符,下一行继续则由单个空格字符开始

3.         每个子句(clause       进一步分解为一个目标(target)和一组由分号分隔的name-value对参数(parameter

 

元素解释:

Bundle-SymbolicName  唯一的bundle名称,相当于在系统中的idsingleton表示是否使用单启动方式 #可选的

 

Bundle-Version  主要的版本号

Bundle-ManifestVersion  定义了bundle遵循规范的规则,1表示r3规范 2表示r4和以后的版本

a)         唯一有效的值是2

b)         没有Bundle-ManifestVersionBundle不要求指定Bundle- SymbolicName属性

 

Bundle-Name   bundel名称

Bundle-Vendor  发布商

Bundle-RequiredExecutionEnvironment  需要的执行环境

Build-Jdk  jdk版本

Created-By  创建者

Bundle-Activator  Activator类路径

Import-Package  引用包的信息,包括包名称和版本号,只有引用了这些包,才能让classloader装载

a)         导入一个包并没有导入它的子包

b)         Import-Package通过属性导入特定的包

c)         java.*

d)         对于任意属性,OSGI只支持相等匹配

e)         Version及其值的格式是OSGI规范所定义,支持更加灵活的匹配方法

 

f)          需要指定一个精确的版本范围,使用“[1.0.1,2.0.1]”这样的格式

g)         当没有指定版本范围时,默认的值是“0.0.0

 

Export-Package  对外暴露的Package

a)         标准Jar文件默认公开一切内容,而Bundle中默认不公开任何内容

b)         可导出多个包,用逗号分隔

c)         可以给导出包增加任意属性

d)         可以给导出包设置Version,默认为0.0.0

        

 

Require-Bundle  直接引用整个bundle

Bundle-ClassPath  Bundle Classpath,内部类路径

Fragment-Host  Fragment 类型 Bundle 所属的 Bundle

DynamicImport-Package  Bundle动态引用的 package

 

OSGI类的查找顺序

1.         如果类所在的包以“java.”开头,委托给父类加载器

2.         如果类所在的包在导入包中,委托给导出该包的Bundle

3.         Bundle自身的类路径上查找

 

依赖解析

含义

1.         只有满足所有的依赖(Import-Package),bundle才可用

2.         OSGI框架的一个最重要任务之一就是:通过自依赖解析自动化地进行依赖管理

 

依赖解析规则

1           级联解析

2           Import-Package的属性约束和版本约束

3           多个Bund满足Import-Package依赖(多个Provider)时:

3.1     已解析的(resolvedbundle优先级高,未解析的(installedbundle优先级低

3.2     相同优先级,有多个匹配时,版本高者优先,版本相同则选最先安装的bundle

4           一个bundle只能看到某个package的唯一一个实例

5           uses 子句

5.1     用于限制Export-Package

5.2     需要用到uses子句的场景

5.2.1     导出包中的类,其方法签名中包含了其Import-Package中的类

5.2.2     导出包中的类,继承了其Import-Package中的类

5.3     users约束是可传递的

5.4     谨慎使用uses,大大·限制解析的灵活性

 

生命周期层

作用

1           在应用程序外部,生命周期层精确低定义了对bundle生命周期的相关操作

2           对生命周期的操作,允许你动态地改变进行于框架中的bundle组成,并以此来管理和演化应用程序

3           在应用程序内部,生命周期层定义了bundle访问其执行上下文的方式,为bundle提供了一种与OSGI框架交互的途径以及一些执行时的便利条件

4           OSGI框架支持对bundle形式的JAR文件实现全生命周期管理,包括:安装、解析、启动、停止、更新和卸载

5           运行时生命周期管理,“动态类路径”

 

下图为 Bundle 生命周期的状态转移图:

 

 

重要接口

生命周期层的API主要是由以下三个核心接口来组成的:

BundleActivatorBundleContext Bundle

 

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

其中:

1           调用start()方法的激活器实例与调用stop()的实例是同一个

2           stop()方法被调用之后,激活器实例就被丢弃并不再不用

3           如果一个bundle被停止后,又重新启动,那么将创建一个新的激活器实例,同时它的start()方法和stop()方法也将被适时触发。

 

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

其中:

1           bundle属于active状态时,BundleContext才有意义,即start()方法被调用和stop()方法被调用之间的时间点

2           注册服务

方法如下:

public ServiceRegistration registerService(String clazz, Object service,

                            Dictionary properties);

 

调用例子:

@Override

    public void start(BundleContext context) throws Exception {

        Dictionary<String, String> props = new Hashtable<String, String>();

        props.put("ServiceName", "Calculation");

        context.registerService(ICalculation.class.getName(), new Calculation(), props);

        System.out.println("Service registered!");

    }

3           获取服务

有几种方式:

1ServiceReference ref = context.getServiceReference(LogService.class.getName());

优点:很难说有什么优点,硬要说几句的话,那就是逻辑够简单,调用最少,适合一次性操作。
缺点:需要判断返回值是否为null,需要手动申请和释放service,由于OSGi的动态性,请在获取ref后尽快使用,无法保证ref长期有效。每次访问都会有service获取和释放的开销。
用途:适合于不频繁的调用service,且在service不可用时也能继续执行后续操作的场景。

 

2使用ServiceListener

优点:只在Service变更时产生一次service获取开销,动态感知service的注册和注销。
缺点:在ServiceListener注册之前已经存在的Service无法监听到。需要自己维护service的获取和释放。在需要监听多个Service实例时,使用并不方便。

 

3使用ServiceTracker

ServiceTracker其实是对ServiceListener实现方式的封装,使得对service的获取更加简洁,同时也解决了不能监听到已经存在的Service的问题(其实就是在增加ServiceListener的同时调用BundleContext.getAllServiceReferences方法以获取现有的Service引用)

 

有一点需要注意的是,tracker需要调用open方法才能监听到Service,另外,在bundle stop以后,bundleopenServiceTracker不会自动关闭,所以一定不要忘记在bundle结束之前,关闭所有在bundleopenServiceTracker

 

4、使用OSGI Blueprint

如下

 

 

 

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

 

 

启动级别

1、启动级别的数值越高,启动顺序越靠后

2、只有System Bundlebundle ID0)的启动级别可以为0,其他Bundle的启动级别都大于0,最大值为Integer.MAX_VALUE

3、动态启动级别

 

系统Bundle

启动过程:Bundlestart()方法为空操作,因为OSGI框架一启动。系统Bundle就已经启动了

停止过程:Bundlestop()方法会立即返回并在另外一条线程中关闭OSGI框架

更新过程:Bundleupdate()方法会立即返回并在另外一条线程中重启OSGI框架

卸载过程:系统Bundle无法卸载,如果执行了Bundleuninstall()方法,那么框架会抛出一个BundleException异常

 

Bundle刷新流程

从某一bundle开始计算受影响的bundle有向图

处于Active状态的bundle被停止并被切换至Resolved状态

处于Resolved状态的bundle,切换至Installed状态,这些bundle的依赖关系不再被解析

处于uninstalled状态的bundle会被从图中移除,同时也会被彻底地从框架中移除(由GC回收)

其他bundle,如果框架重启之前处于Active状态,重启前框架会对这些bundle以及其所依赖的bundle进行解析

当所有的工作完成之后,框架会触发一个FrameworkEvent.PACKAGES_REFRESHED事件

 

服务层

面向服务的设计

1、降低服务提供者和使用者之间的耦合,这样更容易重用组件

2、更强调接口而不是实现类

3、清晰描述依赖关系,让你知道一切是如何结合在一起的(可以有附加的元数据描述)

4、支持多个相互竞争的服务实现,这样你可以互换这些实现(动态替换)

 

OSGI服务

OSGI框架拥有一个集中的服务注册中心,它遵循发布-查询-绑定模型

1、提供者bundle可以将POJOs发布为服务。

1.1、注册的时候可以设置这个 Service 的属性。而在获取 Service的时候可以根据属性进行过滤。

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

2、使用者bundle可以找到并绑定服务

3、服务注册、更新、注销

4、服务注册对象是私有的,不能被别的bundle共享,它们与发布服务的bundle的生命周期是绑定的

5OSGI将会接受以具体类名注册的服务,但是不推荐这样做

6、当一个bundle停止时,任何没有被移除的服务都会被框架自动移除。当bundle停止时,不必明确地注销服务。

7、服务监听

8、服务追踪器 – ListenerServiceTracker

9、服务工厂 为不同的bundle提供相同服务的不同实例

10、配置管理:可将配置文件放置/etc下,随着配置文件的更改,ManagedService接口的实现类的updated方法也会被调用。

 

以上是关于深入理解OSGI的模块化的主要内容,如果未能解决你的问题,请参考以下文章

推荐书籍 | 深入理解Java虚拟机(第2版)

INN实现深入理解

老师推荐的书单

深入理解Java虚拟机的目录

深入理解 webpack 文件打包机制

读 Node.js 源码深入理解 cjs 模块系统