OSGI学习,待消化中

  • 什么是模块化html

与面向对象同样,模块化的目的也是松耦合,高内聚。咱们能够理解为模块化是将对象间的互访作了边界划分,即对一组业务相关的对象进行封装,而且提供可能的更高层次的代码访问隔离机制。java

 

  • 物理模块化 VS 逻辑模块化shell

物理模块化是指应用中的类文件被物理的分割放在不一样的模块中,可是每一个模块间的互访不受控制,各个模块能够访问模块间的内部对象,只要对象是可访问的。只是是对代码自己进行模块化管理。编程

例如JAVA中,应用被分为模块A和B,模块B中有一个public对象B.b,该对象能够彻底被模块A访问,由于它是public的。缓存

 

逻辑模块化是指在物理模块化的基础上,对模块进行控制访问;即模块间实现了访问隔离,而这才是咱们所说的真正的模块化的概念。框架

再看回上面的例子,若是B.b没有定义是其它模块可访问的,那么默认A.a是访问不到B.b这个对象的,无论这个对象的访问级别是什么。jvm

 

  • OSGI的做用模块化

在java中,OSGI是一个实现java模块化互访的平台,咱们能够理解为是一个更高级的JVM。它提供了逻辑上的模块化控制。工具

 

  • OSGI对模块的定义ui

在OSGI中,模块称之为bundle,一个bundle在物理上而言就是一个jar包。Jar包中有一个描述jar包的信息文件,位于jar内部的META-INF目录下的MANIFEST.MF文件。OSGI经过MANIFEST这个文件获取模块的定义信息,好比模块间的互访信息,模块的版本信息等。

 

Note:对于MANIFEST.MF文件的操做,因为这个文件有不少使用约束,好比一行不能超过72个字符,因此通常都是经过IDE工具对它进行编辑

 

  • bundle里有什么

一个bundle中通常包含以下的东西:

部署描述文件(MANIFEST.MF,必要的),各种资源文件(如html、xml等,非必须的),还有类文件。这与一个普通的jar包没有任何的区别。可是,除此以外,bundle里还能够放入其它的jar包,用于提供给bundle内部的类引用,即bundle内部的lib库。(跟一个war很相似)。

Note:实际上bundle里能够存听任何的内容,可是在bundle内部不会有嵌套的bundle,即上面提到的存放于bundle中的jar包就只会当成是一个普通的jar包,无论这些jar包中是否含有bundle定义的信息



MANIFEST.MF的定义

 

MANIFEST.MF位于bundle中的根目录下的META-INF目录下。

一个bundle的必要信息定义以下:


 

咱们称这些内容为bundle的头信息,具有了这4个头信息就是一个bundle

其中,

Bundle-ManifestVersion表示OSGI的参考版本,2表明参考版本使用的是OSGI R4.0+

Bundle-SymbolicName用于做为bundle的ID标识的前缀,通常用模块的顶级包名来命名

Bundle-Version表示bundle当前的版本,并做为bundle的ID标识的后缀

Bundle-Name则是一个可读的bundle的命名定义,没有太大的做用

其它还有不少的头信息的定义,这里就不一一列举了。

 

头信息的格式:

主要由头属性名称加冒号和各个从句组成,从句间用逗号分隔

Property-Name: clause, clause, clause …

 

从句的定义:

target; param1(:)=value1; param2(:)=value2 …

target表示头属性对应的值,后面能够带上不少不一样的参数对,每一个参数分为参数名和参数值,而且都用分号分隔

 

参数对的定义:

参数分为两种,属性和指令,若是是指令,须要在=号前加上冒号表示是一个指令

attr1=value1

dir1:=value1

 

头信息的简写形式:

若是头信息的全部target的参数对定义都是同样的,那么能够将target先定义在前面,全部的参数对定义在最后

Property-Name: target1; target2; attr1=value1; dir1:=value1 ….

 

  • Bundle的Identifier

即bundle的ID标识,这个是用来在OSGI中对bundle进行惟一性的定义的。在Bundle被OSGI读取后,OSGI经过对Bundle-SymbolicName和Bundle-Version进行组合,成为一个惟一的ID标识。组合的方式为<Bundle-SymbolicName>_<Bundle-Version>。

 

