Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。
此前我们学习了Dubbo的服务导出的源码,在DubboBootstrapApplicationListener#startSync方法中,在调用了exportServices方法进行服务导出之后,立即调用了referServices方法进行服务引用,所以说Dubbo3.1中,服务导出和服务引用的入口是相同的,都是DubboDeployApplicationListener这个监听器。
在spring容器启动的最后一个步也就是refresh方法内部最后的finishRefresh方法中,将会向所有监听器发布一个ContextRefreshedEvent事件,表示容器刷新完毕。DubboDeployApplicationListener会监听该事件,进而触发服务的导出和引用。
Dubbo 3.x服务引用源码:
- Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
- Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
Dubbo 3.x服务发布源码:
- Dubbo 3.x源码(11)—Dubbo服务的发布与引用的入口
- Dubbo 3.x源码(12)—Dubbo服务发布导出源码(1)
- Dubbo 3.x源码(13)—Dubbo服务发布导出源码(2)
- Dubbo 3.x源码(14)—Dubbo服务发布导出源码(3)
- Dubbo 3.x源码(15)—Dubbo服务发布导出源码(4)
- Dubbo 3.x源码(16)—Dubbo服务发布导出源码(5)
- Dubbo 3.x源码(17)—Dubbo服务发布导出源码(6)
文章目录
- 1 referServices引用服务入口
- 2 SimpleReferenceCache#get获取缓存
- 3 ReferenceConfig#get服务引用
- 4 ReferenceConfig#init初始化服务引用
- 4.1 appendConfig获取服务引用参数
- 5 总结
1 referServices引用服务入口
该方法获取全部ReferenceConfigBase实例并遍历,判断是否应该初始化,判断init属性,默认true,继续判断是否异步引用,默认同步。最后通过引用缓存对象来进行服务引用,即referenceCache.get(rc)方法实现。
这个init属性在Dubbo2.x版本引入,该值用于判断是否在afterPropertiesSet()时饥饿初始化引用,否则等到有人注入或引用该实例时再初始化,默认false,即懒加载。但是在Dubbo3.1版本中,init仅被用于在shouldInit方法中,而且默认返回true,不再是默认懒加载,只有手动设置为false,才不会引入服务。因为此时dubbo服务的引入已不在ReferenceBean的 afterPropertiesSet方法中。
/*** DefaultModuleDeployer的方法* <p>* 服务引用*/
private void referServices() {//获取全部ReferenceConfigBase实例并遍历configManager.getReferences().forEach(rc -> {try {ReferenceConfig<?> referenceConfig = (ReferenceConfig<?>) rc;//如果还没刷新该引用配置if (!referenceConfig.isRefreshed()) {//刷新配置,即Dubbo配置的重写(基于优先级的覆盖)//设个方法我们在此前Dubbo配置的加载部分已经讲过了referenceConfig.refresh();}//是否应该初始化,判断init属性,默认trueif (rc.shouldInit()) {//如果模块消费端开启异步调用,或者消费者开启异步调用,默认都是false,即同步调用if (referAsync || rc.shouldReferAsync()) {ExecutorService executor = executorRepository.getServiceReferExecutor();CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {try {referenceCache.get(rc);} catch (Throwable t) {logger.error("5-9", "", "", "Failed to async export service config: " + getIdentifier() + " , catch error : " + t.getMessage(), t);}}, executor);asyncReferringFutures.add(future);}//通常的逻辑,通过引用缓存对象来进行服务引用else {referenceCache.get(rc);}}} catch (Throwable t) {logger.error("5-15", "", "", "Model reference failed: " + getIdentifier() + " , catch error : " + t.getMessage(), t);referenceCache.destroy(rc);throw t;}});
}
2 SimpleReferenceCache#get获取缓存
SimpleReferenceCache是一个对于单例的ReferenceConfigBase的缓存对象。该方法中的大概逻辑为:
- 首先构建缓存key,规则为 {group}/{InterfaceName}:{version} ,随后获取引用的服务接口Class。
- 判断是否是单例服务,如果是那么首先尝试从缓存中获取已创建的服务代理实例对象proxy。
- 如果没有缓存,那么将ReferenceConfigBase存入referenceTypeMap和referenceKeyMap这两个缓存map中,随后调用ReferenceConfigBase自身的get方法进行服务引用,获取服务代理实例对象。
可以看到,虽然我们是调用的SimpleReferenceCache#get方法,但是其内部会判断如果没有进行服务引用,那么会引用服务,间接的达成了我们服务引用的目的,同时还构建了缓存。而服务引用的关键方法就是ReferenceConfigBase#get方法。
/*** SimpleReferenceCache的方法** @param rc 服务消费者引用服务配置* @return 服务代理实例对象*/
@Override
@SuppressWarnings("unchecked")
public <T> T get(ReferenceConfigBase<T> rc) {//获取缓存key,规则为 {group}/{InterfaceName}:{version} String key = generator.generateKey(rc);//获取引用的服务接口ClassClass<?> type = rc.getInterfaceClass();//是否是单例,默认trueboolean singleton = rc.getSingleton() == null || rc.getSingleton();T proxy = null;// Check existing proxy of the same 'key' and 'type' first.//如果是单例if (singleton) {//那么首先尝试从缓存中获取已创建的服务代理实例对象proxy = get(key, (Class<T>) type);} else {logger.warn("Using non-singleton ReferenceConfig and ReferenceCache at the same time may cause memory leak. " +"Call ReferenceConfig#get() directly for non-singleton ReferenceConfig instead of using ReferenceCache#get(ReferenceConfig)");}//如果缓存没有服务代理实例对象if (proxy == null) {//引用服务类型到服务消费者引用服务配置的缓存referenceTypeMapList<ReferenceConfigBase<?>> referencesOfType = referenceTypeMap.computeIfAbsent(type, _t -> Collections.synchronizedList(new ArrayList<>()));referencesOfType.add(rc);//引用缓存key到服务消费者引用服务配置的缓存referenceKeyMapList<ReferenceConfigBase<?>> referenceConfigList = referenceKeyMap.computeIfAbsent(key, _k -> Collections.synchronizedList(new ArrayList<>()));referenceConfigList.add(rc);/** 调用ReferenceConfigBase自身的get方法进行服务引用,获取服务代理实例对象*/proxy = rc.get();}//返回服务代理实例对象,不需要特意存入某个换尺寸,因为每个ReferenceConfigBase自身会存储它的取服务代理实例对象return proxy;
}
3 ReferenceConfig#get服务引用
该方法获取接口代理引用对象实例,这里面使用双重检测锁来判断ref是否为null,如果为null,说明该服务还没有进行服务引用,那么调用init方法初始化引用服务。
/*** ReferenceConfig的方法** @return 代理引用服务实例*/
@Override
public T get() {//销毁检查if (destroyed) {throw new IllegalStateException("The invoker of ReferenceConfig(" + url + ") has already destroyed!");}//如果接口代理引用为nullif (ref == null) {// ensure start module, compatible with old api usage//确保启动模块,与旧的API使用兼容getScopeModel().getDeployer().start();//双重校验锁synchronized (this) {//如果接口代理引用为nullif (ref == null) {//初始化引用服务init();}}}return ref;
}
4 ReferenceConfig#init初始化服务引用
该方法用于初始化某个服务引用,大概逻辑为:
- 服务引用前初始化serviceMetadata服务元数据。
- 注册serviceDescriptor服务描述符到本地内存的服务仓库ModuleServiceRepository的services缓存中,即注册service。
- 构建此服务对应的服务消费者模型consumerModel,并且注册到本地内存的服务仓库ModuleServiceRepository的consumers缓存中,即注册consumer。
- 调用createProxy方法,根据服务引用参数map创建服务接口代理引用对象,并赋值给res。这是核心逻辑。
- 设置服务元数据的服务接口代理引用对象。
/*** ReferenceConfig的方法* <p>* 初始化引用服务*/
protected synchronized void init() {//如果已被初始化,那么直接返回if (initialized && ref != null) {return;}try {//如果还没刷新该引用配置if (!this.isRefreshed()) {//刷新配置,即Dubbo配置的重写(基于优先级的覆盖)//设个方法我们在此前Dubbo配置的加载部分已经讲过了this.refresh();}/** 1 服务引用前初始化serviceMetadata服务元数据*/// init serviceMetadata//根据consumer初始化服务元数据initServiceMetadata(consumer);//设置服务类型serviceMetadata.setServiceType(getServiceInterfaceClass());// TODO, uncomment this line once service key is unified//服务key: group/服务接口:版本号serviceMetadata.generateServiceKey();//附加服务引用所需的所有配置Map<String, String> referenceParameters = appendConfig();// init service-application mapping 从本地存储和url参数初始化服务应用程序映射//也就是MetadataServiceNameMappinginitServiceAppsMapping(referenceParameters);//获取Module级别的服务存储仓库,其内部保存着服务提供者和服务消费者的缓存ModuleServiceRepository repository = getScopeModel().getServiceRepository();/** 2 注册serviceDescriptor服务描述符到本地内存的服务仓库ModuleServiceRepository的services缓存中,即注册service*///服务描述符,通过它可以获取服务描述信息,例如服务提供的方法,服务接口名,服务接口Class等ServiceDescriptor serviceDescriptor;if (CommonConstants.NATIVE_STUB.equals(getProxy())) {serviceDescriptor = StubSuppliers.getServiceDescriptor(interfaceName);repository.registerService(serviceDescriptor);} else {//注册服务描述符到服务仓库内部的services集合中serviceDescriptor = repository.registerService(interfaceClass);}/** 3 构建此服务对应的服务消费者模型consumerModel,并且注册到本地内存的服务仓库ModuleServiceRepository的consumers缓存中,即注册consumer*/consumerModel = new ConsumerModel(serviceMetadata.getServiceKey(),//生成动态代理的策略,可以选择两种策略:jdk和javassistproxy,//服务描述符serviceDescriptor,//域模型getScopeModel(),//服务元数据serviceMetadata,//转换和聚合异步方法信息createAsyncMethodInfo(),//服务接口类加载器interfaceClassLoader);// Compatible with dependencies on ServiceModel#getReferenceConfig() , and will be removed in a future version.//兼容ServiceModel#getReferenceConfig()上的依赖项,并将在未来的版本中删除。consumerModel.setConfig(this);//注册服务消费者模型到服务仓库内部的consumers集合中repository.registerConsumer(consumerModel);//将引用参数存入服务元数据的附加数据中serviceMetadata.getAttachments().putAll(referenceParameters);/** 4 根据服务引用参数创建服务接口代理引用对象*/ref = createProxy(referenceParameters);//设置服务元数据的服务接口代理引用对象serviceMetadata.setTarget(ref);serviceMetadata.addAttribute(PROXY_CLASS_REF, ref);consumerModel.setDestroyRunner(getDestroyRunner());consumerModel.setProxyObject(ref);consumerModel.initMethodModels();//检查调用程序可用checkInvokerAvailable();} catch (Throwable t) {try {//销毁invokerif (invoker != null) {invoker.destroy();}} catch (Throwable destroy) {logger.warn("5-3", "", "", "Unexpected error occurred when destroy invoker of ReferenceConfig(" + url + ").", t);}//取消注册consumerModelif (consumerModel != null) {ModuleServiceRepository repository = getScopeModel().getServiceRepository();repository.unregisterConsumer(consumerModel);}//还原属性initialized = false;invoker = null;ref = null;consumerModel = null;serviceMetadata.setTarget(null);serviceMetadata.getAttributeMap().remove(PROXY_CLASS_REF);// Thrown by checkInvokerAvailable().if (t.getClass() == IllegalStateException.class &&t.getMessage().contains("No provider available for the service")) {// 2-2 - No provider available.logger.error("2-2", "server crashed", "", "No provider available.", t);}throw t;}initialized = true;
}
4.1 appendConfig获取服务引用参数
该方法用于获取当前的服务引用参数,用于后续的createProxy方法创建服务代理引用对象。
/*** ReferenceConfig的方法* <p>* 附加服务引用所需的所有配置到一个map中** @return 引用参数*/
private Map<String, String> appendConfig() {Map<String, String> map = new HashMap<>(16);//interface -> 服务接口全路径名map.put(INTERFACE_KEY, interfaceName);//side -> consumer 表示消费端map.put(SIDE_KEY, CONSUMER_SIDE);//添加运行时参数//dubbo -> Dubbo RPC协议版本,默认2.0.2//release -> Dubbo的实现版本,通常是jar版本//timestamp -> 当前时间戳毫秒值//pid -> 当前服务进程pidReferenceConfigBase.appendRuntimeParameters(map);//非泛化接口if (!ProtocolUtils.isGeneric(generic)) {String revision = Version.getVersion(interfaceClass, version);if (StringUtils.isNotEmpty(revision)) {map.put(REVISION_KEY, revision);}String[] methods = methods(interfaceClass);if (methods.length == 0) {logger.warn("5-4", "", "", "No method found in service interface: " + interfaceClass.getName());map.put(METHODS_KEY, ANY_VALUE);} else {map.put(METHODS_KEY, StringUtils.join(new HashSet<>(Arrays.asList(methods)), COMMA_SEPARATOR));}}//Application配置AbstractConfig.appendParameters(map, getApplication());//Module配置AbstractConfig.appendParameters(map, getModule());//consumer配置AbstractConfig.appendParameters(map, consumer);//reference配置AbstractConfig.appendParameters(map, this);appendMetricsCompatible(map);String hostToRegistry = ConfigUtils.getSystemProperty(DUBBO_IP_TO_REGISTRY);if (StringUtils.isEmpty(hostToRegistry)) {hostToRegistry = NetUtils.getLocalHost();} else if (isInvalidLocalHost(hostToRegistry)) {throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);}map.put(REGISTER_IP_KEY, hostToRegistry);if (CollectionUtils.isNotEmpty(getMethods())) {for (MethodConfig methodConfig : getMethods()) {AbstractConfig.appendParameters(map, methodConfig, methodConfig.getName());String retryKey = methodConfig.getName() + ".retry";if (map.containsKey(retryKey)) {String retryValue = map.remove(retryKey);if ("false".equals(retryValue)) {map.put(methodConfig.getName() + ".retries", "0");}}}}return map;
}
5 总结
本次我们学习了Dubbo 3.x服务引用的入口源码。我们知道ReferenceConfig#init用于初始化服务引用,其中调用createProxy方法,**根据服务引用参数map创建服务接口代理引用对象,并赋值给res,**这是核心逻辑,我们下文再学习!
相关文章:
Dubbo 3.x源码(18)—Dubbo服务引用源码(1)
基于Dubbo 3.1,详细介绍了Dubbo服务的发布与引用的源码。 此前我们学习了Dubbo的服务导出的源码,在DubboBootstrapApplicationListener#startSync方法中,在调用了exportServices方法进行服务导出之后,立即调用了referServices方法…...
设计模式:工厂模式和抽象工厂模式的区别
定义 工厂模式(Factory Pattern)通常指的是工厂方法模式(Factory Method Pattern),它定义了一个创建对象的方法,由子类决定要实例化的类。工厂方法让类的实例化推迟到子类。 抽象工厂模式(Abstract Factory Pattern)提供了一个接口,用于创建相关或依赖对象的家族,而…...
python面试题(36~50)
36、如何取一个整数的绝对值? 这可以通过abs函数来实现。 abs(2) #> 2 abs(-2) #> 2 37、如何将两个列表组合成一个元组列表? 可以使用zip函数将列表组合成一个元组列表。这不仅仅限于使用两个列表。也适合3个或更多列表的情况。 a [a,b,c] b [1,2,3] [(k,v) fo…...

