探索 JNI - Rust 与 Java 互调实战

真正的救赎,并非厮杀后的胜利,而是能在苦难之中,找到生的力量和内心的安宁。

——加缪Albert Camus

一、Rust + Java = ? 

Java 和 Rust 是两种现代编程语言,各自具有独特的优势,适用于不同的应用场景。

1、Java 的优势

  1. 跨平台性:Java 的“写一次,运行到处”的理念使得它能够在各种操作系统上运行,只要有 JVM(Java Virtual Machine)支持即可。
  2. 丰富的生态系统:Java 拥有庞大的标准库和第三方库生态系统,涵盖了几乎所有的开发需求,从 web 开发、数据库访问到大数据处理等。
  3. 成熟的工具链:Java 具有成熟的开发工具链,包括 IDE(如 IntelliJ IDEA、Eclipse)、构建工具(如 Maven、Gradle)和调试工具,这些工具极大地提高了开发效率。
  4. 强大的社区支持:Java 社区非常活跃,有大量的开源项目、文档和教程,可以帮助开发者快速解决问题。
  5. 垃圾回收机制:Java 的自动垃圾回收机制简化了内存管理,减少了内存泄漏和其他内存相关错误的风险。
  6. 企业级应用:Java 在企业级应用开发中占据重要地位,特别是在金融、电信和大型分布式系统中,Java 被广泛使用。
  7. 多线程支持:Java 提供了强大的多线程支持,方便开发并发和并行程序。

2、Rust 的优势

  1. 内存安全:Rust 的所有权系统和借用检查器在编译时保证了内存安全,防止了常见的内存错误,如空指针引用、悬挂指针和缓冲区溢出。
  2. 高性能:Rust 生成的代码接近 C 和 C++ 的性能,同时提供了更高的安全性和更少的运行时开销。
  3. 无畏并发:Rust 的所有权模型使得编写并发代码更加安全和简单,避免了数据竞争和死锁等问题。
  4. 现代语言特性:Rust 支持模式匹配、闭包、泛型、trait 等现代编程语言特性,使得代码更加简洁和可维护。
  5. 强类型系统:Rust 的强类型系统在编译时捕获更多的错误,提高了代码的可靠性和可维护性。
  6. 零成本抽象:Rust 提供了高层次的抽象,但这些抽象在编译后不会引入额外的运行时开销,实现了所谓的“零成本抽象”。
  7. 良好的文档和工具链:Rust 提供了优秀的文档和工具链,包括 Cargo(包管理和构建工具)、rustfmt(代码格式化工具)和 Clippy(代码审查工具),这些工具极大地提高了开发体验。
  8. 嵌入式和系统编程:Rust 非常适合嵌入式和系统级编程,能够直接操作硬件,同时保持高安全性和性能。

3、Rust + Java 的应用思路

Java 和 Rust 各有其独特的优势,适用于不同的应用场景:

  • Java 适合需要跨平台兼容性、丰富生态系统和成熟工具链的企业级应用和大型分布式系统。
  • Rust 则适合需要高性能、内存安全和并发控制的系统级编程、嵌入式开发以及对性能和安全性要求极高的应用。

通过结合 Java 和 Rust 的优势,可以在实际项目中实现更高效、安全和功能强大的解决方案。例如,可以使用 Java 实现业务逻辑和用户界面,而使用 Rust 实现性能关键的底层模块和系统组件。

4、应用场景

  1. 性能关键模块:在 Java 应用中,某些性能关键的部分可以用 Rust 编写,以提高执行效率。例如,计算密集型算法、数据处理或图像处理等任务可以用 Rust 实现,然后通过 JNI(Java Native Interface)调用这些 Rust 函数。
  2. 内存安全和并发控制:Rust 的所有权机制和借用检查器可以帮助避免内存泄漏和数据竞争问题。在需要严格内存管理和并发控制的场景下,可以用 Rust 编写核心逻辑,并通过 FFI(Foreign Function Interface)与 Java 进行交互。
  3. 系统级编程:对于需要直接操作系统资源或硬件的功能,例如文件系统操作、网络通信或设备驱动程序,可以用 Rust 编写这些底层代码,然后通过 JNI 或 JNA(Java Native Access)与 Java 应用进行交互。
  4. 跨平台开发:Rust 可以编译成多种平台的二进制文件,而 Java 本身具有良好的跨平台特性。在需要支持多种操作系统的应用中,可以利用 Rust 编写跨平台的库,并通过 Java 调用这些库,从而实现跨平台兼容性。
  5. 安全性要求高的应用:在金融、医疗等对安全性要求极高的领域,可以用 Rust 编写关键的安全模块,如加密算法、身份验证等,然后通过 JNI 与 Java 应用集成,以确保系统的整体安全性。
  6. 微服务架构:在微服务架构中,不同的服务可以用不同的语言实现。如果某些微服务用 Rust 编写,而其他服务用 Java 编写,可以通过 RESTful API 或 gRPC 等方式进行通信,实现互操作。
  7. 游戏开发:游戏引擎或性能要求高的游戏逻辑可以用 Rust 编写,而游戏的业务逻辑、界面等可以用 Java 编写,通过 JNI 调用 Rust 实现高效的游戏运行。

