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

原创
2018/01/16 17:53
阅读数 849

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包中的)

展开阅读全文
打赏
0
0 收藏
分享
加载中
更多评论
打赏
0 评论
0 收藏
0
分享
返回顶部
顶部