Vue 样式技巧总结与整理[中级局]
SFC(单文件组件)由 3 个不同的实体组成:模板、脚本和样式。三者都很重要,但后者往往被忽视,即使它可能变得复杂,且经常导致挫折和 bug。 更好的理解可以改善代码审查并减少调试时间。 这里有 7 个奇技淫巧…...

cesium加载.tif格式文件
最近项目中有需要直接加载三方给的后缀名tif格式的文件 <script src"https://cdn.jsdelivr.net/npm/geotiff"></script> 或者 yarn add geotiff npm install geotiff 新建tifs.js import GeoTIFF, { fromBlob, fromUrl, fromArrayBuffer } from geotif…...

分布式全闪占比剧增 152%,2023 年企业存储市场报告发布
近日,IDC 发布了 2023 年度的中国存储市场报告。根据该报告,在 2023 年软件定义存储的市场占比进一步扩大,分布式全闪的增长尤其亮眼,其市场份额从 2022 年的 7% 剧增到 2023 年的 17.7%,增长了 152%。 01 中国企业存…...
LeetCode 707. 设计链表(单链表、(非循环)双链表 模板)
你可以选择使用单链表或者双链表,设计并实现自己的链表。 单链表中的节点应该具备两个属性:val 和 next 。val 是当前节点的值,next 是指向下一个节点的指针/引用。 如果是双向链表,则还需要属性 prev 以指示链表中的上一个节点…...
深入了解Flutter中Overlay的介绍以及使用
Flutter Overlay 介绍 在 Flutter 中,Overlay 是一种特殊的 Widget,它可以用来在应用程序的其他部分之上显示内容。Overlay 非常适合用于显示模态对话框、弹出菜单、工具提示等。 Overlay 的工作原理 Overlay 位于 Flutter 的渲染树之外,这…...

