Java web 項目中使用JNI技術(如何在程序運行期間改變 java.library.path并生效)

記錄結構:
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設置為空前普?

屏幕快照 2016-11-21 下午2.15.02.png

通過上述情景描述,我們要更改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結果如下圖所示:


屏幕快照 2016-11-21 下午2.56.22.png

屏幕快照 2016-11-21 下午2.57.15.png

上圖紅色注釋為java.library.path注釋有誤特此說明贱纠。
最終程序的正常運行峻厚。

在程序中實現了程序運行時動態(tài)更改java.library.path并生效的效果。
我在web項目中的應用是這樣的:
程序封裝谆焊,對JNI的使用封裝成jniutil工具類:

屏幕快照 2016-11-21 下午3.16.18.png

代碼如下:
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)代碼塊中。
記錄完畢剃执。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末誓禁,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子肾档,更是在濱河造成了極大的恐慌摹恰,老刑警劉巖辫继,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現場離奇詭異俗慈,居然都是意外死亡姑宽,警方通過查閱死者的電腦和手機,發(fā)現死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進店門闺阱,熙熙樓的掌柜王于貴愁眉苦臉地迎上來炮车,“玉大人,你說我怎么就攤上這事酣溃∈菽拢” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵赊豌,是天一觀的道長扛或。 經常有香客問我,道長碘饼,這世上最難降的妖魔是什么熙兔? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮艾恼,結果婚禮上住涉,老公的妹妹穿的比我還像新娘。我一直安慰自己钠绍,他們只是感情好舆声,可當我...
    茶點故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著五慈,像睡著了一般纳寂。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上泻拦,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天,我揣著相機與錄音忽媒,去河邊找鬼争拐。 笑死,一個胖子當著我的面吹牛晦雨,可吹牛的內容都是我干的架曹。 我是一名探鬼主播,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼闹瞧,長吁一口氣:“原來是場噩夢啊……” “哼绑雄!你這毒婦竟也來了?” 一聲冷哼從身側響起奥邮,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤万牺,失蹤者是張志新(化名)和其女友劉穎罗珍,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體脚粟,經...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡覆旱,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了核无。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片扣唱。...
    茶點故事閱讀 38,697評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖团南,靈堂內的尸體忽然破棺而出噪沙,到底是詐尸還是另有隱情,我是刑警寧澤吐根,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布曲聂,位于F島的核電站,受9級特大地震影響佑惠,放射性物質發(fā)生泄漏朋腋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一膜楷、第九天 我趴在偏房一處隱蔽的房頂上張望旭咽。 院中可真熱鬧,春花似錦赌厅、人聲如沸穷绵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽仲墨。三九已至,卻和暖如春揍障,著一層夾襖步出監(jiān)牢的瞬間目养,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工毒嫡, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留癌蚁,地道東北人。 一個月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓兜畸,卻偏偏與公主長得像努释,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子咬摇,可洞房花燭夜當晚...
    茶點故事閱讀 43,587評論 2 350

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,813評論 25 707
  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理伐蒂,服務發(fā)現,斷路器肛鹏,智...
    卡卡羅2017閱讀 134,633評論 18 139
  • 2017-8-11學經匯報: 一、學經日期:2017年8月11日 農歷潤六月二十 多云轉雨 星期五 寶貝...
    b0a4ca4b06a4閱讀 922評論 0 0
  • 文/漿橋 閨蜜群炸開了,M要結婚了烛卧,還是讀研期間佛纫。一群死黨在下面排隊形:呀,別总放!沒有份子錢呈宇。 小綺卻冷不丁的冒出一...
    漿橋閱讀 366評論 2 5