Java Web應用集成OSGI

来源:https://www.cnblogs.com/labber/archive/2018/01/21/8325575.html
-Advertisement-
Play Games

對OSGI的簡單理解 就像Java Web應用程式需要運行在Tomcat、Weblogic這樣的容器中一樣。程式員開發的OSGI程式包也需要運行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程式包在OSGI中稱作Bundle。 Bu ...


對OSGI的簡單理解

就像Java Web應用程式需要運行在Tomcat、Weblogic這樣的容器中一樣。程式員開發的OSGI程式包也需要運行在OSGI容器中。目前主流的OSGI容器包括:Apache Felix以及Eclipse Equinox。OSGI程式包在OSGI中稱作Bundle
Bundle的整個生命周期都交與OSGI容器進行管理。可以在不停止服務的情況下,對Bundle進行載入和卸載,實現熱部署。
Bundle對於外部程式來說就是一個黑盒。他只是向OSGI容器中註冊了供外部調用的服務介面,至於實現則對外部不可見。不同的Bundle之間的調用,也需要通過OSGI容器來實現。

Bundle如何引入jar

剛纔說到Bundle是一個黑盒,他所有實現都包裝到了自己這個“盒子”中。在開發Bundle時,避免不了引用一些比如Spring、Apache commons等開源包。在為Bundle打包時,可以將當前Bundle依賴jar與Bundle的源碼都打包成一個包(all-in-one)。這種打包結果就是打出的包過大,經常要幾兆或者十幾兆,這樣當然我們是不可接受的。下麵就介紹一種更優的做法。

Bundle與OSGI容器的契約

Bundle可以在MANIFEST.MF配置文件中聲明他要想運行起來所要的包以及這些包的版本 !!!而OSGI容器在載入Bundle時會為Bundle提供Bundle所需要的包 !!!在啟動OSGI容器時,需要在OSGI配置文件中定義org.osgi.framework.system.packages.extra,屬性。這個屬性定義了 OSGI容器能提供的包以及包的版本。OSGI在載入Bundle時,會將他自己能提供的包以及版本與Bundle所需要的包以及版本列表進行匹配。如果匹配不成功則直接拋出異常:

Unable to execute command on bundle 248: Unresolved constraint in bundle
com.osgi.demo2 [248]: Unable to resolve 248.0: missing requirement [248.0] osgi
.wiring.package; (&(osgi.wiring.package=org.osgi.framework)(version>=1.8.0)(!(version>=2.0.0)))

也可能載入Bundle通過,但是運行Bundle時報ClassNotFoundException。這些異常都由於配置文件沒配置造成的。理解了配置文件的配置方法,就能解決60%的異常。

Import-Package

BundleImport-Package屬性中通過以下格式配置:

<!--pom.xml-->
 <Import-Package>
javax.servlet,
javax.servlet.http,
org.xml.sax.*,
org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,
org.springframework.util.*;version="[2.5,5.0]"
</Import-Package>
  • 包與包之間通過逗號分隔
  • 可以使用*這類的通配符,表示這個包下的所有包。如果不想使用通配符,則同一個包下的其他包彼此之間可以使用;分隔。
  • 如果需要指定包的版本則在包後面增加;version="[最低版本,最高版本]"。其中[表示大於等於、]表示小於等於、)表示小於。

org.osgi.framework.system.packages.extra

語法與Impirt-Package基本一致,只是org.osgi.framework.system.packages.extra不支持通配符。

  • 錯誤的方式

    org.springframework.beans.factory.*;version=4.1.1.RELEASE
    
  • 正確的方式:

    org.springframework.beans.factory.xml;org.springframework.beans.factory.config;version=4.1.1.RELEASE,
    

Class文件載入

在我們平時開發中有些情況下載入一個Class會使用this.getClassLoader().loadClass。但是通過這種方法載入Bundle中所書寫的類的class會失敗,會報ClassNotFoundException。在Bundle需要使用下麵的方式來替換classLoader.loadClass方法

 public void start(BundleContext context) throws Exception {
     Class classType = context.loadClass(name);
 }

Bundle中載入Spring配置文件時的問題

由於Bundle載入Class的特性,會導致在載入Spring配置文件時報錯。所以需要將Spring啟動所需要的ClassLoader進行更改,使其調用BundleContext.loadClass來載入Class。

