Maven概述
本文最后更新于37 天前,其中的信息可能已经过时,如有错误请发送邮件到2742629153@qq.com

一、Maven 的核心概念

1.1 项目对象模型(POM)

项目对象模型(Project Object Model,POM)是 Maven 的核心,它以一种结构化的方式描述了项目的基本信息、依赖关系、构建配置以及其他与项目相关的元数据。POM 是一个 XML 文件,通常命名为 pom.xml,位于项目的根目录下,它就像是项目的 “灵魂”,指导着 Maven 如何构建、管理和部署项目。

1.1.1 POM 文件结构

POM 文件包含了众多的节点,每个节点都有着特定的含义和用途:

  • modelVersion:指定 POM 文件遵循的模型版本,目前 Maven 强制要求该值为 4.0.0 ,它定义了 POM 文件的结构和规范,确保 Maven 能够正确解析和处理 POM 文件。
  • groupId:通常采用反向域名的形式,如 com.example、org.apache 等,用来唯一标识项目所属的组织、公司或团队 。例如,对于 Apache Maven 团队开发的插件,groupId 可能是 org.apache.maven.plugins,它在 Maven 仓库中起到组织和分组项目的作用,帮助 Maven 精准定位项目,同时也避免了不同组织或团队的项目命名冲突。
  • artifactId:是项目或模块的唯一标识符,通常是项目的名称或者模块名称,例如,在一个 Spring Boot 项目中,artifactId 可能是 spring-boot-starter-web,它用于区分同一组织下的不同项目或模块。
  • version:表示项目的版本号,它可以是具体的数字组合,如 1.0.0,也可以是快照版本,如 2.3.5 – SNAPSHOT。版本号在项目的依赖管理中非常重要,它决定了依赖项的精确版本,确保了构建的一致性和可重复性。在团队协作开发中,统一的版本号能避免因版本不一致导致的兼容性问题;而快照版本则常用于开发阶段,方便开发人员获取最新的开发成果进行测试和集成。
  • name:是一个描述性的字段,用于给项目一个易于理解的名字,它并非 Maven 坐标的一部分,但会在某些地方显示,比如在生成的文档中,或者作为项目的描述性标签,有助于提高项目的可读性和可识别性。例如,一个项目的 name 可以是 “My Awesome Project”,这样在项目文档或相关展示中,能让开发者更直观地了解项目内容。
  • description:对项目进行更详细的描述,它可以包含项目的功能、目标、用途等信息,帮助其他开发者快速了解项目的背景和意义。例如,对于一个电商项目,description 可以详细说明该项目提供的电商服务、主要功能模块以及目标用户群体等。
  • url:指定项目的官方网站地址,通过这个地址,开发者可以获取更多关于项目的信息,如项目文档、最新动态、社区支持等。比如,Spring Framework 项目的 url 为https://spring.io/,开发者可以在该网站上找到丰富的文档、教程以及社区资源。
  • properties:用于定义项目中的属性,这些属性可以在 pom.xml 文件的其他地方引用(使用({xxxx}的形式)。除了自定义属性外,还可以配置编译和运行环境的属性。例如,可以定义一个属性){project.build.sourceEncoding},并将其值设置为 UTF-8,用于指定项目的源代码编码格式,这样在整个项目构建过程中,都可以使用该属性来确保编码的一致性。
  • dependencies:这是 POM 文件中非常重要的一个节点,用于声明项目所依赖的库和模块。每个依赖通过一个元素来定义,包含 groupId、artifactId 和 version 等信息,Maven 会根据这些信息自动下载并管理依赖。例如:
<dependencies>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

上述代码声明了对 JUnit 测试框架的依赖,版本为 4.13.2,并且指定了依赖范围为 test,意味着该依赖仅在测试阶段使用,不会包含在最终的构建产物中。

  • dependencyManagement:用于集中管理依赖的版本信息,它可以确保在一个多模块项目中,所有模块使用的依赖版本一致,避免因版本不一致导致的兼容性问题。通过在 dependencyManagement 中定义依赖的版本,子模块在引入这些依赖时可以不指定版本,直接继承父模块中定义的版本。例如:
<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-core</artifactId>
            <version>5.3.10</version>
        </dependency>
    </dependencies>
</dependencyManagement>

在子模块中引入 spring-core 依赖时,只需声明 groupId 和 artifactId,无需指定版本:

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
    </dependency>
</dependencies>
  • build:包含了项目构建相关的配置信息,如插件配置、资源文件配置、目标目录配置等。通过 build 节点,可以自定义项目的构建过程,满足不同项目的特定需求。例如,可以配置编译插件的版本和参数,指定资源文件的复制规则等。以下是一个配置编译插件的示例:
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>1.8</source>
                <target>1.8</target>
            </configuration>
        </plugin>
    </plugins>
</build>

上述代码配置了 maven-compiler-plugin 插件的版本为 3.8.1,并设置了编译源代码的 Java 版本为 1.8。

1.1.2 依赖管理

在 Maven 中,依赖管理是其核心功能之一,它极大地简化了项目依赖的配置和管理过程。通过在 POM 文件的节点下声明依赖,Maven 能够自动处理依赖的下载、版本冲突解决以及依赖传递等复杂问题。

当在项目中声明一个依赖时,Maven 会首先检查本地仓库中是否存在该依赖。如果存在,Maven 将直接使用本地仓库中的依赖;如果不存在,Maven 会从远程仓库(如 Maven 中央仓库,这是全球最大的 Java 库仓库,汇聚了大量的开源依赖库)下载该依赖,并将其存储到本地仓库中,以便后续使用。例如,当我们在项目中添加对 Spring Boot Starter Web 的依赖时:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.5</version>
    </dependency>
</dependencies>

