1.安装编译环境
yum -y install gcc
yum -y install gcc-c++
yum install -y java-1.8.0-openjdk*
2.编译C动态库
准备三个文件:hello.h 、 hello.c 、 main.c
//hello.h
#ifndef _HELLO_H_
#define _HELLO_H_
void hello();
#endif /* _HELLO_H_ */
//hello.c
#include <stdio.h>
#include "hello.h"
void hello()
{
printf("这是动态链接库接口方法\n");
}
//main.c
#include <stdio.h>
#include "hello.h"
int main(void)
{
hello();
return 0;
}
2.1 编译生成so
gcc hello.c -fPIC -shared -o libnative.so
参数说明:
-fPIC 位置无关码
-shared 按照共享库的方式来链接
2.2 可执行程序链接so
gcc main.c -L. -lnative -o main
参数说明:
-L参数:指明要链接的so库所在路径(如-L. 表示当前路径, -L../so 表示当前路径的上一层目录的so子文件夹中)
-l参数:指明要连接的库的名字,如-lnative 表示要链接libnative.so库
2.3运行可执行程序
./main
注意:运行的时候会提示找不到链接库,需要配置系统链接库的位置
配置系统环境变量:
//当前窗口有效
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/root/native
//永久生效
vim /etc/profile
export LD_LIBRARY_PATH=/root/native
source /etc/profile
3.SpringBoot调用Jni动态库
- 准备源码文件:HelloJNI.java、HelloNative.h、HelloNative.c
//HelloJNI.java
public class HelloJNI {
//链接库的方法
public native static void setNum(int num);
public native static int get();
}
//HelloNative.h
#include <jni.h>
#ifndef _Included_HelloJNI
#define _Included_HelloJNI
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT void JNICALL Java_com_start_printer_HelloJNI_setNum
(JNIEnv *, jclass, jint);
JNIEXPORT jint JNICALL Java_com_start_printer_HelloJNI_get
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
//HelloNative.c
#include "HelloNative.h"
int result=888;
JNIEXPORT void JNICALL Java_com_start_printer_HelloJNI_setNum(JNIEnv * env, jclass jc, jint num)
{
result+=num;
}
JNIEXPORT jint JNICALL Java_com_start_printer_HelloJNI_get(JNIEnv * env, jclass jc)
{
return result;
}
- 配置jdk环境变量:
vim /etc/profile
JAVA_HOME=/usr/lib/jvm/java-1.8.0-openjdk-1.8.0.181-3.b13.el7_5.x86_64
PATH=$PATH:$JAVA_HOME/bin
CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar
export JAVA_HOME CLASSPATH PATH
3.1 编译生成jni的so库
gcc HelloNative.c -fPIC -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -shared -o libHelloNative.so
3.2 集成到springboot工程
- 拷贝HelloJNI.java文件到jni接口声明的包中
- 拷贝so文件到resouces/native文件夹下
- 动态加载so类:
public class NativeLoader {
/**
* 加载项目下的native文件,DLL或SO
*
* @param dirPath 需要扫描的文件路径,项目下的相对路径
* @throws IOException
* @throws ClassNotFoundException
*/
public synchronized static void loader(String dirPath) throws IOException, ClassNotFoundException {
Enumeration<URL> dir = Thread.currentThread().getContextClassLoader().getResources(dirPath);
// 获取操作系统类型
String systemType = System.getProperty("os.name");
//String systemArch = System.getProperty("os.arch");
// 获取动态链接库后缀名
String ext = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";
while (dir.hasMoreElements()) {
URL url = dir.nextElement();
String protocol = url.getProtocol();
if ("jar".equals(protocol)) {
JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
JarFile jarFile = jarURLConnection.getJarFile();
// 遍历Jar包
Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
JarEntry jarEntry = entries.nextElement();
String entityName = jarEntry.getName();
if (jarEntry.isDirectory() || !entityName.startsWith(dirPath)) {
continue;
}
if (entityName.endsWith(ext)) {
loadJarNative(jarEntry);
}
}
} else if ("file".equals(protocol)) {
File file = new File(url.getPath());
loadFileNative(file, ext);
}
}
}
private static void loadFileNative(File file, String ext) {
if (null == file) {
return;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
if (null != files) {
for (File f : files) {
loadFileNative(f, ext);
}
}
}
if (file.canRead() && file.getName().endsWith(ext)) {
try {
System.load(file.getPath());
System.out.println("加载native文件 :" + file + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + file + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
/**
* @throws IOException
* @throws ClassNotFoundException
* @Title: scanJ
* @Description 扫描Jar包下所有class
*/
/**
* 创建动态链接库缓存文件,然后加载资源文件
*
* @param jarEntry
* @throws IOException
* @throws ClassNotFoundException
*/
private static void loadJarNative(JarEntry jarEntry) throws IOException, ClassNotFoundException {
File path = new File(".");
//将所有动态链接库dll/so文件都放在一个临时文件夹下,然后进行加载
//这是应为项目为可执行jar文件的时候不能很方便的扫描里面文件
//此目录放置在与项目同目录下的natives文件夹下
String rootOutputPath = path.getAbsoluteFile().getParent() + File.separator;
String entityName = jarEntry.getName();
String fileName = entityName.substring(entityName.lastIndexOf("/") + 1);
System.out.println(entityName);
System.out.println(fileName);
File tempFile = new File(rootOutputPath + File.separator + entityName);
// 如果缓存文件路径不存在,则创建路径
if (!tempFile.getParentFile().exists()) {
tempFile.getParentFile().mkdirs();
}
// 如果缓存文件存在,则删除
if (tempFile.exists()) {
tempFile.delete();
}
InputStream in = null;
BufferedInputStream reader = null;
FileOutputStream writer = null;
try {
//读取文件形成输入流
in = NativeLoader.class.getResourceAsStream(entityName);
if (in == null) {
in = NativeLoader.class.getResourceAsStream("/" + entityName);
if (null == in) {
return;
}
}
NativeLoader.class.getResource(fileName);
reader = new BufferedInputStream(in);
writer = new FileOutputStream(tempFile);
byte[] buffer = new byte[1024];
while (reader.read(buffer) > 0) {
writer.write(buffer);
buffer = new byte[1024];
}
} catch (IOException e) {
e.printStackTrace();
}
try {
if (in != null) {
in.close();
}
if (writer != null) {
writer.close();
}
} catch (IOException e) {
e.printStackTrace();
}
try {
System.out.println("path :" + tempFile.getPath());
System.load(tempFile.getPath());
System.out.println("加载native文件 :" + tempFile + "成功!!");
} catch (UnsatisfiedLinkError e) {
System.out.println("加载native文件 :" + tempFile + "失败!!请确认操作系统是X86还是X64!!!");
}
}
}
- SpringBoot工程启动类新增初始化加载so的Bean(实现工程启动只加载一次so库)
@Bean
public void loadLib() {
//根据操作系统判断,如果是linux系统则加载c++方法库
String systemType = System.getProperty("os.name");
String ext = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so";
if (ext.equals(".so")) {
try {
NativeLoader.loader("native");
} catch (Exception e) {
System.out.println("加载so库失败");
}
}
System.out.println("loaded");
}
- 加载完成即可调用HelloJNI.java声明的native方法
3.3 注意事项
android studio下编译的jni库,在linux下springboot加载不成功。必须在linux重新编译一次动态库才能加载成功。