Java 呼叫 Go 解決方案

FunTester發表於2025-01-10

Go,常被稱為GoLang,是由 Google 精心打造的一種靜態型別、編譯型程式語言。它以其簡潔的語法、卓越的併發處理能力和高效的效能而著稱,因此在後端系統、雲原生應用以及微服務架構中得到了廣泛應用。Go 語言憑藉其豐富的標準庫,以及 goroutineschannels 等獨特特性,在開發可擴充套件且高效的程式方面展現了顯著優勢。許多開發者傾向於將Go與其他程式語言,如 Java,結合使用,以構建功能更為強大的多語言系統。在本文中,我們將深入探討如何從 Java 環境中呼叫GoLang函式,以實現兩種語言的無縫整合。

依賴項

在從 Java 呼叫 Go 函式之前,讓我們首先看看需要做哪些準備工作:

  • Java 開發工具包(JDK):推薦使用 JDK 11 或更高版本,以確保相容性和效能最佳化。
  • Go 編譯器:確保 Go 已安裝在您的系統上,並且環境變數已正確配置,以便在命令列中直接使用go命令。
  • Java 本地介面(JNI)JNI是 Java 平臺提供的一種機制,用於將原生代碼(如 C/C++ 或 Go 編譯的二進位制檔案)與 Java 程式碼整合。
  • cgocgo是 Go 語言的一個工具,允許 Go 程式碼與 C 程式碼互操作。透過 cgo,您可以生成與 C 相容的二進位制檔案,從而支援 JNI 呼叫。
  • javac 和 java:確保 Java 編譯器和執行時環境已安裝,以便編譯和執行 Java 程式。

功能演示

在接下來的步驟中,我將詳細介紹如何透過編寫 Go 函式、將其編譯為共享庫,並使用 JNI(Java Native Interface)從 Java 呼叫該函式。以下是具體步驟:

編寫 Go 函式

首先,我們需要編寫一個簡單的 Go 函式,並將其匯出為 C 相容的符號,以便 Java 可以透過 JNI 呼叫它。以下是一個示例 Go 函式,它接收兩個整數並返回它們的和:

package main

import "C"

//export AddNumbers
func AddNumbers(a, b int) int {
    // 此函式接收兩個整數作為輸入,
    // 計算它們的和,並返回結果。
    return a + b
}

// main函式是構建共享庫所必需的,
// 即使在此情況下它不執行任何操作。
func main() {}

程式碼解釋:

  1. package main:這是 Go 程式的入口點宣告。任何需要編譯為可執行檔案或共享庫的 Go 程式都必須使用package main
  2. import "C":該語句啟用了 Go 與 C 之間的互操作性。透過匯入C包,Go 程式碼可以生成與 C 相容的二進位制檔案,從而支援 JNI 呼叫。
  3. AddNumbers函式:該函式接收兩個整數引數ab,計算它們的和並返回結果。這是一個簡單的示例,展示瞭如何透過 Go 函式處理輸入並返回輸出。
  4. func main() {}:即使main函式在此不執行任何操作,它也是構建共享庫所必需的。Go 編譯器需要main函式作為程式的入口點。

將 Go 程式碼編譯為共享庫

接下來,我們需要將 Go 程式碼編譯為共享庫(.so檔案),以便Java程式可以載入並呼叫它。使用以下命令完成編譯:

go build -o libadd.so -buildmode=c-shared main.go

命令解釋

  • -o libadd.so:指定輸出檔名為libadd.so,這是一個共享庫檔案。
  • -buildmode=c-shared:告訴 Go 編譯器生成一個 C 相容的共享庫,供其他語言(如 Java)呼叫。

編譯完成後,會生成兩個檔案:libadd.so(共享庫)和libadd.h(C 標頭檔案)。Java 程式將透過JNI載入libadd.so

編寫 Java 程式碼

現在,我們需要編寫一個 Java 程式來載入共享庫並呼叫 Go 函式。以下是示例程式碼:

