JNI示例
-
首先定义一个带有
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(); // } } -
然后将这个类编译成为
.class文件:javac编译文件将会在
com\self\test\目录下生成HelloNative.class文件 -
然后在通过
javah命令生成C\C++需要的头文件:javah生成头文件头文件将会在
src目录下,根据package和类名命名,如本例子生成的com_self_test_HelloNative.h -
本例中使用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) -
确保
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()
例子:
第一次成功:

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

原因是,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();
}
第一次,loadedLib是null的:

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

解决方法
我们需要让加载JNI的代码,不是被每个war的孤立的ClassLoader加载,而是让一个全局的更上一层的ClassLoader加载。
Wildfly和Tomcat的解决方法类似,这里给出Wildfly的方法。
-
将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(); } 将生成的
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_HOME}\standalone\configuration\standalone.xml添加如下配置(只需要test那个):wildfly的standalone配置 重启Wildfly添加Web项目的war包(注意,web项目的war包中不能再含有
HelloNative.class,不然容器会优先使用war包中的)





