【Rust笔记】Rust与Java交互-JNI模块编写-实践总结

近期工作中有Rust和Java互相调用需求,这篇文章主要介绍如何用Rust通过JNI和Java进行交互,还有记录一下开发过程中遇到的一些坑。

JNI简单来说是一套Java与其他语言互相调用的标准,主要是C语言,官方也提供了基于C的C++接口。 既然是C语言接口,那么理论上支持C ABI的语言都可以和Java语言互相调用,Rust就是其中之一。

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

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

文中涉及的代码放在了这个github仓库 https://github.com/metaworm/rust-java-demo

Rust JNI 工程配置

如果你熟悉Cargo和Maven,可以跳过这一节,直接看我提供的github源码即可

Rust工程配置

首先,通过cargo new java-rust-demo创建一个rust工程

然后切换到工程目录cd java-rust-demo,并编辑Cargo.toml:修改类型为动态库、加上对 jni crate 的依赖

[package]
name = "rust-java-demo"
version = "0.1.0"
edition = "2021"[lib]
crate-type = ['cdylib'][dependencies]
jni = {version = '0.19'}

重命名src目录下的main.rslib.rs,Rust库类型的工程编译入口为 lib.rs,然后添加以下代码

use jni::objects::*;
use jni::JNIEnv;#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_init(env: JNIEnv, _class: JClass) {println!("rust-java-demo inited");
}

然后执行cargo build构建,生成的动态库默认会位于target/debug目录下,我这里用的linux系统,动态库文件名为librust_java_demo.so,如果是Windows系统,文件名为rust_java_demo.dll

这样,我们第一个JNI函数就创建成功了! 通过Java_pers_metaworm_RustJNI_init这个导出函数,给了Java的pers.metaworm.RustJNI这个类提供了一个native的静态方法init; 这里只是简单地打印了一句话,后面会通过这个初始化函数添加更多的功能

Java工程配置

还是在这个工程目录里,把Java部分的代码放在java这个目录下,在其中创建pers/metaworm/RustJNI.java文件

package pers.metaworm;public class RustJNI {static {System.loadLibrary("rust_java_demo");}public static void main(String[] args) {init();}static native void init();
}

我们使用流行的 maven 工具来构建Java工程,在项目根目录下创建 maven 的工程文件 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>pers.metaworm</groupId><artifactId>RustJNI</artifactId><version>1.0-SNAPSHOT</version><properties><exec.mainClass>pers.metaworm.RustJNI</exec.mainClass><maven.compiler.source>1.8</maven.compiler.source><maven.compiler.target>1.8</maven.compiler.target><maven.compiler.encoding>UTF-8</maven.compiler.encoding></properties><dependencies></dependencies><build><sourceDirectory>java</sourceDirectory><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><version>2.4</version><configuration><encoding>UTF-8</encoding></configuration></plugin></plugins></build>
</project>

运行 DMEO 工程

上面的工程配置弄好之后,就可以使用cargo build命令构建Rust提供的JNI动态库,mvn compile命令来编译Java代码

Rust和Java代码都编译好之后,执行java -Djava.library.path=target/debug -classpath target/classes pers.metaworm.RustJNI来运行

其中-Djava.library.path=target/debug指定了我们JNI动态库所在的路径,-classpath target/classes指定了Java代码的编译输出的类路径,pers.metaworm.RustJNI是Java main方法所在的类

不出意外的话,运行之后会在控制台输出init函数里打印的"rust-java-demo inited"

Java调用Rust

接口声明

前面的Java_pers_metaworm_RustJNI_init函数已经展示了如何给Java暴露一个native方法,即导出名称为Java_<类完整路径>_<方法名>的函数,然后在Java对应的类里声明对应的native方法

拓展:除了通过导出函数给Java提供native方法,还可以通过 RegisterNatives 函数动态注册native方法,对应的jni封装的函数为JNIEnv::register_native_methods,一般动态注册会在JNI_Onload这个导出函数里执行,jvm加载jni动态库时会执行这个函数(如果有的话)

