《应用导航设计:裂变式路由风暴来袭》——HarmonyOS开发项目时的Navigation路由奇妙使用

文章目录

  • 应用导航设计
    • 引言
    • 概述
    • 场景示例
    • 基本实现
    • 推荐方案
      • 路由管理模块的实现
      • 页面跳转实现
    • 业务实现中的关键点
      • 动态加载
      • 路由栈管理

应用导航设计

引言

在大型应用开发中,如何高效地设计应用导航,处理多模块间的路由跳转与解耦,始终是一个关键挑战。随着应用规模的扩大,业务模块增多,各模块间的 UI 组件相互跳转需求频繁出现,传统的导航设计往往导致模块间相互依赖、耦合严重,影响开发效率、可维护性以及应用的启动速度。本文将深入探讨应用导航设计的优化方案,从华为官方提供的基于 Navigation 的路由设计基础出发,剖析其存在的问题,并详细阐述推荐的路由功能抽取与模块解耦方案,帮助开发者更好地应对大型应用导航设计中的复杂问题,提升应用开发的整体质量与效率。

概述

大型应用开发中,应用可能包含不同的业务模块,每个模块由不同的业务团队负责开发。该场景采用一个Navigation下多个har/hsp的架构,其中一个模块对应一个har/hsp。当多个har/hsp的UI组件存在相互跳转的业务需求时,将出现模块间相互依赖的问题。如“A.har”、“B.har”和“C.har”模块拥有不同的组件,各组件间的路由跳转形成了一个环形链路,导致三个har模块相互耦合,如图所示:

img

针对该场景,华为官方也提供了一套基于Navigation的路由设计的方案实现多模块路由管理和模块间解耦。并在该基础上,通过动态注册路由的方式,解决页面加载多个UI组件时启动速度变慢问题。Navigation组件的使用方法可以见

在哪里?在哪里!设置到底在哪里!!!HarmonyOS移动应用开发——设置组件导航(Navigation组件、Tabs组件实现页面导航)

场景示例

假定工程包含harA和harB两个业务模块,harA模块打包编译为A.har,harB模块打包编译为B.har。在实际业务中,harA模块中的A1组件需要跳转到harB模块中的B1组件,项目关系如下图所示。例如登录场景,登录界面和登录后的主要页面是两个独立的模块,用户在登录结束之后,就可以直接进入首页开始使用app相关内容。

img

基本实现

假设我们现在已经拥有了har.A:default和A1:MaiPage,以及har.B:homepage和B1:HomePageView,和har.C:mine和C1:MineView

具体实现步骤如下

  1. 在defaule模块中的MaiPage页面组件中开发Navigation组件,并关联与之对应的NavPathStack路由栈,示例代码如下:

    @Component
    struct MaiPage {// 创建NavPathStack路由栈对象@Provide('MainPagePathStack') MainPagePathStack: NavPathStack = new NavPathStack();build() {// Navigation组件关联NavPathStack对象Navigation(this.MainPagePathStack) {// ...}}
    }
    
  2. 在defaule模块的oh-package.json5文件中添加Mine模块和homepage模块的依赖,示例代码如下:

    "dependencies": {// 添加依赖"homepage": "file:../../features/homepage","mine": "file:../../features/mine"
    }
    
  3. 在defaulet模块的mainpage中借助Tabs组件实现页面的跳转的逻辑,完整实例代码如下:

    //default模块的mainpage组件
    import { HomePageView } from 'homepage'
    import { MineView } from 'mine';
    import { TabBarType } from '../model/TabBarModel';
    import { CustomTabBar } from '../components/CustomTabBar';@Entry
    @Component
    struct MainPage {// 创建NavPathStack路由栈@Provide('MainPagePathStack') MainPagePathStack: NavPathStack = new NavPathStack();@Provide('minePathStack') minePathStack: NavPathStack = new NavPathStack();@Provide('homePagePathStack') homePagePathStack: NavPathStack = new NavPathStack();@State currentIndex: TabBarType = TabBarType.MAINPAGEbuild() {// Navigation关联NavPathStack对象Navigation(this.MainPagePathStack) {Flex({ direction: FlexDirection.Column }) {Tabs({ index: this.currentIndex }) {TabContent() {//通过Tabs组件跳转页面,同时在该页面也应该注册HomePageView()}TabContent() {MineView()}}.layoutWeight(1).barHeight(0).scrollable(false).onChange((index) => {this.currentIndex = index;})CustomTabBar({ currentIndex: $currentIndex })}.width('100%')}.hideTitleBar(true).mode(NavigationMode.Stack)}
    }
    
    //homepage模块的HomePageView组件
    @Component
    export struct HomePageView {
    //接收相关路由@Consume('homePagePathStack') homePagePathStack: NavPathStack;build() {Navigation(this.homePagePathStack) {Row() {Text('这里是首页')}.width('100%').height('100%')}}
    }
    
    //mine模块的MineView组件
    import { UserInfoView } from "./UserInfoView";
    import { MineHeaderView } from "./MineHeaderView";@Component
    export struct MineView {
    //接收相关路由@Consume('minePathStack') minePathStack: NavPathStack;build() {Navigation(this.minePathStack) {Row() {Column() {MineHeaderView().margin({ top: 10 })UserInfoView().margin({ top: 20 })}.justifyContent(FlexAlign.Start).alignItems(HorizontalAlign.Center).width('100%')}.alignItems(VerticalAlign.Top).width('100%').height('100%')}}
    }
    