String xmlPath = "";
ClassLoader classLoader = new ClassLoader(ClassUtils.getDefaultClassLoader()) {

    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            return currentBundle.loadClass(name);
        } catch (ClassNotFoundException e) {
            return super.loadClass(name);
        }
    }
    };
    DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
    beanFactory.setBeanClassLoader(classLoader);
    GenericApplicationContext ctx = new GenericApplicationContext(beanFactory);
    ctx.setClassLoader(classLoader);
    DefaultResourceLoader resourceLoader = new DefaultResourceLoader(classLoader) {
        @Override
        public void setClassLoader(ClassLoader classLoader) {
            if (this.getClassLoader() == null) {
                super.setClassLoader(classLoader);
            }
        }
    };
    ctx.setResourceLoader(resourceLoader);
    XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(ctx);
    reader.loadBeanDefinitions(xmlPath);
    ctx.refresh();

Web應用集成OSGI

這裡選用了Apache Felix來開發,主要是因為Apache Felix是Apache的頂級項目。社區活躍,對OSGI功能支持比較完備,並且文檔例子比較全面。
其實OSGI支持兩種方式來部署Bundle

  • 單獨部署OSGI容器,通過OSGI自帶的Web中間件(目前只有jetty)來對外提供Web服務
  • 將OSGI容器嵌入到Web應用中,然後就可以使用Weblogic等中間件來運行Web應用

從項目的整體考慮,我們選用了第二種方案。

BundleActivator開發

開發Bundle時,首先需要開發一個BundleActivator。OSGI在載入Bundle時,首先調用BundleActivatorstart方法,對Bundle進行初始化。在卸載Bundle時,會調用stop方法來對資源進行釋放。

public void start(BundleContext context) throws Exception;
public void stop(BundleContext context) throws Exception;

start方法中調用context.registerService來完成對外服務的註冊。

Hashtable props = new Hashtable();
props.put("servlet-pattern", new String[]{"/login","/logout"})
ServiceRegistration servlet = context.registerService(Servlet.class, new DispatcherServlet(), props);
  • context.registerService方法的第一個參數表示服務的類型,由於我們提供的是Web請求服務,所以這裡的服務類型是一個javax.servlet.Servlet,所以需要將javax.servlet.Servlet傳入到方法中
  • 第二個參數為服務處理類,這裡配置了一個路由Servlet,其後會有相應的程式來處理具體的請求。
  • 第三個參數為Bundle對外提供服務的屬性。在例子中,在Hashtable中定義了Bundle所支持的servlet-pattern。OSGI容器所在Web應用通過Bundle定義的servlet-pattern判斷是否將客戶請求分發到這個Bundleservlet-pattern這個名稱是隨意起的,並不是OSGI框架要求的名稱。

應用服務集成OSGI容器

  • 首先工程需要添加如下依賴
   <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.framework</artifactId>
            <version>5.6.10</version>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.bundle</artifactId>
            <version>3.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.bridge</artifactId>
            <version>3.0.18</version>
        </dependency>
        <dependency>
            <groupId>org.apache.felix</groupId>
            <artifactId>org.apache.felix.http.proxy</artifactId>
            <version>3.0.0</version>
        </dependency>
  • 然後在web.xml中添加
    <listener>
        <listener-class>org.apache.felix.http.proxy.ProxyListener</listener-class>
    </listener>
  • 開發ServletContextListener用以初始化並啟動OSGI容器
    請參考Apache Felix提供的例子程式。例子中提供的ProvisionActivator會掃描/WEB-INF/bundles/,載入其中的Bundle包。(當然例子中提供的ProvisionActivator並不帶有Bundle自動發現註冊等機制,這些邏輯需要自行增加。請參照後續的Bundle自動載入章節)

路由開發

通過上面的配置,只是將OSGI容器載入到了Web應用中。還需要修改Web應用程式路由的代碼。

  • Bundle載入到OSGI容器中後,可以通過bundleContext.getBundles()方法獲取到OSGI容器中的所有已經載入的Bundle
  • 可以調用Bundlebundle.getRegisteredServices()方法獲取到該Bundle對外提供的所有服務服務。getRegisteredServices方法返回ServiceReference的數組。前文中我們調用context.registerService(Servlet.class, new DispatcherServlet(), props)我們已經註冊了一個服務,getRegisteredServices返回的數據只有一個ServiceReference對象。
  • 獲取Bundle所能提供的服務
    可以通過ServiceReference對象的getProperty方法獲取context.registerService中傳入的props中的值。這樣我們就能通過調用ServiceReference.getProperty方法獲取到該Bundle所能提供的服務。
  • 通過上面提供的介面,我們可以將Bundle對應ServiceReference以及Bundle對應的servlet-pattern進行緩存。當用戶請求進入到應用伺服器後,通過緩存的servlet-pattern可以判斷Bundle是否能提供用戶所請求的服務,如果可以提供通過下麵的方式,來調用Bundle所提供的服務。
 ServiceReference sr = cache.get(bundleName);
 HttpServlet servlet = (HttpServlet) this.bundleContext.getService(sr);
 servlet.service(request, response);

