Maven实战.依赖(依赖范围、传递性依赖、依赖调解、可选依赖等)
文章目录
- 依赖的配置
- 依赖范围
- 传递性依赖
- 传递性依赖和依赖范围
- 依赖调解
- 可选依赖
- 最佳实践
- 排除依赖
- 归类依赖
- 优化依赖
依赖的配置
依赖会有基本的groupId、artifactld 和 version等元素组成。其实一个依赖声明可以包含如下的一些元素:
<project>
...<dependencies><dependency><groupId>...</groupId><artifactId>...</artifactId><version>...</version><type>...</type><scope>...</scope><optional>...</optional><exclusions><exclusion>...</exclusion>...</exclusions></dependency>...</dependencies>
...
</project>
其中每个标签的意思,直接参考Maven实战.pom.xml标签说明中的<dependencies>处。
依赖范围
参考Maven实战.pom.xml标签说明中的<scope>处。
传递性依赖
考虑一个基于 Spring Framework 的项目,如果不使用 Maven,那么在项目中就需要手动下载相关依赖。由于 Spring Framework 又会依赖于其他开源类库,因此实际中往往会下载一个很大的如 spring-framework-2.5.6-with-dependencies.zip 的包,这里包含了所有 SpringFramework 的 jar 包,以及所有它依赖的其他 jar 包。这么做往往就引人了很多不必要的依赖。另一种做法是只下载 spring-framework-2.5.6.zip 这样一个包,这里不包含其他相关依赖,到实际使用的时候,再根据出错信息,或者查询相关文档,加入需要的其他依赖。很显然,这也是一件非常麻烦的事情。
Maven 的传递性依赖机制可以很好地解决这一问题。假如有一个项目 account-email,该项目有一个 org.springframework : spring-core:2.5.6 的依赖,而实际上 spring-core 也有它自己的依赖,我们可以直接访问位于中央仓库的该构件的 POM,也就是 spring-core-2.5.6.pom。该文件包含了一个 commons-logging 依赖,如下。
<dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.1.1</version><scope>compile</scope>
</dependency>
该依赖的 scope 是 compile(或者没有声明依赖范围则依赖范围就是默认的 compie)。同时项目 account-email 中,spring-core 的依赖范围我们也设置为 compile。那么,此时 commons-logging 就会成为 account-email 的 compile 范围依赖,commons-logging 是 account-email 的一个传递性依赖。如下图

有了传递性依赖机制,在使用 SpringFramework 的时候就不用去考虑它依赖了什么,也不用担心引人多余的依赖。Maven 会解析各个直接依赖的 POM,将那些必要的间接依赖以传递性依赖的形式引人到当前的项目中。
传递性依赖和依赖范围
依赖范围不仅可以控制依赖与三种 classpah 的关系,还对传递性依赖产生影响。上面的例子中,account-email 对于 spring-core 的依赖范围是 compile,spring-core 对于 commons-logging 的依赖范围是 compile,那么 account-email 对于 commons-logging 这一传递性依赖的范围也就是 compile。
假设A依赖于B,B依赖于C,我们说A对于B是第一直接依赖,B对于C是第二直接依赖,A对于C是传递性依赖。第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围,如下图所示,最左边一列表示第一直接依赖范围,最上面一行表示第二直接依赖范围,中间的交叉单元格则表示传递性依赖范围。