Bundle-SymbolicName的命名方式

Bundle-SymbolicName没有任何的命名要求,能够是任意的字符组成,可是通常是用模块的顶级包名来做为它的名称

 

Bundle-Version的命名方式

Bundle-Version有本身的命名要求,格式为[0-9].[0-9].[0-9].{ConstraintName}

前面都是数字做为版本号,最后一个小数点后的名称是通过命名约束的版本名

 

例如1.2.3.alpha1表明大版本号,2表明中版本号,3表明小版本好,而alpha表明小版本中的某个阶段,好比alphabetasnapshotqualifier等,下图展现了版本大小的排序方式


 

 

 

  • 代码访问机制设置

JVM环境:

类之间的互访是经过设置classpath来查找的

 

OSGI环境:

1)bundle内部的classpath

默认是bundle的根目录,可是能够经过头信息来指定bundle内的多个目录

 

 

如图所示,经过配置Bundle-ClassPath进行设置,. 表明bundle的根目录,是必需要有的,customPath表明<bundle>/customPath目录,固然,也能够是具体到某个jar文件。将会按顺序查找

 

2) Export-Package

经过设置Export-Package的头信息进行设置,能够设置多个包,只有在这里定义的包内的类能够被其它的模块进行访问。可是只能访问该包下的,该包的子包的类是不会被曝露的。

例如Export-Package: net.georgezeng.test.modelb.service; version=1.0.0, net.georgezeng.test.modelb.dao; version=1.2.0

 

3) Import-Package

当某个模块曝露了某些包,那么若是你要引用相应的那个模块下的包的类的话,就须要通知OSGI引入你设置的包到该模块,即在该模块的MANIFEST中定义Import-Package头信息。

例如上面的模块曝露了两个包,分别是service和dao,咱们这里假设该模块须要引入service的包来获取当中的类的访问,那么就须要以下设置

Import-Package: net.georgezeng.test.modelb.service; version=1.0.0

这样就能访问该service包下的类了。

Note:该头信息有一个有用的指令resolution,默认值是mandatory,能够设置为optional,当设置为optional时,依赖解析将对其忽略(下面将会提到)

 

4) DynamicImport-Package

使用此头信息时,OSGI将会扫描该头信息设置的全部包

它只能设置target值,能够多个,而且target值可使用通配符,例如

DynamicImport-Package: net.gerogezeng.test.*, net.georgezeng.test2 …

OSGI将会扫描net.georgezeng.test下全部的子包,但不包括test包下自己的类,若是要用到test包下的类,就是第二个target所设置的值的方式

Note:使用该头信息时,依赖解析也会对其忽略

 

5) Require-Bundle

使用Require-Bundle至关因而将被require的Bundle export出来的包所有import

 

  • 关于Export和Import中version属性的定义和匹配

在上面的描述中咱们看到了Export和Import中都有一个version的属性定义,该version采用的命名规则与Bundle-Version是同样的,这里主要是说明如何设置它的范围



 如图,你能够这样设置

Import-Package: net.georgezeng.test.modelb.service; version=”[1.0.0, 2.0.0)”

 

Export和Import间的匹配

1) 版本匹配

经过version属性进行设置,经过上图设置的版本的范围比较进行匹配

 

2) 自定义属性匹配

自定义属性的匹配必须是属性名和属性值都相同,即Import中的属性export中也是有的,且属性值相同

 

Note:Import-Package的规则也适用于Require-Bundle

 

  • 依赖和解析

何为依赖?

若是BundleA Export了某个包,而BundleB Import了这个包,那么就说BundleB依赖于BundleA

或者BundleA中设置了Require-Bundle的头信息,那么Require-Bundle中的全部相关的target Bundle都被A依赖

 

Bundle的依赖解析

当OSGI读取Bundle时,Bundle将会被解析,只有当Bundle被正确解析成功,才能被其它Bundle使用或者运行。即OSGI获取Bundle的MANIFEST里的头信息进行检查分析,而这个解析是连锁式反应的。

例如BundleB依赖于BundleA,假设OSGI要使用BundleB,它须要先解析BundleB,而BundllerB由于是依赖于BundleA的,因此BundleA会先于BundleB被进行解析,若是BundleA解析成功,才会继续解析BundleB,若是BundleB也成功解析,那么OSGI就能够正常使用BundleB了,这个过程因为会引发关联的Bundle进行解析,因此是依赖解析。

 

  • DynamicImport vs optional