文本直接生成2分钟视频,即将开源模型StreamingT2V
Picsart人工智能研究所、德克萨斯大学和SHI实验室的研究人员联合推出了StreamingT2V视频模型。通过文本就能直接生成2分钟、1分钟等不同时间,动作一致、连贯、没有卡顿的高质量视频。 虽然StreamingT2V在视频质量、多元化等还无法与Sora媲美,但在高速运…...

时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测
时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测 目录 时序预测 | Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 1.Matlab实现SOM-BP自组织映射结合BP神经网络时间序列预测(完整源码…...

FPGA高端图像处理开发板-->鲲叔4EV:12G-SDI、4K HDMI2.0、MIPI等接口谁敢与我争锋?
目录 前言鲲叔4EV----高端FPGA图像处理开发板核心板描述底板描述配套例程源码描述配套服务描述开发板测试视频演示开发板获取 前言 在CSDN写博客传播FPGA开发经验已经一年多了,帮助了不少人,也得罪了不少人,有的人用我的代码赢得了某些比赛、…...

linux练习-交互式传参
在shell脚本中,read 向用户显示一行文本并接受用户输入 #!/bin/bash read -p 依次输入你的姓名、年龄、家乡 name age home echo 我是$name,年龄$age,我来自$home...

【数据结构(一)】初识数据结构
❣博主主页: 33的博客❣ ▶文章专栏分类: Java从入门到精通◀ 🚚我的代码仓库: 33的代码仓库🚚 🫵🫵🫵关注我带你学更多数据结构知识 目录 1.前言2.集合架构3.时间和空间复杂度3.1算法效率3.2时间复杂度3.2.1大O的渐进…...

前端三剑客 —— CSS (第六节)
目录 内容回顾: 弹性布局属性介绍 案例演示 商品案例 布局分析 登录案例 网格布局 内容回顾: 变量:定义变量使用 --名称:值; 使用变量: 属性名:var(--名称)&a…...

MyBatis 解决上篇的参数绑定问题以及XML方式交互
前言 上文:MyBatis 初识简单操作-CSDN博客 上篇文章我们谈到的Spring中如何使用注解对Mysql进行交互 但是我们发现我们返回出来的数据明显有问题 我们发现后面三个字段的信息明显没有展示出来 下面我们来谈谈解决方案 解决方案 这里的原因本质上是因为mysql中和对象中的字段属性…...
Rust语言之属性宏(Attribute Macro)derive
文章目录 Rust语言之属性宏(Attribute Macro)derive Rust语言之属性宏(Attribute Macro)derive 属性宏是一种基于属性的宏,用于修改、扩展或注解 Rust 代码。它们通常用于为函数、结构体、枚举、模块等添加元数据或自…...

[技术闲聊]我对电路设计的理解(六)-原理图封装
电路设计的直观体现就是完整的原理图,绘制电路图阶段的第一步,绘制原理图封装库。 封装库一共有两种,一种是原理图封装库,一种是PCB封装库,如下图所示。 原理图封装和PCB封装之间的唯一关联就是 引脚位号,…...

算法(滑动窗口四)
1.串联所有单词的子串 给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。 s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如,如果 words ["ab","cd","ef"]ÿ…...

学习记录:bazel和cmake运行终端指令
Bazel和CMake都是用于构建软件项目的工具,但它们之间有一些重要的区别和特点: Bazel: Bazel是由Google开发的构建和测试工具,用于构建大规模的软件项目。它采用一种称为“基于规则”的构建系统,它利用构建规则和依赖关…...
蓝桥杯刷题--python-37-分解质因数
3491. 完全平方数 - AcWing题库 nint(input()) res1 i2 while i*i<n: if n%i0: t0 while n%i0: n//i t1 if t%2: res*i i1 if n>1: res*n print(res) 4658. 质因数个数 - AcWing题库…...

Appium+python自动化(十六)- ADB命令
简介 Android 调试桥(adb)是多种用途的工具,该工具可以帮助你你管理设备或模拟器 的状态。 adb ( Android Debug Bridge)是一个通用命令行工具,其允许您与模拟器实例或连接的 Android 设备进行通信。它可为各种设备操作提供便利,如安装和调试…...

有限自动机到正规文法转换器v1.0
1 项目简介 这是一个功能强大的有限自动机(Finite Automaton, FA)到正规文法(Regular Grammar)转换器,它配备了一个直观且完整的图形用户界面,使用户能够轻松地进行操作和观察。该程序基于编译原理中的经典…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...

云原生安全实战:API网关Kong的鉴权与限流详解
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 一、基础概念 1. API网关(API Gateway) API网关是微服务架构中的核心组件,负责统一管理所有API的流量入口。它像一座…...
快刀集(1): 一刀斩断视频片头广告
一刀流:用一个简单脚本,秒杀视频片头广告,还你清爽观影体验。 1. 引子 作为一个爱生活、爱学习、爱收藏高清资源的老码农,平时写代码之余看看电影、补补片,是再正常不过的事。 电影嘛,要沉浸,…...
从面试角度回答Android中ContentProvider启动原理
Android中ContentProvider原理的面试角度解析,分为已启动和未启动两种场景: 一、ContentProvider已启动的情况 1. 核心流程 触发条件:当其他组件(如Activity、Service)通过ContentR…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...

C++实现分布式网络通信框架RPC(2)——rpc发布端
有了上篇文章的项目的基本知识的了解,现在我们就开始构建项目。 目录 一、构建工程目录 二、本地服务发布成RPC服务 2.1理解RPC发布 2.2实现 三、Mprpc框架的基础类设计 3.1框架的初始化类 MprpcApplication 代码实现 3.2读取配置文件类 MprpcConfig 代码实现…...

企业大模型服务合规指南:深度解析备案与登记制度
伴随AI技术的爆炸式发展,尤其是大模型(LLM)在各行各业的深度应用和整合,企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者,还是积极拥抱AI转型的传统企业,在面向公众…...

AxureRP-Pro-Beta-Setup_114413.exe (6.0.0.2887)
Name:3ddown Serial:FiCGEezgdGoYILo8U/2MFyCWj0jZoJc/sziRRj2/ENvtEq7w1RH97k5MWctqVHA 注册用户名:Axure 序列号:8t3Yk/zu4cX601/seX6wBZgYRVj/lkC2PICCdO4sFKCCLx8mcCnccoylVb40lP...