Bundle自動載入

Apache Felix例子中提供的ProvisionActivator,只會在系統啟動時載入/WEB-INF/bundles/目錄下的Bundle。當文件夾下的Bundle文件有更新時,並不會自動更新OSGI容器中的Bundle。所以Bundle自動載入的邏輯,需要我們自己增加。下麵提供實現的思路:

  • 在第一次載入文件夾下的Bundle時,記錄Bundle包所對應的最後的更新時間。
  • 在程式中創建一個獨立線程,用以掃描/WEB-INF/bundles/目錄,逐個的比較Bundle的更新時間。如果與記憶體中的不相符合,則從OSGI中獲取Bundle對象然後調用其stop以及uninstall方法,將其從OSGI容器中卸載。
  • 卸載後,再調用bundleContext.installBundle以及bundle.start將最新的Bundle載入到OSGI容器中

BundleListener

最後一個問題,通過上面的方式,可以實現Bundle的自動載入。但是剛纔我們介紹了,在路由程式中,我們會緩存OSGI容器中所有的Bundle所對應的ServiceReference以及所有Bundle所對應的servlet-pattern。所以Bundle自動更新後,我們還需要將路由程式中的緩存同步的進行更新。
可以通過向bundleContext中註冊BundleListener,當OSGI容器中的Bundle狀態更新後,會調用BundleListenerbundleChanged回調方法。然後我們可以在bundleChanged回調方法中書寫更新路由緩存的邏輯

this.bundleContext.addBundleListener(new BundleListener() {
    @Override
    public void bundleChanged(BundleEvent event) {
        if (event.getType() == BundleEvent.STARTED) {
            initBundle(event.getBundle());
        } else if (event.getType() == BundleEvent.UNINSTALLED) {
            String name = event.getBundle().getSymbolicName();
            indexes.remove(name);
        }
     }
 });

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 函數是組織好的,可重覆使用的,用來實現單一,或相關聯功能的代碼段。函數能提高應用的模塊性,和代碼的重覆利用率。你已經知道Python提供了許多內建函數,比如print()。但你也可以自己創建函數,這被叫做用戶自定義函數。 1、語法 Python 定義函數使用 def 關鍵字,一般格式如下: 預設情況 ...
  • 上一篇文章是我們自己模擬的DBUtils工具類,其實有開發好的工具類 這裡使用commons-dbutils-1.6.jar 事務的簡單介紹: 在資料庫中應用事務處理案例:轉賬案例 張三和李四都有有自己的存款 主鍵 帳戶名 餘額 1 張三 1000 2 李四 10 要從張三的賬戶餘額中轉賬800到李 ...
  • 1.編程方式分:面向對象、面向過程、函數式編程 2.區分面向對象 》類 》class面向過程 》過程 》def函數式編程 》函數 》def 3.編程語言中函數的定義: 函數是邏輯結構化和過程化的一種編程方法 4.過程是沒有返回值的函數 5.使用函數的優點: 1)代碼可重覆使用2)代碼可保持一致性3) ...
  • 分支運算;邏輯運算;隨機數import;賦值語句;break和continue;轉義字元;print加強版的使用 ...
  • 一. 準備工作 1. 本文參考 Java併發編程:線程池的使用 二. 相關代碼文件介紹 1. ThreadPoolExecutor.java 線程池中最核心的一個類,提供了四個構造函數用於創建線程池 public class ThreadPoolExecutor extends AbstractEx ...
  • clipse創建Maven結構的web項目的時候選擇Artifact Id為maven-artchetype-webapp,點擊finish之後,一般會遇到如下問題 1. The superclass "javax.servlet.http.HttpServlet" was not found on ...
  • 任務系統是游戲中最重要的系統之一,本文旨在設計一個輕量清晰的任務系統。通用易擴展是本系統關註的重點。任務系統中當角色的條件滿足時,自動觸發每一類型的任務,每個任務有其所需的完成條件,當角色完成了指定的操作後,則會觸發任務自動完成,任務完成後一般玩家會領取對應的獎勵,結束任務,此任務的生命周期結束,如... ...
  • # 函數式編程 函數是Python內建支持的一種封裝,而啊、函數式編程通俗說來就是玉虛把函數本身作為參數傳入另一個函數,允許返回一個函數。 > 函數名其實也是變數,也可以被賦值。如果函數名被賦值為其他值,則不再指向原來函數。 高階函數:既然變數可以指向函數,函數的參數能接收變數... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...