为了能够更好地理解,这里再举个例子。account-email 项目有一个com.icegreen : greenmail : 1.3.1b 的直接依赖,我们说这是第一直接依赖,其依赖范围是 test,而 greenmail 又有一个 javax.mail : mail : 1.4 的直接依赖,我们说这是第二直接依赖,其依赖范围是 compile。显然javax.mail : mail : 1.4 是 account-email 的传递性依赖,对照上图可以知道,当第一直接依赖范围为 test,第二直接依赖范围是 compile 的时候,传递性依赖的范围是 test,因此 javax.mail : mail : 1.4 是 account-email 的一个范围是 test 的传递性依赖。
仔细观察一下上图,可以发现这样的规律:
- 当第二直接依赖的范围是 compile 的时候,传递性依赖的范围与第一直接依赖的范围一致
- 当第二直接依赖的范围是 test 的时候依赖不会得以传递
- 当第二直接依赖的范围是 provided的时候,只传递第一直接依赖范围也为 provided 的依赖,且传递性依赖的范围同样为 provided;
- 当第二直接依赖的范围是 runtime 的时候,传递性依赖的范围与第一直接依赖的范围一致,但 compile 例外,此时传递性依赖的范围为runtime。
依赖调解
Maven 引人的传递性依赖机制,一方面大大简化和方便了依赖声明,另一方面,大部分情况下我们只需要关心项目的直接依赖是什么,而不用考虑这些直接依赖会引入什么传递性依赖。但有时候,当传递性依赖造成问题的时候,我们就需要清楚地知道该传递性依赖是从哪条依赖路径引入的。
例如,项目 A 有这样的依赖关系:A -> B -> C -> X(1.0)、A -> D -> X(2.0),X 是 A 的传递性依赖,但是两条依赖路径上有两个版本的 X,那么哪个 X 会被 Maven 解析使用呢?两个版本都被解析显然是不对的,因为那会造成依赖重复,因此必须选择一个。
Maven依赖调解(Dependency Mediation)的第一原则是:路径最近者优先。该例中 X(1.0) 的路径长度为3,而 X(2.0) 的路径长度为2,因此 X(2.0) 会被解析使用。
依赖调解第一原则不能解决所有问题,比如这样的依赖关系:A -> B -> Y(1.0)、A -> C -> Y(2.0),Y(1.0) 和 Y(2.0) 的依赖路径长度是一样的,都为2。那么到底谁会被解析使用呢?
在 Maven 2.0.8 及之前的版本中,这是不确定的,但是从 Maven2.0.9 开始,为了尽可能避免构建的不确定性,Maven 定义了依赖调解的第二原则:第一声明者优先。在依赖路径长度相等的前提下,在 POM 中依赖声明的顺序决定了谁会被解析使用,顺序最靠前的那个依赖优胜。该例中,如果 B 的依赖声明在 C 之前,那么 Y(1.0) 就会被解析使用。
可选依赖
假设有这样一个依赖关系,项目 A 依赖于项目 B,项目 B 依赖于项目 X 和 Y,B 对于 X 和 Y 的依赖都是可选依赖:A -> B、B -> X(可选)、B -> Y(可选)。根据传递性依赖的定义,如果所有这三个(A 对 B 的依赖、B 对 X 的依赖 以及 B 对 Y 的依赖)依赖的范围都是 compile,那么 X、Y 就是 A 的 compile 范围传递性依赖。然而,由于这里 X、Y 是可选依赖,依赖将不会得以传递。换句话说,X、Y 将不会对 A 有任何影响,如下图所示。