上面提到了能够经过Import-Package添加resolution指令设为optional或经过DynamicImport-Package进行配置令使得OSGI在进行依赖解析时对其进行忽略。

他们的相同之处都是在运行期间当某个Bundle用到了他们export的包时才会去解析,若是exported的Bundle以前已经解析成功,那么就直接搜索class,若是还未解析过,那么optional只会解析一次,若是失败了,就不会再尝试解析它,直到它已被从新解析过;而Dynamic的方式则是每次都尝试从新解析

 

  • 依赖匹配

Bundle间的依赖须要进行匹配校验,好比bundleA须要某个包,而BundleB曝露了这个包,可是BundleC也曝露了相同的包,那么这时BundleA究竟是要依赖哪一个呢?这个时候就是经过上面提到的export和import间的匹配来进行校验的。默认会对比version属性,若是import中有自定义属性,那么export中也必须有相应的自定义属性且属性值相同才能匹配成功

 

匹配的顺序:

1)对version属性进行范围匹配,若是不止一个bundle匹配成功,则进入下一步

2)若是有自定义属性,进行严格的自定义属性匹配,若是不止一个bundle匹配成功则进入下一步

3)查找Bundle-Version更高的Bundle,以最高的版本为主

 

  • Fragment Bundle

一个模块就是一个Bundle,但有时候咱们会须要将Bundle在物理上分红多个块,并且让细分出来的块属于同一个模块下,那么这个时候就有了Fragment的概念。

Fragment没办法独立存在于OSGI中,须要依附于某个Bundle下,即有一个Bundle做为宿主。一个Fragment只能依附于一个Bundle。

Note:Fragment自己没有本身的classloader,使用的是Bundle的classloader

 

  • Class的搜索顺序

在了解整个class的搜索路径前,咱们须要先了解下面2个内容:

1) Bundle的Classloader

在OSGI环境中,一个Bundle有一个ClassLoader,用于读取bundle内部的类文件

 

2) boot delegation

在OSGI的配置文件中咱们能够配置一个选项用于将须要的包委派给jvm的classloader进行搜索

例如org.osgi.framework.bootdeletation=sun.*,com.sun.*

 

Bundle请求一个class的顺序以下:

1)若是请求的class来自于java.*下,那么bundle的classsloader会经过父classsloader(即jvm的classloader)进行搜索,找不到则抛错,若是不是java包下的,则直接进入2)

2)若是请求的class非java包下的,那么将搜索boot delegation的设置的包,一样也是经过1)的classloader进行搜索,搜索不到则进入下一步

3)经过Import-Package对应的Bundle的classloader进行搜索,搜索不到则进入下一步

4)经过Require-Bundle对应的Bundle的classloader进行搜索,搜索不到则进入下一步

5)经过Bundle自身的classpath进行搜索,若是搜索不到则进入下一步

6)经过Fragment的classpath进行搜索,若是找不到则进入下一步

7)若是设置了DynamicImport-Package的头信息,将经过扫描DynamicImport-Package中设置的包,而后去相应的exportBundle中查找,有则返回,没有则抛错,搜索完毕;若是没有设置该头信息,则直接抛错

 

  • Bundle的生命周期



 如图,椭圆表示状态,bundle在OSGI中一共能够查看到5个状态,分别是Installed,Resolved,starting,Active,stopping。Uninstalled则是一个虚的状态,即当Bundle不存在于OSGI中时的状态。

 

OSGI里对Bundle的操做一共有6个,分别是Install,Update,Refresh,Start,Stop,Uninstall

经过这些操做就能够达到不一样的状态

 

如何安装和启动Bundle?

能够经过3种方式:

1)OSGI的配置文件

2)OSGI的shell

3)OSGI的API

 

关于Resolved

上面提到解析就是从Installed到Resolved间的动做,Resolved表示解析成功,进入已解析状态,此状态下Bundle能够被其它Bundle使用或启动运行Bundle自身

只有在Resolved状态时当调用start命令后才能激活starting状态,并在调用结束后自动转入Active状态,若是starting时有异常则自动返回Resolved状态

 