当在Java里首次调用native方法时,JVM就会寻找对应名称的导出的或者动态注册的native函数,并将Java的native方法和Rust的函数关联起来;如果JVM没找到对应的native函数,则会报java.lang.UnsatisfiedLinkError异常

为了演示,我们再添加一些代码来覆盖更多的交互场景

lib.rs

use jni::objects::*;
use jni::sys::{jint, jobject, jstring};
use jni::JNIEnv;#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_addInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) -> jint {a + b
}#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_getThisField(env: JNIEnv,this: JObject,name: JString,sig: JString,
) -> jobject {let result = env.get_field(this,&env.get_string(name).unwrap().to_string_lossy(),&env.get_string(sig).unwrap().to_string_lossy(),).unwrap();result.l().unwrap().into_inner()
}

RustJNI.java

package pers.metaworm;public class RustJNI {static {System.loadLibrary("rust_java_demo");}public static void main(String[] args) {init();System.out.println("test addInt: " + (addInt(1, 2) == 3));RustJNI jni = new RustJNI();System.out.println("test getThisField: " + (jni.getThisField("stringField", "Ljava/lang/String;") == jni.stringField));System.out.println("test success");}String stringField = "abc";static native void init();static native int addInt(int a, int b);native Object getThisField(String name, String sig);
}

其中,addInt方法接收两个int参数,并返回相加的结果;getThisField是一个实例native方法,它获取this对象指定的字段并返回

参数传递

从上一节的例子里可以看到,jni函数的第一个参数总是JNIEnv,很多交互操作都需要通过这个对象来进行; 第二个参数是类对象(静态native方法)或this对象(实例native方法); 从第三个参数开始,每一个参数对应Java的native方法所声明的参数

对于基础的参数类型,可以直接用use jni::sys::*提供的j开头的系列类型来声明,类型对照表:

Java 类型Native 类型类型描述
booleanjbooleanunsigned 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsigned 16 bits
intjintsigned 32 bits
longjlongsigned 64 bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidnot applicable

对于引用类型(复合类型/对象类型),可以统一用jni::objects::JObject声明;JObject是对jobject的rust封装,带有生命周期参数;对于String类型,也可以用 JString 来声明,JString是对JObject的一层简单封装

抛异常

前面的Java_pers_metaworm_RustJNI_getThisField函数里,用了很多unwrap,这在生产环境中是非常危险的,万一传了一个不存在的字段名,就直接crash了;所以我们改进一下这个函数,让他支持抛异常,出错的时候能让Java捕获到

#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_getThisFieldSafely(env: JNIEnv,this: JObject,name: JString,sig: JString,
) -> jobject {let result = (|| {env.get_field(this,&env.get_string(name)?.to_string_lossy(),&env.get_string(sig)?.to_string_lossy(),)?.l()})();match result {Ok(res) => res.into_inner(),Err(err) => {env.exception_clear().expect("clear");env.throw_new("Ljava/lang/Exception;", format!("{err:?}")).expect("throw");std::ptr::null_mut()}}
}

Java层的测试代码为

try {System.out.println("test getThisFieldSafely: " + (jni.getThisFieldSafely("stringField", "Ljava/lang/String;") == jni.stringField));jni.getThisFieldSafely("fieldNotExists", "Ljava/lang/String;");} catch (Exception e) {System.out.println("test getThisFieldSafely: catched exception: " + e.toString());}

通过env.throw_new("Ljava/lang/Exception;", format!("{err:?}"))抛出了一个异常,从JNI函数返回后,Java就会捕获到这个异常; 代码里可以看到在抛异常之前,调用了env.exception_clear()来清除异常,这是因为前面的get_field已经抛出一个异常了,当env里已经有一个异常的时候,后续再调用env的函数都会失败,这个异常也会继续传递到上层的Java调用者,所以其实这里没有这两句,直接返回null的话,Java也可以捕获到异常;但我们通过throw_new可以自定义异常类型及异常消息

这其实不是一个典型的场景,典型的场景应该是Rust里的某个调用返回了Error,然后通过抛异常的形式传递到Java层,比如除0错误

