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重新编译一次动态库才能加载成功。