Maven 会自动从中央仓库下载 spring-boot-starter-web 及其所有传递性依赖(即 spring-boot-starter-web 所依赖的其他库),并将它们添加到项目的类路径中,使得我们在项目中可以直接使用 Spring Boot Web 相关的功能,而无需手动去下载和管理这些依赖库。

Maven 还提供了强大的依赖冲突解决机制。当项目中存在多个依赖引入了同一库的不同版本时,Maven 会采用以下策略来解决冲突:

  • 最短路径优先原则:Maven 会选择依赖路径最短的版本。例如,假设项目 A 依赖 B,B 依赖 C,C 依赖 X 的 1.0 版本;同时项目 A 还依赖 D,D 依赖 E,E 依赖 X 的 2.0 版本。由于从 A 到 X 通过 B – C 路径更短,所以 Maven 会选择 X 的 1.0 版本。
  • 声明优先原则:如果依赖路径长度相同,Maven 会选择在 POM 文件中最先声明的那个版本。例如,在 POM 文件中先声明了对 X 的 1.0 版本的依赖,之后又声明了对 X 的 2.0 版本的依赖,且这两个依赖路径长度相同,那么 Maven 会选择 1.0 版本。

此外,Maven 还支持通过元素来排除特定的传递性依赖,以及通过元素来控制依赖的作用范围。例如,如果我们不想让某个依赖的传递性依赖被引入项目中,可以在依赖声明中使用元素:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>library-a</artifactId>
    <version>1.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.example</groupId>
            <artifactId>library-b</artifactId>
        </exclusion>
    </exclusions>
</dependency>

上述代码表示在引入 library-a 依赖时,排除其对 library-b 的传递性依赖。而元素则可以指定依赖的作用范围,常见的作用范围有 compile(默认范围,编译、测试和运行时可用)、provided(编译和测试可用,但不包含在最终打包中,适用于容器已提供的依赖,如 Servlet API)、runtime(测试和运行时可用,编译阶段不可见,常用于驱动程序或数据库连接)、test(仅用于测试阶段,编译和运行时不可见)等。例如:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
    <scope>test</scope>
</dependency>

这里将 JUnit 的依赖范围设置为 test,意味着 JUnit 仅在测试阶段被使用,不会被包含在项目的最终打包文件中,从而减小了打包文件的体积,也避免了不必要的依赖冲突。

1.2 坐标系统

Maven 的坐标系统是其实现依赖管理和项目构建的重要基础,它通过 groupId、artifactId 和 version 这三个关键元素来唯一标识一个项目或库。

  • groupId:如前文所述,它通常采用反向域名的形式,用于唯一标识项目所属的组织、公司或团队 ,在 Maven 仓库中起到组织和分组项目的作用,是坐标系统中的组织标识符。例如,org.springframework 代表 Spring 框架的开发组织,所有 Spring 相关的项目和库都以这个 groupId 作为标识的一部分。
  • artifactId:作为项目或模块的唯一标识符,用于区分同一组织下的不同项目或模块,是坐标系统中的项目标识符。比如,在 Spring 生态系统中,spring-core、spring-web 等不同的模块都有各自独特的 artifactId,通过这些 artifactId 可以准确地定位到具体的模块。
  • version:表示项目的版本号,用于标识项目的不同发布版本,确保构建的一致性和可重复性,是坐标系统中的版本标识符。不同版本的项目或库可能在功能、性能、稳定性等方面存在差异,通过明确的版本号,开发者可以精确控制项目所依赖的库的版本。例如,在开发一个 Java Web 应用时,可能需要使用特定版本的 Spring MVC 框架,通过指定 spring-webmvc 的版本号,如 5.3.15,就可以确保项目在构建和运行时使用的是这个特定版本的框架,避免因版本不一致导致的兼容性问题。

这三个元素组合在一起,构成了 Maven 坐标的核心,就像一个唯一的 “地址”,可以在 Maven 仓库中精准定位到一个特定的项目或库。例如,对于 JUnit 测试框架,其坐标为:

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.13.2</version>
</dependency>

