1. 引言

Java和Go是两种各具优势的编程语言:Java以跨平台能力和丰富生态著称,Go则以简洁高效和并发性能见长。在某些场景下,结合两者优势能构建更强大的应用。

本文将介绍如何通过**Java Native Access (JNA)**库,在不编写C代码的情况下实现Java调用Go函数。相比传统的JNI方案,JNA简化了跨语言集成的复杂度。

2. 使用JNA桥接Java和Go

传统JNI方案需要编写C胶水代码,而JNA允许直接从Java调用原生共享库,彻底绕过C代码开发。核心流程包括:

  1. 编写Go函数并编译为共享库
  2. 通过JNA创建Java接口映射Go函数
  3. 在Java中直接调用Go代码

这种方案既保留了Go的性能优势,又无缝融入Java生态,大幅简化了混合开发流程。

3. 项目设置

准备开发环境需要以下组件:

  • JDK:编译运行Java代码
  • Go环境:编写编译Go代码
  • JNA库:通过Maven引入依赖
  • 构建工具:Maven(Java) + Go编译器

pom.xml添加JNA依赖:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.15.0</version>
</dependency>

4. 从Java调用Go函数

4.1. 构建Go共享库

创建hello.go文件,关键点:

package main

/*
#include <stdlib.h>
*/
import "C"
import "fmt"

//export SayHello
func SayHello() {
    fmt.Println("Hello Baeldung from Go!")
}

func main() {}

⚠️ 注意事项

  • import "C"启用CGO特性
  • //export指令导出函数为C调用约定
  • 空main函数是编译共享库的必需项

根据操作系统编译共享库:

# Linux
go build -o libhello.so -buildmode=c-shared hello.go

# Windows
go build -o libhello.dll -buildmode=c-shared hello.go

# macOS
go build -o libhello.dylib -buildmode=c-shared hello.go

4.2. 创建JNA接口

在Java中定义接口映射Go函数:

import com.sun.jna.Library;

public interface GoLibrary extends Library {
    void SayHello();
}

调用示例:

import com.sun.jna.Native;

public class HelloGo {
    public static void main(String[] args) {
        GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
        goLibrary.SayHello();
    }
}

⚠️ 运行前配置

# Linux
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:.

# Windows
set PATH=%PATH%;C:\path\to\library

# macOS
export DYLD_LIBRARY_PATH=$DYLD_LIBRARY_PATH:.

输出结果:

Hello Baeldung from Go!

5. 传递参数和返回值

扩展Go函数支持参数传递:

//export AddNumbers
func AddNumbers(a, b C.int) C.int {
    return a + b
}

更新Java接口:

public interface GoLibrary extends Library {
    // ... 其他方法
    int AddNumbers(int a, int b);
}

调用测试:

public static void main(String[] args) {
    GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
    int result = goLibrary.AddNumbers(2, 3);
    System.out.printf("Result is %d%n", result);
}

输出:

Result is 5

6. 处理复杂数据类型

以字符串处理为例,Go端实现:

//export Greet
func Greet(name *C.char) *C.char {
    greeting := fmt.Sprintf("Hello, %s!", C.GoString(name))
    return C.CString(greeting)
}

Java端处理指针:

public static void main(String[] args) {
    GoLibrary goLibrary = Native.load("hello", GoLibrary.class);
    Pointer ptr = goLibrary.Greet("Alice");
    String greeting = ptr.getString(0);
    System.out.println(greeting);
    Native.free(Pointer.nativeValue(ptr)); // 关键:释放内存
}

输出:

Hello, Alice!

常见踩坑:忘记释放Go分配的内存会导致泄漏!

7. 结论

通过JNA实现Java调用Go函数的优势:

  • 零C代码:彻底避开JNI的C胶水代码
  • 跨平台:同一套代码支持多操作系统
  • 开发效率:简化集成流程,加速开发

关键注意事项:

  1. 确保Java/Go数据类型兼容性
  2. 严格管理内存避免泄漏
  3. 建立健壮的错误处理机制

这种混合开发模式既发挥了Go的高性能特性,又充分利用了Java的生态优势,是构建高性能系统的实用方案。

完整代码示例见GitHub仓库


原始标题:Invoke a GoLang Function from Java | Baeldung