为什么要使用可选依赖这一特性呢?可能项目 B 实现了两个特性,其中的特性一依赖于 X,特性二依赖于 Y,而且这两个特性是互斥的,用户不可能同时使用两个特性。比如 B 是一个持久层隔离工具包,它支持多种数据库,包括 MSQL、PostgreSQL等。在构建这个工具包的时候,需要这两种数据库的驱动程序,但在使用这个工具包的时候,只会依赖一种数据库。
项目 B 的依赖声明如下:
<dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.10</version><optional>true</optional></dependency><dependency><groupId>postgresql</groupId><artifactId>postgresql</artifactId><version>8.4-701.jdbc3</version><optional>true</optional></dependency>
</dependencies>
上述 XML 代码片段中,使用<optional>元素表示 mysql-connector-java和 postgresql 这两个依赖为可选依赖,它们只会对当前项目 B 产生影响,当其他项目依赖于B的时候,这两个依赖不会被传递。因此,当项目 A 依赖于项目 B 的时候,如果其实际使用基于MySOL数据库,那么在项目 A 中就需要自己显式地声明 mysql-connector-java 这一依赖。
最后,关于可选依赖需要说明的一点是,在理想的情况下,是不应该使用可选依赖的。前面我们可以看到,使用可选依赖的原因是某一个项目实现了多个特性,在面向对象设计中,有个单一职责性原则,意指一个类应该只有一项职责,而不是糅合太多的功能。这个原则在规划 Maven 项目的时候也同样适用。在上面的例子中,更好的做法是为MYSQL 和 PostgreSQL 分别创建一个 Maven 项目,基于同样的groupld 分配不同的 artifactld,在各自的 POM 中声明对应的JDBC驱动依赖,而且不使用可选依赖,用户则根据需要选择使用哪个驱动的 Maven 项目的构件。由于传递性依赖的作用,就不用再声明 JDBC 驱动依赖。
最佳实践
Maven 依赖涉及的知识点比较多,在理解了主要的功能和原理之后,最需要的当然就是经验总结了,这里归纳了一些使用 Maven 依赖常见的技巧,方便用来避免和处理很多常见的问题。
排除依赖
传递性依赖会给项目隐式地引入很多依赖,这极大地简化了项目依赖的管理,但是有些时候这种特性也会带来问题。例如,当前项目有一个第三方依赖,而这个第三方依赖由于某些原因依赖了另外一个类库的 SNAPSHOT 版本,那么这个 SNAPSHOT 就会成为当前项目的传递性依赖,而 SNAPSHOT 的不稳定性会直接影响到当前的项目。这时就需要排除掉该 SNAPSHOT,并且在当前项目中声明该类库的某个正式发布的版本。还有一些情况,你可能也想要替换某个传递性依赖,比如 Sun JTA API,Hibernate 依赖于这个 JAR,但是由于版权的因素,该类库不在中央仓库中,而 Apache Geronimo 项目有一个对应的实现。这时你就可以排除 Sun JAT API,再声明 Geronimo 的 JTA API 实现,示例代码如下:
<dependencies><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-b</artifactId><version>1.0.0</version><exclusions><exclusion><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId></exclusion></exclusions></dependency><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId><version>1.0.0</version></dependency>
</dependencies>
上述代码中,当前项目 A 依赖于项目 B,但是由于一些原因,不想引入传递性依赖 C 而是自己显式地声明对于项目 C 1.1.0 版本的依赖。代码中使用 exclusions 元素声明排除依赖,exclusions 可以包含一个或者多个 exclusion 子元素,因此可以排除一个或者多个传递性依赖。需要注意的是,声明 exclusion 的时候只需要 groupId 和 artifactld,而不需要 version 元素,这是因为只需要 groupId 和 artifactld 就能唯一定位依赖图中的某个依赖。换句话说,Maven 解析后的依赖中,不可能出现 groupId 和 artifactld 相同,但是 version 不同的两个依赖

