一. 简介
Maven 是 Apache 组织下的一个跨平台的项目管理工具,它主要用来帮助实现项目的构建、测试、打包和部署。Maven 提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。它的跨平台性保证了在不同的操作系统上可以使用相同的命令来完成相应的任务。Maven 将构建的过程抽象成一个个的生命周期过程,在不同的阶段使用不同的已实现插件来完成相应的实际工作,这种设计方法极大的避免了设计和脚本编码的重复,极大的实现了复用。
Maven 配置项目的核心是pom.xml 文件,POM 即 Project Object Module,项目对象模型,在 pom.xml 文件中定义了项目的基本信息、源代码、配置文件、开发者的信息和角色、问题追踪系统、组织信息、项目授权、项目的 url、以及构建项目所用的插件,依赖继承关系。
二. 安装
下载安装步骤如下:
1)下载,在官网 http://maven.apache.org/download.cgi 下载即可
2)将压缩包解压到指定路径,比如我放到了 /opt 下
3)设置环境变量,命令如下
export M2_HOME=/yourPath/apache-maven-3.5.2
export PATH=M2_HOME/bin: PATH
4)验证命令
mvn -v
三. Maven POM
刚才上面提到了maven 配置项目的核心就是pom.xml 文件,pom 文件的各节点分布大致如下
<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">
<!--声明当前POM模型的版本,对maven2和maven3来说它只能是4.0.0。模型本身的版本很少改变,虽然如此,但它
仍然是必不可少的,这是为了当Maven引入了新的特性或者其他模型变更的时候,确保稳定性。-->
<modelVersion>4.0.0</modelVersion>
<!-- 基本配置 -->
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
<packaging>...</packaging>
<!-- 依赖配置 -->
<dependencies>...</dependencies>
<parent>...</parent>
<dependencyManagement>...</dependencyManagement>
<modules>...</modules>
<properties>...</properties>
<!-- 构建配置 -->
<build>...</build>
<reporting>...</reporting>
<!-- 项目信息 -->
<name>...</name>
<description>...</description>
<url>...</url>
<inceptionYear>...</inceptionYear>
<licenses>...</licenses>
<organization>...</organization>
<developers>...</developers>
<contributors>...</contributors>
<!-- 环境设置 -->
<issueManagement>...</issueManagement>
<ciManagement>...</ciManagement>
<mailingLists>...</mailingLists>
<scm>...</scm>
<prerequisites>...</prerequisites>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<distributionManagement>...</distributionManagement>
<profiles>...</profiles>
</project>
基本配置
定义了该项目的坐标,
- groupId:项目所在组的id,一般和公司相关
- artifactId:项目在组内的id
- version:项目版本号,如 1.0-SNAPSHOT,SNAPSHOT意为快照,说明项目在开发中,不是稳定版本
- packaging:项目的打包方式,有以下值:pom, jar, maven-plugin, ejb, war, ear, rar, par
依赖配置
1)parent:如果项目是一个子项目,那么这个元素用来声明父项目的坐标
<parent>
<groupId>com.whx</groupId>
<artifactId>project</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
2)modules:如果该项目包含子项目,这个元素用来指定包含的所有模块
<modules>
<!-- 子模块的artifactId -->
<module>demo</module>
<module>library</module>
</modules>
3)properties:属性,用来定义常量,Properties可以在整个POM中使用,格式是<name>value</name>,使用时用${name},遇到${name} 就替换为相应的值
<properties>
<spring.version>4.3.0.RELEASE</spring.version>
<testng.configxml>src/main/resources/testngxml/frameworks.xml</testng.configxml>
</properties>
4)dependencies:项目相关依赖配置,如果在父项目写的依赖,会被子项目引用,一般父项目会将子项目公用的依赖引入(将在之后详细讲解)。通过dependency 元素来精确声明依赖的坐标。
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!--scope是依赖范围,test表示该依赖只对测试代码有效,不声明时该值默认是compile,即对主代码和测试代码都有效-->
<scope>test</scope>
</dependency>
</dependencies>
5)dependencyManagement:配置写法同dependencies
<dependencyManagement>
<dependencies>
.....
</dependencies>
</dependencyManagement>
使用dependencyManagement 声明的依赖不会被子模块立即解析,只有子模块中只声明groupID和artifactID时,才使用此处的依赖信息。
关于配置依赖的更多内容请见后面的 “坐标与依赖”。
构建配置
1)build:用于配置项目构建相关信息
<build>
<!--该元素设置了项目源码目录,当构建项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。默认值是src/main/java -->
<sourceDirectory/>
<!--该元素设置了项目脚本源码目录,该目录和源码目录不同:绝大多数情况下,该目录下的内容 会被拷贝到输出目录(因为脚本是被解释的,而不是被编译的)。默认值src/main/scripts -->
<scriptSourceDirectory/>
<!--该元素设置了项目单元测试使用的源码目录,当测试项目的时候,构建系统会编译目录里的源码。该路径是相对于pom.xml的相对路径。默认值src/test/java -->
<testSourceDirectory/>
<!--被编译过的应用程序class文件存放的目录。-->
<outputDirectory/>
<!--被编译过的测试class文件存放的目录。-->
<testOutputDirectory/>
<!--使用来自该项目的一系列构建扩展-->
<extensions>
<!--描述使用到的构建扩展。-->
<extension>
<!-- 坐标 -->
<groupId> ... </groupId>
<artifactId> ... </artifactId>
<version> ... </version>
</extension>
</extensions>
<!--当项目没有规定目标(Maven2 叫做阶段)时的默认值-->
<defaultGoal/>
<!--这个元素描述了项目相关的所有资源路径列表,例如和项目相关的属性文件,这些资源被包含在最终的打包文件里。-->
<resources>
<!--这个元素描述了项目相关或测试相关的所有资源路径-->
<resource>
<!-- 描述了资源的目标路径。该路径相对target/classes目录(例如${project.build.outputDirectory})。举个例子,如果你想资源在特定的包里(org.apache.maven.messages),你就必须该元素设置为org/apache/maven /messages。然而,如果你只是想把资源放到源码目录结构里,就不需要该配置。-->
<targetPath>... </targetPath>
<!--是否使用参数值代替参数名。参数值取自properties元素或者文件里配置的属性,文件在filters元素里列出。-->
<filtering/>
<!--描述存放资源的目录,该路径相对POM路径-->
<directory/>
<!--包含的模式列表,例如**/*.xml.-->
<includes/>
<!--排除的模式列表,例如**/*.xml-->
<excludes/>
</resource>
</resources>
<!--这个元素描述了单元测试相关的所有资源路径,例如和单元测试相关的属性文件。-->
<testResources>
<!--这个元素描述了测试相关的所有资源路径,参见build/resources/resource元素的说明-->
<testResource>
...
</testResource>
</testResources>
<!--构建产生的所有文件存放的目录-->
<directory/>
<!--产生的构件的文件名,默认值是${artifactId}-${version}。-->
<finalName/>
<!--当filtering开关打开(true)时,使用到的过滤器属性文件列表-->
<filters>
<filter> ... </filter>
</filters>
<!--子项目可以引用的默认插件信息。该插件配置项直到被引用时才会被解析或绑定到生命周期。给定插件的任何本地配置都会覆盖这里的配置,主要用于继承-->
<pluginManagement>
<!--使用的插件列表 。-->
<plugins>
<!--plugin元素包含描述插件所需要的信息。-->
<plugin>
<!-- 坐标 -->
<groupId> ... </groupId>
<artifactId> ... </artifactId>
<version> ... </version>
<!--是否从该插件下载Maven扩展(例如打包和类型处理器),由于性能原因,只有在真需要下载时,该元素才被设置成true -->
<extensions> false </extensions>
<!--在构建生命周期中执行一组目标的配置。每个目标可能有不同的配置。-->
<executions>
<!--execution元素包含了插件执行需要的信息-->
<execution>
<!--执行目标的标识符,用于标识构建过程中的目标,或者匹配继承过程中需要合并的执行目标-->
<id/>
<!--绑定了目标的构建生命周期阶段,如果省略,目标会被绑定到源数据里配置的默认阶段-->
<phase/>
<!--配置的执行目标-->
<goals/>
<!--配置是否被传播到子POM-->
<inherited/>
<!--作为DOM对象的配置-->
<configuration/>
</execution>
</executions>
<!--项目引入插件所需要的额外依赖-->
<dependencies>
<!--参见dependencies/dependency元素-->
<dependency>
......
</dependency>
</dependencies>
<!--任何配置是否被传播到子项目-->
<inherited/>
<!--作为DOM对象的配置-->
<configuration/>
</plugin>
</plugins>
</pluginManagement>
</build>
2)reporting:该元素描述使用报表插件产生报表的规范,当用户执行 “mvn site”,这些报表就会运行,在页面导航栏就能看到所有报表的链接。
<reporting>
<!--true,则,网站不包括默认的报表。这包括“项目信息”菜单中的报表。-->
<excludeDefaults/>
<!--所有产生的报表存放到哪里。默认值是${project.build.directory}/site。-->
<outputDirectory/>
<!--使用的报表插件和他们的配置。-->
<plugins>
<!--plugin元素包含描述报表插件需要的信息-->
<plugin>
<!--报表插件在仓库里的坐标 -->
<groupId/>
<artifactId/>
<version/>
<!--任何配置是否被传播到子项目-->
<inherited/>
<!--报表插件的配置-->
<configuration/>
<!--一组报表的多重规范,每个规范可能有不同的配置。一个规范(报表集)对应一个执行目标 。例如,有1,2,3,4,5,6,7,8,9个报表。1,2,5构成A报表集,对应一个执行目标。2,5,8构成B报表集,对应另一个执行目标-->
<reportSets>
<!--表示报表的一个集合,以及产生该集合的配置-->
<reportSet>
<!--报表集合的唯一标识符,POM继承时用到-->
<id/>
<!--产生报表集合时,被使用的报表的配置-->
<configuration/>
<!--配置是否被继承到子POMs-->
<inherited/>
<!--这个集合里使用到哪些报表-->
<reports/>
</reportSet>
</reportSets>
</plugin>
</plugins>
</reporting>
项目的描述信息
- name:给用户提供更为友好的项目名
- description:项目描述,maven文档中保存
- url:主页的URL,maven文档中保存
- inceptionYear:项目创建年份,4位数字。当产生版权信息时需要使用这个值
- licenses:该元素描述了项目所有License列表。 应该只列出该项目的license列表,不要列出依赖项目的 license列表。如果列出多个license,用户可以选择它们中的一个而不是接受所有license。
<license>
<!--license用于法律上的名称-->
<name>...</name>
<!--官方的license正文页面的URL-->
<url>....</url>
<!--项目分发的主要方式:repo,可以从Maven库下载 manual, 用户必须手动下载和安装依赖-->
<distribution>repo</distribution>
<!--关于license的补充信息-->
<comments>....</comments>
</license>
- organization:1.name 组织名 2.url 组织主页url
- developers:项目开发人员列表(如下)
- contributors:项目其他贡献者列表,同developers
<developers>
<!--某个开发者信息-->
<developer>
<!--开发者的唯一标识符-->
<id>....</id>
<!--开发者的全名-->
<name>...</name>
<!--开发者的email-->
<email>...</email>
<!--开发者的主页-->
<url>...<url/>
<!--开发者在项目中的角色-->
<roles>
<role>Java Dev</role>
<role>Web UI</role>
</roles>
<!--开发者所属组织-->
<organization>sun</organization>
<!--开发者所属组织的URL-->
<organizationUrl>...</organizationUrl>
<!--开发者属性,如即时消息如何处理等-->
<properties>
<!-- 和主标签中的properties一样,可以随意定义子标签 -->
</properties>
<!--开发者所在时区, -11到12范围内的整数。-->
<timezone>+8</timezone>
</developer>
</developers>
环境设置
1)issueManagement:目的问题管理系统(Bugzilla, Jira, Scarab)的名称和URL
<issueManagement>
<system>Bugzilla</system>
<url>http://127.0.0.1/bugzilla/</url>
</issueManagement>
2)ciManagement:持续集成系统信息
<ciManagement>
<!-- 系统名称 -->
<system>continuum</system>
<url>http://127.0.0.1:8080/continuum</url>
<!-- 构建完成时,需要通知的开发者/用户的配置项。包括被通知者信息和通知条件(错误,失败,成功,警告)-->
<notifiers>
<notifier>
<!-- 通知方式 -->
<type>mail</type>
<!-- 错误时是否通知 -->
<sendOnError>true</sendOnError>
<!-- 失败时是否通知 -->
<sendOnFailure>true</sendOnFailure>
<!-- 成功时是否通知 -->
<sendOnSuccess>false</sendOnSuccess>
<!-- 警告时是否通知 -->
<sendOnWarning>false</sendOnWarning>
<!-- 通知发送到的地址 -->
<address>continuum@127.0.0.1</address>
<!-- 扩展项 -->
<configuration></configuration>
</notifier>
</notifiers>
</ciManagement>
3)mailLists:项目相关邮件列表信息
<mailingLists>
<mailingList>
<name>User List</name>
<subscribe>user-subscribe@127.0.0.1</subscribe>
<unsubscribe>user-unsubscribe@127.0.0.1</unsubscribe>
<post>user@127.0.0.1</post>
<archive>http://127.0.0.1/user/</archive>
<otherArchives>
<otherArchive>http://base.google.com/base/1/127.0.0.1</otherArchive>
</otherArchives>
</mailingList>
.....
</mailingLists>
- subscribe, unsubscribe: 订阅邮件(取消订阅)的地址或链接,如果是邮件地址,创建文档时,mailto: 链接会被自动创建
- archive:浏览邮件信息的URL
- post:接收邮件的地址
4)scm:允许你配置你的代码库,供Maven web站点和其它插件使用
<scm>
<connection>scm:svn:http://127.0.0.1/svn/my-project</connection>
<developerConnection>scm:svn:https://127.0.0.1/svn/my-project</developerConnection>
<tag>HEAD</tag>
<url>http://127.0.0.1/websvn/my-project</url>
</scm>
- connection, developerConnection:这两个表示我们如何连接到maven的版本库。connection只提供读,developerConnection将提供写的请求
写法如:scm:[provider]:[provider_specific]
如果连接到CVS仓库,可以配置如下:scm:cvs:pserver:127.0.0.1:/cvs/root:my-project
tag:项目标签,默认HEAD
url:共有仓库路径
5)prerequisite:项目构建的前提
<prerequisites>
<maven>2.0.6</maven>
</prerequisites>
6)repositories、pluginRepositories:依赖和扩展的远程仓库列表
<repositories>
<repository>
<releases>
<enabled>false</enabled>
<updatePolicy>always</updatePolicy>
<checksumPolicy>warn</checksumPolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<updatePolicy>never</updatePolicy>
<checksumPolicy>fail</checksumPolicy>
</snapshots>
<id>codehausSnapshots</id>
<name>Codehaus Snapshots</name>
<url>http://snapshots.maven.codehaus.org/maven2</url>
<layout>default</layout>
</repository>
</repositories>
<pluginRepositories>
...
</pluginRepositories>
releases, snapshots: 这是各种构件的策略,release或者snapshot。这两个集合,POM就可以根据独立仓库任意类型的依赖改变策略。如:一个人可能只激活下载snapshot用来开发。
enable:true或者false,决定仓库是否对于各自的类型激活(release 或者 snapshot)。
updatePolicy: 这个元素决定更新频率。maven将比较本地pom的时间戳(存储在仓库的maven数据文件中)和远程的. 有以下选择: always, daily (默认), interval:X (x是代表分钟的整型) , never。
checksumPolicy:当Maven向仓库部署文件的时候,它也部署了相应的校验和文件。可选的为:ignore,fail,warn,或者不正确的校验和。
layout:在上面描述仓库的时候,提到他们有统一的布局。Maven 2 & 3 的仓库布局是default。然而,Maven 1.x有不同布局。使用这个元素来表明它是default还是legacy。
7)distributionManagement:管理发布时的相关信息,
<distributionManagement>
...
<downloadUrl>http://mojo.codehaus.org/my-project</downloadUrl>
<status>deployed</status>
<repository>
<uniqueVersion>false</uniqueVersion>
<id>corp1</id>
<name>Corporate Repository</name>
<url>scp://repo/maven2</url>
<layout>default</layout>
</repository>
<snapshotRepository>
...
</snapshotRepository>
<site>
<id>mojo.website</id>
<name>Mojo Website</name>
<url>scp://beaver.codehaus.org/home/projects/mojo/public_html/</url>
</site>
<relocation>
<groupId>org.apache</groupId>
<artifactId>my-project</artifactId>
<version>1.0</version>
<message>We have moved the Project under Apache</message>
</relocation>
</distributionManagement>
downloadUrl: 其他pom可以通过此url的仓库抓取组件
status:给出该构件在远程仓库的状态,有以下几种
none: 默认,没有特殊状态
converted: 将被早期Maven 2 POM转换过来
partner: 这个项目会从合作者仓库同步过来
deployed: 最常见的状态,从Maven 2或3实例部署,这也是我们使用deploy 命令手动部署时的状态
verified: 已经被验证并且是最终的版本repository:指定Maven pom从远程下载控件到当前项目的位置和方式,当snapshot 模式发布时,如果snapshotRepository没有被定义则使用repository相关的配置
id, name:仓库的唯一标识
uniqueVersion:true或false,指明控件部署的时候是否获取独立的版本号。
url:repository元素的核心。指定位置和部署协议发布控件到仓库。
layout:布局,default或legacysite:多分布存储库,定义如何部署项目的网站和文档,包含的元素和repository 中相同。
relocation:重新部署-项目不是静态的,是活的,随着项目的增长,有时需要将它转移到更合适的地方。如:当你的下个成功的开源项目移到Apache下,重命名为org.apache:my-project:1.0 对你项目更有好处。
8)profiles:profile 是POM 4.0 新增的功能,可以让我们定义一系列的配置信息,然后指定其激活条件。 根据每个profile对应不同的激活条件和配置信息,从而达到不同环境使用不同配置。
<profiles>
<profile>
<id>test</id>
<!-- 激活条件 -->
<activation>...</activation>
<!-- 下面这些元素已经介绍过了 -->
<build>...</build>
<modules>...</modules>
<properties>...</properties>
<repositories>...</repositories>
<pluginRepositories>...</pluginRepositories>
<dependencies>...</dependencies>
<reporting>...</reporting>
<dependencyManagement>...</dependencyManagement>
<distributionManagement>...</distributionManagement>
</profile>
</profiles>
- activation:激活条件,当有一个或更多条件满足时就会触发激活,当遇到第一个激活条件时,匹配过程会停止并把该profile 标记为活动状态
<activation>
<activeByDefault>false</activeByDefault>
<jdk>1.5</jdk>
<os>
<name>Windows XP</name>
<family>Windows</family>
<arch>x86</arch>
<version>5.1.2600</version>
</os>
<property>
<name>sparrow-type</name>
<value>African</value>
</property>
<file>
<exists>${basedir}/file2.properties</exists>
<missing>${basedir}/file1.properties</missing>
</file>
</activation>
jdk:检测到对应jdk版本就激活
os:针对不同操作系统
property:当maven检测到property(pom中如${name} 这样的)profile将被激活
file:<exists>如果存在该文件则激活,<missing>如果不存在该文件则激活
关于pom 文件的介绍就暂时这么多。下面介绍一下maven 的其他知识
四. 坐标与依赖
坐标
maven 使用坐标来唯一标识一个构件,构件可以理解为jar/war 包。maven根据坐标,去中央仓库http://repo1.maven.org/maven2下载构件,该中央仓库包含了世界上大部分流行的开源项目构件。
坐标构成:groupId, artifactId, version, packing, classifier 。
- groupId: 定义当前Maven项实际隶属的项目。之前说对应公司或者组织,而实际上会更加具体
- artifactId: 定义实际项目中的一个Maven项目(模块)
- version: 版本
- packing(非必须声明): 打包方式,打包方式有jar和war,默认方式为jar,两者会使用不同的命令。并非一定与构件扩展名对应,比如packaging为maven-plugin的扩展名为jar
- classifier(不能直接定义): 是项目的附属构件,不是由项目直接生成的,而是由附加的插件帮助生成
依赖
maven 中使用dependency 元素定义依赖,定义一个依赖需要包含坐标信息,除此之外还可以包含以下元素:
- type:依赖的类型,默认是jar
- scope:依赖的范围,有compile,test,provided,runtime,system 等,默认compile。
compile 范围的 Maven 依赖, 对于编译、测试、运行都有效,如spring-core;
test 范围的 Maven 依赖, 只对于测试有效,在 编译主代码或者运行项目的使用时将无法使用此类依赖,比如JUnit;
provided 范围的 Maven 依赖,对于编译和测试有效,但在运行时无效,比如 servlet-api, 编译和测试项目的时候 需要该依赖,但在运行项目的时候,由于容器已经提供, 就不需要 Maven 重复的引入一遍;
runtime 范围的 Maven 依赖, 对于测试和运行有效, 但在编译主代码时无效,典型的例子是 JDBC 驱动实现,项目主代码的编译只需要 JDK 提供的 JDBC 接口, 只有在执行测试或者运行项目的时候才需要实 现上述接口的具体 JDBC 驱动;
system 依赖与provided 依赖范围完全一 致,但是, 使用 System 范围的依赖时必须通过 systemPath 元素显式地指定依赖文件的路径,由于此类依赖不是通过 Maven 仓库解析的,而且往往与本机系统绑定, 可能造成构建的不可移植, 因此应该谨慎使用。
import (仅可在maven 2.0.9 及以后使用)这个范围只能使用在type 为pom 的dependencyManagement 元素中,意为从其他pom 导入dependency 配置。不参与依赖的传递。举个栗子
<project>
...
<packaging>pom</packaging>
<artifactId>B</artifactId>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.whx</groupId>
<artifactId>A</artifactId>
<version>1.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
<!-- 这样就把 A 中定义的dependencyManagement 元素的配置导入到了 B 中
- optional,依赖是否可选
- exclusions,排除传递性依赖
传递性依赖:A依赖于B,B依赖于C,那么C是A的传递性依赖
注:
- Maven会解析直接依赖,然后把传递性依赖引入项目中
- Maven解析后的依赖,不会出现groupId和artifactId相同,version不同的情况(依赖冲突),也就是说maven会确保任何一个构件只有唯一版本在依赖中存在。
- 如果依赖是可选的,optional=true,那么依赖不会传递,如C是可选依赖,那么C对A将没有任何影响。
- 使用exclusions可以排除传递性依赖C,然后在B中自己显示声明依赖C,声明exclusion的时候不需要version。
依赖调解:
A间接依赖X的多个版本,那maven如保管理这个依赖呢,例如
例1:
A → B → C →X(1.0)
A → D → X(2.0)
例2:
A → B → X(1.0)
A → D → X(2.0)
maven 有两条原则:路径最短优先 和 声明最早优先
但是这可能会导致一个问题,比如例2,A 会优先依赖X(1.0),所以A 的直接依赖D(依赖的是X 2.0)可能就会出现问题,若要解决这种问题就需要上面提到的exclusions 来进行排除依赖。
传递依赖与依赖范围:
上面提到,在声明依赖时,可以声明依赖的作用范围(scope),那么假如在上面的例2 中A → B → X(1.0),如果B → X 声明的依赖范围是 test,那么A 引入 X 的作用范围是什么呢?关于依赖范围的传递,其实有着这样的规则:
当B → X 的scope 是compile 的时候,传递性依赖的范围与A → B 依赖的范围一致
当B → X 的scope 是test 的时候,依赖不会得以传递
当B → X 的scope 是provided 的时候,只传递A → B 范围也为provided的依赖,且传递性依赖的范围同样是provided
当B → X 的scope 是runtime 的时候,传递性依赖的范围与A → B 的范围一致,但compile除外,当A → B 的范围是compile 时传递性依赖范围为runtime
本篇已经够长了,其余内容请见Maven 学习(二)