OSGi部落格筆記

weixin_33785972發表於2018-02-07

本文是對killko一些部落格的筆記,可以在《不可錯過的OSGi入門學習資源》中找到,包括:

此外,還參考了:

閱讀前建議先閱讀

走近Java模組化系統OSGi

OSGi是什麼

  1. OSGi作為Java的模組化規範,其目標(也是軟體設計的目標)是複用、內聚、耦合。
  2. 被神化的“動態性”、“熱插拔”的特性,是OSGi規範帶來的一種可能,需要配合好的設計才能達到,並不是一定會有的。
  3. OSGi是不是一個應用層面的框架(如spring、structs等),而是設計層面規範,類似物件導向的設計。所以,不要問“怎麼將spring和OSGI整合?”這樣的問題。
  4. OSGi的目的是進行模組化,模組(bundle)的物理形式是Jar包。
  5. OSGi的課程主要是讓你去設計應用的,是形而上需要個人領悟的,不是去學一套應用框架那種。

OSGi framework

OSGi規範定義了一個OSGi framework平臺,它是執行在JVM上的應用,負責管理模組bundle。

bundle生命週期

bundle需要關注它的生命週期。

  1. install:框架通過classloader(類載入器)來裝載bundle裡的類和資源。
  2. resolve:檢查bundle依賴的package是否可用。
  3. start:執行activator裡的start方法,方法執行完畢後進入"ACTIVE"狀態,至此bundle可用正式使用了。
8118972-2e1d55556d19e3e9.png
生命週期層狀態轉移

bundle的隔離

  1. 模組以Jar包形式存在,Jar包的物理邊界也是模組的物理邊界。
  2. 在OSGi規範下,得顯式地說明模組之間的依賴關係。OSGi是利用JVM的classloader和它的父委託模型(PDM:Parent Delegation Mode)來實現這點的。
  3. 每個bundle都分別用一個classloader來載入裡面的類,所以不同bundle之間的類,在預設情況下,是互不可見的。
  4. 如果沒有額外處理,一個bundle裡的類要訪問另一個bundle裡的類時,通常會出現ClassNotFound的異常。

bundle之間的耦合

方式一:import/export package的機制

  • OSGi通過import/export package的機制來控制bundle間有限地耦合。
  • Export/Import package是通過bundle裡的META-INF/manifest.mf檔案裡指定的。

方式二:OSGi service方式(更鬆散的耦合)

  • OSGi framework實現服務的註冊、查詢和使用。該服務是實現某種介面的bean例項。
  • 該服務是實現某種介面的bean例項,所以本質上osgi service就是一個bean。
  • 通常會把介面定義在bundle A裡,介面的實現則在bundle B裡,並將介面實現例項化後註冊成osgi service,bundle C可以引用這個service。bundle A需export介面定義所在的package,而bundle B和C則需import這個package。bundle B和C之間就不需用export/import package來耦合了,實現了B和C之間的解耦。
  • OSGi應用中,會有大量的osgi service存在,可以說osgi service是osgi規範中最重要的機制,沒有之一。

其它機制

OSGi規範還提供了Event、配置管理(ConfigAdmin)、宣告式服務(Declarative Service)、Service Tracker、Blueprint等等執行時機制,方便我們構建模組化的應用系統。

建立OSGi Hello World工程

本節內容參考了《建立OSGi Hello World工程》,需要注意的是,這篇文章中maven配置檔案pom.xml中的<name/>標籤需要進行修改,不能以冒號分隔,示例如下:

<name>osgi-demo</name>
   

模組構建

1.建立Maven專案。
2.在包中建立 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安裝

為了使用模組,需要一個支援模組化執行的環境,參考文章的作者使用了servicemix。

servicemix介紹

  • Apache ServiceMix是一個靈活、開源的整合容器。
  • 它可以將Apache ActiveMQ、Camel、CXF和Karaf整合為一個強大的執行平臺。
  • 你可以通過它來建立自己的整合解決方案。
  • 它提供了一個由OSGi支援的完整、企業級的ESB(企業服務匯流排)。

安裝

  • servicemix官網下載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!"表示模組已經載入。
8118972-349c5fc93e18552f.png
模組啟動
  • 使用list命令,可以檢視模組狀態。可以看到模組ID為220,狀態為 Active。
