Java JNI示例及Wildfly下热更新war导致UnstatisfiedLinkError的问题

JNI示例

  1. 首先定义一个带有native关键字的类:

    package com.self.test;
    
    public class HelloNative {
        public native void helloWorld();
        
    //    public static void main(String[] args) {
    //        System.loadLibrary("libHello");
    //        HelloNative helloNative = new HelloNative();
    //        helloNative.helloWorld();
    //    }
    }
    
  2. 然后将这个类编译成为.class文件:

    javac编译文件

    将会在com\self\test\目录下生成HelloNative.class文件

  3. 然后在通过javah命令生成C\C++需要的头文件:

    javah生成头文件

    头文件将会在src目录下,根据package和类名命名,如本例子生成的com_self_test_HelloNative.h

  4. 本例中使用Window平台,所以使用Visual Studio生成dll,如果使用Linux,则类似的生产so就可以了。

    新建一个Visual C++项目:

    新建VC++项目1
    新建VC++项目2

    得到项目:

    VC++项目结构

    其中, jni.h是在{JAVA_HOME}\include\中,jni_md.h{JAVA_HOME}\include\win32\中,编辑source.cpp

    #include <iostream>
    #include "com_self_test_HelloNative.h"
    using namespace std;
    
    // source.cpp
    JNIEXPORT void JNICALL Java_com_self_test_HelloNative_helloWorld(JNIEnv *, jobject)
    {
     cout << "Hello World" << endl;
    }
    

    然后编译成dll,注意:64位的JDK需要生成64位的dll,32位的JDK需要生成32位的dll

    将生成的dll修改名为libHello.dll并放到java.library.path其中一个目录下(在Window中可以修改环境变量PATH

  5. 确保java.library.path生效后,可以在eclipse中执行如下代码:

    package com.self.test;
    
    public class HelloNative {
        public native void helloWorld();
        
        public static void main(String[] args) {
            System.loadLibrary("libHello");
            HelloNative helloNative = new HelloNative();
            helloNative.helloWorld();
        }
    }
    

    如无意外则会正确输出“Hello world”

Wildfly下热更新war导致UnstatisfiedLinkError

现象

但是,如果将JNI调用,简单的直接放到Wildfly、Tomcat等Web容器中,第一次部署是没有问题的,但是当不重启Web容器,直接热更新war包,则会出现UnstatisfiedLinkError错误。

package com.self.test;

// 类
public class HelloNative {
    
    private static boolean neverLoaded = true;
    
    public static void LoadLibrary() {
        if(neverLoaded) {
            System.loadLibrary("libHello");
            neverLoaded = false;
            
            
        }
    }
    
    
    public native void helloWorld();
    
}
// 调用方法
HelloNative.LoadLibrary();
HelloNative helloNative = new HelloNative();
helloNative.helloWorld()

例子:

第一次成功:

jni第一次调用成功

当热更新替换到新的war包后:

Jni第二次调用失败

原因是,Web容器会使用自定义的ClassLoader来加载war包,当替换到新的war包后,``ClassLoader`已经不同了。

或者可能会觉得,既然JVM没重启,使用System.setProperty()设置一个标记,判断是否已经加载Library

package com.self.test;

public class HelloNative {
    public static void LoadLibrary() {
        
        String value = System.getProperty("loadedLib");
        System.out.println(value);
        if(value == null) {
            System.loadLibrary("libHello");
            System.setProperty("loadedLib", "true");
        }
    }
    
    public native void helloWorld();
    
}

第一次,loadedLibnull的:

使用property做标记第一次成功

第二次,loadedLib已经能读取出来,但由于不在同一个ClassLoader中,依然调用失败:

输入图片说明

解决方法

我们需要让加载JNI的代码,不是被每个war的孤立的ClassLoader加载,而是让一个全局的更上一层的ClassLoader加载。

Wildfly和Tomcat的解决方法类似,这里给出Wildfly的方法。

  1. 将JNI对应的代码封装成一个独立的jar包,比如示例中,直接将 com.self.test.HelloNative打包成 test.jar:

      package com.self.test;
     
     public class HelloNative {
     
         private static boolean neverLoaded = true;
     
         public static void LoadLibrary() {
             if (neverLoaded) {
                 System.loadLibrary("libHello");
             neverLoaded = false;
     
         }
     }
     
         public native void helloWorld();
     }
    
  2. 将生成的test.jar保存到{WILDFLY_HOME}\modules\system\layers\base\com\self\test\main中,并创建module.xml

<?xml version="1.0" encoding="UTF-8"?>
<module xmlns="urn:jboss:module:1.0" name="com.self.test">
  <resources>
    <resource-root path="test.jar"/>
  </resources>
  <dependencies>
  </dependencies>
</module>

文件夹的结构如图:

Wildfly Module结构
  1. {WILDFLY_HOME}\standalone\configuration\standalone.xml添加如下配置(只需要test那个):

    wildfly的standalone配置
  2. 重启Wildfly添加Web项目的war包(注意,web项目的war包中不能再含有HelloNative.class,不然容器会优先使用war包中的)

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容