二、JNI:实现 Rust 与 Java 互调的桥梁

1、如何理解 JNI

Java Native Interface(JNI)是 Java 平台的一部分,它允许 Java 代码与用其他编程语言(如 C、C++ )编写的本地代码进行交互。JNI 的角色可以理解为桥梁或接口,连接了 Java 虚拟机(JVM)和本地代码库,使得两者能够相互调用和通信。

跨语言调用:JNI 使得 Java 程序能够调用本地代码中的函数,也使得本地代码能够调用 Java 方法。这种双向调用能力使得开发者可以在 Java 应用中利用其他语言的特性和性能优势。

性能优化:在某些情况下,Java 代码可能无法提供足够的性能,例如在处理大量数据或执行复杂计算时。通过 JNI,可以将这些性能关键的部分用更高效的语言(如 C、C++)实现,然后在 Java 中调用,从而提升整体应用的性能。

访问底层系统资源:Java 本身是一个高级语言,通常不直接提供对底层系统资源(如硬件设备、操作系统 API)的访问。通过 JNI,Java 程序可以调用本地代码来访问这些底层资源,实现系统级编程。

复用现有库和代码:很多已有的库和代码是用其他语言编写的。通过 JNI,Java 程序可以直接调用这些现有的库,而无需重新实现,从而节省开发时间和成本。

内存管理:JNI 提供了一套机制来管理 Java 和本地代码之间的内存交互。虽然 Java 有自动垃圾回收机制,但本地代码需要手动管理内存。JNI 提供了必要的工具和方法来确保内存安全和有效管理。

错误处理和调试:JNI 提供了一些工具和方法来处理和调试 Java 与本地代码之间的交互问题。例如,通过 JNI 可以捕获和处理本地代码中的异常,并将其转换为 Java 异常,从而在 Java 层面进行处理。

平台独立性:尽管 JNI 允许调用本地代码,这些本地代码往往是平台相关的。然而,JNI 本身作为一个标准接口,使得开发者可以编写跨平台的 Java 代码,同时针对不同的平台编写相应的本地代码库,从而实现一定程度的跨平台兼容性。

总结来说,JNI 的角色是充当 Java 和本地代码之间的桥梁,提供了一种机制,使得 Java 应用能够利用其他语言的特性和性能优势,同时保持一定的灵活性和扩展性。JNI 既然是 C 语言接口,那么理论上支持 C ABI 的语言都可以和 Java 语言互相调用,Rust 就是其中之一。

关于 JNI 的历史背景以及更详细的介绍可以参考 官方文档

2、JNI 相关概念

Java 本地方法:在 Java 中声明但由本地代码实现的方法。

JNI 环境指针 (JNIEnv *):用于访问 JNI 提供的函数和 Java 虚拟机(JVM)的接口。

本地库:包含本地方法实现的共享库或动态链接库(如 .dll.so 文件)。

