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
#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 :
文件夹的结构如图:
在{WILDFLY_HOME}\standalone\configuration\standalone.xml添加如下配置(只需要test那个):
重启Wildfly添加Web项目的war包(注意,web项目的war包中不能再含有HelloNative.class,不然容器会优先使用war包中的)