Maven 根据这个坐标,首先在本地仓库中查找是否存在 junit:junit:4.13.2 这个库。如果本地仓库中没有找到,就会根据配置的远程仓库地址(如 Maven 中央仓库的地址https://repo.maven.apache.org/maven2/ )去远程仓库中查找并下载该库。在下载过程中,Maven 会根据坐标信息,确保下载的是指定组织(junit)、指定项目(junit)的指定版本(4.13.2)的库,从而保证了依赖管理的准确性和可靠性。同时,在多模块项目中,坐标系统也使得模块之间的依赖关系更加清晰和易于管理,每个模块可以通过坐标明确地声明对其他模块或外部库的依赖,Maven 能够根据这些坐标信息自动处理依赖的下载、解析和整合,大大提高了项目开发和构建的效率。

1.3 生命周期和阶段

Maven 定义了一套标准的构建生命周期,它将项目的构建过程划分为一系列有序的阶段,每个阶段都代表了构建过程中的一个特定步骤,并且每个阶段都可以绑定一个或多个插件目标,通过执行这些插件目标来完成具体的构建任务。这种标准化的生命周期使得 Maven 项目的构建过程具有一致性和可重复性,开发者可以根据项目的需求,灵活地控制构建过程的各个环节。

1.3.1 生命周期类型

Maven 主要有以下三种内置的生命周期类型:​

  1. Clean Lifecycle(清理生命周期):该生命周期主要负责清理项目,删除之前构建生成的文件,为新的构建做好准备。它包含以下几个阶段:​
    • pre-clean:在项目实际进行清理之前执行一些预处理工作,例如关闭相关进程、备份某些重要文件等。​
    • clean:这是核心的清理阶段,会移除所有上一次构建过程生成的文件,通常是删除项目目录下的 target 目录及其内容,该目录一般存放编译后的字节码文件、打包文件等构建产物。​
    • post-clean:完成最终项目清理工作的收尾操作,比如清理临时文件、释放资源等后续处理工作。​
  2. Default Lifecycle(默认生命周期):这是 Maven 最常用且涵盖项目构建核心流程的生命周期,负责从项目的编译、测试、打包到部署等一系列完整的过程。它包含众多阶段,以下列举一些关键阶段:​
    • validate:验证项目是否正确,确保所有必需的资源(如依赖库、配置文件等)可用,项目的基本结构和配置符合规范。​
    • initialize:初始化编译状态,例如设置一些项目构建所需的属性(properties),创建编译过程中需要的目录结构等。​
    • generate-sources:生成在编译阶段需要的额外源代码,有些项目可能会在构建时通过代码生成工具动态生成部分源代码,此阶段会执行相关操作。​
    • generate-resources:生成项目打包时需要包含的资源文件,如将 src/main/resources 目录下的配置文件、静态资源等按规则处理,以便后续能正确打包到最终的项目产物中。​
    • compile:将项目的 Java 源文件编译为字节码文件,编译后的文件通常存放在 target/classes 目录下。​
    • process-classes:对编译生成的类文件进行一些额外处理,比如字节码增强、混淆等操作(虽然不是常见的基础操作,但在特定场景下会用到)。​
    • generate-test-sources:生成测试阶段所需的源代码,一些项目可能会有专门用于测试的代码生成逻辑。​
    • process-test-sources:处理测试源代码,例如将测试源文件移动到合适位置、进行预处理等。​
    • generate-test-resources:生成测试相关的资源文件,比如测试用的配置文件、测试数据文件等。​
    • process-test-resources:处理测试资源,如将测试资源文件复制到正确的测试执行目录。​
    • test-compile:编译测试源代码,生成测试类的字节码文件,存放于 target/test-classes 目录。​
    • process-test-classes:对测试类的字节码文件进行处理,类似主代码的字节码处理操作,可能用于测试相关的增强或优化。​
    • test:使用合适的单元测试框架(如 JUnit)来运行已编译的测试代码,验证项目功能是否符合预期,并生成测试报告。​
    • package:将编译后的代码打包成可发布的格式,例如将 Java 项目打包成.jar(Java 应用程序或库)、.war(Web 应用程序)、.ear(企业级应用程序)等文件,打包后的文件存放在 target 目录下。​
    • install:将打包后的文件安装到本地 Maven 仓库,以便其他项目可以将其作为依赖引入使用。​
    • deploy:在集成或发布环境下执行,将最终版本的包拷贝到远程仓库(如公司内部的私服、Maven 中央仓库等),使得其他开发者或项目能够共享该项目构建产物。​
  3. Site Lifecycle(站点生命周期):此生命周期用于生成项目的站点文档,帮助团队成员、其他开发者或用户更好地了解项目信息、使用方法、API 文档等内容。它包含以下阶段:​
    • pre-site:在生成项目站点之前执行所需的流程,例如准备生成站点所需的数据、配置相关工具等。​
    • site:生成项目站点文档,包括生成项目报告(如项目概述、开发进度、测试结果等)、API 文档(如果项目提供 API)、用户指南等内容,生成的站点文件通常存放在 target/site 目录下。​
    • post-site:执行生成项目站点之后需要完成的工作,例如对生成的站点文件进行最后的整理、优化等操作。​
    • site-deploy:将生成的项目站点发布到指定的 Web 服务器上,以便对外公开访问,方便他人查阅项目相关信息。​

每个生命周期都由一系列有序的阶段组成,后面的阶段往往依赖于前面阶段的成功执行。在实际使用中,我们可以通过执行特定的 Maven 命令来触发相应生命周期的部分或全部阶段,从而高效地管理项目构建过程。例如,执行mvn clean命令会触发 Clean Lifecycle 中的 pre-clean 和 clean 阶段;执行mvn install命令会触发 Default Lifecycle 从 validate 到 install 的所有阶段。

1.3.2 阶段执行顺序

Maven 生命周期中的阶段有着严格的先后执行顺序,这种顺序确保了项目构建过程的合理性与完整性。当执行 Maven 构建命令时,若指定了某一阶段,Maven 会自动按顺序执行该阶段之前的所有阶段。​

以 Default Lifecycle 为例,假设执行mvn package命令,Maven 首先会执行validate阶段,验证项目的正确性与所需信息的完备性,比如检查项目的 POM 文件是否规范,所有必要的依赖是否都已声明等;接着进入initialize阶段,对构建过程进行初始化,例如设置一些构建相关的属性、创建必要的目录结构等;随后是generate-sources阶段,若项目存在构建时生成源代码的需求,如通过代码生成工具生成一些基础代码,此阶段便会执行相应操作;之后依次执行process-sources(处理源代码,如对源代码进行过滤替换操作)、generate-resources(生成项目资源文件,如将一些配置文件、静态资源等按规则准备好)、process-resources(处理资源文件,将其复制到合适的位置,为打包做准备)、compile(使用 Java 编译器将src/main/java目录下的 Java 源文件编译成字节码文件,生成的字节码文件通常存放在target/classes目录)、process-classes(对编译生成的类文件进行后续处理,例如字节码增强等,不过此操作并非所有项目都会用到)、generate-test-sources(生成测试阶段所需的源代码,如果项目有专门用于测试的代码生成逻辑)、process-test-sources(处理测试源代码,如将测试源文件移动到正确位置、进行预处理)、generate-test-resources(生成测试相关的资源文件,如测试用的配置文件、测试数据文件等)、process-test-resources(将测试资源文件复制到正确的测试执行目录)、test-compile(编译测试源代码,生成测试类的字节码文件,存放于target/test-classes目录)、process-test-classes(对测试类的字节码文件进行处理,类似主代码的字节码处理操作,可能用于测试相关的增强或优化)、test(使用单元测试框架,如 JUnit,运行已编译的测试代码,验证项目功能是否符合预期,并生成测试报告)等阶段,最终才执行到指定的package阶段,将编译后的代码打包成可发布的格式,如.jar(Java 应用程序或库)、.war(Web 应用程序)等文件,打包后的文件存放在target目录下。​

Clean Lifecycle 也是同样的执行逻辑,当执行mvn clean命令时,Maven 会先执行pre-clean阶段,进行清理前的准备工作,比如关闭相关进程、备份某些重要文件等,然后执行clean阶段,删除之前构建生成的文件,通常是删除项目目录下的target目录及其内容。若执行mvn post-clean命令,那么pre-clean和clean阶段也会依次执行。​

Site Lifecycle 同样遵循此规则,例如执行mvn site-deploy命令,Maven 会依次执行pre-site(在生成项目站点之前执行所需的流程,例如准备生成站点所需的数据、配置相关工具等)、site(生成项目站点文档,包括生成项目报告、API 文档、用户指南等内容,生成的站点文件通常存放在target/site目录下)、post-site(执行生成项目站点之后需要完成的工作,例如对生成的站点文件进行最后的整理、优化等操作)阶段,最后执行site-deploy阶段,将生成的项目站点发布到指定的 Web 服务器上,以便对外公开访问。​

这种阶段执行顺序的设计,使得开发者在执行构建命令时无需手动依次执行每个步骤,大大简化了项目构建过程,提高了开发效率。同时,由于每个阶段都有其明确的职责和执行顺序,也保证了项目构建过程的稳定性和可重复性。

二、Maven 的使用场景

2.1 构建和打包项目

在 Maven 项目中,构建和打包是项目开发过程中的重要环节,通过简单的命令就能实现从源代码到可部署文件的转换,极大地提高了开发效率。例如,在开发一个 Java Web 项目时,我们可以使用 Maven 的命令来完成构建和打包操作。

在项目的根目录下打开命令行工具,执行 mvn clean package 命令。其中,mvn 是 Maven 的命令行工具前缀,clean 是 Maven 生命周期中的清理阶段,它会删除之前构建生成的文件,主要是删除项目的 target 目录及其内容,确保项目在干净的环境下进行后续构建,避免旧的构建产物对新构建过程产生干扰;package 是 Maven 生命周期中的打包阶段,它会将项目编译后的类文件、资源文件等打包成可分发的格式,对于 Java 项目,通常会打包成 JAR 文件(用于 Java 类库项目);而对于 Java Web 项目,则会打包成 WAR 文件,方便部署到 Web 服务器上。

当执行 mvn clean package 命令时,Maven 会按照以下步骤进行操作:

  1. 首先执行 clean 阶段,删除 target 目录及其下的所有文件和子目录,确保项目处于初始状态。
  2. 接着进入 package 阶段,Maven 会先执行 validate 阶段,验证项目的 POM 文件是否正确,所有必要的信息是否可用;然后执行 initialize 阶段,初始化构建环境,设置一些构建相关的属性和变量。
  3. 随后依次执行 generate-sources、process-sources、generate-resources、process-resources 等阶段,生成并处理项目的源代码和资源文件,将 src/main/java 目录下的 Java 源代码编译成字节码文件(.class 文件),并将 src/main/resources 目录下的资源文件复制到 target/classes 目录下。
  4. 完成编译和资源处理后,执行 test-compile 阶段,编译 src/test/java 目录下的测试源代码,生成测试字节码文件;接着执行 process-test-classes 阶段,对测试字节码文件进行一些额外的处理。
  5. 然后执行 test 阶段,运行测试代码,对项目的功能进行全面测试,确保代码的正确性和稳定性。如果测试过程中发现错误或异常,Maven 会停止后续操作,并提示错误信息。
  6. 若测试通过,最后执行 package 阶段,将编译后的类文件和资源文件按照项目的打包配置进行打包,生成最终的 JAR 或 WAR 文件,存放在 target 目录下。例如,对于一个名为 my-web-project 的 Java Web 项目,执行 mvn clean package 命令后,在 target 目录下会生成 my-web-project.war 文件,这个文件可以直接部署到 Tomcat、Jetty 等 Web 服务器上运行。

除了 mvn clean package 命令,还可以根据项目的具体需求,单独执行某个阶段的命令。例如,执行 mvn compile 命令,只进行项目源代码的编译操作,将 Java 源代码编译成字节码文件,生成的字节码文件会存放在 target/classes 目录下;执行 mvn test 命令,只运行测试代码,对项目进行测试;执行 mvn install 命令,不仅会完成项目的清理、编译、测试和打包操作,还会将打包后的文件安装到本地 Maven 仓库中,供其他项目依赖使用。通过灵活运用这些命令,开发者可以根据项目的不同阶段和需求,高效地完成项目的构建和打包工作。

2.2 依赖管理

在项目开发过程中,依赖管理是一个至关重要的环节,它直接影响到项目的稳定性、可维护性和开发效率。Maven 提供了强大而灵活的依赖管理功能,使得项目依赖的添加、更新和排除变得简单而高效。

2.2.1 添加依赖

在 Maven 项目中,添加依赖非常简单,只需在项目的 pom.xml 文件中的标签内添加相应的依赖声明即可。每个依赖声明通过一个元素来定义,包含 groupId、artifactId 和 version 等关键信息,这些信息构成了 Maven 坐标,用于唯一标识一个依赖库。例如,当我们在开发一个基于 Spring Boot 的 Web 应用时,需要添加 Spring Boot Starter Web 依赖,以便快速构建 Web 应用。在 pom.xml 文件中添加如下依赖声明:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.7.5</version>
    </dependency>
</dependencies>

上述代码声明了对 Spring Boot Starter Web 的依赖,版本为 2.7.5。当保存 pom.xml 文件后,Maven 会自动从远程仓库(默认是 Maven 中央仓库,若配置了其他远程仓库,也会从相应仓库查找)下载该依赖及其所有传递性依赖(即 spring-boot-starter-web 所依赖的其他库,如 Spring Core、Spring MVC 等),并将它们添加到项目的类路径中,使得我们在项目中可以直接使用 Spring Boot Web 相关的功能,无需手动去下载和管理这些依赖库。在实际开发中,可能还需要添加其他依赖,如数据库连接依赖、日志依赖等,都可以按照类似的方式在 pom.xml 文件中进行声明。例如,添加 MySQL 数据库连接依赖:

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>8.0.30</version>
    <scope>runtime</scope>
</dependency>

这里不仅声明了依赖的坐标,还通过元素指定了依赖的作用范围为 runtime,表示该依赖仅在运行时需要,编译阶段不需要,这样可以减小项目编译时的依赖范围,提高编译速度。

2.2.2 更新依赖

随着项目的发展和开源库的不断更新,我们经常需要更新项目中依赖的版本,以获取新的功能、修复已知的漏洞或提高性能。在 Maven 中更新依赖版本也很方便,只需在 pom.xml 文件中修改依赖的 version 标签的值即可。例如,将之前添加的 Spring Boot Starter Web 依赖从 2.7.5 版本更新到 2.7.6 版本,只需将 pom.xml 文件中的依赖声明修改为:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.6</version>
</dependency>

修改完成后,保存 pom.xml 文件,Maven 会自动检测到依赖版本的变化。如果本地仓库中已经存在该版本的依赖,Maven 会直接使用本地仓库中的依赖;如果本地仓库中没有该版本的依赖,Maven 会从远程仓库下载新的版本,并更新项目的类路径。为了确保依赖更新的正确性,在更新依赖版本后,建议重新编译和测试项目,以验证新的依赖版本是否与项目兼容,是否会引入新的问题。在一些大型项目中,可能存在多个模块,每个模块都有自己的 pom.xml 文件,并且可能共享一些依赖。在这种情况下,为了确保所有模块使用相同版本的依赖,可以在父项目的 pom.xml 文件中的标签内统一管理依赖版本。例如:

<dependencyManagement>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>2.7.6</version>
        </dependency>
        <!-- 其他共享依赖 -->
    </dependencies>
</dependencyManagement>

这样,子模块在引入 Spring Boot Starter Web 依赖时,只需声明 groupId 和 artifactId,无需指定版本,就会自动继承父项目中定义的版本:

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
</dependencies>

当需要更新共享依赖的版本时,只需在父项目的标签内修改版本号,所有子模块都会自动使用新的版本,大大简化了依赖版本管理的工作。

2.2.3 排除依赖

在某些情况下,项目中引入的某个依赖可能会传递性地引入一些我们不需要的依赖,这些不必要的依赖可能会导致依赖冲突、增加项目的体积或带来其他潜在问题。此时,我们可以使用 Maven 的依赖排除功能,将不需要的传递性依赖排除掉。在 pom.xml 文件的依赖声明中,通过标签来指定要排除的依赖。例如,假设我们引入了 spring-boot-starter-web 依赖,而它传递性地引入了 Tomcat 依赖,但我们在项目中使用的是其他 Web 服务器,不需要 Tomcat 依赖,就可以在 spring-boot-starter-web 的依赖声明中添加如下排除配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
    <version>2.7.5</version>
    <exclusions>
        <exclusion>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-tomcat</artifactId>
        </exclusion>
    </exclusions>
</dependency>

上述代码表示在引入 spring-boot-starter-web 依赖时,排除其对 spring-boot-starter-tomcat 的传递性依赖。这样,Maven 在下载 spring-boot-starter-web 及其依赖时,就不会下载 spring-boot-starter-tomcat 依赖,从而避免了不必要的依赖引入。除了排除具体的依赖,还可以使用通配符来排除一组依赖。例如,如果要排除某个 groupId 下的所有依赖,可以这样配置:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>library-a</artifactId>
    <version>1.0.0</version>
    <exclusions>
        <exclusion>
            <groupId>com.example</groupId>
            <artifactId>*</artifactId>
        </exclusion>
    </exclusions>
</dependency>

这表示在引入 library-a 依赖时,排除 com.example 这个 groupId 下的所有依赖。通过合理使用依赖排除功能,可以有效地控制项目的依赖范围,解决依赖冲突问题,提高项目的稳定性和可维护性。

2.3 持续集成(CI)

在现代软件开发中,持续集成(Continuous Integration,CI)是一种重要的开发实践,它强调频繁地将开发人员的代码集成到共享的代码仓库中,并进行自动化的构建、测试和验证,以尽早发现和解决代码集成过程中出现的问题,提高软件的质量和开发效率。Maven 作为一款强大的项目管理和构建工具,与持续集成工具的集成非常紧密,能够很好地支持持续集成流程的实现。

以使用 Jenkins 作为持续集成工具为例,来介绍 Maven 在持续集成中的应用。Jenkins 是一个开源的自动化服务器,广泛应用于持续集成和持续交付(CD)的流程中,它允许开发团队自动化构建、测试、部署等过程,减少人工干预和错误。

2.3.1 环境搭建

  1. 安装 Jenkins:从 Jenkins 官方网站(https://www.jenkins.io/download/ )下载适合操作系统的安装包,然后按照安装向导进行安装。安装完成后,启动 Jenkins 服务。例如,在 Ubuntu 系统上,可以通过以下命令安装 Jenkins:
sudo apt update
sudo apt install openjdk-11-jdk  # 安装Java运行环境,Jenkins需要Java支持
wget -q -O - https://pkg.jenkins.io/debian/jenkins.io.key | sudo apt-key add -
echo "deb http://pkg.jenkins.io/debian/ stable main" | sudo tee -a /etc/apt/sources.list
sudo apt update
sudo apt install jenkins
sudo systemctl start jenkins  # 启动Jenkins服务
sudo systemctl status jenkins  # 检查Jenkins服务状态
  1. 安装插件:登录 Jenkins 的 Web 界面(通常是http://localhost:8080 ,如果修改了端口,使用相应的端口号),在 “插件管理” 页面中,安装与 Git 集成的 “Git Plugin” 插件,用于从 Git 仓库拉取代码;安装 “Maven Integration plugin” 插件,用于执行 Maven 构建命令。在插件管理页面的 “可选插件” 标签下,搜索并勾选 “Git Plugin” 和 “Maven Integration plugin”,然后点击 “直接安装” 按钮进行安装。安装过程中,Jenkins 会自动下载并安装所选插件及其依赖。

2.3.2 配置项目

  1. 配置 Git 凭证:在 Jenkins 的管理界面中,进入 “凭据” -> “系统” -> “全局凭据”,点击 “添加凭据”。选择 “SSH Username with private key” 类型(如果使用 HTTPS 协议访问 Git 仓库,可以选择 “Username with password” 类型),输入 Git 仓库的 SSH 用户名和私钥(或 HTTPS 协议的用户名和密码),添加完成后保存。例如,如果使用 GitHub 仓库,需要生成 SSH 密钥对,将公钥添加到 GitHub 仓库的部署密钥中,然后在 Jenkins 中配置私钥。
  2. 创建 Jenkins 任务:在 Jenkins 的主界面中,点击 “新建 Item”,输入任务名称,选择 “自由风格项目”,点击 “确定”。在任务配置页面中:
    • 源码管理:选择 “Git”,在 “Repository URL” 中输入 Git 仓库地址,在 “Credentials” 中选择刚刚添加的 Git 凭据。例如,如果是 GitHub 仓库,仓库地址类似https://github.com/yourusername/yourrepository.git 。
    • 构建触发器:可以选择多种触发方式,如定时构建(Poll SCM),设置一个定时检查代码变更的规则,例如 */10 * * * * 表示每 10 分钟检查一次;也可以选择使用 GitHub 的 Webhook 触发,这样在代码提交时能立即触发构建。如果选择使用 Webhook 触发,需要在 GitHub 仓库的设置中配置 Webhook,将 Webhook 的 URL 指向 Jenkins 的相应地址(例如http://localhost:8080/github-webhook/ ,根据实际情况修改),并选择触发事件(如 Push 事件)。
    • 构建环境:勾选 “Delete workspace before build starts”,确保每次构建前清理工作空间,避免残留文件影响构建。这样在每次构建前,Jenkins 会删除之前构建留下的文件,从干净的状态开始构建,确保构建的一致性。
    • 构建步骤:点击 “增加构建步骤”,选择 “Invoke top-level Maven targets”,在 “Goals” 中输入 clean install,表示执行 Maven 的清理和安装操作。clean 操作会删除之前构建生成的文件,install 操作会编译代码、运行测试、打包项目,并将打包后的文件安装到本地 Maven 仓库(如果是多模块项目,还会按照依赖关系依次构建各个模块)。如果项目有特殊的 Maven 构建需求,还可以在 “Goals” 中添加其他 Maven 命令或参数,例如 clean package -DskipTests,表示跳过测试阶段直接打包。

2.3.3 实现持续集成流程

  1. 代码提交:开发人员在本地完成代码开发后,使用 Git 将代码提交到远程仓库。例如:
git add.
git commit -m "Add new feature: search book by author"
git push origin master
  1. 构建触发:Jenkins 检测到 Git 仓库的代码变更后(根据配置的触发方式,如定时检查或 Webhook 触发),会自动触发构建任务。Jenkins 从 Git 仓库拉取最新代码到本地工作空间。
  2. 执行构建:Jenkins 执行 Maven 的 clean install 命令。在构建过程中,Maven 会根据 pom.xml 文件中的配置,下载项目所需的依赖,编译代码,运行测试用例,并将项目打包成可部署的文件(如 JAR 文件或 WAR 文件)。如果测试用例执行失败,Maven 会停止后续操作,并在 Jenkins 的构建日志中显示错误信息,开发人员可以根据日志定位和解决问题。例如,如果项目的测试用例中存在一个单元测试方法 testAddition,用于测试加法运算,代码如下:
import org.junit.Test;
import static org.junit.Assert.assertEquals;

public class MathUtilsTest {
    @Test
    public void testAddition() {
        int result = MathUtils.add(2, 3);
        assertEquals(5, result);
    }
}

如果 MathUtils 类中的 add 方法实现有误,导致测试失败,Maven 在执行测试时会捕获到错误,并在构建日志中输出详细的错误信息,提示开发人员检查 add 方法的实现。

  1. 构建结果反馈:构建完成后,Jenkins 会在 Web 界面中显示构建结果(成功或失败),并可以通过邮件、Slack 等方式通知开发人员。如果构建成功,开发人员可以继续进行后续的开发工作;如果构建失败,开发人员需要及时修复代码中的问题,重新提交代码,触发新一轮的构建,直到构建成功为止。通过这种持续集成的方式,开发团队能够及时发现代码中的问题,避免问题在项目后期积累,提高了软件的质量和开发效率。同时,自动化的构建和测试过程也减少了人工操作的错误,使得项目的构建和测试更加可靠和可重复。

三、Maven 常用命令详解

3.1 mvn clean

mvn clean 是 Maven 的一个常用命令,主要作用是清理项目编译输出文件,具体来说就是删除项目中的target目录及其下的所有内容。在 Maven 项目中,target目录是存放构建输出的默认目录,包含了编译后的字节码文件(.class文件)、生成的资源文件、打包后的文件(如 JAR、WAR 文件)以及测试报告等。在进行项目构建之前,执行mvn clean命令可以确保项目处于一个干净的状态,避免旧的构建产物对新构建过程产生干扰。

例如,在开发一个 Java Web 项目时,随着开发的进行,target目录下会积累大量的文件。当修改了项目的源代码或依赖配置后,如果不执行mvn clean命令就直接进行重新构建,Maven 可能会使用旧的编译结果,导致新的代码修改无法生效,或者出现依赖冲突等问题。而执行mvn clean命令后,target目录被清空,再次构建时,Maven 会从全新的状态开始,重新编译源代码,重新处理资源文件,重新打包项目,从而保证构建的准确性和一致性。此外,在项目发布之前,也通常会执行mvn clean命令,以确保发布的包中不包含任何旧的或不必要的文件,减小发布包的体积,提高发布的质量。

3.2 mvn compile

mvn compile 命令用于将 Java 源文件编译为字节码文件(.class文件)。在 Maven 项目中,Java 源文件通常存放在src/main/java目录下,执行mvn compile命令后,Maven 会调用 Java 编译器(通常是javac)对src/main/java目录下的所有 Java 源文件进行编译,并将生成的字节码文件输出到target/classes目录中。

以一个简单的 Java 类为例,假设在src/main/java/com/example目录下有一个名为HelloWorld.java的文件,内容如下:

package com.example;

public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello, Maven!");
    }
}

当执行mvn compile命令时,Maven 会读取这个 Java 源文件,根据 Java 语言的语法规则进行编译。如果源文件中存在语法错误,Maven 会在命令行中输出详细的错误信息,提示开发者进行修改。例如,如果HelloWorld.java文件中main方法的拼写错误,写成了mian,执行mvn compile命令后,会得到类似以下的错误提示:

[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project my-project: Compilation failure
[ERROR] /path/to/src/main/java/com/example/HelloWorld.java:[5,1] error: method main not found in class com.example.HelloWorld; did you mean main?

开发者根据错误提示,将mian修改为main后,再次执行mvn compile命令,编译成功,生成的HelloWorld.class字节码文件会被放置在target/classes/com/example目录下。此时,项目就可以使用这个编译后的类进行后续的操作,如运行测试用例、打包项目等。mvn compile命令是项目构建过程中的重要步骤,它将人类可读的 Java 源代码转换为计算机可执行的字节码文件,为项目的运行和部署奠定基础。

3.3 mvn test

mvn test 命令用于运行项目中的单元测试,并生成测试报告,帮助开发者验证代码的正确性和稳定性。在 Maven 项目中,单元测试代码通常存放在src/test/java目录下,并且遵循一定的命名规范,一般以Test结尾,例如HelloWorldTest.java。当执行mvn test命令时,Maven 会使用测试框架(如 JUnit、TestNG 等,默认使用 JUnit)来运行这些测试类中的测试方法。

假设我们有一个简单的数学运算类MathUtils,在src/main/java/com/example目录下,代码如下:

package com.example;

public class MathUtils {
    public static int add(int a, int b) {
        return a + b;
    }
}

为了测试MathUtils类中的add方法,我们在src/test/java/com/example目录下创建一个测试类MathUtilsTest,使用 JUnit 5 框架编写测试代码:

package com.example;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

public class MathUtilsTest {
    @Test
    public void testAddition() {
        int result = MathUtils.add(2, 3);
        assertEquals(5, result);
    }
}

执行mvn test命令后,Maven 会自动发现MathUtilsTest测试类,并运行其中的testAddition测试方法。在测试过程中,Maven 会启动测试框架,加载测试类,执行测试方法,并根据测试结果生成测试报告。如果测试方法执行成功,Maven 会在命令行中输出类似以下的信息:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.MathUtilsTest
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.example.MathUtilsTest
[INFO] 
[INFO] Results:
[INFO] 
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------

这表明testAddition测试方法执行成功,没有出现失败或错误的情况。如果测试方法执行失败,例如将MathUtils类中的add方法实现错误,返回值改为a – b,再次执行mvn test命令,Maven 会在命令行中输出详细的失败信息,提示开发者检查代码:

[INFO] -------------------------------------------------------
[INFO]  T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.example.MathUtilsTest
[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0, Time elapsed: 0.002 s -- in com.example.MathUtilsTest
[INFO] 
[INFO] Results:
[INFO] 
[ERROR] Failures: 
[ERROR]   MathUtilsTest.testAddition:8 expected: 5 but was: -1
[INFO] 
[INFO] Tests run: 1, Failures: 1, Errors: 0, Skipped: 0
[INFO] 
[INFO] ------------------------------------------------------------------------
[INFO] BUILD FAILURE
[INFO] ------------------------------------------------------------------------

通过mvn test命令生成的测试报告,开发者可以快速了解项目中测试用例的执行情况,及时发现代码中的问题,保证项目的质量。

3.4 mvn package

mvn package 命令的主要作用是将编译后的代码打包成可分发的格式,常见的格式有 JAR(Java Archive)文件和 WAR(Web Application Archive)文件,具体的打包格式取决于项目的类型和配置。对于 Java 类库项目,通常会打包成 JAR 文件,这种文件包含了项目的编译后的字节码文件、资源文件以及相关的元数据,方便其他项目引用;而对于 Java Web 项目,则会打包成 WAR 文件,它不仅包含了项目的代码和资源,还包含了 Web 应用所需的配置文件、静态资源等,可直接部署到 Web 服务器上运行。

以一个 Spring Boot 项目为例,假设我们开发了一个简单的 Spring Boot Web 应用,在项目的pom.xml文件中,默认的打包方式为jar:

<packaging>jar</packaging>

执行mvn package命令后,Maven 会按照以下步骤进行操作:首先,它会执行mvn compile命令,确保项目的源代码被编译成字节码文件,生成的字节码文件存放在target/classes目录下;接着,Maven 会将src/main/resources目录下的资源文件(如配置文件、静态资源等)复制到target/classes目录中;然后,Maven 会根据项目的配置,将target/classes目录下的字节码文件和资源文件打包成一个 JAR 文件。在打包过程中,Maven 会读取pom.xml文件中的相关信息,如项目的groupId、artifactId和version等,将这些信息添加到 JAR 文件的元数据中,以便其他项目能够正确识别和引用该 JAR 包。打包完成后,生成的 JAR 文件会存放在target目录下,文件名格式为artifactId-version.jar,例如my-spring-boot-app-1.0.0.jar。

如果项目是一个 Java Web 项目,需要将pom.xml文件中的packaging标签修改为war:

<packaging>war</packaging>

同时,还需要添加对 Servlet 容器的依赖,如 Tomcat:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <scope>provided</scope>
</dependency>

执行mvn package命令后,Maven 会将项目打包成一个 WAR 文件,生成的 WAR 文件同样存放在target目录下,文件名格式为artifactId-version.war,例如my-web-app-1.0.0.war。这个 WAR 文件可以直接部署到 Tomcat、Jetty 等支持 WAR 部署的 Web 服务器上,启动服务器后,即可访问 Web 应用。mvn package命令是项目构建过程中的关键步骤,它将项目的各种资源和代码整合在一起,生成一个可分发和部署的文件,为项目的发布和使用提供了便利。

3.5 mvn install

mvn install 命令的主要功能是将项目打包后的文件安装到本地 Maven 仓库中,以便其他项目能够方便地引用该项目作为依赖。在 Maven 的生态系统中,本地仓库是一个重要的组成部分,它用于存储从远程仓库下载的依赖库以及本地项目构建生成的文件。当执行mvn install命令时,Maven 会首先执行mvn clean、mvn compile和mvn package等命令,确保项目处于干净的状态,源代码被成功编译,并且项目被正确打包成可分发的格式(如 JAR 文件或 WAR 文件)。

以一个 Java 类库项目为例,假设我们开发了一个名为my-library的类库项目,在项目的pom.xml文件中定义了如下信息:

<groupId>com.example</groupId>
<artifactId>my-library</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>

执行mvn install命令后,Maven 会将my-library-1.0.0.jar文件安装到本地 Maven 仓库中。本地 Maven 仓库的默认位置是用户主目录下的.m2/repository目录,在这个目录下,Maven 会根据项目的groupId、artifactId和version创建相应的目录结构,将 JAR 文件放置在对应的位置。例如,my-library-1.0.0.jar文件会被放置在.m2/repository/com/example/my-library/1.0.0/目录下,同时,Maven 还会在该目录下生成一个my-library-1.0.0.pom文件,这个文件包含了项目的元数据和依赖信息,方便其他项目在引用my-library时获取相关信息。

当其他项目需要使用my-library类库时,只需在其pom.xml文件中添加对my-library的依赖声明:

<dependency>
    <groupId>com.example</groupId>
    <artifactId>my-library</artifactId>
    <version>1.0.0</version>
</dependency>

Maven 在构建这个项目时,会自动从本地 Maven 仓库中查找并下载my-library-1.0.0.jar文件及其相关的依赖,将它们添加到项目的类路径中,使得项目能够使用my-library类库提供的功能。mvn install命令在多模块项目中也非常有用,它可以将各个子模块依次安装到本地仓库,方便模块之间的相互引用和依赖管理,提高了项目的开发效率和可维护性。

3.6 mvn deploy

mvn deploy 命令用于将项目打包后的文件部署到远程仓库,实现项目的共享和发布,使得其他团队成员或项目能够从远程仓库中获取并使用该项目。远程仓库通常是一个集中存储和管理项目依赖的服务器,常见的有 Maven 中央仓库、公司内部的私有仓库(如 Nexus、Artifactory 等)。在执行mvn deploy命令之前,需要在项目的pom.xml文件中配置远程仓库的相关信息,包括仓库的 URL、认证信息(如果需要)等。

以使用 Nexus 作为私有仓库为例,假设我们有一个项目需要部署到 Nexus 仓库中,首先在pom.xml文件中添加如下配置:

<distributionManagement>
    <repository>
        <id>nexus-releases</id>
        <name>Nexus Releases Repository</name>
        <url>http://your-nexus-server/repository/maven-releases/</url>
    </repository>
    <snapshotRepository>
        <id>nexus-snapshots</id>
        <name>Nexus Snapshots Repository</name>
        <url>http://your-nexus-server/repository/maven-snapshots/</url>
    </snapshotRepository>
</distributionManagement>

上述配置中,repository标签用于配置发布版本的仓库信息,snapshotRepository标签用于配置快照版本的仓库信息。id属性是仓库的唯一标识符,name属性是仓库的描述性名称,url属性是仓库的 URL 地址。同时,如果 Nexus 仓库需要认证,还需要在 Maven 的settings.xml文件中配置认证信息:

<servers>
    <server>
        <id>nexus-releases</id>
        <username>your-username</username>
        <password>your-password</password>
    </server>
    <server>
        <id>nexus-snapshots</id>
        <username>your-username</username>
        <password>your-password</password>
    </server>
</servers>

配置完成后,执行mvn deploy命令,Maven 会首先执行mvn clean、mvn compile、mvn package和mvn install等命令,确保项目被正确打包并安装到本地仓库。然后,Maven 会将打包后的文件(如 JAR 文件或 WAR 文件)以及项目的 POM 文件上传到配置的远程仓库中。如果项目是发布版本(版本号不以-SNAPSHOT结尾),文件会被上传到repository配置的仓库中;如果项目是快照版本(版本号以-SNAPSHOT结尾),文件会被上传到snapshotRepository配置的仓库中。其他团队成员或项目在其pom.xml文件中添加对该项目的依赖声明后,Maven 会从远程仓库中下载项目及其依赖,实现项目的共享和使用。mvn deploy命令在团队协作开发和项目发布过程中起着重要的作用,它使得项目能够在团队内部或更广泛的范围内进行共享和复用,提高了软件开发的效率和协作性。

文末附加内容
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