Java 本地方法(Native Method)、JNI 环境指针(JNIEnv *)和本地库在一起工作时,通常遵循以下流程:

  1. 声明本地方法
    在 Java 代码中,你需要声明一个本地方法。这个方法使用 native 关键字,并且没有方法体。例如:

    public class MyClass {public native void myNativeMethod();static {System.loadLibrary("MyNativeLib");}
    }

    System.loadLibrary("MyNativeLib") 用于加载包含本地方法实现的本地库。

  2. 生成头文件
    使用 javac 编译 Java 文件,然后使用 javah 工具生成对应的 C/C++ 头文件。例如:

    javac MyClass.java
    javah -jni MyClass

    这会生成一个名为 MyClass.h 的头文件,其中包含 JNI 函数签名。

  3. 实现本地方法
    在生成的头文件基础上,用 C 或 C++ 实现本地方法。例如:

    #include <jni.h>
    #include "MyClass.h"JNIEXPORT void JNICALL Java_MyClass_myNativeMethod(JNIEnv *env, jobject obj) {// 本地方法的实现
    }
  4. 编译本地库
    将实现本地方法的 C/C++ 代码编译成共享库或动态链接库。例如,在 Linux 上可以使用 gcc

    gcc -shared -o libMyNativeLib.so -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux MyClass.c

    在 Windows 上可以使用类似的命令生成 .dll 文件。

  5. 加载本地库
    当 Java 程序运行并调用 System.loadLibrary("MyNativeLib") 时,JVM 会加载指定的本地库。

  6. 调用本地方法
    当 Java 代码调用 myNativeMethod() 时,JVM 通过 JNI 调用对应的本地方法实现。此时,JNI 环境指针 (JNIEnv *) 被传递给本地方法,允许它访问 JNI 提供的函数和 JVM 的接口。

  7. 执行本地代码
    本地方法在本地库中执行,可以进行各种操作,包括调用底层系统 API、处理硬件设备、执行高性能计算等。

  8. 返回结果
    如果本地方法有返回值,它会将结果返回给 Java 层。如果发生异常,本地方法可以通过 JNIEnv * 抛出 Java 异常。

整个流程如下图所示:

Java 层:MyClass.java (声明本地方法)|v编译 -> MyClass.class|v生成头文件 -> MyClass.h|v
C/C++ 层:MyClass.c (实现本地方法)|v编译 -> libMyNativeLib.so / MyNativeLib.dll|v
Java 层:System.loadLibrary("MyNativeLib")|v调用本地方法 -> JNI 调用 -> 执行本地代码|v返回结果 / 抛出异常

通过这种方式,Java 程序可以与本地代码进行交互,实现更底层的功能或优化性能。

3、JNI 的 Rust 绑定

在 Rust 中和 Java 互相调用,可以使用原始的 JNI 接口,也就是自己声明 JNI 的 C 函数原型,在Rust 里按照 C 的方式去调用,但这样写起来会很繁琐,而且都是 unsafe 的操作。不过 Rust 社区里已经有人基于原始的 JNI 接口,封装好了一套 safe 的接口,crate 的名字就叫 jni ,用这个库来开发就方便多了。

三、互调示例

Step1、定义一个异步耗时操作 CostTimeOption

Step2、Java 端调用 Rust 端,由 Rust 端执行一个异步耗时操作 CostTimeOption

Step3、Rust 端调用 Java 端报告操作已消耗时长和进度

Step 1、创建一个 Maven 项目

import java.time.Duration;
import java.time.LocalDateTime;/*** @version: V1.0* @author: 余衫马* @description: MyJobAgent* @data: 2024-11-12 15:58**/
public class MyJobAgent {/*** 声明一个本地方法* 将 MyJobAgent 实例作为参数传递给 Rust 端*/private static native void runCostTimeFuncAsync(MyJobAgent callback);/*** 开始执行时间*/private static LocalDateTime startDatetime;// 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_bingding_demo");}public static void main(String[] args) {startDatetime = LocalDateTime.now();runCostTimeFuncAsync(new MyJobAgent());}/*** 回调方法,由 Rust 端回调 progress 进度*/public void asyncCallback(float progress) {// 计算两个时间点之间的 DurationDuration duration = Duration.between(startDatetime, LocalDateTime.now());// 获取分钟、秒和毫秒long minutes = duration.toMinutes();long seconds = duration.getSeconds();long millis = duration.toMillis();System.out.printf("当前进度:%f,已耗时:%d 分 %d 秒 %d 毫秒\n", progress, minutes, seconds, millis);}
}

我们创建了一个 MyJobAgent  代理工作类,声明了一个本地方法 runCostTimeFuncAsync ,参数是 MyJobAgent 实例是为了让 Rust 端能调用该实例的回调方法 asyncCallback,

