一种基于JNA实现三方动态库本地方法调用的技术

233 阅读5分钟

前言

什么是JNA?有哪些特性?工作原理?应用场景?使用方法?


一、JNA是什么?

  1. 定义:JNA(Java Native Access)是一个开源的Java框架,建立在JNI(Java Native Interface,Java本地接口)技术之上。它提供了一组Java工具类,使得Java代码能够在运行时动态地访问系统本地库(Native Library),如Windows的动态链接库(.dll)和Linux的共享库(.so),而无需编写任何Native/JNI代码。
  2. 工作原理: JNA的工作原理可以简单理解为提供一个“桥梁”,使Java代码能够直接访问动态链接库中的函数。开发人员只需在Java接口中描述目标本地库的函数与结构,JNA就会自动实现Java接口到Native Library的映射。这样,调用本地方法就如同调用普通Java方法一样便捷
  3. 优点: 1)简化开发:使用JNA后,开发人员无需编写繁琐的JNI代码,只需在Java接口中描述目标Native Library的函数与结构即可。 2)跨平台:JNA支持多种操作系统,如Windows、Linux等,使得Java代码能够更方便地访问不同操作系统上的本地库。 3)动态加载:JNA允许在运行时动态加载本地库,这提高了程序的灵活性和可扩展性。
  4. 应用场景: 1)调用本地库函数:Java程序需要调用本地库中的函数时,可以使用JNA来简化调用过程。 2)跨语言通信:Java与其他编程语言(如C/C++)进行通信时,JNA可以作为一个方便的桥梁。 3)性能优化:某些情况下,使用本地库可以提高程序的性能。通过JNA,Java程序可以方便地调用这些高性能的本地库函数。

二、使用步骤

1.引入依赖

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

2.开发模板

三方本地方法库(.dll或.so)引入、实现、调用过程开发模板及规约(增强模板复用和开发规范) 1、设置三方本地方法库枚举类型,约定好各字段默认常量值。 2、根据定义的枚举属性创建文件夹,根据层级关系引入动态库文件。 3、创建三方库对应的接口,此接口必须继承Library。 4、创建三方业务handler并继承类库加载器,同时可以实现个性化定制方法或功能。

代码如下(示例):

package com.onefish.toolkit.natives.support;

/**
 * 三方本地方法库枚举类型
 *
 * @author onefish
 */
public enum CustomNativeEnum {
    ONE_FISH("一只鱼科技有限公司", "1", "one-fish", "biz");
    private String customName;
    private String customValue;
    private String customFolder;
    private String customLibName;

    CustomNativeEnum() {
    }

    CustomNativeEnum(String customName, String customValue, String customFolder, String customLibName) {
        this.customName = customName;
        this.customValue = customValue;
        this.customFolder = customFolder;
        this.customLibName = customLibName;
    }

    public String getCustomLibName() {
        return customLibName;
    }

    public void setCustomLibName(String customLibName) {
        this.customLibName = customLibName;
    }

    public String getCustomFolder() {
        return customFolder;
    }

    public void setCustomFolder(String customFolder) {
        this.customFolder = customFolder;
    }

    public String getCustomValue() {
        return customValue;
    }

    public void setCustomValue(String customValue) {
        this.customValue = customValue;
    }

    public String getCustomName() {
        return customName;
    }

    public void setCustomName(String customName) {
        this.customName = customName;
    }
}
package com.onefish.toolkit.natives.support;

import java.util.Objects;

/**
 * 三方本地方法库复合键(库路径 + 接口类)
 *
 * @author onefish
 */
public class LibraryKey {
    //  进行hash计算的字段不可变,否则会导致对象存入hash表后无法检索到
    private final String libPath;
    private final Class<?> interfaceClass;

    public LibraryKey(String libPath, Class<?> interfaceClass) {
        this.libPath = libPath;
        this.interfaceClass = interfaceClass;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        LibraryKey that = (LibraryKey) o;
        return Objects.equals(libPath,that.libPath) &&
                Objects.equals(interfaceClass,that.interfaceClass);
    }

    @Override
    public int hashCode() {
        //  自定义hash哈希码生成算法,考虑哈希冲突、哈希分布、性能高低。
        //  31 是奇质数,且 31 * i 可优化为 (i << 5) - i,提升计算速度。
        return 31 * libPath.hashCode() + interfaceClass.hashCode();
    }
}

package com.onefish.toolkit.natives.support;

import com.sun.jna.Library;
import com.sun.jna.Native;

import java.util.concurrent.ConcurrentHashMap;

public class LibraryPool {
    private static final ConcurrentHashMap<LibraryKey, Library> LIBS_POOL = new ConcurrentHashMap<>();

    /**
     * 加载或获取已缓存的 JNA 接口实例
     *
     * @param libPath        动态库路径(如 "/one-fish/linux-x64/libbiz.so")
     * @param interfaceClass JNA 接口类(需继承 Library)
     * @return 已加载的接口实例
     */
    @SuppressWarnings("unchecked")
    public static <T extends Library> T load(String libPath, Class<T> interfaceClass) {
        LibraryKey key = new LibraryKey(libPath, interfaceClass);
        return (T) LIBS_POOL.computeIfAbsent(key, k ->
                Native.load(libPath, interfaceClass)
        );
    }
}

