本文是對killko一些博客的筆記,可以在《不可錯過的OSGi入門學習資源》中找到,包括:
- 《走近Java模塊化系統(tǒng)OSGi》
- 《創(chuàng)建OSGi Hello World工程》
- 《OSGi中Bundle間的耦合:Export/Import Package與服務(wù)》
- 《動態(tài)的OSGi服務(wù)》
- 《初次接觸OSGI Blueprint》
- 《OSGi的配置管理:ConfigAdmin》
此外,還參考了:
閱讀前建議先閱讀
走近Java模塊化系統(tǒng)OSGi
OSGi是什么
- OSGi作為Java的模塊化規(guī)范,其目標(也是軟件設(shè)計的目標)是復(fù)用、內(nèi)聚、耦合。
- 被神化的“動態(tài)性”、“熱插拔”的特性,是OSGi規(guī)范帶來的一種可能,需要配合好的設(shè)計才能達到,并不是一定會有的。
- OSGi是不是一個應(yīng)用層面的框架(如spring、structs等),而是設(shè)計層面規(guī)范,類似面向?qū)ο蟮脑O(shè)計。所以,不要問“怎么將spring和OSGI集成?”這樣的問題。
- OSGi的目的是進行模塊化,模塊(bundle)的物理形式是Jar包。
- OSGi的課程主要是讓你去設(shè)計應(yīng)用的,是形而上需要個人領(lǐng)悟的,不是去學一套應(yīng)用框架那種。
OSGi framework
OSGi規(guī)范定義了一個OSGi framework平臺,它是運行在JVM上的應(yīng)用,負責管理模塊bundle。
bundle生命周期
bundle需要關(guān)注它的生命周期。
- install:框架通過classloader(類加載器)來裝載bundle里的類和資源。
- resolve:檢查bundle依賴的package是否可用。
- start:運行activator里的start方法,方法運行完畢后進入"ACTIVE"狀態(tài),至此bundle可用正式使用了。

