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 
/* 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.(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文件通过上述几种方式加入到系统运行时的搜索路径下,实现动态加载。

你可能感兴趣的:(javaee项目进行JNI开发)