#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_divInt(env: JNIEnv,_class: JClass,a: jint,b: jint,
) -> jint {if b == 0 {env.throw_new("Ljava/lang/Exception;", "divide zero").expect("throw");0} else {a / b}
}

Rust调用Java

创建对象、调用方法、访问字段...

下面用一段代码展示如何在Rust中创建Java对象、调用方法、获取字段、处理异常等常见用法

#[allow(non_snake_case)]
fn call_java(env: &JNIEnv) {match (|| {let File = env.find_class("java/io/File")?;// 获取静态字段let separator = env.get_static_field(File, "separator", "Ljava/lang/String;")?;let separator = env.get_string(separator.l()?.into())?.to_string_lossy().to_string();println!("File.separator: {}", separator);assert_eq!(separator, format!("{}", std::path::MAIN_SEPARATOR));// env.get_static_field_unchecked(class, field, ty)// 创建实例对象let file = env.new_object("java/io/File","(Ljava/lang/String;)V",&[JValue::Object(env.new_string("")?.into())],)?;// 调用实例方法let abs = env.call_method(file, "getAbsolutePath", "()Ljava/lang/String;", &[])?;let abs_path = env.get_string(abs.l()?.into())?.to_string_lossy().to_string();println!("abs_path: {}", abs_path);jni::errors::Result::Ok(())})() {Ok(_) => {}// 捕获异常Err(jni::errors::Error::JavaException) => {let except = env.exception_occurred().expect("exception_occurred");let err = env.call_method(except, "toString", "()Ljava/lang/String;", &[]).and_then(|e| Ok(env.get_string(e.l()?.into())?.to_string_lossy().to_string())).unwrap_or_default();env.exception_clear().expect("clear exception");println!("call java exception occurred: {err}");}Err(err) => {println!("call java error: {err:?}");}}
}#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_callJava(env: JNIEnv) {println!("call java");call_java(&env)
}

总结一下常用的函数,具体用法可以参考JNIEnv的文档

  • 创建对象 new_object

  • 创建字符串对象 new_string

  • 调用方法 call_method call_static_method

  • 获取字段 get_field get_static_field

  • 修改字段 set_field set_static_field

要注意的是调用方法、创建对象等需要传一个方法类型签名,这是因为Java支持方法重载,同一个类里一个名称的函数可能有多个,所以需要通过类型签名来区分,类型签名的规则可以参考官方文档

异常处理

call_java函数展示了如何在Rust中处理Java的异常: 通过JNIEnv对象动态获取字段或者调用方法,都会返回一个jni::errors::Result类型,对应的Error类型为jni::errors::Error;如果Error是jni::errors::Error::JavaException则表明在JVM执行过程中,某个地方抛出了异常,这种情况下就可以用exception_occurred函数来获取异常对象进行处理,然后调用exception_clear来清除异常,如果再返回到Java便可以继续执行

在非Java线程中调用Java

从Java中调用的Rust代码,本身就处于一个Java线程中,第一个参数为JNIEnv对象,Rust代码用这个对象和Java进行交互; 实际应用场景中,可能需要从一个非Java线程或者说我们自己的线程中去调用Java的方法,但我们的线程没有JNIEnv对象,这时就需要调用JavaVM::attach_current_thread函数将当前线程附加到JVM上,来获得一个JNIEnv

#[no_mangle]
pub unsafe extern "C" fn Java_pers_metaworm_RustJNI_callJavaThread(env: JNIEnv) {let vm = env.get_java_vm().expect("get jvm");std::thread::spawn(move || {println!("call java in another thread");let env = vm.attach_current_thread().expect("attach");call_java(&env);});
}

attach_current_thread函数返回一个AttachGuard对象,可以解引用为JNIEnv,并且在作用域结束drop的时候自动调用detach_current_thread函数;原始的AttachCurrentThreadJNI函数,如果当前线程已经attach了,则会抛异常,jni crate里的JavaVM::attach_current_thread做了一层封装,如果当前已经attach了,则会返回之前attach的对象,保证不会重复attach

JavaVM对象通过JNIEnv::get_java_vm函数获取,可以在初始化的时候将这个变量存起来,给后续的其他线程使用

局部引用、全局引用与对象缓存

关于局部引用与全局引用的官方文档

Rust提供的native函数,传过来的对象引用都是局部引用,局部引用只在本次调用JNI调用范围内有效,而且不能跨线程使用;如果跨线程,必须使用全局引用

可以通过JNIEnv::new_global_ref来获取JClass、JObject的全局引用,这个函数返回一个GlobalRef对象,可以通过GlobalRef::as_object转成JObject或者JClass等对象;GlobalRef对象drop的时候,会调用DeleteGlobalRef将JVM内部的引用删除

前面的代码,从Rust调用Java方法都是通过名称加方法签名调用的,这种方式,写起来很舒服,但运行效率肯定是非常低的,因为每次都要通过名称去查找对应的方法

其实JNI原始的C接口,是通过jobjectID、jclassID、jmethodID、jfieldID来和Java交互的,只不过是jni crate给封装了一层比较友好的接口

如果我们对性能要求比较高,则可以在初始化的时候获取一些JClass、JObject的全局引用,缓存起来,后面再转成JClass、JObject来使用,千万不要对jmethodID、jfieldID获取全局引用,因为这俩都是通过jclassID生成的,其声明周期和jclassID对应的对象相同,不是需要GC的对象,如果对jmethodID获取全局引用然后调用,会导致某些JVM Crash;对于jmethodID、jfieldID,则可以基于JClass、JObject的全局引用获取,后面直接使用即可

获取到这些全局的ID之后,就可以通过JNIEnv::call_method_unchecked系列函数,来更高效地调用Java

我用Rust强大的宏,实现了这个过程,可以让我们直接在Rust中以声明的方式缓存的所需类及其方法ID

#[allow(non_snake_case)]
pub mod cache {use anyhow::Context;use jni::errors::Result as JniResult;use jni::objects::*;use jni::JNIEnv;pub fn method_global_ref<'a>(env: JNIEnv<'a>,class: JClass,name: &str,sig: &str,) -> JniResult<JMethodID<'a>> {let method = env.get_method_id(class, name, sig)?.into_inner();Ok(JMethodID::from(method.cast()))}pub fn static_method_global_ref<'a>(env: JNIEnv<'a>,class: JClass,name: &str,sig: &str,) -> ::jni::errors::Result<JStaticMethodID<'a>> {let method = env.get_static_method_id(class, name, sig)?.into_inner();Ok(JStaticMethodID::from(method.cast()))}macro_rules! gen_global_ref {(@method_type) => { JMethodID<'static> };(@method_type static) => { JStaticMethodID<'static> };(@method_ref) => { method_global_ref };(@method_ref static) => { static_method_global_ref };($(#[name = $classname:literal]class $name:ident {$($method:ident : $($modify:ident)* $sig:literal,)*})*) => {$(#[allow(non_snake_case)]pub struct $name {pub class: JClass<'static>,$(pub $method: gen_global_ref!(@method_type $($modify)*),)*}impl $name {pub fn from_env(env: JNIEnv<'static>) -> anyhow::Result<Self> {Self::from_class(env, env.find_class($classname)?)}pub fn from_class(env: JNIEnv<'static>, class: JClass) -> anyhow::Result<Self> {let cls = env.new_global_ref(class)?;let class = JClass::from(*cls.as_obj());core::mem::forget(cls);Ok(Self {class,$($method: gen_global_ref!(@method_ref $($modify)*)(env, class, stringify!($method), $sig).context(stringify!($method))?,)*})}}// TODO: impl Drop)*pub struct CachedClasses {$(pub $name: $name,)*}impl CachedClasses {pub fn from_env(env: JNIEnv<'static>) -> anyhow::Result<Self> {Ok(Self {$($name: $name::from_env(env).context(stringify!($name))?,)*})}}unsafe impl Sync for CachedClasses {}unsafe impl Send for CachedClasses {}}}gen_global_ref! {#[name = "java/lang/Thread"]class Thread {currentThread: static "()Ljava/lang/Thread;",getStackTrace: "()[Ljava/lang/StackTraceElement;",}#[name = "java/lang/StackTraceElement"]class StackTraceElement {getLineNumber: "()I",toString: "()Ljava/lang/String;",}#[name = "java/io/File"]class File {getAbsolutePath: "()Ljava/lang/String;",}}static mut CLASSES: Option<Box<CachedClasses>> = None;pub unsafe fn init(env: JNIEnv<'static>) -> anyhow::Result<Option<Box<CachedClasses>>> {Ok(CLASSES.replace(CachedClasses::from_env(env)?.into()))}pub fn get() -> &'static CachedClasses {unsafe { CLASSES.as_ref().expect("Cached Java Classed not inited") }}
}

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

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

相关文章

介绍6种解决电脑找不到vcomp140.dll,无法继续执行代码的方法。

在编程和软件开发领域&#xff0c;我们经常会遇到各种错误和问题。其中&#xff0c;找不到vcomp140.dll文件导致无法继续执行代码是一个非常常见的问题。这个问题可能会影响到软件的正常运行&#xff0c;甚至导致整个项目延期。因此&#xff0c;我们需要找到解决方案来解决这个…

【07】基础知识:React中的事件处理

React 中 通过 onXxx 属性指定事件处理函数&#xff08;注意大小写&#xff09;&#xff0c;通过 event.target 得到发生事件的 DOM 元素对象 不要过度使用 ref &#xff08;比如&#xff0c;元素获取自身属性时&#xff0c;通过 event 对象&#xff0c;而不是 ref&#xff09…

如何导出带有材质的GLB模型?

1、为什么要使用 GLB 模型? GLB格式&#xff08;GLTF Binary&#xff09;是一种用于存储和传输3D模型及相关数据的文件格式&#xff0c;具有以下优点和作用&#xff1a; 统一性&#xff1a;GLB是一种开放标准的3D文件格式&#xff0c;由Khronos Group制定和维护。它融合了GL…

C# 图解教程 第5版 —— 第6章 方法

文章目录 6.1 方法的结构6.2 方法体内部的代码执行6.3 局部变量6.3.1 类型推断和 var 关键字6.3.2 嵌套块中的局部变量 6.4 局部常量6.5 控制流6.6 方法调用&#xff08;*&#xff09;6.7 返回值&#xff08;*&#xff09;6.8 返回语句和 void 方法6.9 局部函数6.10 参数&#…

Vue3 条件语句

条件判断 v-if 条件判断使用 v-if 指令&#xff0c;指令的表达式返回 true 时才会显示&#xff1a; <div id"app"><p v-if"seen">现在你看到我了</p> </div><script> const app {data() {return {seen: true /* 改为fal…

mysql面试题50:500台数据库,如何在最快时间之内重启

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:500台db,如何在最快时间之内重启呢? 如果需要在最快时间内重启500台数据库,可以考虑采用并行化和自动化的方法来提高效率。比如: 编写自动化脚…

vh、vw、vmin、vmax

1、分别是什么&#xff1f; vh:指屏幕可见视窗的高&#xff0c; vw:指屏幕可见视窗的宽&#xff0c; vmin:vh和vw之间选较小的值&#xff0c; vmax:vh和vw之间选较大的值。 2、和百分比的区别 百分比时基于父元素的宽高&#xff0c;而vh\vw\vmin\vmax基于屏幕可见视图的宽…

protobuf 插件(option)使用

protobuf的option使用 一、需求 来源于工作中的一个需求&#xff1a;在传递message时需要对message中不同的字段进行不同的处理&#xff0c;而处理方式通过注释标注在了每个字段的定义后。 类似于有下面这样一个消息&#xff1a; 其中字段1是始终需要的&#xff0c;字段2和3…

Mall脚手架总结(五) —— SpringBoot整合MinIO实现文件管理

前言 在项目中我们经常有资源的上传和下载的功能需求&#xff0c;比如用户头像、产品图片、配置文件等等&#xff0c;大数据量的文件存储无疑需要更高性能的数据存储服务&#xff0c;对于无需对结构实现复杂查询的文件对象来说&#xff0c;对象存储服务无疑是一个较好的选择&am…

uniapp编译到小程序Component is not found in path “components/energy/illumination“

Component is not found in path "components/energy/illumination" 直接清除缓存重新编译

Ubuntu下怎么配置vsftpd

2023年10月12日&#xff0c;周四中午 目录 首先要添加一个系统用户然后设置这个系统用户的密码给新创建的系统用户创建主目录启动vsftpd服务查看vsftpd服务的状态打开外界访问vsftpd服务所需的端口获取服务器的IP地址大功告成 首先要添加一个系统用户 useradd 用户名然后设置…

APP备案避坑指南,值得收藏

目录 什么时间节点前需完成备案&#xff1f; APP/小程序一定要做备案吗&#xff1f; 涉及前置审批的APP有哪些&#xff1f; APP 支持安卓、IOS 多个运行平台&#xff0c;应该备案多少次&#xff1f; 企业是自有服务器&#xff0c;该如何进行APP备案&#xff1f; APP备案可…

【Spring框架】Spring监听器的简介和基本使用

目录 一、观察者模式 1.1 模型介绍 1.2 观察者模式Demo 1.2.1 观察者实体 1.2.2 主题实体 1.2.3 测试代码 二、Spring监听器的介绍 2.1 事件&#xff08;ApplicationEvent&#xff09; 2.1.1 Spring内置事件 2.1.2 Spring内置事件 2.2 事件监听器&#xff08;Applic…

C/C++基础讲解(一百三十一)之经典篇(信息合并/平均分数存储)

C/C++基础讲解(一百三十一)之经典篇(信息合并/平均分数存储) 程序之美 前言 很多时候,特别是刚步入大学的学子们,对于刚刚开展的计算机课程基本上是一团迷雾,想要弄明白其中的奥秘,真的要花费一些功夫,我和大家一样都是这么啃过来的,从不知到知知,懵懂到入门,每一步…

三大方法快速发现商业规律

文章目录 三大方法快速发现商业规律一、市场调研二、数据分析三、案例分析 &#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作者、产品软文创造者、技术文章评审老师、问卷调查…

PHP LFI 利用临时文件Getshell

PHP LFI 利用临时文件 Getshell 姿势-安全客 - 安全资讯平台 LFI 绕过 Session 包含限制 Getshell-安全客 - 安全资讯平台 目录 PHP LFI 利用临时文件Getshell 临时文件 linux 和 windows的 临时文件存储规则 linux和windows对临时文件的命名规则 PHPINFO()特性 原理 条…

Python点击exe后报错:Failed to execute script xxxx问题的解决办法

最近工作在弄人脸识别的问题&#xff0c;从gitee来pull了一个但是发现报了一个Failed to execute script XXX的问题 造成这个问题的原因是执行文件exe存放的目录不对&#xff0c;可能在打包前exe文件并不是存在在这个位置。 解决方案将exe文件尝试存在在不同目录下&#xff…

详解Pinia和Vuex

一、vuex介绍 1.什么是vuex&#xff1f;为什么要使用vuex? Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式 库。它采用集中式存储管理应用的所有组件的状态&#xff0c;并以相应的规则保证状态以一种可预测的方式发生变化。 在vue最重要的就是数据驱动和组件化&#x…

JAVA经典百题之按位与运算符 ``的使用

当学习Java语言中的按位与运算符 & 时&#xff0c;需要理解其用途、应用场景、示例源代码以及相应的注意事项。以下是一篇关于Java语言按位与运算符的详细文章&#xff0c;包括示例源代码和注释。 Java语言中的按位与运算符 & 按位与运算符 & 是Java语言中用于对…

Qt之submodule编译

工作中会遇到这样一种情况&#xff1a;qt应用程序在运行时提示找不到某个qt的动态库。我遇到的是缺少libQt5Websocket.so&#xff0c;因为应用程序是在x86平台银河麒麟v10上开发&#xff0c;能够正常编译运行&#xff0c;然后移植到rk3588&#xff08;aarch64架构&#xff09;上…