bundle的隔離
- 模塊以Jar包形式存在,Jar包的物理邊界也是模塊的物理邊界。
- 在OSGi規(guī)范下,得顯式地說明模塊之間的依賴關(guān)系。OSGi是利用JVM的classloader和它的父委托模型(PDM:Parent Delegation Mode)來實現(xiàn)這點的。
- 每個bundle都分別用一個classloader來加載里面的類,所以不同bundle之間的類,在默認情況下,是互不可見的。
- 如果沒有額外處理,一個bundle里的類要訪問另一個bundle里的類時,通常會出現(xiàn)ClassNotFound的異常。
bundle之間的耦合
方式一:import/export package的機制
- OSGi通過import/export package的機制來控制bundle間有限地耦合。
- Export/Import package是通過bundle里的META-INF/manifest.mf文件里指定的。
方式二:OSGi service方式(更松散的耦合)
- OSGi framework實現(xiàn)服務(wù)的注冊、查找和使用。該服務(wù)是實現(xiàn)某種接口的bean實例。
- 該服務(wù)是實現(xiàn)某種接口的bean實例,所以本質(zhì)上osgi service就是一個bean。
- 通常會把接口定義在bundle A里,接口的實現(xiàn)則在bundle B里,并將接口實現(xiàn)實例化后注冊成osgi service,bundle C可以引用這個service。bundle A需export接口定義所在的package,而bundle B和C則需import這個package。bundle B和C之間就不需用export/import package來耦合了,實現(xiàn)了B和C之間的解耦。
- OSGi應(yīng)用中,會有大量的osgi service存在,可以說osgi service是osgi規(guī)范中最重要的機制,沒有之一。
其它機制
OSGi規(guī)范還提供了Event、配置管理(ConfigAdmin)、聲明式服務(wù)(Declarative Service)、Service Tracker、Blueprint等等運行時機制,方便我們構(gòu)建模塊化的應(yīng)用系統(tǒng)。
創(chuàng)建OSGi Hello World工程
本節(jié)內(nèi)容參考了《創(chuàng)建OSGi Hello World工程》,需要注意的是,這篇文章中maven配置文件pom.xml中的<name/>標簽需要進行修改,不能以冒號分隔,示例如下:
<name>osgi-demo</name>
模塊構(gòu)建
1.創(chuàng)建Maven項目。
2.在包中創(chuàng)建 OSGI Activator。
package mindw.osgi.demo;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
public class MyActivator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
System.out.println("Hello world!");
}
@Override
public void stop(BundleContext context) throws Exception {
System.out.println("Stop bundle!");
}
}
3.配置pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns:pom="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<groupId>mindw.osgi</groupId>
<artifactId>demo</artifactId>
<packaging>jar</packaging>
<version>0.1</version>
<name>osgi-demo</name>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.8</source>
<target>1.8</target>
<encoding>${project.build.sourceEncoding}</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
<manifestEntries>
<Class-Path>${project.build.finalName}.jar</Class-Path>
<Built-By>Ponder</Built-By>
<Bundle-ManifestVersion>2</Bundle-ManifestVersion>
<Bundle-Name>${project.groupId}.${project.ArtifactId}</Bundle-Name>
<Bundle-SymbolicName>${project.name}</Bundle-SymbolicName>
<Bundle-Version>${project.version}</Bundle-Version>
<Bundle-Vendor>${project.groupId}</Bundle-Vendor>
<Bundle-Activator>${project.groupId}.${project.ArtifactId}.MyActivator</Bundle-Activator>
<Export-Package>${project.groupId}.${project.ArtifactId};version="0.1"</Export-Package>
<Import-Package>org.osgi.framework</Import-Package>
</manifestEntries>
</archive>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>4.2.0</version>
<type>jar</type>
</dependency>
</dependencies>
</project>
4打包生成 demo-0.1.jar。
servicemix安裝
為了使用模塊,需要一個支持模塊化運行的環(huán)境,參考文章的作者使用了servicemix。
servicemix介紹
- Apache ServiceMix是一個靈活、開源的集成容器。
- 它可以將Apache ActiveMQ、Camel、CXF和Karaf集成為一個強大的運行平臺。
- 你可以通過它來建立自己的集成解決方案。
- 它提供了一個由OSGi支持的完整、企業(yè)級的ESB(企業(yè)服務(wù)總線)。
安裝
- 去servicemix官網(wǎng)下載zip壓縮文件。(版本都可,5.4.0版本86M左右,7.0.1版本113M左右)。
- 解壓壓縮包即可。
模塊使用(生命周期變化)
- 將demo-0.1.jar放入servicemix的deploy目錄下,本人放在 apache-servicemix-5.4.0\deploy 中。
- 進入 apache-servicemix-5.4.0\bin 目錄,運行 servicemix.bat??梢钥吹揭韵陆缑妗?Hello world!"表示模塊已經(jīng)加載。

- 使用
list命令,可以查看模塊狀態(tài)。可以看到模塊ID為220,狀態(tài)為 Active。

- 輸入
stop 220,可以看到"Stop bundle!",然后再輸入list,可以看到當前模塊處于Resolved狀態(tài)。