当前方案主要存在以下问题:

  • 模块无法独立编译且存在开发态模块间循环依赖问题。
  • 子模块的页面跳转也可能出现过于耦合的情况。
  • 因为使用了Tabs组件,所以跳转的组件是平行跳转的,而在真正的项目开发中,在TabContent中也一定会再次跳转。

所以官方还给出了routerMap路由表的方式:

  1. 在harA模块中的A1页面组件中开发Navigation组件,并关联与之对应的NavPathStack路由栈,示例代码如下:

    @Component
    struct A1 {// 创建NavPathStack路由栈对象@State harARouter: NavPathStack = new NavPathStack();build() {// Navigation组件关联NavPathStack对象Navigation(this.harARouter) {// ...}}
    }
    
  2. 在harA模块的oh-package.json5文件中添加harB模块的依赖,并且把harB模块中需要跳转的B1组件添加到harA模块的Navigation组件路由表中,示例代码如下

    "dependencies": {// 添加对harB的依赖"@ohos/harb": "file:../harB"
    }

    在harA模块的A1组件中的routerMap路由表中,添加harB模块的B1组件,示例代码如下:

    import { B1 } from '@ohos/harb';
    struct A1 {@State harARouter: NavPathStack = new NavPathStack();@BuilderrouterMap(builderName: string, param: object) {if (builderName === 'B1') {B1() // 在routerMap中添加需要跳转的harB模块的B1页面}}build() {Navigation(this.harARouter) {// ...}.navDestination(this.routerMap) // Navigation关联上routerMap路由表}
    }
    
  3. 在harA模块的Navigation组件中添加跳转到harB模块的B1页面的逻辑,完整示例代码如下:

    import { B1 } from '@ohos/harb';struct A1 {// 创建NavPathStack路由栈@State harARouter: NavPathStack = new NavPathStack();@BuilderrouterMap(builderName: string, param: object) {if (builderName === 'B1') {B1() // 在routerMap中添加需要跳转的harB模块的B1页面}}build() {// Navigation关联NavPathStack对象Navigation(this.harARouter) {Button('跳转到HarB的B1页面').onClick(() => {// 跳转到已在路由表注册的harB模块的B1页面this.harARouter.pushPathByName('B1', null);})}.navDestination(this.routerMap) // Navigation关联上routerMap路由表}
    }
    

当前基本方案主要存在以下问题:

  • 使用Navigation时,所有路由页面需要主动通过import方式逐个导入当前页面,并存入页面路由表routerMap中。
  • 主动使用import的方式需显性指定加载路径,造成开发态模块耦合严重。
  • 模块无法独立编译,且存在开发态模块间循环依赖问题。

为解决上述问题,推荐如下方案。

推荐方案

将路由功能抽取成单独的模块并以har包形式存在,命名为RouterModule。RouterModule内部对路由进行管理,对外暴露RouterModule对象供其他模块使用。由于Entry.hap是应用必备的主入口,利用该特性考虑将主入口模块作为其他业务模块的依赖注册中心,在入口模块中使用Navigation组件并依赖其他业务模块。业务模块仅依赖RouterModule,业务模块中的路由统一委托到RouterModule中管理,实现业务模块间的解耦。按照推荐方案,上述场景各模块依赖关系如下:

img

此方案中,各模块的依赖关系如下:

  • Entry.hap、A.har和B.har均依赖了RouterModule.har;
  • Entry.hap在工程配置中依赖了A.har和B.har;
  • 对于业务开发团队之间,A.har在工程和源码上无需依赖B.har的库,实现了业务模块间的解耦。

路由管理模块的实现

RouterModule模块包含全局的路由栈和路由表信息。路由栈是NavPathStack对象,该对象与Entry.hap的Navigation组件绑定,RouterModule通过持有NavPathStack管理Navigation组件的路由信息。路由表builderMap是Map结构,以key-vaule的形式存储了需要路由的页面组件信息,其中key是自定义的唯一路由名,value是WrappedBuilder对象,该对象包裹了路由名对应的页面组件。RouterModule模块结构如下:

img

RouterModule模块的实现主要包含以下步骤:

  1. 定义路由表和路由栈。
export class RouterModule {// WrappedBuilder支持@Builder描述的组件以参数的形式进行封装存储static builderMap: Map<string, WrappedBuilder<[object]>> = new Map<string, WrappedBuilder<[object]>>();// 初始化路由栈,需要关联Navigation组件static routerMap: Map<string, NavPathStack> = new Map<string, NavPathStack>();// ...
}
  1. 路由表增加路由注册和路由获取方法,业务har模块通过路由注册方法将需要路由的页面组件委托给RouterModule管理。
// 通过名称注册路由栈
public static registerBuilder(builderName: string, builder: WrappedBuilder<[object]>): void {RouterModule.builderMap.set(builderName, builder);
}// 获取路由表中指定的页面组件
public static getBuilder(builderName: string): WrappedBuilder<[object]> {const builder = RouterModule.builderMap.get(builderName);if (!builder) {Logger.info('not found builder ' + builderName);}return builder as WrappedBuilder<[object]>;
}

​ 3.路由表增加路由跳转方法,业务har模块通过调用该方法并指定跳转信息实现模块间路由跳转。

public static async push(router: RouterModel): Promise<void> {const harName = router.builderName.split('_')[0];await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(router.builderName));RouterModule.getRouter(router.routerName).pushPath({ name: router.builderName, param: router.param });
}

页面跳转实现

路由管理模块RouterModule实现之后,需要使用RouterModule模块实现业务模块harA的页面跳转到业务模块harB的页面功能。主要步骤如下:

  1. 在工程主入口模块Entry.hap中引入RouterModule模块和所有需要进行路由注册的业务har模块。

    "dependencies": {"@ohos/routermodule": "file:../RouterModule","@ohos/hara": "file:../harA","@ohos/harb": "file:../harB","@ohos/harc": "file:../harC"
    }
    
  2. 在工程主入口模块Entry.hap中配置build-profile.json5文件,在该文件中修改packages字段,将需要进行路由注册的业务har模块写入配置。

    {// ..."buildOption": {"arkOptions": {"runtimeOnly": {"sources": [],"packages": ["@ohos/hara","@ohos/harb","@ohos/harc"]}}},
    }
    
  3. 在工程主入口模块的首页Navigation组件上关联RouterModule模块的路由栈和路由表。其中RouterModule.createRouter()与RouterModule.getBuilder()方法的实现。

    @Entry
    @Component
    struct EntryHap {@State entryHapRouter: NavPathStack = new NavPathStack();aboutToAppear() {if (!this.entryHapRouter) {this.entryHapRouter = new NavPathStack();}RouterModule.createRouter(RouterNameConstants.ENTRY_HAP, this.entryHapRouter);};@BuilderrouterMap(builderName: string, param: object) {// Obtain the WrappedBuilder object based on the module name, create a page through the builder interface, and import the param parameter.RouterModule.getBuilder(builderName).builder(param);};build() {Navigation(this.entryHapRouter) {// ...}.title('NavIndex').navDestination(this.routerMap);}
    }
    
  4. 在harB中声明需要跳转的页面,并且调用registerBuilder接口将页面注册到RouterModule模块的全局路由表上。以下注册逻辑会在harB的B1页面被首次加载时触发。

    // harB模块的B1页面
    @Builder
    export function harBuilder(value: object) {NavDestination() {Column() {// ...}// ...}// ...
    }// 在页面首次加载时触发执行
    const builderName = BuilderNameConstants.HARB_B1;
    // 判断表中是否已存在路由信息,避免重复注册
    if (!RouterModule.getBuilder(builderName)) {// 通过系统提供的wrapBuilder接口封装@Builder装饰的方法,生成harB1页面builderlet builder: WrappedBuilder<[object]> = wrapBuilder(harBuilder);// 注册harB1页面到全局路由表RouterModule.registerBuilder(builderName, builder);
    }
    
  5. 在harA模块中的A1页面调用RouterModule模块的push方法实现跳转到harB的B1页面。当harB的B1页面被首次通过push方法跳转时,会动态加载B1页面,并且触发步骤4中B1页面的路由注册逻辑,把B1页面注册到RouterModule的全局路由表builderMap中。

    @Builder
    export function harBuilder(value: object) {NavDestination() {Column() {// ...Button($r("app.string.to_harb_pageB1"), { stateEffect: true, type: ButtonType.Capsule }).width('80%').height(40).margin(20).onClick(() => {buildRouterModel(RouterNameConstants.ENTRY_HAP, BuilderNameConstants.HARB_B1);})}.width('100%').height('100%')}.title('A1Page').onBackPressed(() => {RouterModule.pop(RouterNameConstants.ENTRY_HAP);return true;})
    }
    

    上述方案,当在entry模块页面上点击跳转到harA模块的页面时序图如下:

img

业务实现中的关键点

动态加载

上述方案实现解耦的关键是使用了动态加载的方式和自执行的函数。针对该解决方案可以根据业务需求进一步优化和封装。例如harB中需要注册多个页面:B1、B2、B3。改进方式如下:

  1. 在harB的对外导出类Index.ets中定义加载时的初始化函数harInit,该函数对harB中需要注册路由的页面组件进行加载管理,被调用时将根据不同的路径动态加载不同的页面。
export function harInit(builderName: string): void {// 根据routerModule中路由表的key值动态加载要跳转的页面的相对路径switch (builderName) {case BuilderNameConstants.HARB_B1:import("./src/main/ets/components/mainpage/B1");break;case BuilderNameConstants.HARB_B2:import("./src/main/ets/components/mainpage/B2");break;case BuilderNameConstants.HARB_B3:import("./src/main/ets/components/mainpage/B3");break;default:break;}
}
  1. 优化RouterModule模块中的push方法。为了便于路由跳转时能携带更多信息,增加路由信息类RouterModel作为push时的入参。在push方法中通过该参数获取跳转页面所在的包名、路由名和所需的参数信息。通过包名成功加载har 模块后,根据路由名builderName调用har 模块index页面上定义的harInit函数,实现har模块内多页面的动态加载。
// 路由信息类,便于跳转时传递更多信息
export class RouterModel {// 路由页面别名,形式为${包名}_${页面名}builderName: string = "";// 路由栈名称routerName: string = "";// 需要传入页面的参数param?: object = new Object();
}// 创建路由信息,并放到路由栈表中
export function buildRouterModel(routerName: string, builderName: string, param?: object) {let router: RouterModel = new RouterModel();router.builderName = builderName;router.routerName = routerName;router.param = param;RouterModule.push(router);
}

路由栈管理

有些业务场景中存在需要使用多个Navigation组件的情况,该场景下需要在RouterModule中管理多个与Navigation组件对应的路由栈NavPathStack对象。此时,可以在RouterModule模块中建立一个路由栈表,以key-value的形式存储多个Navigation组件对应的的路由栈,以此实现多路由栈的管理。增加路由栈后,RouterModule中的路由方法都需要先通过routerName获取到路由栈,再进行方法调用。代码如下:

export class RouterModule {// ...// 初始化路由栈,需要关联Navigation组件static routerMap: Map<string, NavPathStack> = new Map<string, NavPathStack>();// 通过名称注册路由栈public static createRouter(routerName: string, router: NavPathStack): void {RouterModule.routerMap.set(routerName, router);}// 通过名称获取路由栈public static getRouter(routerName: string): NavPathStack {return RouterModule.routerMap.get(routerName) as NavPathStack;}// 通过传入RouterModule跳转到指定页面组件,RouterModule中需要增加routerName字段用于获取路由栈public static async push(router: RouterModel): Promise<void> {const harName = router.builderName.split('_')[0];await import(harName).then((ns: ESObject): Promise<void> => ns.harInit(router.builderName));RouterModule.getRouter(router.routerName).pushPath({ name: router.builderName, param: router.param });}

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

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

相关文章

【腾讯云】AI驱动TDSQL-C Serveress 数据库技术实战营-如何是从0到1体验电商可视化分析小助手得统计功能,一句话就能输出目标统计图

欢迎来到《小5讲堂》 这是《腾讯云》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解。 温馨提示&#xff1a;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 目录 背景效果图流程图创建数据库基本信息数据库配置设置密码控制台开启…

win服务器的架设、windows server 2012 R2 系统的下载与安装使用

文章目录 windows server 2012 R2 系统的下载与安装使用1 windows server 2012 的下载2 打开 VMware 虚拟机软件&#xff08;1&#xff09;新建虚拟机&#xff08;2&#xff09;设置虚拟机&#xff08;3&#xff09;打开虚拟机 windows server 2012&#xff08;4&#xff09;进…

如何在谷歌浏览器中开启安全浏览

在数字化时代&#xff0c;网络安全变得愈发重要。作为全球最受欢迎的网络浏览器之一&#xff0c;谷歌浏览器提供了多种功能来保护用户的在线安全。本文将详细介绍如何在谷歌浏览器中开启安全浏览&#xff0c;并额外提供一些有用的页面滚动设置、地址栏快捷搜索和跟踪防护的相关…

djiango DRF的使用

djiango DRF的使用 一 、初始 DRF序列化环境安装环境配置数据模型定义定义DRF序列化模型对象 二 、DRF请求和响应请求对象&#xff08;Request objects&#xff09;响应对象&#xff08;Response objects&#xff09;状态码&#xff08;Status codes&#xff09;包装&#xff0…

计算机网络-HTTP协议

HTTP HTTP是一种不保存状态&#xff0c;即无状态的协议。HTTP协议自身不对请求和响应之间的通信进行保存。为了保存状态因此后面也有一些技术产生比如Cookies技术。 HTTP是通过URI定位网上的资源&#xff0c;理论上将URI可以访问互联网上的任意资源。 如果不是访问特定的资源…

CTFHub 命令注入-综合练习(学习记录)

综合过滤练习 命令分隔符的绕过姿势 ; %0a %0d & 那我们使用%0a试试&#xff0c;发现ls命令被成功执行 /?ip127.0.0.1%0als 发现一个名为flag_is_here的文件夹和index.php的文件&#xff0c;那么我们还是使用cd命令进入到文件夹下 http://challenge-438c1c1fb670566b.sa…

【AI知识】逻辑回归介绍+ 做二分类任务的实例(代码可视化)

1. 分类的基本概念 在机器学习的有监督学习中&#xff0c;分类一种常见任务&#xff0c;它的目标是将输入数据分类到预定的类别中。具体来说&#xff1a; 分类任务的常见应用&#xff1a; 垃圾邮件分类&#xff1a;判断一封电子邮件是否是垃圾邮件 。 医学诊断&#xff1a;…

为SSH2协议服务器的用户设置密钥

目录 私钥的创建1. 在服务器上直接生成2. 如果需要配置免密登录3. 查看生成的密钥 导出私钥至SSH用户获取sudo权限 新的一台服务器类型是SSH2&#xff1a;这表示服务器支持SSH&#xff08;Secure Shell&#xff09;协议的第二个版本。SSH是一个网络协议&#xff0c;用于加密方式…

level2逐笔委托查询接口

沪深逐笔委托队列查询 前置步骤 分配数据库服务器 查询模板 以下是沪深委托队列查询的请求模板&#xff1a; http://<数据库服务器>/sql?modeorder_book&code<股票代码>&offset<offset>&token<token>查询参数说明 参数名类型说明mo…

文献研读|基于像素语义层面图像重建的AI生成图像检测

前言&#xff1a;本篇文章主要对基于重建的AI生成图像检测的四篇相关工作进行介绍&#xff0c;分别为基于像素层面重建的检测方法 DIRE 和 Aeroblade&#xff0c;以及基于语义层面重建的检测方法 SimGIR 和 Zerofake&#xff1b;并对相应方法进行比较。 相关文章&#xff1a;论…

VScode MAC按任意键关闭终端 想要访问桌面文件

说明 最近配置MAC上CPP的运行环境&#xff0c;在安装必要的CPP插件后&#xff0c;配置launch和task等json文件后&#xff0c;点击运行三角形&#xff0c;每次都会跳出main想要访问桌面上的文件。并且输出也是在调试控制台&#xff0c;非常逆天。 尝试 尝试1:尽管我尝试将ta…

Linux Shell 脚本编程基础知识篇

ℹ️大家好&#xff0c;我是练小杰&#xff0c;从本文是Linux shell脚本编程的基础知识内容&#xff0c;后续我会不断补充~~ 更多Linux 相关内容请点击&#x1f449;“Linux专栏”~ 假面驾驭&#xff0c;时王&#xff0c;假面骑士时王~~ 文章目录 什么是 Linux Shell主要功能…

QT绘制同心扇形

void ChartForm::paintEvent(QPaintEvent *) {QPainter painter(this);painter.setRenderHint(QPainter::Antialiasing);// 设置抗锯齿painter.save();// 设置无边框&#xff08;不需要设置QPen&#xff0c;因为默认是不绘制边框的&#xff09;QPen pen(Qt::NoPen);// QPen pen…

TL3568/TL3562更改主机名,在Kernel用menuconfig失效

前言 最近在玩RK3562开发板&#xff0c;想改串口调试时看到的主机名&#xff0c;开发板的主机名默认是RK3562-Tronlong&#xff0c;如图&#xff1a; 按照之前玩T113开发版&#xff0c;在Kernel通过make menuconfig&#xff0c;可以改。但是在这个RK3562&#xff0c;改了后&…

【PLL】ISSCC 2024 Tutorial: Calibration Techniques in PLLs

1. 数字辅助模拟电路 为什么要辅助&#xff0c;或替换模拟电路&#xff1f; 利用CMOS管子尺寸缩小&#xff0c;降低功耗 和 减小面积校正模拟电路的 非线性行为 和 失配 数字辅助的好处&#xff1a; 简化模拟电路设计提高能源效率&#xff0c;提高准确度 2. 锁相环基础 2.1 概…

STM32-笔记5-按键点灯(中断方法)

1、复制03-流水灯项目&#xff0c;重命名06-按键点灯&#xff08;中断法&#xff09; 在\Drivers\BSP目录下创建一个文件夹exti&#xff0c;在该文件夹下&#xff0c;创建两个文件exti.c和exti.h文件&#xff0c;并且把这两个文件加载到项目中&#xff0c;打开项目工程文件 加载…

Mvc、Springmvc框架

一.Mvc&#xff1a; 1.概念&#xff1a; MVC它是一种设计理念。把程序按照指定的结构来划分: Model模型 、View视图 、Controller控制层&#xff1b; 结构图&#xff1a; 二.Springmvc: 1.概念&#xff1a; springmvc框架它是spring框架的一个分支。它是按照mvc架构思想设计…

spring使用rabbitmq当rabbitmq集群节点挂掉 spring rabbitmq怎么保证高可用,rabbitmq网络怎么重新连接

##spring rabbitmq代码示例 Controller代码 import com.alibaba.fastjson.JSONObject; import com.newland.mi.config.RabbitDMMQConfig; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageProperties; import org.springframewo…

前端面试问题集合

0 HTML5相关 websocket WebSocket 使用ws或wss协议&#xff0c;Websocket是一个持久化的协议&#xff0c;相对于HTTP这种非持久的协议来说。WebSocket API最伟大之处在于服务器和客户端可以在给定的时间范围内的任意时刻&#xff0c;相互推送信息。WebSocket并不限于以Ajax(或X…

RabbitMQ如何构建集群?

大家好&#xff0c;我是锋哥。今天分享关于【RabbitMQ如何构建集群&#xff1f;】面试题。希望对大家有帮助&#xff1b; RabbitMQ如何构建集群&#xff1f; 1000道 互联网大厂Java工程师 精选面试题-Java资源分享网 在RabbitMQ中&#xff0c;集群&#xff08;Cluster&#x…