Active状态

只有当在Active状态时,调用stop命令才能激活stopping的状态,并在结束后(不论是否有异常)自动转入Resolved状态

 

Starting和stopping

这两个状态能够交由developer控制,好比对资源进行载入和释放,作一些bundle初始化的事情。

Note:

1)若是Bundle是经过配置文件启动的话,那么Bundle将会经过start level event dispatcher的线程进行starting的操做,而且bunlde间的start是按顺序的,可是因为不是在OSGI的console线程中启动,因此在starting过程当中能够对shell进行操做。

2)若是Bunlde是经过shell进行启动的,OSGI将使用shell的当前线程,即OSGI Console线程进行启动,在该状态中shell将没法作任何操做。若是在starting中bundle出现死锁或须要长时间的过程那么将会致使没法对shell进行操做,从而只能选择重启OSGI环境。

3)因为stopping是不可能在OSGI启动时发生的,通常都是在shell下经过stop命令调用发生,这种状况与2)相同。

因此要慎重的考虑starting和stopping的逻辑要如何处理。

在starting和stopping的状态下调用任何相关的操做都是不合理的

 

关于update和refresh

Update会对bundle内容进行更新,refresh不会对Bundle内容进行更新

1)当Bundle处于Installed状态时,调用update保持状态不变,调用refresh将会自动进行解析,若是成功则进入resolved,不然仍是处于installed

2)当Bundle处于Resolved时,调用update命令将会回到installed状态,而调用refresh时会再次解析,成功则自定启动bundle,直到进入active

3)当Bundle处于Active状态时,调用update后若是解析成功将返回active状态,refresh同样

Note:使用refresh会形成依赖解析的发生

 

关于Activator

Starting和stopping究竟是在哪里进行定义呢?OSGI定义了一个Activator对starting和stopping进行操做,这个类是org.osgi.framework.BundleActivator。只要继承该类,并在MANIFEST.MF中定义Bundle-Activator头信息便可实现

 

关于BundleContext

经过BundleContext咱们能够与OSGI框架进行交互,好比获取OSGI下当前的bundle列表。

一个Bundle都有一个对应的BundleContext,当Bundle被start的时候建立且在stop的时候被销毁。每启动一次Bundle都将得到一个新的BundleContext

如何获取BundleContext?

在Activator中,OSGI将会在start和stop方法中传入这个对象

 

 

关于event

生命周期的控制总会有一些event能够处理,好比经过实现OSGI提供的listener接口便可,这里不详细熬述

 

  • OSGI的缓存

OSGI对于Bundle的install操做是持久化的操做,即当install某一个bundle的时候,OSGI将会把该bundle进行副本保存,而再也不须要当前的bundle的jar包。这是OSGI的默认行为,当OSGI中止再启动时,此Bundle将会自动载入保存的副本。

在update的时候OSGI将会保存一个新的bundle副本,可是老的副本也还在(不过只在当前的OSGI环境下存在了,重启则会找到最新的副本),由于可能有其它的bundle依赖于老副本存在,此时须要使用refresh来进行同步刷新。这种会引发依赖解析再次进行。

若是使用uninstall则bundle的副本再也不保存

Note:能够经过添加-clean的启动参数来清理缓存

 

  • Bundle的persistent storage area

OSGI会为bundle开辟一块持久化数据区域,用于给bundle进行资源的存取,这块区域经过BundleContext进行访问,并在uninstall的时候销毁

 

  • OSGI Service

什么是OSGI Service?

咱们有了export和import,已经能够很好的管理模块间的代码互访,可是,若是咱们只但愿曝露接口,而不是实现的话,如何让依赖的Bundle得到接口的实现呢?这个时候咱们就须要一种相似服务查找的方式经过OSGI来得到实现了。

因此Bundle有两种角色:生产者和消费者

生产者Bundle经过注册服务将实现注入OSGI中,消费者Bundle则是经过查找服务的方式从OSGI中得到实现。

OSGI目前提供了2种方式用于注册和查找服务:

1)编程式

经过BundleContext进行注册或查找

 

2)声明式

是OSGI R4.0中引入的新的方式,经过XML文件的配置进行服务的注册和查找

相关文章
相关标签/搜索