Java web 项目中使用JNI技术

桂林seo半杯酒博客

Java web项目中使用JNI技术(如何在运行时改变java.library.path并生效)

记录结构:JNI技术入门详解,注意:手记与接下来要记录的web项目中使用JNI技术是无缝连接的。应用场景:当我们根据不同的平台生成不同的JNI libaray时,例如:linux .so,mac jnilib,windows .dll。我们想在打包web应用程序时让程序动态调用c,或者c ++对Java Native Inteface的具体底层实现时,有一种方法是借助配置在idea中的vm选项中设置库文件所在的路径,即-Djava.path.library,刚哥手记最后一部分有说明。精准定位问题:

1.那么有没有另外一种方式使得Java的程序在调用本地inteface中抽象本地方法自动加载所需要的代码呢?也就是说应用程序自动加载的.so || (或) .jnilib || .dll ?. 2.我们知道Java应用程序在调用底层代码生成的库文件时,需要指定库文件所在的路径。那么我们的问题就清楚了,问题的痛点在于如何让应用程序在程序运行期间动态加载库文件所在的路径,进而加载所需的库文件。网上的一种说法是:在使用的System.loadLibrary( “具体库文件所在的路径的相对路径”),之前使用System.load(“具体库文件所在的根目录的全路径“),本人试了一下,发现并不起作用。

java.library.path 系统属性指示JVM在哪里搜索本地库。您必须使用-Djava.library.path = / path / to / lib将它指定为JVM参数 ,然后当您尝试使用System.loadLibrary(“foo”)加载库时,JVM将搜索库路径指定的库。如果找不到,你会得到一个异常,如下所示:

大致的意思是:

系统属性 - java.library.path指引JVM去寻找底层的库文件,你必须为JVM声明一个属性,类似于Djava.library.path = / path / to / lib,当你需要使用System.loadLibrary(“foo “)加载底层foo的库文件的时候,JVM会按照你声明的路径去加载这个库文件,如果你不声明的话,会出现下面错误:

线程“main”中的异常java.lang.UnsatisfiedLinkError:java.library.path中没有foo 在java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734) 在java.lang.Runtime.loadLibrary0(Runtime.java:823) 在java.lang.System.loadLibrary(System.java:1028)

。这个错告诉我们富并不库在我们所要加载的路径下面接下来说明原因:

当JVM启动时,java.library.path 只读一次。如果使用System.setProperty更改此属性,则不会有任何区别。

意思是:java.library.path只会在JVM启动的时候被都到,如果你直接使用System.setProperty(“java.path.libarary”,“库所在路径”)这样是不起作用的,因为JVM已经启动了所以这个JVM后期不能找到这个库文件所在的路径,所以就报如上错误。源码中ClassLoader.loadLibrary有这样一句代码:

if(sys_paths == null){ usr_paths = initializePath(“java.library.path”); sys_paths = initializePath(“sun.boot.library.path”);}

为什么就定位问题到上述几行代码,我们得从源码的角度来分析,看下源码:首先是System.loadLibaray(),借助想法看下源码:

/ ** *加载由<code> libname </ code>指定的本地库 *参数。<code> libname </ code>参数不能包含任何平台 *特定的前缀,文件扩展名或路径。如果一个本地库 *称为<code> libname </ code>是静态链接到虚拟机,然后是 调用库导出的JNI__ <code> libname </ code>函数。 *有关更多详细信息,请参阅JNI规范。 * *否则,从系统库加载libname参数 *位置并映射到实现中的本地库图像 - *依赖的方式。 * <p> *调用<code> System.loadLibrary(name)</ code>是有效的 *相当于通话 * <blockquote> <pre> * Runtime.getRuntime()。loadLibrary(name) * </ pre> </ blockquote> * * @param libname库的名称。 * @exception SecurityException如果存在一个安全管理器及其 * <code> checkLink </ code>方法不允许 *加载指定的动态库 * @exception UnsatisfiedLinkError如果是libname参数 *包含文件路径,本地库不是静态的 *与VM链接,或者库无法映射到一个 *主机系统的本地库图像。 * @exception NullPointerException如果<code> libname </ code>是 * <code> null </ code> * @see java.lang.Runtime#loadLibrary(java.lang.String) * @see java.lang.SecurityManager#checkLink(java.lang.String) * / @CallerSensitive public static void loadLibrary(String libname){ Runtime.getRuntime()。loadLibrary0(Reflection.getCallerClass(),libname); }

