可执行Jar包调用动态链接库(DLL/SO)
踩过了很多的坑,查了很多资料,在此记录一下,以SpringBoot项目为基础。
Maven加入JNA依赖
<!-- JNA start --> <dependency> <groupId>com.sun.jna</groupId> <artifactId>jna</artifactId> </dependency> <!-- JNA end -->
动态链接库放在classpath下的natives文件夹下
主入口中的代码
@ServletComponentScan @SpringBootApplication @ComponentScan("com.yunzhitx.sdy") @MapperScan(basePackages = "com.yunzhitx.sdy.core.**.infra") public class TaskApplication { @Bean public RestTemplate restTemplate() { return new RestTemplate(); } public static void main(String[] args) { //加载动态链接库,注意和SpringBoot的启动 String systemType = System.getProperty("os.name"); if (systemType.toLowerCase().indexOf("win") != -1) loadNative("dhnetsdk"); else loadNative("libdhnetsdk"); SpringApplication.run(TaskApplication.class); } private synchronized static void loadNative(String nativeName) { String systemType = System.getProperty("os.name"); String fileExt = (systemType.toLowerCase().indexOf("win") != -1) ? ".dll" : ".so"; // String sysUserTempDir = System.getProperty("java.io.tmpdir"); /*String javaLibraryPath = System.getProperty("java.library.path"); String sysUserTempDir = "" ; if(systemType.toLowerCase().indexOf("win") != -1) { String[] dirs = javaLibraryPath.split(";");//分号 sysUserTempDir = dirs[0]; }else { String[] dirs = javaLibraryPath.split(":"); //冒号 sysUserTempDir = dirs[0]; }*/ File path = new File("."); //将所有动态链接库dll/so文件都放在一个临时文件夹下,然后进行加载 //这是应为项目为可执行jar文件的时候不能很方便的扫描里面文件 //此目录放置在与项目同目录下的natives文件夹下 String sysUserTempDir = path.getAbsoluteFile().getParent() + File.separator + "natives"; System.out.println("------>>native lib临时存放目录 : " + sysUserTempDir); String fileName = nativeName + fileExt; InputStream in = null; BufferedInputStream reader = null; FileOutputStream writer = null; File tempFile = new File(sysUserTempDir + File.separator + fileName); if(!tempFile.getParentFile().exists()) tempFile.getParentFile().mkdirs() ; if (tempFile.exists()) { tempFile.delete(); } try { //读取文件形成输入流 in = TaskApplication.class.getResourceAsStream("/native/" + fileName); if (in == null) in = TaskApplication.class.getResourceAsStream("native/" + fileName); TaskApplication.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(); } finally { try { if (in != null) in.close(); if (writer != null) writer.close(); } catch (IOException e) { e.printStackTrace(); } } System.load(tempFile.getPath()); System.out.println("------>> 加载native文件 :" + tempFile + "成功!!"); } }
主入口函数的代码主要是进行加载操作,当然也可以在需要使用到的地方在进行加载。
加载的时候进行了如下操作,1、将所有动态链接库dll/so文件都放在一个临时文件夹下。2、读取临时文件IO流进行加载。
这是应为项目为可执行jar文件的时候不能很方便的扫描里面文件
此目录代码放置在与项目同目录下的natives文件夹下。
在通过编写好的接口类进行实例化操作:
NetSDKLib monitorNetSdk = (NetSDKLib) Native.loadLibrary("dhnetsdk", NetSDKLib.class);
Linux系统下,需要将放置dll/so文件的文件夹加入到环境变量中,否则依然会提示找不到文件的错误:
export LD_LIBRARY_PATH=/home/sdy_task/natives
但是这种方式是临时性的,一旦重启就将失效,所有建议写入配置文件中:
修改/etc/profile,添加如下代码
LD_LIBRARY_PATH=/home/sdy_task/natives
export LD_LIBRARY_PATH
升级版
package org.yoki.edu.image.utils; import java.io.*; import java.net.JarURLConnection; import java.net.URL; import java.util.Enumeration; import java.util.jar.JarEntry; import java.util.jar.JarFile; /** * @author Sky$ * @Description: TODO * @date 2018/2/8$ 17:55$ */ 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.load(tempFile.getPath()); System.out.println("加载native文件 :" + tempFile + "成功!!"); } catch (UnsatisfiedLinkError e) { System.out.println("加载native文件 :" + tempFile + "失败!!请确认操作系统是X86还是X64!!!"); } } }