private static native void runCostTimeFuncAsync(MyJobAgent callback);

 而回调方法 asyncCallback 需要接收一个参数 progress 输出进度,并计算目前已耗时多久,

    /*** 回调方法,由 Rust 端回调 progress 进度*/public void asyncCallback(float progress) {// 计算两个时间点之间的 DurationDuration duration = Duration.between(startDatetime, LocalDateTime.now());// 获取分钟、秒和毫秒long minutes = duration.toMinutes();long seconds = duration.getSeconds();long millis = duration.toMillis();System.out.printf("当前进度:%f,已耗时:%d 分 %d 秒 %d 毫秒", progress, minutes, seconds, millis);}

Linux 下的动态库 librust_jni_bingding_demo.so 只需要填 rust_jni_bingding_demo,

  // 用于加载包含本地方法实现的本地库。static {System.loadLibrary("rust_jni_bingding_demo");}

Step 2、创建一个 Rust 项目

cargo new  rust_jni_bingding_demo --lib

修改 Cargo.toml,添加 Rust jni 绑定库,并且设置 crate_type = ["cdylib"]

[dependencies]
jni = "0.21.1"[lib]
crate_type = ["cdylib"]

修改 lib.rs,

use std::sync::mpsc; // 引入多生产者单消费者通道模块
use std::thread::{self}; // 引入线程模块
use std::time::Duration; // 引入时间模块use jni::objects::*; // 引入JNI对象模块
use jni::sys::jfloat; // 引入JNI浮点数类型
use jni::JNIEnv; // 引入JNI环境模块// 定义一个外部函数,供Java调用
#[no_mangle]
pub extern "system" fn Java_MyJobAgent_runCostTimeFuncAsync<'local>(mut env: JNIEnv<'local>, // JNI环境参数_class: JClass<'local>, // 调用该方法的Java类callback: JObject<'local>, // 回调对象
) {// 获取当前Java虚拟机实例let jvm = env.get_java_vm().unwrap();// 创建全局引用,以便在其他线程中使用回调对象let mycallback = env.new_global_ref(callback).unwrap();// 创建一个多生产者单消费者通道let (tx, rx) = mpsc::channel();// 启动一个新线程let _ = thread::spawn(move || {// 在线程中发送一个信号,表示线程已经启动tx.send(()).unwrap();// 将当前线程附加到JVMlet mut myenv = jvm.attach_current_thread().unwrap();// 模拟一个耗时操作,并定期调用回调函数报告进度for i in 0..100 {let progress = i as jfloat; // 将进度转换为JNI浮点数类型// 调用回调方法,将进度传递给Java层 // 函数签名为(float)voidmyenv.call_method(&mycallback, "asyncCallback", "(F)V", &[progress.into()]).unwrap();// 休眠100毫秒,模拟耗时操作thread::sleep(Duration::from_millis(100));}});// 接收线程启动信号,确保线程已成功启动rx.recv().unwrap();
}

代码解析:

  1. 引入必要的模块:首先引入了标准库中的mpscthreadtime模块,以及JNI相关的模块。
  2. 定义外部函数:定义了一个外部函数Java_MyJobAgent_runCostTimeFuncAsync,这个函数将被Java代码调用。
  3. 获取JVM实例:通过env.get_java_vm()获取当前的Java虚拟机实例。
  4. 创建全局引用:将回调对象创建为全局引用,以便在线程中使用。
  5. 创建通道:创建一个多生产者单消费者通道,用于线程间通信。
  6. 启动新线程:启动一个新线程,在新线程中执行耗时操作。
  7. 附加线程到JVM:将新线程附加到JVM,以便在新线程中调用Java方法。
  8. 模拟耗时操作:在循环中模拟一个耗时操作,每次循环都会调用一次回调方法,将当前进度传递给Java层,并休眠100毫秒。
  9. 接收线程启动信号:主线程等待接收子线程发送的启动信号,确保子线程已成功启动。

这样,通过上述步骤,我们实现了一个异步任务,并在任务执行过程中定期向Java层报告进度。

Step 3、编译代码

编译 Rust 库

cargo build

我这里用的 linux 系统,动态库文件名为 librust_jni_bingding_demo.so,如果是 Windows 系统,文件名为 librust_jni_bingding_demo.dll,

编译 Java 代码

修改 pom.xml,在构建配置文件中指定主类,

<build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-jar-plugin</artifactId><version>3.2.0</version><configuration><archive><manifest><addDefaultImplementationEntries>true</addDefaultImplementationEntries><mainClass>MyJobAgent</mainClass></manifest></archive></configuration></plugin></plugins></build>

然后执行 maven 打包指令, 

mvn package

在 target 目录下生成 classes 字节码文件与 RustJniDemo-1.0-SNAPSHOT.jar 。 

Step 4、运行效果

这里使用的是 Open JDK8,

