記錄結構:
JNI技術入門詳解夸溶,參照剛哥的手記:http://www.reibang.com/p/fe42aa3150a0
注意:剛哥手記與接下來要記錄的web項目中使用JNI技術是無縫連接的。
應用場景:當我們根據不同的平臺生成不同的JNI libaray時,例如:linux .so浪腐、mac jnilib强霎、windows .dll。我們想在打包web 應用時讓程序動態(tài)調用c,或者c++對Java Native Inteface 的具體底層實現時熊经,有一種方法是借助配置在idea中的vm option中設置庫文件所在的路徑覆劈,即-Djava.path.library缘厢,剛哥手記最后一部分有說明吃度。
精準定位問題:
1.那么有沒有另外一種方式使得Java 程序在調用native inteface 中抽象本地方法自動加載所需要的代碼呢?也就是說應用程序自動加載.so || (或).jnilib** || **.dll?贴硫。
2.我們知道Java 應用程序在調用底層代碼生成的庫文件時椿每,需要指定庫文件所在的path。那么我們的問題就清晰了英遭,問題的痛點在于如何讓應用程序在程序運行期間動態(tài)加載庫文件所在的路徑间护,進而加載所需的庫文件。
網上的一種說法是:在使用System.loadLibrary("具體庫文件所在的路徑的相對路徑")挖诸,之前使用System.load("具體庫文件所在的根目錄的全路徑")汁尺,本人試了一下,發(fā)現并不起作用多律。
繼續(xù)找解決方案痴突,無意中發(fā)現了一篇博客,博客地址是:http://ju.outofmemory.cn/entry/150717
這篇文章講述的是如何在運行時改變 java.library.path并生效狼荞。
我想這正是我要的答案辽装,無奈是英文的,還是硬著頭皮看吧
首先開篇很簡明扼要說明問題:
The java.library.path
system property instructs the JVM where to search for native libraries. You have to specify it as a JVM argument using -Djava.library.path=/path/to/lib
and then when you try to load a library using System.loadLibrary("foo")
, the JVM will search the library path for the specified library. If it cannot be found you will get an exception which looks like:
大致的意思是:
系統屬性- java.library.path指引JVM去尋找底層的庫文件相味,你必須為JVM聲明一個屬性拾积,類似于Djava.library.path=/path/to/lib ,當你需要使用System.loadLibrary("foo")加載底層foo庫文件的時候,jvm會按照你聲明的path去加載這個庫文件,如果你不聲明的話拓巧,會出現下面錯誤:
Exception in thread "main" java.lang.UnsatisfiedLinkError: no foo in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1734)
at java.lang.Runtime.loadLibrary0(Runtime.java:823)
at java.lang.System.loadLibrary(System.java:1028)
這個錯告訴我們foo庫并不在我們所要加載的路徑下面斯碌。
接下來說明原因:
The java.library.path
is read only once when the JVM starts up. If you change this property usingSystem.setProperty
, it won’t make any difference.
意思是: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()瘩绒,借助idea看下源碼:
/**
* Loads the native library specified by the <code>libname</code>
* argument. The <code>libname</code> argument must not contain any platform
* specific prefix, file extension or path. If a native library
* called <code>libname</code> is statically linked with the VM, then the
* JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
* See the JNI Specification for more details.
*
* Otherwise, the libname argument is loaded from a system library
* location and mapped to a native library image in an implementation-
* dependent manner.
* <p>
* The call <code>System.loadLibrary(name)</code> is effectively
* equivalent to the call
* <blockquote><pre>
* Runtime.getRuntime().loadLibrary(name)
* </pre></blockquote>
*
* @param libname the name of the library.
* @exception SecurityException if a security manager exists and its
* <code>checkLink</code> method doesn't allow
* loading of the specified dynamic library
* @exception UnsatisfiedLinkError if either the libname argument
* contains a file path, the native library is not statically
* linked with the VM, or the library cannot be mapped to a
* native library image by the host system.
* @exception NullPointerException if <code>libname</code> is
* <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(), 從這行代碼我們知道庫文件是在運行時被加載起作用的猴抹。
我們繼續(xù)看loadLibrary0()
/**
* Loads the native library specified by the <code>libname</code>
* argument. The <code>libname</code> argument must not contain any platform
* specific prefix, file extension or path. If a native library
* called <code>libname</code> is statically linked with the VM, then the
* JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
* See the JNI Specification for more details.
*
* Otherwise, the libname argument is loaded from a system library
* location and mapped to a native library image in an implementation-
* dependent manner.
* <p>
* First, if there is a security manager, its <code>checkLink</code>
* method is called with the <code>libname</code> as its argument.
* This may result in a security exception.
* <p>
* The method {@link System#loadLibrary(String)} is the conventional
* and convenient means of invoking this method. If native
* methods are to be used in the implementation of a class, a standard
* strategy is to put the native code in a library file (call it
* <code>LibFile</code>) and then to put a static initializer:
* <blockquote><pre>
* static { System.loadLibrary("LibFile"); }
* </pre></blockquote>
* within the class declaration. When the class is loaded and
* initialized, the necessary native code implementation for the native
* methods will then be loaded as well.
* <p>
* If this method is called more than once with the same library
* name, the second and subsequent calls are ignored.
*
* @param libname the name of the library.
* @exception SecurityException if a security manager exists and its
* <code>checkLink</code> method doesn't allow
* loading of the specified dynamic library
* @exception UnsatisfiedLinkError if either the libname argument
* contains a file path, the native library is not statically
* linked with the VM, or the library cannot be mapped to a
* native library image by the host system.
* @exception NullPointerException if <code>libname</code> is
* <code>null</code>
* @see 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 security = System.getSecurityManager();
if (security != null) {
security.checkLink(libname);
}
if (libname.indexOf((int)File.separatorChar) != -1) {
throw new UnsatisfiedLinkError(
"Directory separator should not appear in library name: " + libname);
}
ClassLoader.loadLibrary(fromClass, libname, false);
}
題外話:loadLibrary(),loadLibrary0()這兩個方法的命名還是挺不符合規(guī)范的,歷史遺留問題吧锁荔。
在loadLibrary中我們看到了ClassLoader.loadLibrary(fromClass, libname, false);方法
繼續(xù)追溯
// Invoked in the java.lang.Runtime class to implement load and loadLibrary.
static void loadLibrary(Class<?> fromClass, String name,
boolean isAbsolute) {
ClassLoader loader =
(fromClass == null) ? null : fromClass.getClassLoader();
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
if (isAbsolute) {
if (loadLibrary0(fromClass, new File(name))) {
return;
}
throw new UnsatisfiedLinkError("Can't load library: " + name);
}
if (loader != null) {
String libfilename = loader.findLibrary(name);
if (libfilename != null) {
File libfile = new File(libfilename);
if (!libfile.isAbsolute()) {
throw new UnsatisfiedLinkError(
"ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
}
if (loadLibrary0(fromClass, libfile)) {
return;
}
throw new UnsatisfiedLinkError("Can't load " + libfilename);
}
}
for (int i = 0 ; i < sys_paths.length ; i++) {
File libfile = new File(sys_paths[i], System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
if (loader != null) {
for (int i = 0 ; i < usr_paths.length ; i++) {
File libfile = new File(usr_paths[i],
System.mapLibraryName(name));
if (loadLibrary0(fromClass, libfile)) {
return;
}
libfile = ClassLoaderHelper.mapAlternativeName(libfile);
if (libfile != null && loadLibrary0(fromClass, libfile)) {
return;
}
}
}
// Oops, it failed
throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
}
這其中有段代碼很重要:
if (sys_paths == null) {
usr_paths = initializePath("java.library.path");
sys_paths = initializePath("sun.boot.library.path");
}
對于上述代碼的解釋我們可以從這篇博客中獲取到答案:
if you set sys_paths to null, the library path will be re-initialised when you try to load a library.
意思是說蟀给,如果我們通過代碼將sys_paths,設置為null,那么java.library.path將被重新加載一次阳堕。
那么問題來了跋理,通過剛才的源代碼追溯,我們知道System.loadLibray()調用ClassLoader.loadLibrary()方法恬总,
我們應該如何將sys_paths設置為空前普?
通過上述情景描述,我們要更改sys_paths的值為null,只能在sys_paths初始化之前做手腳(反射在程序動態(tài)運行期間更改程序中的屬性值)壹堰。
代碼如下:
/**
* Sets the java library path to the specified path
*
* @param path the new library path
* @throws Exception
*/
public static void setLibraryPath(String path) throws Exception {
System.setProperty("java.library.path", path);
//set sys_paths to null
final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
sysPathsField.setAccessible(true);
sysPathsField.set(null, null);
}
追溯上述代碼拭卿,debug結果如下圖所示:
上圖紅色注釋為java.library.path,注釋有誤特此說明贱纠。
最終程序的正常運行峻厚。
在程序中實現了程序運行時動態(tài)更改java.library.path并生效的效果。
我在web項目中的應用是這樣的:
程序封裝谆焊,對JNI的使用封裝成jniutil工具類:
代碼如下:
GetDownloadID.java 聲明本地方法惠桃,依賴底層實現。
package com.fxmms.common.jniutil;
public class GetDownloadID{
public native String getDownloadID(String mac);
}
GetDownloadIDUtil.java辖试,工具類辜王,調用上述GetDownloadID類的實例方法getDownloadID()
package com.fxmms.common.jniutil;
import org.apache.http.util.Asserts;
import java.lang.reflect.Field;
/**
* @usage JNI調用底層c算法將mac地址轉化為downloadid
*/
public class GetDownloadIDUtil {
static{
try{
setLibraryPath("/Users/mark/mms/src/main/java/com/fxmms/common/jniutil");
System.loadLibrary("GetDownloadID");
}catch(Exception e){
System.err.println("Native code library failed to load.\n" + e);
System.exit(1);
}
}
public static String getDownLoadId(String mac){
GetDownloadID test = new GetDownloadID();
String downLoadId = test.getDownloadID(mac);
return downLoadId;
}
/**
* Sets the java library path to the specified path
* @usage 動態(tài)更改sys_paths,使得usr_paths 重新初始化
* @param path the new library path
* @throws Exception
*/
public static void setLibraryPath(String path) throws Exception {
System.setProperty("java.library.path", path);
//set sys_paths to null
final Field sysPathsField = ClassLoader.class.getDeclaredField("sys_paths");
sysPathsField.setAccessible(true);
sysPathsField.set(null, null);
}
public static void main(String[] args){
//-Djava.library.path="/Users/mark/mms/src/main/java/com/fxmms/common/jniutil"
///Users/mark/mms/src/main/java/com/fxmms/common/jniutil
System.out.println(System.getProperty("java.library.path"));
String mac = "CC:81:DA:86:42:E7";
Asserts.check(mac!=null,"mac null");
GetDownloadID test = new GetDownloadID();
System.out.println(test.getDownloadID(mac));
}
}
注意:對庫文件的加載放置在靜態(tài)代碼塊中。
記錄完畢剃执。