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
文件:将会在
com\self\test\
目录下生成HelloNative.class
文件 -
然后在通过
javah
命令生成C\C++
需要的头文件:头文件将会在
src
目录下,根据package
和类名命名,如本例子生成的com_self_test_HelloNative.h
-
本例中使用Window平台,所以使用Visual Studio生成
dll
,如果使用Linux,则类似的生产so
就可以了。新建一个Visual C++项目:
得到项目:
其中,
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添加Web项目的war包(注意,web项目的war包中不能再含有
HelloNative.class
,不然容器会优先使用war包中的)