(base) sam@sam-PC:~/AwesomeWorkSpace/RustStudy/jni/rust_jni_bingding_demo$ java -version
openjdk version "1.8.0_212"
OpenJDK Runtime Environment (build 1.8.0_212-8u212-b01-1~deb9u1-b01)
OpenJDK 64-Bit Server VM (build 25.212-b01, mixed mode)
# 临时添加环境变量,否则会报错找不到库文件
# export PATH=$PATH:~/AwesomeWorkSpace/RustStudy/rust_jni_binding_demo/target/debug
java -Djava.library.path=target/debug -classpath target/classes MyJobAgent
# 或者直接跑 jar 包
java -Djava.library.path=target/debug -jar target/RustJniDemo-1.0-SNAPSHOT.jar 
  1. -Djava.library.path=target/debug:这是一个系统属性设置,-D选项用于定义系统属性。在这里,java.library.path属性被设置为target/debug,这通常是用来指定本地库(如JNI库)的搜索路径。

  2. -classpath target/classes:这是指定类路径的选项,-classpath-cp用于告诉JVM在哪里可以找到用户定义的类和包。在这个例子中,类路径被设置为target/classes,这意味着JVM会在target/classes目录下查找需要的类文件。

  3. MyJobAgent:这是要运行的主类的名称。这个类应该包含一个public static void main(String[] args)方法,这是Java应用程序的入口点。

四、总结

通过以上示例,我们成功实现了 Rust 与 Java 的互调。利用 JNI 技术,可以充分发挥 Rust 的性能优势,同时保持 Java 的跨平台特性。这种技术组合适用于对性能要求较高的应用场景,如图像处理、数据分析和系统级编程等。

参考资料

Rust与Java交互-JNI模块编写-实践总结 - Rust语言中文社区

Leveraging Rust in our high-performance Java database

https://docs.oracle.com/javase/8/docs/technotes/guides/jni/

jni - Rust

https://github.com/jni-rs/jni-rs

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/470099.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

使用HTML、CSS和JavaScript创建动态圣诞树

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 ✨特色专栏&#xff1a…

3DTiles之i3dm介绍

3DTiles之i3dm介绍 3D Tiles 是一种用于高效存储和传输三维城市、建筑、地形、点云等空间数据的开放标准格式。i3dm&#xff08;Intel 3D Model&#xff09;是 3D Tiles 中用于表示三维模型&#xff08;如建筑物或其他对象&#xff09;的一个子格式。i3dm 格式的出现&#xff…

重新认识HTTPS

一. 什么是 HTTPS HTTP 由于是明文传输&#xff0c;所谓的明文&#xff0c;就是说客户端与服务端通信的信息都是肉眼可见的&#xff0c;随意使用一个抓包工具都可以截获通信的内容。 所以安全上存在以下三个风险&#xff1a; 窃听风险&#xff0c;比如通信链路上可以获取通信…

pycharm快速更换虚拟环境

目录 1. 选择Conda 虚拟环境2. 创建环境3. 直接选择现有虚拟环境 1. 选择Conda 虚拟环境 2. 创建环境 3. 直接选择现有虚拟环境

AI生活之我用AI处理Excel表格

AI生活之我用AI处理Excel表格 场景再现AI提问词AI代码运行调试结果心得感受 场景再现 因学习需要&#xff0c;整理了某个题库&#xff0c;方便自己刷题使用。 已将每套题打上了制定标签&#xff0c;得到一个Excel表格。截图如下&#xff1a; 需求是&#xff1a;一共35套题&…

Cesium加载大量点数据卡顿处理办法

1.使用entity绘制随机点 // 随机生成 1000 个点 const numPoints 1000;for (let i 0; i < numPoints; i) {const lon Math.random() * 360 - 180;const lat Math.random() * 180 - 90;const height 50000 Math.random() * 5000;// 将点添加到 Cesium Viewer 中viewer…

SCUI Admin + Laravel 整合

基于 Vue3 和 Element Plus 和 Laravel 整合开发 项目地址&#xff1a;持续更新 LaravelVueProject: laravel vue3 scui

DAY6 线程

作业1&#xff1a; 多线程实现文件拷贝&#xff0c;线程1拷贝一半&#xff0c;线程2拷贝另一半&#xff0c;主线程回收子线程资源。 代码&#xff1a; #include <myhead.h> sem_t sem1; void *copy1()//子线程1函数 拷贝前一半内容 {int fd1open("./1.txt",O…

Docker入门系列——Docker-Compose

Docker Compose 是 Docker 官方编排工具&#xff0c;用于定义和运行多容器 Docker 应用程序。它是一个轻量级的工具&#xff0c;用于快速配置和启动应用程序的不同服务。 Docker Compose 是什么 Docker Compose 最初是由 Docker 公司开发&#xff0c;并于 2014 年 6 月首次发布…