/**  
 * Go呼叫者, 用於呼叫Go生成的共享庫。  
 */  
public class GoInvoker {  

    static {  
    // 載入由Go生成的共享庫。  
    // 確保庫檔案在系統庫路徑中,或指定其完整路徑。  
        System.loadLibrary("add"); // 載入共享庫  
    }  

    // 宣告一個本地方法,與Go函式對應。  
    // 方法簽名必須與Go函式的引數和返回型別匹配。  
    public native int AddNumbers(int a, int b);  

    public static void main(String[] args) {  
        GoInvoker invoker = new GoInvoker();  
        // 呼叫本地方法並傳遞兩個整數。  
        int result = invoker.AddNumbers(10, 20);  
        // 列印從Go函式接收到的結果。  
        System.out.println("Result from Go Function: " + result);  
    }  

}

程式碼解釋:

  1. 靜態塊(static { ... }: 在類載入時,靜態塊會執行System.loadLibrary("add"),載入名為add的共享庫(即libadd.so)。確保庫檔案在系統庫路徑中,或提供其完整路徑。
  2. native關鍵字native用於宣告一個本地方法,表示該方法的實現由外部庫(如 Go 編譯的共享庫)提供。
  3. AddNumbers方法: 該方法與 Go 函式AddNumbers對應,接收兩個整數引數並返回一個整數。方法簽名必須與 Go 函式完全匹配。
  4. main方法: 在main方法中,建立GoInvoker例項並呼叫AddNumbers方法,傳遞引數1020。呼叫結果儲存在result變數中,並列印到控制檯。

編譯和執行 Java 程式碼

完成 Java 程式碼編寫後,按照以下步驟編譯和執行程式:

  1. 編譯 Java 程式碼
    使用以下命令編譯 Java 程式:

    javac GoInvoker.java
    
  2. 執行 Java 程式
    使用以下命令執行程式:

    java GoInvoker
    
  3. 確保共享庫可用
    確保libadd.so檔案在系統庫路徑中,或透過以下方式指定路徑:
    在 Linux 上,設定LD_LIBRARY_PATH環境變數:

    export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH
    

    在 Windows 上,將共享庫所在目錄新增到PATH環境變數中。

  4. 輸出結果
    如果一切配置正確,程式將輸出以下結果:

    Result from Go Function: 30
    

透過以上步驟,我們成功實現了從 Java 呼叫 Go 函式的功能。這種方法結合了 Java 的跨平臺能力和 Go 的高效能特性,適用於需要多語言整合的複雜系統開發。

處理複雜資料型別

在實際開發中,我們經常需要處理更復雜的資料型別,例如結構體。為了在 Go 和 Java 之間傳遞複雜資料,可以使用JSON作為中間格式進行序列化和反序列化。以下是一個示例,展示如何在 Go 中定義一個結構體,將其序列化為JSON,並透過 JNI 在 Java 中解析。

將結構體匯出為 JSON

首先,我們在 Go 中定義一個Person結構體,並編寫一個函式將其序列化為 JSON 字串:

package main

import (
    "C"
    "encoding/json"
    "fmt"
    )

// 定義Person結構體
type Person struct {
    Name string `json:"name"`
    Ageint`json:"age"`
}

// 匯出一個函式,將結構體序列化為JSON字串
//export GetPersonJSON
func GetPersonJSON() *C.char {
    person := Person{Name: "John Doe", Age: 30} // 建立Person例項
    jsonData, err := json.Marshal(person)// 序列化為JSON
    if err != nil {
        fmt.Println("Error marshaling data:", err)
        return nil
    }
    return C.CString(string(jsonData)) // 返回C相容的字串
}

// main函式是構建共享庫所必需的
func main() {}

程式碼解釋

  1. Person結構體: 定義了一個包含Name(字串型別)和Age(整數型別)的結構體。
  2. GetPersonJSON函式
    • 該函式建立了一個Person例項,並將其序列化為 JSON 字串。
    • 使用json.Marshal函式將結構體轉換為 JSON 格式。
    • 如果序列化失敗,函式會返回nil
    • 使用C.CString將 Go 字串轉換為 C 相容的字串,以便透過 JNI 傳遞。
  3. main函式: 雖然main函式為空,但它是構建共享庫所必需的。

處理 JSON 並呼叫 Go 函式

接下來,我們在 Java 中編寫程式碼,載入共享庫並呼叫 Go 函式以獲取 JSON 字串,然後解析該字串:

import com.alibaba.fastjson2.JSON;  
import com.alibaba.fastjson2.JSONObject;  

/**  
 * Go呼叫者, 用於呼叫Go生成的共享庫。  
 */  
public class GoInvoker {  

    static {  
        // 載入Go共享庫  
        System.loadLibrary("add"); // 確保庫名與Go生成的共享庫一致  
    }  

    // 宣告本地方法,用於呼叫Go函式並接收JSON字串  
    public native String GetPersonJSON();  

    public static void main(String[] args) {  
        GoInvoker invoker = new GoInvoker();  
        // 呼叫Go函式,獲取JSON字串  
        String jsonResult = invoker.GetPersonJSON();  

        // 解析JSON字串  
        try {  
            JSONObject personObject = JSON.parseObject(jsonResult);  
            String name = personObject.getString("name");  
            int age = personObject.getIntValue("age");  
            // 列印解析後的結果  
            System.out.println("Name: " + name);  
            System.out.println("Age: " + age);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  

}

程式碼解釋:

  1. 載入共享庫: 使用System.loadLibrary("add")載入 Go 生成的共享庫(libadd.so)。
  2. 本地方法宣告public native String GetPersonJSON();宣告瞭一個本地方法,用於呼叫 Go 函式並返回 JSON 字串。
  3. 解析 JSON
    • 使用org.json.JSONObject解析從 Go 函式返回的 JSON 字串。
    • 提取nameage欄位,並列印到控制檯。
  4. 異常處理: 如果 JSON 解析失敗,捕獲並列印異常資訊。

編譯和執行程式碼

按照以下步驟編譯和執行程式碼:

  1. 編譯 Go 程式碼
    使用以下命令將 Go 程式碼編譯為共享庫:

    go build -o libadd.so -buildmode=c-shared main.go
    
  2. 編譯 Java 程式碼
    使用以下命令編譯 Java 程式:

    javac -cp .:org.json.jar GoInvoker.java
    

    (確保org.json.jar在類路徑中,或使用 Maven/Gradle 管理依賴。)

  3. 執行 Java 程式
    使用以下命令執行程式:

    java -cp .:org.json.jar GoInvoker
    
  4. 輸出結果
    如果一切配置正確,程式將輸出以下結果:

    Name: John Doe
    Age: 30
    

總結

本文深入探討了如何透過 JNI(Java Native Interface)與共享庫技術實現 Java 與 Go 的高效整合,從基礎資料型別的傳遞到複雜結構體的處理,全面展示了跨語言呼叫的技術細節。透過將 Go 的高效能與 Java 的生態優勢相結合,開發者能夠構建兼具高效性與擴充套件性的多語言系統。文章不僅提供了從 Go 函式編譯到 Java 呼叫的完整實踐指南,還透過 JSON 序列化與反序列化,解決了複雜資料型別的跨語言互動問題,為現代分散式系統與微服務架構提供了強有力的技術支撐。無論是後端開發、雲原生應用,還是多語言微服務整合,本文都提供了極具參考價值的解決方案。

FunTester 原創精華

【連載】從 Java 開始效能測試

  • 混沌工程、故障測試、Web 前端
  • 服務端功能測試
  • 效能測試專題
  • Java、Groovy、Go
  • 白盒、工具、爬蟲、UI 自動化
  • 理論、感悟、影片
如果覺得我的文章對您有用,請隨意打賞。您的支援將鼓勵我繼續創作!
打賞支援
暫無回覆。

相關文章