前言
jni是什么相信很多人都了解了实辑,這里也不多做解釋圾旨。這篇文章主要介紹在進(jìn)行javaweb或者javaee服務(wù)器開發(fā)時(shí)如何進(jìn)行JNI的開發(fā)以及如何正確引入第三方提供的so庫(kù)
環(huán)境
- linux centos6.8
- gcc 4.4.7
- java "1.8.0_171"
一 在linux環(huán)境進(jìn)行簡(jiǎn)單的JNI開發(fā)
1.編寫一個(gè)簡(jiǎn)單的java程序
進(jìn)入任意一個(gè)工作目錄,我這里是/home/ctest/java/
[root@centos68 java]# vim JNITest.java
//java類代碼
public class JNITest {
public static void main(String[] args){
System.out.println("hello wrold");
}
}
//保存并退出
#進(jìn)行編譯
[root@centos68 java]# javac JNITest.java
//編譯后再目錄下應(yīng)該可以看到JNITest.class
[root@centos68 java]# ls
JNITest.class JNITest.java
#運(yùn)行java程序(這里不要寫JNITest.class)
[root@centos68 java]# java JNITest
hello wrold
//可以看到正確輸出了hello world
上面的這個(gè)簡(jiǎn)單的java程序是為了驗(yàn)證你的機(jī)器已經(jīng)正確配置了jdk環(huán)境變量
2.加入本地方法
//對(duì)上面那個(gè)JNITest.java進(jìn)行修改
public class JNITest {
static {
//對(duì)應(yīng)的庫(kù)名稱是libhello.so
System.loadLibrary("hello");
}
public static void main(String[] args){
System.out.println(getStringFromJNI());
}
/**
* 添加本地方法 返回一個(gè)字符串
* @return
*/
public native static String getStringFromJNI();
}
上面這段代碼我們引入了一個(gè)本地方法getStringFromJNI() 這個(gè)方法的實(shí)現(xiàn)在本地庫(kù)libhello.so, 下面我們?nèi)ゾ帉戇@個(gè)庫(kù)
- 使用javah生成頭文件hello.h
[root@centos68 java]# javah -o hello.h -classpath . -jni JNITest
[root@centos68 java]# ls
hello.h JNITest.class JNITest.java
查看一下這個(gè)hello.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNITest */
#ifndef _Included_JNITest
#define _Included_JNITest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNITest
* Method: getStringFromJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_JNITest_getStringFromJNI
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
- 源文件hello.c編寫
#include "JNITest.h"
JNIEXPORT jstring JNICALL Java_JNITest_getStringFromJNI
(JNIEnv * env, jclass jcls)
{
return (*env)->NewStringUTF(env,"string from jni");
}
- 編譯所需動(dòng)態(tài)庫(kù)libhello.so
//下面這段編譯參數(shù)
*-fPIC 表示生成的動(dòng)態(tài)庫(kù)不要包含地址信息(主要是指一些內(nèi)存地址馒闷,如果包含了地址信息攘滩,那么在不同的進(jìn)程加載的該庫(kù)無(wú)法實(shí)現(xiàn)真正的內(nèi)存共享,而是會(huì)做一份拷貝)
*-I 表示到指定的目錄下尋找頭文件 因?yàn)槲覀円肓? jni.h 這個(gè)頭文件所在的目錄是在$JAVA_HOME/include/中 所以要告訴gcc編譯器到這個(gè)目錄下尋找頭文件 多個(gè)-I 可以指定多個(gè)檢索目錄
* -shared 表示要生成的是動(dòng)態(tài)庫(kù)
* -o 指定生成的動(dòng)態(tài)庫(kù)名字 這里注意必須是libxxxxx.so格式
[root@centos68 java]# gcc -fPIC -I $JAVA_HOME/include/ -I $JAVA_HOME/include/linux/ -shared -o libhello.so hello.c
[root@centos68 java]# ls
hello.c hello.h JNITest.class JNITest.java libhello.so
#運(yùn)行后可以看到生成了libhello.so
- 運(yùn)行測(cè)試
//我們?cè)囍幾g運(yùn)行一下JNITEst.java
[root@centos68 java]# javac JNITEst.java
[root@centos68 java]# java JNITest
Exception in thread "main" java.lang.UnsatisfiedLinkError: no hello in java.library.path
at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1867)
at java.lang.Runtime.loadLibrary0(Runtime.java:870)
at java.lang.System.loadLibrary(System.java:1122)
at JNITest.<clinit>(JNITest.java:4)
//可以看到這邊報(bào)錯(cuò)了 報(bào)錯(cuò)的原因是hello這個(gè)庫(kù)不在java.library.path這個(gè)目錄下 那應(yīng)該如何才能運(yùn)行呢?
方法一:顯示指定java.library.path的值告訴jvm啟動(dòng)的時(shí)候可以在這個(gè)目錄下搜索動(dòng)態(tài)庫(kù)
//.表示當(dāng)前目錄
[root@centos68 java]# java -Djava.library.path=. JNITest
string from jni
方法二: 配置環(huán)境變量LD_LIBRARY_PATH
LD_LIBRARY_PATH環(huán)境變量用于在程序加載運(yùn)行期間查找動(dòng)態(tài)鏈接庫(kù)時(shí)指定除了系統(tǒng)默認(rèn)路徑之外的其他路徑走芋。LD_LIBRARY_PATH中指定的路徑會(huì)在系統(tǒng)默認(rèn)路徑之前進(jìn)行查找。
臨時(shí)生效 在當(dāng)前shell設(shè)置環(huán)境變量值LD_LIBRARY_PATH
[root@centos68 java]# export LD_LIBRARY_PATH=.
[root@centos68 java]# java JNITest
string from jni
永久生效 在~/.bashrc 或者/etc/profile中添加環(huán)境變量LD_LIBRARY_PATH
//這邊為了讓所有用戶都有效 可以在/etc/profile中添加
[root@centos68 java]# vim /etc/profile
//添加如下代碼 可以在export PATH 下方添加
LD_LIBRARY_PATH=/home/ctest/java
export LD_LIBRARY_PATH
//使修改生效
[root@centos68 java]# source /etc/profile
//運(yùn)行
[root@centos68 java]# java JNITest
string from jni
方法三:/etc/ld.so.conf中指定動(dòng)態(tài)庫(kù)搜索路徑
在linux系統(tǒng)中 ldconfig程序用于從磁盤預(yù)裝動(dòng)態(tài)庫(kù)到內(nèi)存中潘鲫,這樣可以提高程序運(yùn)行時(shí)對(duì)共享庫(kù)的調(diào)用速度,ldconfig搜索路徑在/etc/ld.so.conf文件中配置翁逞,每一行指定一個(gè)搜索路徑,可以在該文件下添加一行/home/ctest/java溉仑,然后調(diào)用ldconfig重新去裝載動(dòng)態(tài)庫(kù)
[root@centos68 java]# vim /etc/ld.so.conf
//添加一行/home/ctest/java
#重新裝載動(dòng)態(tài)庫(kù)
[root@centos68 java]# ldconfig
注意: 這種配置方式是基于linux系統(tǒng)的挖函,在純c開發(fā)中測(cè)試沒有問題,但在java調(diào)用中并沒有起作用浊竟,雖然通過ldconfig -p | grep libhello.so 可以查看到動(dòng)態(tài)庫(kù)已經(jīng)被裝載怨喘,但是運(yùn)行java JNITest 還是報(bào)錯(cuò),不知道是為什么逐沙。
方法四:將動(dòng)態(tài)庫(kù)添加到系統(tǒng)默認(rèn)搜索動(dòng)態(tài)庫(kù)的路徑下哲思,例如/lib,/usr/lib 64位系統(tǒng)是/lib64,/usr/lib64
動(dòng)態(tài)庫(kù)的搜索路徑搜索的先后順序是:
1.編譯目標(biāo)代碼時(shí)指定的動(dòng)態(tài)庫(kù)搜索路徑洼畅;(這是針對(duì)c或者c++可執(zhí)行程序而言吩案,在編譯時(shí)通過添加編譯參數(shù)-Wl,-rpath=.來(lái)指定運(yùn)行時(shí)動(dòng)態(tài)鏈接的搜索路徑)
2.環(huán)境變量LD_LIBRARY_PATH指定的動(dòng)態(tài)庫(kù)搜索路徑;
3.配置文件/etc/ld.so.conf中指定的動(dòng)態(tài)庫(kù)搜索路徑帝簇;
4.默認(rèn)的動(dòng)態(tài)庫(kù)搜索路徑/lib徘郭;
5.默認(rèn)的動(dòng)態(tài)庫(kù)搜索路徑/usr/lib。
引入第三方so庫(kù)
其實(shí)通過上面那個(gè)例子 大家應(yīng)該已經(jīng)可以知道怎么去添加第三方編寫好的so庫(kù)了
這里我們以騰訊TLS后臺(tái)api接口引入作為測(cè)試
文檔地址
下載資源包 tls_sig_api-linux-64 因?yàn)槭窃票P下載 所以先下載到window電腦再通過ftp傳到服務(wù)器
解壓并拷貝所需要的文件:
tls_sig_api-linux-64\tls_sig_api-linux-64\example\java
文件夾和
tls_sig_api-linux-64\tls_sig_api-linux-64\lib\jni\jnisigcheck.so
以及
tls_sig_api-linux-64\tls_sig_api-linux-64\java\tls_sigcheck.java
以上java文件夾丧肴,so庫(kù)残揉,和tls_sigcheck.java通過ftp上傳到服務(wù)器
1.將類tls_sigcheck.java放到com/tls/sigcheck下 刪除tls_sigcheck.class 因?yàn)槲覀円薷倪@個(gè)類
2.修改jnisigcheck.so 為 libjnisigcheck.so
[root@centos68 tls]# pwd
/home/ctest/java/tls
[root@centos68 tls]# ls
com Demo.class Demo.java ec_key.pem jnisigcheck.so public.pem README
[root@centos68 tls]# mv jnisigcheck.so libjnisigcheck.so
[root@centos68 tls]# ls
com Demo.class Demo.java ec_key.pem libjnisigcheck.so public.pem README
[root@centos68 tls]# rm -rf Demo.class
[root@centos68 tls]# ls
com Demo.java ec_key.pem libjnisigcheck.so public.pem README
[root@centos68 tls]#
修改tls_sigcheck.java
/**
* @param libPath 動(dòng)態(tài)庫(kù)的絕對(duì)路徑
* 加載動(dòng)態(tài)庫(kù)
*/
// public void loadJniLib(String libPath) {
// System.load(libPath);
// }
#注釋上方的代碼 添加如下代碼 我們使用相對(duì)路徑加載動(dòng)態(tài)庫(kù)libjnisigcheck.so
/**
* @param libPath 動(dòng)態(tài)庫(kù)的絕對(duì)路徑
* 加載動(dòng)態(tài)庫(kù)
*/
static {
System.loadLibrary("jnisigcheck");
}
修改Demo.java
// 使用前請(qǐng)修改動(dòng)態(tài)庫(kù)的加載路徑
#注釋下面兩行代碼 因?yàn)槲覀冊(cè)趖ls_sigcheck.java中使用了靜態(tài)代碼塊加載動(dòng)態(tài)庫(kù),因此這里不需要再調(diào)用加載
// demo.loadJniLib("D:\\src\\oicq64\\tinyid\\tls_sig_api\\windows\\64\\lib\\jni\\jnisigcheck.dll");
//demo.loadJniLib("/home/tls/tls_sig_api/src/jnisigcheck.so");
重新編譯并運(yùn)行
[root@centos68 tls]# javac Demo.java
[root@centos68 tls]# ls
com Demo.class Demo.java ec_key.pem libjnisigcheck.so public.pem README
[root@centos68 tls]# java -Djava.library.path=. Demo
sig:
eJxlj1FPgzAUhd-5FYRXjGnL2m0mPiybTgyiDF2iL6SuLakM6NpCtxj-uxGXSOJ9-b6cc*6n5-t*8Jzkl3S3a7vGFvakeOBf*QEILv6gUpIV1BaRZv8gPyqpeUGF5XqAEGOMABg7kvHGSiHPxlHS9qNrRoJhVTG0-CZMwM8hMh0rshzgw83LMs6WVZ0*ancXJnmfEH1bp6QyoJy9hnR7jyvxtl1PI8eUzlaLuMwdMH27D4FYQxFDglq1oocnW3eTzSHevO*dzhYmdY6461GllTU-v4QwmQM4m49oz7WRbTMICEAMUTTMDrwv7xtapl6r
--
verify ok -- expire time 15552000 -- init time 1525690189
[root@centos68 tls]#
需要注意的是:一般帶有native方法的類 比如上面的tls_sigcheck.java 它所在的包路徑是固定的 必須按照官方建議的組織結(jié)構(gòu)進(jìn)行放置芋浮, 比如這邊是在com.tls.sigcheck這個(gè)包下 抱环, 這是因?yàn)樵趕o庫(kù)中所定義的方法名稱包含有包信息 不能變更
通過上面的運(yùn)行我們成功的跑起了demo并且生成了需要的sig 在正式項(xiàng)目中 在正式項(xiàng)目中,可以將so文件通過上述幾種方式加入到系統(tǒng)運(yùn)行時(shí)的搜索路徑下,實(shí)現(xiàn)動(dòng)態(tài)加載镇草。