实战指南:理解 ThreadLocal 原理并用于Java 多线程上下文管理

目录 一、ThreadLocal基本知识回顾分析 &#xff08;一&#xff09;ThreadLocal原理 &#xff08;二&#xff09;既然ThreadLocalMap的key是弱引用&#xff0c;GC之后key是否为null&#xff1f; &#xff08;三&#xff09;ThreadLocal中的内存泄漏问题及JDK处理方法 &…

Java基础-组件及事件处理(下)

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 面板组件 说明 常见组件 JScrollPane常用构造方法 JScrollPane设置面板滚动策略的方法 JScrollPane滚…

HarmonyOS ArkTS 下拉列表组件

Entry Component struct Index {defaultValue: string 下拉列表;// 定义选项数组&#xff0c;包含 value 和可选的 labeloptions: Array<SelectOption> [{ value: aaa },{ value: bbb },{ value: ccc },{ value: ddd },{ value: eee },{ value: fff },{ value: ggg },{…

第8章 利用CSS制作导航菜单

8.1 水平顶部导航栏 水平莱单导航栏是网站设计中应用范围最广的导航设计&#xff0c;一般放置在页面的顶部。水平 导航适用性强&#xff0c;几乎所有类型的网站都可以使用&#xff0c;设计难度较低。 如果导航过于普通&#xff0c;无法容纳复杂的信息结构&#xff0c;就需要在…

使用VSCode远程连接服务器并解决Neo4j无法登陆问题

摘要&#xff1a;本文介绍了如何通过VSCode连接内网部署的Neo4j服务器&#xff0c;并启动服务。在访问Neo4j登录界面时&#xff0c;遇到了端口映射问题导致无法登录。通过手动添加7687端口的映射后&#xff0c;成功登录Neo4j。 我在内网部署了一台服务器&#xff0c;并在其上运…

Linux手动安装nginx

本次以安装nginx-1.12.2为例 1、首先说明一下,安装nginx之前需要安装如下素材: 2、开始安装 第一步,安装依赖yum -y install gcc zlib zlib-devel pcre-devel openssl openssl-devel第二步,下载并安装nginx安装包(nginx官网:http://nginx.org/)# 下载 wget http://nginx…

Flink CDC(SQL Client)连接 MySQL 数据库教程

Flink CDC&#xff08;SQL Client&#xff09;连接 MySQL 数据库教程 这篇文章将指导如何使用 Flink CDC 连接到 MySQL 数据库&#xff0c;并捕获数据变更。我们将逐步完成以下操作&#xff1a; 1. 检查 Binlog 是否启用 首先&#xff0c;您需要确保 MySQL 的 Binlog 功能已…

蓝凌OA-EKP hrStaffWebService 任意文件读取漏洞

0x01 产品描述&#xff1a; ‌ 蓝凌OA-EKP‌是由深圳市蓝凌软件股份有限公司自主研发的一款数字化办公系统&#xff0c;主要适用于大中型企业在线化办公。它集成了流程管理、知识管理、会议管理、公文管理、合同管理、费用管控等多个模块&#xff0c;旨在帮助企业解决基础…

管家婆财贸ERP BB059.银行流水导入对账

最低适用版本: C系列 22.8 插件简要功能说明: 系统支持按固定模板导入银行流水明细银行流水支持销售单、销售退货单快捷对账,按单生成收款单银行流水支持生成其他付款业务单据更多细节描述见下方详细文档插件操作视频: 进销存类定制插件--银行流水导入对账 插件详细功能文…

第8章利用CSS制作导航菜单

8.1 水平顶部导航栏 8.1.1 简单水平导航栏的设计与实现 <nav>标签是HTML5新增的文档结构标签&#xff0c;用于标即导航栏&#xff0c;以便后续与网站的其他其内容整合&#xff0c;使用常用<nav>标签在页面上创建导航栏菜单区域。 8.1.1.1导航栏的创建 <!DOC…

给查询业务添加redis缓存和缓存更新策略

目录 一、添加redis缓存的主要逻辑 二、代码 三、结果 四、缓存更新策略 五、代码 一、添加redis缓存的主要逻辑 在未添加缓存时&#xff0c;前端向后端发起查询请求时&#xff0c;后端收到请求就直接查数据库&#xff0c;它的速度如下&#xff1a; 其实速度也不慢&#…