首先创建文件夹
#创建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
springboot工程的创建这里省略
将NativeLib.java与libNativeLib.so文件复制到springboot工程中的相应目录下,参考下图
将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
结果如下:
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