javaee项目进行JNI开发

前言

jni是什么相信很多人都了解了,这里也不多做解释。这篇文章主要介绍在进行javaweb或者javaee服务器开发时如何进行JNI的开发以及如何正确引入第三方提供的so库

环境

  • linux centos6.8
  • gcc 4.4.7
  • java "1.8.0_171"

一 在linux环境进行简单的JNI开发

1.编写一个简单的java程序

进入任意一个工作目录,我这里是/home/ctest/java/

[root@centos68 java]# vim JNITest.java

//java类代码
    public class JNITest {
 
    public static void main(String[] args){
        System.out.println("hello wrold");
    }
}

//保存并退出

#进行编译
[root@centos68 java]# javac JNITest.java
//编译后再目录下应该可以看到JNITest.class
[root@centos68 java]# ls
JNITest.class   JNITest.java

#运行java程序(这里不要写JNITest.class)
[root@centos68 java]# java JNITest
hello wrold

//可以看到正确输出了hello world

上面的这个简单的java程序是为了验证你的机器已经正确配置了jdk环境变量

2.加入本地方法

//对上面那个JNITest.java进行修改
public class JNITest {
   static {
        //对应的库名称是libhello.so
        System.loadLibrary("hello");
    }

    public static void main(String[] args){
        System.out.println(getStringFromJNI());
    }

    /**
     * 添加本地方法  返回一个字符串
     * @return
     */
    public native static String getStringFromJNI();
}

上面这段代码我们引入了一个本地方法getStringFromJNI() 这个方法的实现在本地库libhello.so, 下面我们去编写这个库

  • 使用javah生成头文件hello.h
[root@centos68 java]# javah -o hello.h -classpath . -jni JNITest
[root@centos68 java]# ls
hello.h  JNITest.class  JNITest.java

查看一下这个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");
}
  • 编译所需动态库libhello.so

//下面这段编译参数
*-fPIC 表示生成的动态库不要包含地址信息(主要是指一些内存地址,如果包含了地址信息,那么在不同的进程加载的该库无法实现真正的内存共享,而是会做一份拷贝)
*-I 表示到指定的目录下寻找头文件 因为我们引入了  jni.h   这个头文件所在的目录是在$JAVA_HOME/include/中   所以要告诉gcc编译器到这个目录下寻找头文件  多个-I 可以指定多个检索目录
* -shared 表示要生成的是动态库
* -o 指定生成的动态库名字  这里注意必须是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

#运行后可以看到生成了libhello.so

  • 运行测试
//我们试着编译运行一下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)
        
        
//可以看到这边报错了   报错的原因是hello这个库不在java.library.path这个目录下 那应该如何才能运行呢?

方法一:显示指定java.library.path的值告诉jvm启动的时候可以在这个目录下搜索动态库

//.表示当前目录
[root@centos68 java]# java -Djava.library.path=. JNITest
string from jni

方法二: 配置环境变量LD_LIBRARY_PATH

LD_LIBRARY_PATH环境变量用于在程序加载运行期间查找动态链接库时指定除了系统默认路径之外的其他路径。LD_LIBRARY_PATH中指定的路径会在系统默认路径之前进行查找。

临时生效 在当前shell设置环境变量值LD_LIBRARY_PATH

[root@centos68 java]# export LD_LIBRARY_PATH=.
[root@centos68 java]# java JNITest
string from jni

永久生效 在~/.bashrc 或者/etc/profile中添加环境变量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 

//运行
[root@centos68 java]# java JNITest
string from jni

方法三:/etc/ld.so.conf中指定动态库搜索路径

在linux系统中 ldconfig程序用于从磁盘预装动态库到内存中,这样可以提高程序运行时对共享库的调用速度,ldconfig搜索路径在/etc/ld.so.conf文件中配置,每一行指定一个搜索路径,可以在该文件下添加一行/home/ctest/java,然后调用ldconfig重新去装载动态库

[root@centos68 java]# vim /etc/ld.so.conf

//添加一行/home/ctest/java

#重新装载动态库
[root@centos68 java]# ldconfig

注意: 这种配置方式是基于linux系统的,在纯c开发中测试没有问题,但在java调用中并没有起作用,虽然通过ldconfig -p | grep libhello.so 可以查看到动态库已经被装载,但是运行java JNITest 还是报错,不知道是为什么。

方法四:将动态库添加到系统默认搜索动态库的路径下,例如/lib,/usr/lib 64位系统是/lib64,/usr/lib64

动态库的搜索路径搜索的先后顺序是:


1.编译目标代码时指定的动态库搜索路径;(这是针对c或者c++可执行程序而言,在编译时通过添加编译参数-Wl,-rpath=.来指定运行时动态链接的搜索路径)

2.环境变量LD_LIBRARY_PATH指定的动态库搜索路径;

3.配置文件/etc/ld.so.conf中指定的动态库搜索路径;

4.默认的动态库搜索路径/lib;

5.默认的动态库搜索路径/usr/lib。

引入第三方so库

其实通过上面那个例子 大家应该已经可以知道怎么去添加第三方编写好的so库了

这里我们以腾讯TLS后台api接口引入作为测试

文档地址

TLS后台API

下载资源包 tls_sig_api-linux-64 因为是云盘下载 所以先下载到window电脑再通过ftp传到服务器

解压并拷贝所需要的文件:

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库,和tls_sigcheck.java通过ftp上传到服务器

1.将类tls_sigcheck.java放到com/tls/sigcheck下 删除tls_sigcheck.class 因为我们要修改这个类

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 动态库的绝对路径
     * 加载动态库
     */
 //   public void loadJniLib(String libPath) {
 //       System.load(libPath);
 //   }

#注释上方的代码   添加如下代码  我们使用相对路径加载动态库libjnisigcheck.so

 /**
     * @param libPath 动态库的绝对路径
     * 加载动态库
     */
    static {
        System.loadLibrary("jnisigcheck");
    }

修改Demo.java


// 使用前请修改动态库的加载路径
#注释下面两行代码  因为我们在tls_sigcheck.java中使用了静态代码块加载动态库,因此这里不需要再调用加载
// 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");

重新编译并运行

[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 它所在的包路径是固定的 必须按照官方建议的组织结构进行放置, 比如这边是在com.tls.sigcheck这个包下 , 这是因为在so库中所定义的方法名称包含有包信息 不能变更

通过上面的运行我们成功的跑起了demo并且生成了需要的sig 在正式项目中 在正式项目中,可以将so文件通过上述几种方式加入到系统运行时的搜索路径下,实现动态加载。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容

  • 一,apk以进程的形式运行,进程的创建是由zygote。 参考文章《深入理解Dalvik虚拟机- Android应...
    Kevin_Junbaozi阅读 2,785评论 0 12
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,259评论 25 707
  • 非常非常喜欢秋天,即使天气干燥,刮来的风让脸上一整天都是紧绷的感觉,但是这都不影响我对这个季节的喜欢。 秋天的菜园...
    温妮小世界阅读 267评论 0 0
  • 刚开学,果然不适应,更何况有那么些棘手的事。 文章要改,课题要开,学问还得接着做。 想想美好的事,或者沉浸于自己的...
    鹰王守仁阅读 157评论 0 1
  • 吴京《战狼2》50亿庆功宴只有两明星到场,真正的原因让人心酸:在过去的这一个月,电影《战狼2》的票房和口碑直线上升...
    可耐的牛牛阅读 192评论 0 0