8118972-d050d9ffbc75be0d.png
模組狀態檢視
  • 輸入stop 220,可以看到"Stop bundle!",然後再輸入list,可以看到當前模組處於Resolved狀態。
8118972-073ffa223eac0e66.png
模組停止
  • 輸入uninstall 220,模組就被解除安裝了,再輸入list就看不到模組資訊了。
  • conrtrol + d 可以結束servicemix容器執行。

Package與服務

該部分可以參考《OSGi中Bundle間的耦合:Export/Import Package與服務》

註冊服務

在BundleActivator的start函式中可以通過BundleContext註冊服務。

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!");
    }

    ...
}

引用服務

在BundleActivator的start函式中可以通過BundleContext獲得服務。

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));
         }
    }
    ...
}

依賴疑問

在開發中,Demo2中提供服務(包含介面定義),Demo3中使用服務(不包含介面,但依賴介面),但是編譯器環境中OSGi模組依賴不能解析,如何表明依賴?翻看作者程式碼後,發現只要正常新增maven依賴即可,打包的時候不用包含進去。

動態的OSGi服務

該部分可以參考《動態的OSGi服務》。包括:

  • OSGI服務的動態性
  • Service Listener
  • Service Tracker

初次接觸OSGI Blueprint

該部分可以參考《初次接觸OSGI Blueprint》。包括:

  • Blueprint簡介
  • Blueprint的入門例子:OSGI服務的註冊
  • Blueprint的入門例子:OSGI服務的引用
  • OSGI blueprint的機制原理

OSGi的配置管理:ConfigAdmin

該部分可以參考《OSGi的配置管理:ConfigAdmin》。包括:

  • 動態的OSGI配置
  • 利用blueprint來實現ConfigAdmin

OSGi還有用武之地嗎?

本人在網上搜尋部落格的時候,發現OSGi近幾年的部落格介紹很少,關注度不高,而且學習的OSGi中文網站2015年後也不怎麼更新文章了。在分散式微服務流行的現在,OSGi的確有些尷尬,但是個人感覺對於一些複雜的單體應用,OSGi還是有用武之地的,比如Java 9學習了OSGi,增加了模組化功能並重構了Java庫。此外,OSGi解決模組依賴的方法和基於介面和模組的設計理念還是很有價值的。

接下來將對反面觀點進行一下介紹。

明顯不足

OSGi的關注不是很高,該文作者也談了它本身的一些不足:相對來說適合單體應用(但單體應用也可以通過容器的等幫助部署),不適合分散式。具體如下所示。

關於隔離與奔潰

OSGi最終基於jvm,無法對bundle對應的服務實現計算資源的隔離,一個服務的故障依然會導致整個jvm crush,這使得在一個執行時的osgi上部署模組級服務只獲得了模組部署和啟停隔離。

關於擴充套件能力

服務明確依賴的好處,但是沒辦法實現計算節點的線性擴充套件,在當前分散式,微服務,網路計算的趨勢下,使得osgi只適合構建單一服務節點的內部應用,但是其分離的bundle的部署負擔對於微服務架構來說,有點用大炮打蚊子的臭味。

推薦的應用架構方式

目標

  • 採用“程式間構建的分散式應用”和“程式內的單一應用”分開來進行架構設計。
  • 對於程式間構建的分散式應用,採取基於soa的理念進行容器模式的服務部署模式,服務互動基於遠端服務互動相關協議,採用可忍受網路失敗的架構設計原則。
  • 對於程式內的應用,如果需要模組級的獨立生命週期熱部署和模組管理,可以考慮採用OSGi。

容器更方便

但是,容器內基於本地程式間通訊的模組交付方式不僅能提供同樣的獨立生命熱部署和模組管理,而且具備隨時脫離出去部署成單獨容器級服務應用的能力,加速程式間的服務交付提供的整體管理和監視環境基礎。

OSGi比較適合嵌入式

OSGi還有用武之地嗎?當然我前述都是以構建分散式企業和麵向網際網路這類應用為前提來討論的,對於嵌入式的jvm應用,比如著名的osgi案例寶馬的車載系統,osgi依然是最好的原則,不過我懷疑基於andriod系統的機制構建類似應用,osgi的採用依然值得商榷。因此,osgi確實面臨雞肋之嫌。

相關文章