- 輸入
uninstall 220,模塊就被卸載了,再輸入list就看不到模塊信息了。 - conrtrol + d 可以結(jié)束servicemix容器運行。
Package與服務(wù)
該部分可以參考《OSGi中Bundle間的耦合:Export/Import Package與服務(wù)》。
注冊服務(wù)
在BundleActivator的start函數(shù)中可以通過BundleContext注冊服務(wù)。
public class MyActivator implements BundleActivator {
@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!");
}
...
}
引用服務(wù)
在BundleActivator的start函數(shù)中可以通過BundleContext獲得服務(wù)。
public class MyActivator implements BundleActivator {
@Override
public void start(BundleContext context) throws Exception {
ServiceReference[] refs = context.getServiceReferences(ICalculation.class.getName(), "(ServiceName=Calculation)");
System.out.println("demo3:"+ICalculation.class.getName());
if(refs!=null && refs.length>0){
ICalculation service=(ICalculation)context.getService(refs[0]);
System.out.println("1+1="+service.add(1, 1));
System.out.println("2-1="+service.sub(2, 1));
System.out.println("2*3="+service.sub(2, 3));
}
}
...
}
依賴疑問
在開發(fā)中,Demo2中提供服務(wù)(包含接口定義),Demo3中使用服務(wù)(不包含接口,但依賴接口),但是編譯器環(huán)境中OSGi模塊依賴不能解析,如何表明依賴?翻看作者代碼后,發(fā)現(xiàn)只要正常添加maven依賴即可,打包的時候不用包含進去。
動態(tài)的OSGi服務(wù)
該部分可以參考《動態(tài)的OSGi服務(wù)》。包括:
- OSGI服務(wù)的動態(tài)性
- Service Listener
- Service Tracker
初次接觸OSGI Blueprint
該部分可以參考《初次接觸OSGI Blueprint》。包括:
- Blueprint簡介
- Blueprint的入門例子:OSGI服務(wù)的注冊
- Blueprint的入門例子:OSGI服務(wù)的引用
- OSGI blueprint的機制原理
OSGi的配置管理:ConfigAdmin
該部分可以參考《OSGi的配置管理:ConfigAdmin》。包括:
- 動態(tài)的OSGI配置
- 利用blueprint來實現(xiàn)ConfigAdmin
OSGi還有用武之地嗎?
- 正面觀點,可以參考《簡單了解osgi》。
- 反面觀點,可以參考《osgi確實面臨雞肋之嫌》。
本人在網(wǎng)上搜索博客的時候,發(fā)現(xiàn)OSGi近幾年的博客介紹很少,關(guān)注度不高,而且學習的OSGi中文網(wǎng)站2015年后也不怎么更新文章了。在分布式微服務(wù)流行的現(xiàn)在,OSGi的確有些尷尬,但是個人感覺對于一些復(fù)雜的單體應(yīng)用,OSGi還是有用武之地的,比如Java 9學習了OSGi,增加了模塊化功能并重構(gòu)了Java庫。此外,OSGi解決模塊依賴的方法和基于接口和模塊的設(shè)計理念還是很有價值的。
接下來將對反面觀點進行一下介紹。
明顯不足
OSGi的關(guān)注不是很高,該文作者也談了它本身的一些不足:相對來說適合單體應(yīng)用(但單體應(yīng)用也可以通過容器的等幫助部署),不適合分布式。具體如下所示。
關(guān)于隔離與奔潰
OSGi最終基于jvm,無法對bundle對應(yīng)的服務(wù)實現(xiàn)計算資源的隔離,一個服務(wù)的故障依然會導(dǎo)致整個jvm crush,這使得在一個運行時的osgi上部署模塊級服務(wù)只獲得了模塊部署和啟停隔離。
關(guān)于擴展能力
服務(wù)明確依賴的好處,但是沒辦法實現(xiàn)計算節(jié)點的線性擴展,在當前分布式,微服務(wù),網(wǎng)絡(luò)計算的趨勢下,使得osgi只適合構(gòu)建單一服務(wù)節(jié)點的內(nèi)部應(yīng)用,但是其分離的bundle的部署負擔對于微服務(wù)架構(gòu)來說,有點用大炮打蚊子的臭味。
推薦的應(yīng)用架構(gòu)方式
目標
- 采用“進程間構(gòu)建的分布式應(yīng)用”和“進程內(nèi)的單一應(yīng)用”分開來進行架構(gòu)設(shè)計。
- 對于進程間構(gòu)建的分布式應(yīng)用,采取基于soa的理念進行容器模式的服務(wù)部署模式,服務(wù)交互基于遠程服務(wù)交互相關(guān)協(xié)議,采用可忍受網(wǎng)絡(luò)失敗的架構(gòu)設(shè)計原則。
- 對于進程內(nèi)的應(yīng)用,如果需要模塊級的獨立生命周期熱部署和模塊管理,可以考慮采用OSGi。
容器更方便
但是,容器內(nèi)基于本地進程間通信的模塊交付方式不僅能提供同樣的獨立生命熱部署和模塊管理,而且具備隨時脫離出去部署成單獨容器級服務(wù)應(yīng)用的能力,加速進程間的服務(wù)交付提供的整體管理和監(jiān)視環(huán)境基礎(chǔ)。
OSGi比較適合嵌入式
OSGi還有用武之地嗎?當然我前述都是以構(gòu)建分布式企業(yè)和面向互聯(lián)網(wǎng)這類應(yīng)用為前提來討論的,對于嵌入式的jvm應(yīng)用,比如著名的osgi案例寶馬的車載系統(tǒng),osgi依然是最好的原則,不過我懷疑基于andriod系統(tǒng)的機制構(gòu)建類似應(yīng)用,osgi的采用依然值得商榷。因此,osgi確實面臨雞肋之嫌。