ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Ubuntu 20.04 Java 开发环境搭建与 JNI 调用:TaoToken 统一 Key 配置实战

Ubuntu 20.04 Java 开发环境搭建与 JNI 调用:TaoToken 统一 Key 配置实战 1. Ubuntu 20.04 上把 Java 和 JNI 一次跑通如果你刚装好 Ubuntu 20.04想写 Java还想让 Java 调 C 的本地库那这篇就是给你准备的。Java 开发环境搭建本身不复杂麻烦的是 JNI 这一段头文件怎么生成、动态库怎么编、运行时UnsatisfiedLinkError怎么排。我按「装 JDK → 配环境变量 → 编译运行 Java → 生成 JNI 头文件 → 写 C 实现 → 编译 so → 运行验证」的顺序走一遍每条命令都能直接复制。同时我会加一段现在很实用的内容用 TaoToken 的统一 Key 给 AI 辅助编码工具以 Cline 为例配settings.json骨架。这样你在 Ubuntu 上写 JNI 这种「Java C 混合、报错信息又长又绕」的代码时可以让 AI 帮你读编译错误、补方法签名省掉大量翻文档的时间。TaoToken 在这里的角色是统一的模型调用入口一个 Key 走通对话和编码场景不改变你本地的编译链路。适合谁刚接触 Ubuntu 的 Java 新手、需要做 JNI 跨语言调用的后端/算法同学、以及想给本地编码工具接一个统一模型通道的开发者。下面所有路径以 OpenJDK 11 为例Ubuntu 20.04 默认源里就是它长期支持够用。2. 前置准备TaoToken 统一 Key 与 API 通道在动手写代码前先把「AI 辅助」这条线铺好后面排错会轻松很多。TaoToken 提供统一的 Key 和 API 通道你不需要为不同工具分别记不同的地址和密钥。官网入口是 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 基址是 https://taotoken.net/api 。操作顺序很简单先注册登录进控制台创建 API Key然后按你要用的工具去对应文档页拿配置片段。几个常用入口我列一下方便你按需跳转创建和管理 Keyhttps://taotoken.net/api-keys?utm_sourcetaotoken_aicg_blog_endutm_contentapi_keysutm_campaignrewrite控制台总览https://taotoken.net/console?utm_sourcetaotoken_aicg_blog_endutm_contentconsoleutm_campaignrewrite接入文档https://taotoken.net/doc?utm_sourcetaotoken_aicg_blog_endutm_contentdocutm_campaignrewrite模型对话体验https://taotoken.net/models?utm_sourcetaotoken_aicg_blog_endutm_contentmodelsutm_campaignrewrite长期编码 / Agent 场景https://taotoken.net/coding-plan?utm_sourcetaotoken_aicg_blog_endutm_contentcoding_planutm_campaignrewrite注意Key 只存在你本地配置文件里不要提交到 Git 仓库。JNI 项目经常带.so和构建脚本建议顺手把settings.json、.env加进.gitignore。如果你只是想让 AI 帮你解释javap输出的方法签名用模型对话页就够如果你打算长期在 Cline 里做 JNI 开发走 Coding Plan 更合适额度模型更贴合持续编码。3. 可复制配置JDK 安装、环境变量与 JNI 编译脚本3.1 安装 OpenJDK 11 并验证Ubuntu 20.04 的源里自带 OpenJDK 11直接装sudo apt-get update sudo apt-get -y install openjdk-11-jdk java --version javac --versionjava --version应该输出类似openjdk 11.0.x。如果系统里装了多个 JDK用 alternatives 切换默认版本sudo update-alternatives --config java sudo update-alternatives --config javac终端会列出已安装的 Java 路径输入对应序号回车即可。3.2 配置 JAVA_HOME先确认 JDK 实际安装位置update-alternatives --config java输出里通常能看到/usr/lib/jvm/java-11-openjdk-amd64/bin/java。编辑系统环境文件sudo vim /etc/environment在文件末尾追加一行注意不要有多余空格JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64让配置生效并验证source /etc/environment echo $JAVA_HOME如果你用 VS Code 写 Java还需要在项目或用户settings.json里指定 JDK 路径否则语言服务器可能找不到{ java.jdt.ls.java.home: /usr/lib/jvm/java-11-openjdk-amd64 }3.3 编译运行普通 Java 程序先建一个最小工程验证工具链。目录结构如下jni-demo/ ├── src/ │ └── com/huhu/test/ │ ├── JNIDemo.java │ ├── Java2Cplusplus.java │ └── BaseClass.java └── native/ └── com_huhu_test_JNIDemo.cpp编译单个文件或整个包# 编译单个文件 javac test.java # 按包结构编译到当前目录 javac -d . *.java # 指定输出目录编译 javac -d ../ JNIDemo.java Java2Cplusplus.java BaseClass.java运行带本地库路径的程序java -Djava.library.path./JNIDemo test3.4 生成 JNI 头文件JNIDemo.java里声明 native 方法package com.huhu.test; public class JNIDemo { public native void testHello(); public native float[] execute(int[] data, int width, int height, float ep); }用javac -h直接生成头文件省去手写javah的麻烦javac -h . JNIDemo.java生成的com_huhu_test_JNIDemo.h里会有两个函数声明函数名遵循Java_包名_类名_方法名规则包名里的点变成下划线JNIEXPORT void JNICALL Java_com_huhu_test_JNIDemo_testHello (JNIEnv *, jobject); JNIEXPORT jfloatArray JNICALL Java_com_huhu_test_JNIDemo_execute (JNIEnv *, jobject, jintArray, jint, jint, jfloat);3.5 实现 C 并编译成 socom_huhu_test_JNIDemo.cpp实现这两个函数包含数组读取、返回 float 数组以及通过GetMethodID回调 Java 方法的逻辑#include com_huhu_test_JNIDemo.h #include jni.h #include cstdio #include iostream #include vector #define JNI_JAVA2C_PATH com/huhu/test/Java2Cplusplus #define JNI_BASE_CLASS_PATH com/huhu/test/BaseClass JNIEXPORT void JNICALL Java_com_huhu_test_JNIDemo_testHello (JNIEnv *env, jobject thiz) { std::cout this is Java_com_huhu_test_JNIDemo_testHello C print std::endl; } JNIEXPORT jfloatArray JNICALL Java_com_huhu_test_JNIDemo_execute (JNIEnv *env, jobject thiz, jintArray data, jint width, jint height, jfloat ep) { unsigned char *data_src (unsigned char *)env-GetIntArrayElements(data, NULL); std::cout data_src data_src[0] data_src[1] data_src[2] std::endl; std::cout width height ep width height ep std::endl; std::vectorfloat result{0.1, 0.2, 0.3}; jfloatArray resultArray env-NewFloatArray(result.size()); env-SetFloatArrayRegion(resultArray, 0, result.size(), result[0]); jclass clazz env-FindClass(JNI_JAVA2C_PATH); jmethodID method_init_id env-GetMethodID(clazz, init, ()V); jmethodID method_set_id env-GetMethodID(clazz, setNum, (I)V); jmethodID method_get_id env-GetMethodID(clazz, getNum, ()I); jclass base env-FindClass(JNI_BASE_CLASS_PATH); jmethodID method_base_init_id env-GetMethodID(base, init, ()V); jobject objbase env-NewObject(base, method_base_init_id); jmethodID method_get_jobject_id env-GetMethodID(clazz, getNum, (Lcom/huhu/test/BaseClass;)I); jobject obj env-NewObject(clazz, method_init_id); env-CallVoidMethod(obj, method_set_id, 21); int year env-CallIntMethod(obj, method_get_id); int year2 env-CallIntMethod(obj, method_get_jobject_id, objbase); std::cout year year std::endl; std::cout year2 year2 std::endl; return resultArray; }编译命令的关键是-I指向 JDK 的include和include/linux-fPIC和-shared生成位置无关的动态库g -fPIC \ -I /usr/lib/jvm/java-11-openjdk-amd64/include/ \ -I /usr/lib/jvm/java-11-openjdk-amd64/include/linux/ \ -shared -o libcom_huhu_test_JNIDemo.so com_huhu_test_JNIDemo.cpp注意System.loadLibrary(com_huhu_test_JNIDemo)加载的是libcom_huhu_test_JNIDemo.so库名不带lib前缀和.so后缀这是 Linux 下的命名约定。3.6 方法签名速查GetMethodID和GetFieldID的第三个参数是签名格式为(形参类型列表)返回值。基本类型对照如下Java 类型签名Java 类型签名byteBintIcharClongJdoubleDbooleanZfloatFvoidVshortS对象L类名;数组类型在基本签名前加[比如int[]是[Ifloat[]是[F。对象数组是[L类名;。不想手拼就用javap生成javac Java2Cplusplus.java javap -s -p Java2Cplusplus.class输出里descriptor:后面就是签名比如getNum(com.huhu.test.BaseClass)的签名是(Lcom/huhu/test/BaseClass;)I。3.7 Cline 的 settings.json 骨架在 VS Code 里装好 Cline 后把 TaoToken 的统一 Key 填进配置。下面是一个骨架baseUrl指向 TaoToken 的 API 地址apiKey换成你在控制台创建的那串{ cline.apiProvider: openai, cline.openAiBaseUrl: https://taotoken.net/api, cline.openAiApiKey: sk-你的TaoToken密钥, cline.openAiModelId: 你的模型ID, cline.customInstructions: 回答 JNI 问题时优先给出可编译的 C 片段和 javap 签名验证命令。 }字段名以你当前 Cline 版本的文档为准核心是baseUrl和apiKey两项。配好后在 Cline 里问一句「GetMethodID的签名(Lcom/huhu/test/BaseClass;)I是什么意思」能正常返回就说明通道通了。4. 验证请求从 javac 到 java 的完整成功链路配置完别急着写业务先跑一遍最小闭环。按下面顺序执行每一步都有预期输出。第一步编译 Java 源码cd src javac -d ../ JNIDemo.java Java2Cplusplus.java BaseClass.java没有报错即通过../com/huhu/test/下会出现.class文件。第二步生成头文件javac -h . JNIDemo.java ls com_huhu_test_JNIDemo.h能看到头文件即成功。第三步编译动态库cd ../native g -fPIC \ -I /usr/lib/jvm/java-11-openjdk-amd64/include/ \ -I /usr/lib/jvm/java-11-openjdk-amd64/include/linux/ \ -shared -o libcom_huhu_test_JNIDemo.so com_huhu_test_JNIDemo.cpp用file确认产物file libcom_huhu_test_JNIDemo.so应显示ELF 64-bit LSB shared object。第四步运行 Java 程序cd .. javac test.java java -Djava.library.path./native test预期输出包含 C 打印的this is Java_com_huhu_test_JNIDemo_testHello C print、数组数据、year 21以及 Java 侧打印的三个 float 值。看到这些说明 JDK、JNI 头文件、C 编译、动态库加载、跨语言回调全部打通。如果你在 Cline 里配好了 TaoToken可以把上面某一步的报错整段贴进去让它帮你定位是签名写错还是路径不对。实测下来JNI 报错里最耗时的就是签名和库名AI 读一遍javap输出基本能指出来。5. 本篇常见错排查5.1 UnsatisfiedLinkError: no xxx in java.library.path最常见。原因通常是-Djava.library.path没指对或者库名写错。检查两点System.loadLibrary(com_huhu_test_JNIDemo)对应的文件必须是libcom_huhu_test_JNIDemo.so运行时-Djava.library.path指向.so所在目录不是文件名。用绝对路径更稳java -Djava.library.path/home/你的用户名/jni-demo/native test5.2 头文件函数名对不上javac -h生成的函数名由包名决定。如果你改了package声明却没重新生成头文件C 里实现的还是旧名字链接阶段就会找不到符号。改包名后务必重新执行javac -h . JNIDemo.java并同步更新.cpp里的函数名。5.3 g 找不到 jni.h说明-I路径不对。先确认 JDK 实际路径ls /usr/lib/jvm/如果目录名不是java-11-openjdk-amd64把编译命令里的-I换成实际路径。jni.h在include/下jni_md.h在include/linux/下两个都要加。5.4 GetMethodID 返回 NULL 导致崩溃签名写错时GetMethodID返回 NULL后续CallIntMethod直接段错误。用javap -s -p核对签名特别注意对象参数要写成Lcom/huhu/test/BaseClass;分号不能丢斜杠不能写成点。构造函数名固定是init签名()V。5.5 数组读取后没释放GetIntArrayElements拿到的是可能被复制的指针用完要ReleaseIntArrayElements否则内存泄漏。JNI 里这类资源成对出现写的时候养成习惯。6. 接下来怎么用这套环境环境跑通后你可以把native/目录的编译命令写进Makefile或 shell 脚本改一次 C 就重新编一次 soJava 侧不用动。JNI 调试阶段建议在 C 里多打日志配合-Djava.library.path快速切换库版本。如果你打算长期做 Java C 混合开发把 Cline 接到 TaoToken 的 Coding Plan 上让 AI 常驻帮你读javap签名、补GetFieldID调用、解释UnsatisfiedLinkError比每次手动查表快得多。统一 Key 的好处是你不用在多个工具间同步密钥一个入口管住对话和编码两条线。先把这篇的命令跑一遍再按自己的包名替换基本就能直接开工了。
返回列表