package com.onefish.toolkit.natives.support;

import com.sun.jna.Library;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 三方本地方法动态库抽象泛型加载器
 *
 * @author onefish
 */
public abstract class AbstractNativeLibraryLoader<T> {
    private static final ConcurrentHashMap<String, String> ARCH = new ConcurrentHashMap<>();

    //  区分不同架构
    static {
        ARCH.put("x86_64", "x64");
        ARCH.put("x86", "x86");
        ARCH.put("amd64", "x64");
        ARCH.put("arm64", "aarch64");
        ARCH.put("aarch64", "aarch64");
        ARCH.put("mips64el", "mips64el");
        ARCH.put("sw_64", "sw64");
        ARCH.put("sw64", "sw64");
        ARCH.put("loongarch64", "loongarch64");
    }

    private String libPath;
    private CustomNativeEnum customType;

    public AbstractNativeLibraryLoader() {}

    /**
     * 获取 cpu架构
     *
     * @return 架构
     */
    public static String obtainCpuArch() {
        return ARCH.getOrDefault(System.getProperty("os.arch"), "unknown");
    }

    /**
     * 判断平台类型(不考虑 mac)
     *
     * @return true or false
     */
    public static boolean judgeIsLinux() {
        return System.getProperty("os.name").toLowerCase().contains("linux");
    }

    /**
     * 通用模板方法
     */
    public void generateTemplateStruct() {
        //  定义三方枚举类型
        this.customType = setTripartite();
        //  生成三方本地方法库路径
        this.libPath = generateLibPath(this.customType);
        //  自定义业务逻辑
        customBizLogic();

    }

    /**
     * 加载三方本地方法库
     *
     * @param libPath        库路径
     * @param interfaceClass 库接口类型
     * @return T 库接口实例
     */
    public <T extends Library> T load(String libPath, Class<T> interfaceClass) {
        return LibraryPool.load(libPath, interfaceClass);
    }

    protected abstract CustomNativeEnum setTripartite();

    protected void customBizLogic() {
    }

    private String generateLibPath(CustomNativeEnum customNativeEnum) {
        if (customNativeEnum == null) throw new NullPointerException("customNativeEnum is null");
        String arch = obtainCpuArch();
        String archFolder = generateArchFolder(arch);
        String archFile = generateArchFile(customNativeEnum.getCustomLibName(), arch);
        String customFolder = customNativeEnum.getCustomFolder();
        //  形式如:one-fish/linux-x64/libbiz.so
        return String.format("%s%s%s%s%s", customFolder, "/", archFolder, "/", archFile);
    }

    private String generateArchFolder(String arch) {
        return judgeIsLinux() ? "linux-" + arch : "windows-" + arch;
    }

    private String generateArchFile(String libPrefName, String arch) {
        return judgeIsLinux() ? "lib" + libPrefName + "_" + arch + ".so" : libPrefName + "_" + arch + ".dll";
    }

    public String getLibPath() {
        return libPath;
    }

    public void setLibPath(String libPath) {
        this.libPath = libPath;
    }

    public CustomNativeEnum getCustomType() {
        return customType;
    }

    public void setCustomType(CustomNativeEnum customType) {
        this.customType = customType;
    }
}

package com.onefish.toolkit.natives.custom;

import com.sun.jna.Library;

/**
 * 三方业务接口 (方法签名,返回值,形参列表)
 *
 * @author onefish
 */
public interface CustomLibrary extends Library {
    //  两数加和运算
    int add(int a, int b);
}

package com.onefish.toolkit.natives.custom;

import com.onefish.toolkit.natives.support.AbstractNativeLibraryLoader;
import com.onefish.toolkit.natives.support.CustomNativeEnum;
import com.sun.jna.Library;

/**
 * 三方业务处理器
 *
 * @author onefish
 */
public class CustomHandler extends AbstractNativeLibraryLoader<Library> {
    private static final CustomHandler INSTANCE = new CustomHandler();

    public CustomHandler() {
        //  拒绝反射方式创建实例,保证单例完整性
        if (INSTANCE != null) throw new IllegalStateException("CustomHandler already initialized");
        super.generateTemplateStruct();
    }

    public static CustomHandler getInstance() {
        return INSTANCE;
    }

    /**
     * 定制算法逻辑
     *
     * @param a 形参1
     * @param b 形参2
     * @return 5倍加和
     */
    public static int customAdd(int a, int b) {
        CustomHandler customHandler = getInstance();
        String libPath = customHandler.getLibPath();
        CustomLibrary customLibrary = customHandler.load(libPath, CustomLibrary.class);
        return 5 * customLibrary.add(a, b);
    }

    public CustomNativeEnum getNativeEnum() {
        return CustomNativeEnum.ONE_FISH;
    }

    @Override
    protected CustomNativeEnum setTripartite() {
        return getNativeEnum();
    }
}

总结

上面只是给出了简单的实现思路,对于Java跨语言调用还需要考虑性能和稳定性等,比如中间层jna如何降低内存占用,提高加载效率?若在执行本地方法库方法时出现段错误等严重错误时,上层Java应用如何容错处理保证自身稳定性不受影响?