JNI 在 spring boot中的应用

编译jni动态库

首先创建文件夹

#创建java层文件夹
mkdir -p java/com/gavinandre/jnispringboot/jni
#创建native层文件夹
mkdir native

创建java文件

echo "package com.gavinandre.jnispringboot.jni;

public class NativeLib {
    static {
        System.loadLibrary(\"NativeLib\");
    }

    public static void main(String[] args) {
        String uuid = generateUUID();
        System.out.println(\"generateUUID: \" + uuid);
    }

    public static native String generateUUID();
}" > java/com/gavinandre/jnispringboot/jni/NativeLib.java

生成class文件和jni头文件,并把头文件移动到native文件夹内

cd java
javac com/gavinandre/jnispringboot/jni/NativeLib.java
javah com.gavinandre.jnispringboot.jni.NativeLib
mv com_gavinandre_jnispringboot_jni_NativeLib.h ../native/NativeLib.h

创建NativeLib.cpp文件

echo "#include \"NativeLib.h\"
#include \"jni_lib.hpp\"
#include \"uuid.hpp\"

JNIEXPORT jstring JNICALL Java_com_gavinandre_jnispringboot_jni_NativeLib_generateUUID
  (JNIEnv *env, jclass type) {
    std::string uuid = generate_hex(16);
    return string_to_jstring(env, uuid);
}" > ../native/NativeLib.cpp

创建jni_lib.hpp文件

echo "#include 
#include 

jstring string_to_jstring(JNIEnv *env, std::string str) {
    return env->NewStringUTF(str.c_str());
}

std::string jstring_to_string(JNIEnv *env, jstring j_str) {
    const char *c = env->GetStringUTFChars(j_str, 0);
    std::string c_str = std::string(c);
    env->ReleaseStringUTFChars(j_str, c);
    return c_str;
}" > ../native/jni_lib.hpp

创建uuid.hpp文件

echo "#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

unsigned char random_char() {
    std::random_device rd;
    std::mt19937 gen(rd());
    std::uniform_int_distribution<> dis(0, 255);
    return static_cast(dis(gen));
}

std::string generate_hex(const unsigned int len) {
    std::stringstream ss;
    for(auto i = 0; i < len; i++) {
        auto rc = random_char();
        std::stringstream hexstream;
        hexstream << std::hex << int(rc);
        auto hex = hexstream.str();
        ss << (hex.length() < 2 ? '0' + hex : hex);
    }
    return ss.str();
}" > ../native/uuid.hpp

最后创建CMakeLists.txt文件

echo "cmake_minimum_required(VERSION 2.8)

#c++11支持
add_definitions(-std=c++11)

#设置java路径
set(java_home \"/usr/lib/jvm/default\")

#jni头文件
include_directories(${jdk_home}/include/
                    ${jdk_home}/include/linux/)

add_library(NativeLib SHARED
            NativeLib.cpp)

target_link_libraries(NativeLib)" > ../native/CMakeLists.txt

注意:这里${jdk_home}需要替换为你自己的jdk安装路径

然后执行编译命令

#进入native文件夹
cd ../native
#创建build文件夹并进入
mkdir build && cd build
#生成预编译文件
cmake -DCMAKE_BUILD_TYPE=Release ..
#编译
cmake --build .

编译成功的话,build目录下会生成libNativeLib.so文件,将该文件复制到java目录内

cp libNativeLib.so ../../java

测试libNativeLib.so动态库

cd ../../java
#运行class文件
java -Djava.library.path=. com.gavinandre.jnispringboot.jni.NativeLib

可以看到命令行打印了类似下面的字符串

generateUUID: e2e42b0089c84f5fb4915e2e8b5a6d65

spring boot调用jni动态库

springboot工程的创建这里省略

将NativeLib.java与libNativeLib.so文件复制到springboot工程中的相应目录下,参考下图

JNI 在 spring boot中的应用_第1张图片

将NativeLib.java内的System.loadLibrary("NativeLib");替换为如下语句

URL url = NativeLib.class.getClassLoader().getResource("jniLibs/libNativeLib.so");
System.load(url.getPath());

修改springboot的Application文件中的代码

@RestController
@SpringBootApplication
public class JniSpringBootApplication {

    public static void main(String[] args) {
        SpringApplication.run(JniSpringBootApplication.class, args);
    }

    @RequestMapping("/")
    public String greeting() {
        String uuid = NativeLib.generateUUID();
        return "generateUUID: " + uuid;
    }

}

最后运行springboot

结果如下:

JNI 在 spring boot中的应用_第2张图片

4.30更新

上面的方法在idea中或tomcat中运行工程是没有问题的,但是如果打包成jar包运行的话,会找不到动态库,原因是jar包运行时resources文件不会存放在本地路径中,因此需要换一种方式来读取动态库

public class LibLoader {
    public static void loadLib(String libName) {
        String resourcePath = "/" + libName;
        String folderName = System.getProperty("java.io.tmpdir") + "/lib/";
        File folder = new File(folderName);
        folder.mkdirs();
        File libFile = new File(folder, libName);
        if (libFile.exists()) {
            System.load(libFile.getAbsolutePath());
        } else {
            try {
                InputStream in = LibLoader.class.getResourceAsStream(resourcePath);
                FileUtils.copyInputStreamToFile(in, libFile);
                in.close();
                System.load(libFile.getAbsolutePath());
            } catch (Exception e) {
                e.printStackTrace();
                throw new RuntimeException("Failed to load required lib", e);
            }
        }
    }
}

使用方式:

// URL url = NativeLib.class.getClassLoader().getResource("jniLibs/libNativeLib.so");
// System.load(url.getPath());
LibLoader.loadLib("jniLibs/libNativeLib.so");

大致原理就是使用JavaClass的getResourceAsStream函数获取动态库文件的流,然后将这个流保存在本地路径中,最后使用System.load从本地路径读取动态库

注意点

springboot的工程结构是src->main->java->com->gavinandre->jnispringboot,因此我们如果把NativeLib.java文件放在jni文件夹下那它的package就是com.gavinandre.jnispringboot.jni,因此jni调用generateUUID时就会去查找名为Java_com_gavinandre_jnispringboot_jni_NativeLib_generateUUID的jni函数

所以我们生成libNativeLib.so时就应该使用这样的路径来生成头文件,因此我们一开始使用mkdir -p java/com/gavinandre/jnispringboot/jni命令创建了这个路径,这也是为什么NativeLib.java一开始需要放在这样一个路径下的原因

demo: https://github.com/GavinAndre/JNISpringBoot

参考

https://www.jianshu.com/p/f3d4359f96ff

你可能感兴趣的:(JavaWeb,JNI/NDK,springboot,spring,boot,jni)