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真人版猫爪老鼠活动报名平台系统
🐾“真人版猫爪老鼠活动报名平台系统”——趣味追逐,等你来战!🐭 🐱【萌宠变主角,现实版趣味游戏】 厌倦了电子屏幕的虚拟游戏?来试试“真人版猫爪老鼠活动”吧!在这个平台上&…...
生成xcframework
打包 XCFramework 的方法 XCFramework 是苹果推出的一种多平台二进制分发格式,可以包含多个架构和平台的代码。打包 XCFramework 通常用于分发库或框架。 使用 Xcode 命令行工具打包 通过 xcodebuild 命令可以打包 XCFramework。确保项目已经配置好需要支持的平台…...

深入浅出Asp.Net Core MVC应用开发系列-AspNetCore中的日志记录
ASP.NET Core 是一个跨平台的开源框架,用于在 Windows、macOS 或 Linux 上生成基于云的新式 Web 应用。 ASP.NET Core 中的日志记录 .NET 通过 ILogger API 支持高性能结构化日志记录,以帮助监视应用程序行为和诊断问题。 可以通过配置不同的记录提供程…...

ESP32读取DHT11温湿度数据
芯片:ESP32 环境:Arduino 一、安装DHT11传感器库 红框的库,别安装错了 二、代码 注意,DATA口要连接在D15上 #include "DHT.h" // 包含DHT库#define DHTPIN 15 // 定义DHT11数据引脚连接到ESP32的GPIO15 #define D…...
在 Nginx Stream 层“改写”MQTT ngx_stream_mqtt_filter_module
1、为什么要修改 CONNECT 报文? 多租户隔离:自动为接入设备追加租户前缀,后端按 ClientID 拆分队列。零代码鉴权:将入站用户名替换为 OAuth Access-Token,后端 Broker 统一校验。灰度发布:根据 IP/地理位写…...

页面渲染流程与性能优化
页面渲染流程与性能优化详解(完整版) 一、现代浏览器渲染流程(详细说明) 1. 构建DOM树 浏览器接收到HTML文档后,会逐步解析并构建DOM(Document Object Model)树。具体过程如下: (…...
关于 WASM:1. WASM 基础原理
一、WASM 简介 1.1 WebAssembly 是什么? WebAssembly(WASM) 是一种能在现代浏览器中高效运行的二进制指令格式,它不是传统的编程语言,而是一种 低级字节码格式,可由高级语言(如 C、C、Rust&am…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲
文章目录 前言第一部分:体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分:体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

嵌入式学习之系统编程(九)OSI模型、TCP/IP模型、UDP协议网络相关编程(6.3)
目录 一、网络编程--OSI模型 二、网络编程--TCP/IP模型 三、网络接口 四、UDP网络相关编程及主要函数 编辑编辑 UDP的特征 socke函数 bind函数 recvfrom函数(接收函数) sendto函数(发送函数) 五、网络编程之 UDP 用…...
人工智能 - 在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型
在Dify、Coze、n8n、FastGPT和RAGFlow之间做出技术选型。这些平台各有侧重,适用场景差异显著。下面我将从核心功能定位、典型应用场景、真实体验痛点、选型决策关键点进行拆解,并提供具体场景下的推荐方案。 一、核心功能定位速览 平台核心定位技术栈亮…...

ArcGIS Pro+ArcGIS给你的地图加上北回归线!
今天来看ArcGIS Pro和ArcGIS中如何给制作的中国地图或者其他大范围地图加上北回归线。 我们将在ArcGIS Pro和ArcGIS中一同介绍。 1 ArcGIS Pro中设置北回归线 1、在ArcGIS Pro中初步设置好经纬格网等,设置经线、纬线都以10间隔显示。 2、需要插入背会归线…...