归类依赖
简单说就是一批依赖来自同一项目(groupId)的不同模块(artifactId),但是版本号相同,此时可以不用为每个依赖声明一个 version,而是统一定义版本号,然后每个依赖引用这个版本号即可。
<properties><myproject.version>1.0.0</myproject.version>
</properties><dependencies><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-b</artifactId><version>${myproject.version}</version></dependency><dependency><groupId>com.juvenxu.mvnbook</groupId><artifactId>project-c</artifactId><version>${myproject.version}</version></dependency>
</dependencies>
这里简单用到了Maven属性,首先使用 <properties > 元素定义 Maven 属性,该例中定义了一个 myproject.version 子元素,其值为1.0.0。有了这个属性定义之后,Mave n运行的时候会将POM 中的所有的 ${myproject.version} 替换成实际值1.0.0。也就是说,可以使用美元符号和大括弧环绕的方式引用 Maven 属性。然后,将所有统一依赖的版本值用这一属性引用表示。
优化依赖
在软件开发过程中,程序员会通过重构等方式不断地优化自己的代码,使其变得更简洁、更灵活。同理,程序员也应该能够对 Maven 项目的依赖了然于胸,并对其进行优化如去除多余的依赖,显式地声明某些必要的依赖。
Maven 会自动解析所有项目的直接依赖和传递性依赖,并且根据规则正确判断每个依赖的范围,对于一些依赖冲突,也能进行调节,以确保任何一个构件只有唯一的版本在依赖中存在。在这些工作之后,最后得到的那些依赖被称为已解析依赖(Resolved Dependency)。可以运行如下的命令查看当前项目的已解析依赖:
mvn dependency:list
在你的项目中执行该命令,结果会显示项目的所有的已解析依赖,同时,每个依赖的范围也得以明确标示。在此基础上,还能进一步了解已解析依赖的信息。
将直接在当前项目 POM 声明的依赖定义为顶层依赖,而这些顶层依赖的依赖则定义为第二层依赖,以此类推,有第三、第四层依赖。当这些依赖经 Maven 解析后,就会构成一个依赖树,通过这棵依赖树就能很清楚地看到某个依赖是通过哪条传递路径引入的。可以运行如下命令查看当前项目的依赖树:
mvn dependency:tree
通过结果你会发现,有的依赖没有声明,但是通过别的依赖传递了进来,也能看见每个依赖的范围。
使用 dependency:list 和 dependency:tree 可以帮助我们详细了解项目中所有依赖的具体信息,在此基础上,还有dependency:analyze 工具可以帮助分析当前项目的依赖。
mvn dependency:analyze
该结果中重要的是两个部分。首先是 Used umdeclared dependencies,意指项目中使用到的,但是没有显式声明的依赖,这种依赖意味着潜在的风险,当前项目可能在使用它们,例如有很多相关的 Java import 声明,而这种依赖是通过直接依赖传递进来的,当升级直接依赖的时候,相关传递性依赖的版本也可能发生变化,这种变化不易察觉,但是有可能导致当前项目出错。例如由于接口的改变,当前项目中的相关代码无法编译。这种隐藏的、潜在的威胁一旦出现,就往往需要耗费大量的时间来查明真相。因此,最好显式声明任何项目中直接用到的依赖。
结果中还有一个重要的部分是 Unused declared dependeneies,意指项目中未使用的,但显式声明的依赖,需要注意的是,对于这样一类依赖,我们不应该简单地直接删除其声明,而是应该仔细分析。由于 dependency:analyze 只会分析编译主代码和测试代码需要用到的依赖,一些执行测试和运行时需要的依赖它就发现不了。
相关文章:
Maven实战.依赖(依赖范围、传递性依赖、依赖调解、可选依赖等)
文章目录 依赖的配置依赖范围传递性依赖传递性依赖和依赖范围依赖调解可选依赖最佳实践排除依赖归类依赖优化依赖 依赖的配置 依赖会有基本的groupId、artifactld 和 version等元素组成。其实一个依赖声明可以包含如下的一些元素: <project> ...<depende…...
关于React17的setState
不可变值 state必须在构造函数中定义 在setState之前不能修改state的值,不要直接修改state,使用不可变值 可能是异步更新 直接使用时异步的 this.setState({count: this.state.count 1 }, () > {console.log(count by callback, this.state.count) // 回调函…...
2024华为OD机试真题-英文输入法Python-C卷D卷-100分
2024华为OD机试题库-(C卷+D卷)-(JAVA、Python、C++) 题目描述 主管期望你来实现英文输入法单词联想功能,需求如下: 依据用户输入的单词前缀,从已输入的英文语句中联想出用户想输入的单词。 按字典序输出联想到的单词序列,如果联想不到,请输出用户输入的单词前缀。 注意 英…...
magento2 安装win环境和linux环境
win10 安装 安装前提,php,mysql,apach 或nginx 提前安装好 并且要php配置文件里,php.ini 把错误打开 display_errorsOn开始安装 检查环境 填写数据库信息 和ssl信息,如果ssl信息没有,则可以忽略 填写域名和后台地址࿰…...
【城市数据集】世界城市数据库和访问门户工具WUDAPT
世界城市数据库和访问门户工具WUDAPT WUDAPTLCZ分类具体步骤参考 在 城市气候研究中,用于描述城市特征的数据集一般采用基于类别的传统方法,将城市地区分为数量有限的类型,从而导致精确度下降。越来越多的新数据集以亚米微尺分辨率描述城市的…...
网络爬虫必备工具:代理IP科普指南
文章目录 1. 网络爬虫简介1.1 什么是网络爬虫?1.2 网络爬虫的应用领域1.3 网络爬虫面临的主要挑战 2. 代理IP:爬虫的得力助手2.1 代理IP的定义和工作原理2.2 爬虫使用代理IP的必要性 3. 代理IP的类型及其在爬虫中的应用3.1 动态住宅代理3.2 动态数据中心…...
JMeter接口测试-5.JMeter高级使用
JMeter高级使用 案例: 用户登录后-选择商品-添加购物车-创建订单-验证结果 问题: JMeter测试中,验证结果使用断言,但断言都是固定的内容假如要判断的内容(预期内容)是在变化的, 有时候还是不确定的, 那该怎么办呢? 解决&…...
网络安全大模型开源项目有哪些?
01 Ret2GPT 它是面向CTF二进制安全的工具,结合ChatGPT API、Retdec和Langchain进行漏洞挖掘,它能通过问答或预设Prompt对二进制文件进行分析。 https://github.com/DDizzzy79/Ret2GPT 02 OpenAI Codex 它是基于GPT-3.5-turbo模型,用于编写…...
【赠书第18期】人工智能B2B落地实战:基于云和Python的商用解决方案
文章目录 前言 1 方案概述 2 方案实施 2.1 云平台选择 2.2 Python环境搭建 2.3 应用开发与部署 2.4 应用管理 2.5 安全性与隐私保护 3 方案优势与效益 4 推荐图书 5 粉丝福利 前言 随着云计算技术的快速发展,越来越多的企业开始将业务迁移至云端&#x…...
《昇思25天学习打卡营第24天》
接续上一天的学习任务,我们要继续进行下一步的操作 构造网络 当处理完数据后,就可以来进行网络的搭建了。按照DCGAN论文中的描述,所有模型权重均应从mean为0,sigma为0.02的正态分布中随机初始化。 接下来了解一下其他内容 生成…...
KeePass密码管理工具部署
KeePass密码管理工具部署 安装包下载入口 双击执行,根据提示完成安装: 安装完成后如图:...
C#中导出dataGridView数据为Excel
C#中导出dataGridView数据为Excel #region 导出Excel功能函数 /// <summary> /// dataGridView 导出Excel功能函数 /// </summary> /// <param name"dataView">dataGridView数据表</param> /// <param name"filePath">路径…...
算法学习6——贪心算法
什么是贪心算法? 贪心算法是一种在每一步选择中都采取当前状态下最优或最有利的选择的算法。其核心思想是通过一系列局部最优选择来达到全局最优解。贪心算法广泛应用于各种优化问题,如最短路径、最小生成树、背包问题等。 贪心算法的特点 局部最优选…...
【C++】标准库:介绍string类
string 一.string类介绍二.string类的静态成员变量三.string类的常用接口1.构造函数(constructor)2.析构函数(destructor)3.运算符重载(operator)1.operator2.operator[]3.operator4.operator 4.string的四…...
未来不会使用 AI 的人真的会被淘汰吗?
AI 是今年大火的一个话题,随着 ChatGPT 之类的一系列大模型开始流行以后,有不少的培训机构宣称这样的口号: “未来不会使用 AI 的人将会被淘汰”。我觉得这个观点本身并没有错,但是关键在于那些培训机构出于自身的利益,故意忽略了…...
K8S及Rancher部署
前言 这篇文写的有点子啰嗦,甚至为了控制篇幅我还分出了其他好几篇文章,只在本文中保留了我认为必须存在。而之所以篇幅这么长,一方面是我在相关领域完全新手,啥啥都不会;而另一方面是我所参考的资料都过于精简&#…...
Qt Creator使用git管理代码
1.在GitHub中新建仓库,设置好仓库名后,其它的设置默认即可。 2.打开git bash,输入以下命令: git config --global user.name "xxxxx" #设置你的GitHub用户名 git config --global user.email "xxxxxxxxx.…...
pandas教程:pandas读取csv文件并指定字段数据类型
文章目录 pandas指定数据类型处理数据类型错误parse_dates参数pandas数据类型处理示例pandas指定数据类型 在读取csv文件时,我们可以使用dtype参数来指定每个列的数据类型。这个参数接受一个字典类型的值,其中键是列名,值是数据类型。数据类型可以是Pandas类型或NumPy类型,…...
c#中使用数据验证器
前言 在很多情况下,用户的输入不一定满足我们的设计要求,需要验证输入是否正确,传统的方案是拿到控件数据进行逻辑判定验证后,给用户弹窗提示。这种方法有点职责延后的感觉,数据视图层应该很好的处理用户的输入。使用…...
Java真人版猫爪老鼠活动报名平台系统
🐾“真人版猫爪老鼠活动报名平台系统”——趣味追逐,等你来战!🐭 🐱【萌宠变主角,现实版趣味游戏】 厌倦了电子屏幕的虚拟游戏?来试试“真人版猫爪老鼠活动”吧!在这个平台上&…...
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明
LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造,完美适配AGV和无人叉车。同时,集成以太网与语音合成技术,为各类高级系统(如MES、调度系统、库位管理、立库等)提供高效便捷的语音交互体验。 L…...
Android Wi-Fi 连接失败日志分析
1. Android wifi 关键日志总结 (1) Wi-Fi 断开 (CTRL-EVENT-DISCONNECTED reason3) 日志相关部分: 06-05 10:48:40.987 943 943 I wpa_supplicant: wlan0: CTRL-EVENT-DISCONNECTED bssid44:9b:c1:57:a8:90 reason3 locally_generated1解析: CTR…...
脑机新手指南(八):OpenBCI_GUI:从环境搭建到数据可视化(下)
一、数据处理与分析实战 (一)实时滤波与参数调整 基础滤波操作 60Hz 工频滤波:勾选界面右侧 “60Hz” 复选框,可有效抑制电网干扰(适用于北美地区,欧洲用户可调整为 50Hz)。 平滑处理&…...
2024年赣州旅游投资集团社会招聘笔试真
2024年赣州旅游投资集团社会招聘笔试真 题 ( 满 分 1 0 0 分 时 间 1 2 0 分 钟 ) 一、单选题(每题只有一个正确答案,答错、不答或多答均不得分) 1.纪要的特点不包括()。 A.概括重点 B.指导传达 C. 客观纪实 D.有言必录 【答案】: D 2.1864年,()预言了电磁波的存在,并指出…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
安卓基础(aar)
重新设置java21的环境,临时设置 $env:JAVA_HOME "D:\Android Studio\jbr" 查看当前环境变量 JAVA_HOME 的值 echo $env:JAVA_HOME 构建ARR文件 ./gradlew :private-lib:assembleRelease 目录是这样的: MyApp/ ├── app/ …...
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...
【电力电子】基于STM32F103C8T6单片机双极性SPWM逆变(硬件篇)
本项目是基于 STM32F103C8T6 微控制器的 SPWM(正弦脉宽调制)电源模块,能够生成可调频率和幅值的正弦波交流电源输出。该项目适用于逆变器、UPS电源、变频器等应用场景。 供电电源 输入电压采集 上图为本设计的电源电路,图中 D1 为二极管, 其目的是防止正负极电源反接, …...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能
1. 开发环境准备 安装DevEco Studio 3.1: 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK 项目配置: // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...