可以 看到方法调用中出现Runtime.getRuntime()。loadLibrary0(),从这行代码我们知道库文件是在运行时被加载起来用的。我们继续看loadLibrary0()

/ ** *加载由<code> libname </ code>指定的本地库 *参数。<code> libname </ code>参数不能包含任何平台 *特定的前缀,文件扩展名或路径。如果一个本地库 *称为<code> libname </ code>是静态链接到虚拟机,然后是 调用库导出的JNI__ <code> libname </ code>函数。 *有关更多详细信息,请参阅JNI规范。 * *否则,从系统库加载libname参数 *位置并映射到实现中的本地库图像 - *依赖的方式。 * <p> *首先,如果有一个安全管理器,它的<code> checkLink </ code> *以<code> libname </ code>作为参数调用方法。 *这可能会导致安全异常。 * <p> *方法{@系统#loadLibrary(字符串)}是传统的 *和调用此方法的便捷方法。如果是本机 *方法被用于执行一个类,一个标准 *策略是把本地代码放在一个库文件中(称之为 * <code> LibFile </ code>),然后放入一个静态初始化程序: * <blockquote> <pre> * static {System.loadLibrary(“LibFile”); } * </ pre> </ blockquote> *在类声明中。当这个类被加载和 *初始化,本地必需的本地代码实现 *方法将被加载。 * <p> *如果这个方法被同一个库调用多次 *名称,第二个和后续的调用被忽略。 * * @param libname库的名称。 * @exception SecurityException如果存在一个安全管理器及其 * <code> checkLink </ code>方法不允许 *加载指定的动态库 * @exception UnsatisfiedLinkError如果是libname参数 *包含文件路径,本地库不是静态的 *与VM链接,或者库无法映射到一个 *主机系统的本地库图像。 * @exception NullPointerException如果<code> libname </ code>是 * <code> null </ code> * @查看java.lang.SecurityException * @see java.lang.SecurityManager#checkLink(java.lang.String) * / @CallerSensitive public void loadLibrary(String libname){ loadLibrary0(Reflection.getCallerClass(),libname); } synchronized void loadLibrary0(Class <?> fromClass,String libname){ SecurityManager安全性= System.getSecurityManager(); if(security!= null){ security.checkLink(LIBNAME); } if(libname.indexOf((int)File.separatorChar)!= -1){ 抛出新的UnsatisfiedLinkError( “目录分隔符不应出现在库名称中:”+ libname); } ClassLoader.loadLibrary(fromClass,libname,false); }

题外话:loadLibrary(),loadLibrary0()这两个方法的命名还是挺不符合规范的,历史遗留问题吧。在loadLibrary中我们看到了ClassLoader.loadLibrary(fromClass,libname,false) ;方法继续追溯

对于上述代码的解释我们可以从这篇博客中获取到答案:

如果将sys_paths设置为null,则当您尝试加载库时,库路径将被重新初始化。

意思是说,如果我们通过代码将sys_paths,设置为null,那么java.library.path将被重新加载一次。那么问题来了,通过刚才的源代码追溯,我们知道System.loadLibray()调用ClassLoader.loadLibrary ()方法,我们应该如何将sys_paths设置为空?通过上述情景描述,我们要更改sys_paths的值为空,只能在sys_paths初始化之前做手脚(反射在程序动态运行期间更改程序中的属性值)。代码如下:

追溯上述代码,调试结果如下图所示:

上图红色注释为java.library.path,注释有误特此说明。最终程序的正常运行。

在程序中实现了程序运行时动态更改的java.library.path并生效的效果。我在网络项目中的应用是这样的:程序封装,对JNI的使用封装成jniutil工具类: