{"owner":"Snailclimb","repo":"JavaGuide","hasSkills":true,"totalSkillsCount":5,"totalTokensCount":25701,"categories":["anthropic-skill","plugin-manifest"],"hasMcp":false,"mcpConfig":null,"found":["docs/tools/git/github-tips.md","docs/tools/maven/maven-core-concepts.md","docs/tools/docker/docker-intro.md","docs/tools/gradle/gradle-core-concepts.md","docs/tools/docker/README.md"],"skills":{"docs/tools/git/github-tips.md":"---\ntitle: GitHub 实用小技巧总结\ndescription: 汇总 GitHub 的高效使用技巧，包括个人主页、项目徽章、代码阅读、GitHub Actions、Explore/Trending 和开源协作提效方法。\ncategory: 开发工具\ntag:\n  - Git\nhead:\n  - - meta\n    - name: keywords\n      content: GitHub 技巧,个人主页,README,统计信息,开源贡献,GitHub Actions,代码阅读\n---\n\nGitHub 不只是代码托管平台。对开发者来说，它同时承担了项目展示、代码阅读、开源协作、自动化构建和个人主页的作用。这篇文章整理一些比较实用的 GitHub 使用技巧。\n\n## 一键生成 GitHub 简历与 GitHub 年报\n\n通过 [https://resume.github.io/](https://resume.github.io/) 这个网站你可以一键生成一个在线的 GitHub 简历。\n\n不过，简历里是否放 GitHub 链接要看账号内容质量。如果账号里有完整项目、持续维护记录、清晰 README 和比较规范的提交历史，GitHub 链接会加分；如果只有空仓库或者临时练习代码，就没必要强行放。生成后的效果如下图所示。\n\n![GitHub简历](https://oss.javaguide.cn/2020-11/image-20201108192205620.png)\n\n通过 <https://www.githubtrends.io/wrapped> 这个网站，你可以生成一份 GitHub 个人年报，这个年报会列举出你在这一年的项目贡献情况、最常使用的编程语言、详细的贡献信息。\n\n![](https://oss.javaguide.cn/github/dootask/image-20211226144607457.png)\n\n## 个性化 GitHub 首页\n\nGitHub 目前支持在个人主页自定义展示一些内容。展示效果如下图所示。\n\n![个性化首页展示效果](https://oss.javaguide.cn/java-guide-blog/image-20210616221212259.png)\n\n想要做到这样非常简单，你只需要创建一个和你的 GitHub 账户同名的仓库，然后自定义`README.md`的内容即可。\n\n展示在你主页的自定义内容就是`README.md`的内容（_不会 Markdown 语法的小伙伴自行面壁 5 分钟_）。\n\n![创建一个和你的GitHub账户同名的仓库](https://oss.javaguide.cn/java-guide-blog/image-20201107110309341.png)\n\n这个也是可以玩出花来的！比如说：通过 [github-readme-stats](https://hellogithub.com/periodical/statistics/click/?target=https://github.com/anuraghazra/github-readme-stats) 这个开源项目，你可以在 README 中展示动态生成的 GitHub 统计信息。展示效果如下图所示。\n\n![通过github-readme-stats动态生成GitHub统计信息 ](https://oss.javaguide.cn/java-guide-blog/image-20210616221312426.png)\n\n关于个性化首页这个就不多提了，感兴趣的小伙伴自行研究一下。\n\n## 自定义项目徽章\n\n你在 GitHub 上看到的项目徽章都是通过 [https://shields.io/](https://shields.io/) 这个网站生成的。我的 JavaGuide 这个项目的徽章如下图所示。\n\n![项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143136559.png)\n\n并且，你不光可以生成静态徽章，shield.io 还可以动态读取你项目的状态并生成对应的徽章。\n\n![自定义项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143502356.png)\n\n生成的描述项目状态的徽章效果如下图所示。\n\n![描述项目状态的徽章](https://oss.javaguide.cn/2020-11/image-20201107143752642.png)\n\n## 自动为项目添加贡献情况图标\n\n通过 repobeats 这个工具可以为 GitHub 项目添加如下图所示的项目贡献基本情况图表。\n\n![](https://oss.javaguide.cn/github/dootask/repobeats.png)\n\n地址：<https://repobeats.axiom.co/> 。\n\n## GitHub 表情\n\n![GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162254582.png)\n\n如果你想要在 GitHub 使用表情的话，可以在这里找找：[www.webfx.com/tools/emoji-cheat-sheet/](https://www.webfx.com/tools/emoji-cheat-sheet/)。\n\n![在线GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162432941.png)\n\n## 高效阅读 GitHub 项目的源代码\n\nGitHub Codespaces 可以提供类似 VS Code 的在线开发环境，适合临时阅读、调试或快速参与开源项目。对于大型项目或需要本地服务依赖的项目，还是建议 clone 到本地，用自己熟悉的 IDE 阅读和调试。\n\n简单介绍几种常用的 GitHub 项目源码阅读方式。\n\n### Chrome 插件 Octotree\n\n这个已经老生常谈了，是我最喜欢的一种方式。使用了 Octotree 之后网页侧边栏会按照树形结构展示项目，为我们带来 IDE 般的阅读源代码的感受。\n\n![Chrome插件Octotree](https://oss.javaguide.cn/2020-11/image-20201107144944798.png)\n\n### Sourcegraph\n\n不想将项目 clone 到本地时，也可以使用 Sourcegraph 这类代码搜索和阅读工具。Sourcegraph 支持跨仓库代码搜索、引用跳转等功能，阅读大型项目时比较有帮助。\n\n当你下载了这个插件之后，你的项目主页会多出一个小图标如下图所示。点击这个小图标即可在线阅读项目源代码。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107145749659.png)\n\n使用 Sourcegraph 阅读代码的效果类似下面这样，同样是树形结构展示代码，还支持类之间的跳转。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107150307314.png)\n\n### 克隆项目到本地\n\n先把项目克隆到本地，然后使用自己喜欢的 IDE 来阅读。想深入理解一个项目，首选这种方式。\n\n```bash\ngit clone https://github.com/Snailclimb/JavaGuide.git\n```\n\n## 扩展 GitHub 的功能\n\n**Enhanced GitHub** 可以让你的 GitHub 更好用。这个浏览器插件可以展示仓库大小、文件大小，并支持快速下载单个文件。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107160817672.png)\n\n## 自动为 Markdown 文件生成目录\n\n如果你想为 Markdown 文件生成目录，通过 VS Code 的 **Markdown Preview Enhanced** 这类插件就可以了。\n\n生成的目录效果如下图所示。你直接点击目录中的链接即可跳转到文章对应的位置，可以优化阅读体验。\n\n![](<https://oss.javaguide.cn/2020-11/iShot2020-11-07%2016.14.14%20(1).png>)\n\n不过，目前 GitHub 已经会为 Markdown 文件自动生成目录，只是需要通过页面上的目录按钮展开。\n\n![](https://oss.javaguide.cn/github/cosy/image-20211227093215005.png)\n\n## 善用 GitHub Explore\n\nGitHub 自带的 Explore 是一个非常强大且好用的功能，适合用来发现项目、主题和技术趋势。\n\n简单来说，GitHub Explore 可以提供下面这些服务：\n\n1. 可以根据你的个人兴趣为你推荐项目；\n2. GitHub Topics 按照类别/话题将一些项目进行了分类汇总。比如 [Data visualization](https://github.com/topics/data-visualization) 汇总了数据可视化相关的一些开源项目，[Awesome Lists](https://github.com/topics/awesome) 汇总了 Awesome 系列的仓库；\n3. 通过 GitHub Trending 我们可以看到最近比较热门的一些开源项目，我们可以按照语言类型以及时间维度对项目进行筛选；\n4. GitHub Collections 类似一个收藏夹集合。比如 [Teaching materials for computational social science](https://github.com/collections/teaching-computational-social-science) 这个收藏夹就汇总了计算机课程相关的开源资源，[Learn to Code](https://github.com/collections/learn-to-code) 这个收藏夹就汇总了对你学习编程有帮助的一些仓库；\n5. ……\n\n![](https://oss.javaguide.cn/github/javaguide/github-explore.png)\n\n## GitHub Actions 很强大\n\n你可以简单地将 GitHub Actions 理解为 GitHub 自带的自动化平台。通过 GitHub Actions，你可以直接在 GitHub 上完成构建、测试、部署、依赖扫描、定时任务等工作。\n\n关于 GitHub Actions 的详细介绍，推荐看一下阮一峰老师写的 [GitHub Actions 入门教程](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html) 。\n\nGitHub Actions 有一个官方市场，上面有很多别人提交的 Actions，可以直接复用。\n\n![](https://oss.javaguide.cn/github/javaguide/image-20211227100147433.png)\n\n## 后记\n\nGitHub 技巧不需要一次性全部记住。个人主页、项目徽章、代码阅读、Explore/Trending、GitHub Actions 这几块先用起来，就已经能覆盖大部分日常场景。\n\n另外，这篇文章没有展开讲 GitHub 搜索语法。实际使用中，关键词搜索、语言筛选、Star 数排序、更新时间筛选，往往比死记复杂语法更常用。\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/maven/maven-core-concepts.md":"---\ntitle: Maven 核心概念总结\ndescription: Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Maven坐标,Maven仓库,Maven生命周期,Maven多模块管理\n---\n\n> 这部分内容主要根据 Maven 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\n## Maven 介绍\n\n[Maven](https://github.com/apache/maven) 官方文档是这样介绍的 Maven 的：\n\n> Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.\n>\n> Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\n\n**什么是 POM？** 每一个 Maven 工程都有一个 `pom.xml` 文件，位于根目录中，包含项目构建生命周期的详细信息。通过 `pom.xml` 文件，我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。\n\n对于开发者来说，Maven 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\n关于 Maven 的基本使用这里就不介绍了，建议看看官网的 5 分钟上手 Maven 的教程：[Maven in 5 Minutes](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) 。\n\n## Maven 坐标\n\n项目中依赖的第三方库以及插件可统称为构件。每一个构件都可以使用 Maven 坐标唯一标识，坐标元素包括：\n\n- **groupId**(必须): 定义了当前 Maven 项目隶属的组织或公司。groupId 一般分为多段，通常情况下，第一段为域，第二段为公司名称。域又分为 org、com、cn 等，其中 org 为非营利组织，com 为商业组织，cn 表示中国。以 apache 开源社区的 tomcat 项目为例，这个项目的 groupId 是 org.apache，它的域是 org（因为 tomcat 是非营利项目），公司名称是 apache，artifactId 是 tomcat。\n- **artifactId**(必须)：定义了当前 Maven 项目的名称，项目的唯一的标识符，对应项目根目录的名称。\n- **version**(必须)：定义了 Maven 项目当前所处版本。\n- **packaging**（可选）：定义了 Maven 项目的打包方式（比如 jar，war...），默认使用 jar。\n- **classifier**(可选)：常用于区分从同一 POM 构建的具有不同内容的构件，可以是任意的字符串，附加在版本号之后。\n\n只要你提供正确的坐标，就能从 Maven 仓库中找到相应的构件供我们使用。\n\n举个例子（引入阿里巴巴开源的 EasyExcel）：\n\n```xml\n<dependency>\n    <groupId>com.alibaba</groupId>\n    <artifactId>easyexcel</artifactId>\n    <version>3.1.1</version>\n</dependency>\n```\n\n你可以在 <https://mvnrepository.com/> 这个网站上找到几乎所有可用的构件，如果你的项目使用的是 Maven 作为构建工具，那这个网站你一定会经常接触。\n\n![Maven 仓库](https://oss.javaguide.cn/github/javaguide/tools/maven/mvnrepository.com.png)\n\n## Maven 依赖\n\n如果使用 Maven 构建产生的构件（例如 Jar 文件）被其他的项目引用，那么该构件就是其他项目的依赖。\n\n### 依赖配置\n\n**配置信息示例**：\n\n```xml\n<project>\n    <dependencies>\n        <dependency>\n            <groupId></groupId>\n            <artifactId></artifactId>\n            <version></version>\n            <type>...</type>\n            <scope>...</scope>\n            <optional>...</optional>\n            <exclusions>\n                <exclusion>\n                  <groupId>...</groupId>\n                  <artifactId>...</artifactId>\n                </exclusion>\n          </exclusions>\n        </dependency>\n      </dependencies>\n</project>\n```\n\n**配置说明**：\n\n- dependencies：一个 pom.xml 文件中只能存在一个这样的标签，是用来管理依赖的总标签。\n- dependency：包含在 dependencies 标签中，可以有多个，每一个表示项目的一个依赖。\n- groupId,artifactId,version(必要)：依赖的基本坐标，对于任何一个依赖来说，基本坐标是最重要的，Maven 根据坐标才能找到需要的依赖。我们在上面解释过这些元素的具体意思，这里就不重复提了。\n- type(可选)：依赖的类型，对应于项目坐标定义的 packaging。大部分情况下，该元素不必声明，其默认值是 jar。\n- scope(可选)：依赖的范围，默认值是 compile。\n- optional(可选)：标记依赖是否可选\n- exclusions(可选)：用来排除传递性依赖,例如 jar 包冲突\n\n### 依赖范围\n\n**classpath** 用于指定 `.class` 文件存放的位置，类加载器会从该路径中加载所需的 `.class` 文件到内存中。\n\nMaven 在编译、执行测试、实际运行有着三套不同的 classpath：\n\n- **编译 classpath**：编译主代码有效\n- **测试 classpath**：编译、运行测试代码有效\n- **运行 classpath**：项目运行时有效\n\nMaven 的依赖范围如下：\n\n- **compile**：编译依赖范围（默认），使用此依赖范围对于编译、测试、运行三种都有效，即在编译、测试和运行的时候都要使用该依赖 Jar 包。\n- **test**：测试依赖范围，从字面意思就可以知道此依赖范围只能用于测试，而在编译和运行项目时无法使用此类依赖，典型的是 JUnit，它只用于编译测试代码和运行测试代码的时候才需要。\n- **provided**：此依赖范围，对于编译和测试有效，而对运行时无效。比如 `servlet-api.jar` 在 Tomcat 中已经提供了，我们只需要的是编译期提供而已。\n- **runtime**：运行时依赖范围，对于测试和运行有效，但是在编译主代码时无效，典型的就是 JDBC 驱动实现。\n- **system**：系统依赖范围，使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径，不依赖 Maven 仓库解析，所以可能会造成建构的不可移植。\n\n### 传递依赖性\n\n### 依赖冲突\n\n**1、对于 Maven 而言，同一个 groupId 同一个 artifactId 下，只能使用一个 version。**\n\n```xml\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.48</version>\n</dependency>\n<!-- 只会使用 1.0.49 这个版本的依赖 -->\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.49</version>\n</dependency>\n```\n\n若相同类型但版本不同的依赖存在于同一个 pom 文件，只会引入后一个声明的依赖。\n\n**2、项目的两个依赖同时引入了某个依赖。**\n\n举个例子，项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0)\n依赖链路二：A -> D -> X(2.0)\n```\n\n这两条依赖路径上有两个版本的 X，为了避免依赖重复，Maven 只会选择其中的一个进行解析。\n\n**哪个版本的 X 会被 Maven 解析使用呢?**\n\nMaven 在遇到这种问题的时候，会遵循 **路径最短优先** 和 **声明顺序优先** 两大原则。解决这个问题的过程也被称为 **Maven 依赖调解** 。\n\n**路径最短优先**\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0) // dist = 3\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n依赖链路二的路径最短，因此，X(2.0)会被解析使用。\n\n不过，你也可以发现。路径最短优先原则并不是通用的，像下面这种路径长度相等的情况就不能单单通过其解决了：\n\n```plain\n依赖链路一：A -> B -> X(1.0) // dist = 2\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n因此，Maven 又定义了声明顺序优先原则。\n\n依赖调解第一原则不能解决所有问题，比如这样的依赖关系：A->B->Y(1.0)、A-> C->Y(2.0)，Y(1.0)和 Y(2.0)的依赖路径长度是一样的，都为 2。Maven 定义了依赖调解的第二原则：\n\n**声明顺序优先**\n\n在依赖路径长度相等的前提下，在 `pom.xml` 中依赖声明的顺序决定了谁会被解析使用，顺序最前的那个依赖优胜。该例中，如果 B 的依赖声明在 D 之前，那么 X (1.0)就会被解析使用。\n\n```xml\n<!-- A pom.xml -->\n<dependencies>\n    ...\n    dependency B\n    ...\n    dependency D\n</dependencies>\n```\n\n### 排除依赖\n\n单纯依赖 Maven 来进行依赖调解，在很多情况下是不适用的，需要我们手动排除依赖。\n\n举个例子，当前项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n根据路径最短优先原则，X(1.0) 会被解析使用，也就是说实际用的是 1.0 版本的 X。\n\n但是！！！这会一些问题：如果 C 依赖用到了 1.5 版本的 X 中才有的一个类，运行项目就会报`NoClassDefFoundError`错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法，运行项目就会报`NoSuchMethodError`错误。\n\n现在知道为什么你的 Maven 项目总是会报`NoClassDefFoundError`和`NoSuchMethodError`错误了吧？\n\n**如何解决呢？** 我们可以通过`exclusion`标签手动将 X(1.0) 给排除。\n\n```xml\n<dependency>\n    ......\n    <exclusions>\n      <exclusion>\n        <artifactId>x</artifactId>\n        <groupId>org.apache.x</groupId>\n      </exclusion>\n    </exclusions>\n</dependency>\n```\n\n一般我们在解决依赖冲突的时候，都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。\n\n如果高版本修改了低版本的一些类或者方法的话，这个时候就不能直接保留高版本了，而是应该考虑优化上层依赖，比如升级上层依赖的版本。\n\n还是上面的例子：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n我们保留了 1.5 版本的 X，但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候，我们可以考虑升级 D 的版本到一个 X 兼容的版本。\n\n## Maven 仓库\n\n在 Maven 世界中，任何一个依赖、插件或者项目构建的输出，都可以称为 **构件** 。\n\n坐标和依赖是构件在 Maven 世界中的逻辑表示方式，构件的物理表示方式是文件，Maven 通过仓库来统一管理这些文件。 任何一个构件都有一组坐标唯一标识。有了仓库之后，无需手动引入构件，我们直接给定构件的坐标即可在 Maven 仓库中找到该构件。\n\nMaven 仓库分为：\n\n- **本地仓库**：运行 Maven 的计算机上的一个目录，它缓存远程下载的构件并包含尚未发布的临时构件。`settings.xml` 文件中可以看到 Maven 的本地仓库路径配置，默认本地仓库路径是在 `${user.home}/.m2/repository`。\n- **远程仓库**：官方或者其他组织维护的 Maven 仓库。\n\nMaven 远程仓库可以分为：\n\n- **中央仓库**：这个仓库是由 Maven 社区来维护的，里面存放了绝大多数开源软件的包，并且是作为 Maven 的默认配置，不需要开发者额外配置。另外为了方便查询，还提供了一个[查询地址](https://central.sonatype.com/)，开发者可以通过这个地址更快的搜索需要构件的坐标。\n- **私服**：私服是一种特殊的远程 Maven 仓库，它是架设在局域网内的仓库服务，私服一般被配置为互联网远程仓库的镜像，供局域网内的 Maven 用户使用。\n- **其他的公共仓库**：有一些公共仓库是为了加速访问（比如阿里云 Maven 镜像仓库）或者部分构件不存在于中央仓库中。\n\nMaven 依赖包寻找顺序：\n\n1. 先去本地仓库找寻，有的话，直接使用。\n2. 本地仓库没有找到的话，会去远程仓库找寻，下载包到本地仓库。\n3. 远程仓库没有找到的话，会报错。\n\n## Maven 生命周期\n\nMaven 的生命周期就是为了对所有的构建过程进行抽象和统一，包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。\n\nMaven 定义了 3 个生命周期`META-INF/plexus/components.xml`：\n\n- `default` 生命周期\n- `clean`生命周期\n- `site`生命周期\n\n这些生命周期是相互独立的，每个生命周期包含多个阶段(phase)。并且，这些阶段是有序的，也就是说，后面的阶段依赖于前面的阶段。当执行某个阶段的时候，会先执行它前面的阶段。\n\n执行 Maven 生命周期的命令格式如下：\n\n```bash\nmvn 阶段 [阶段2] ...[阶段n]\n```\n\n### default 生命周期\n\n`default`生命周期是在没有任何关联插件的情况下定义的，是 Maven 的主要生命周期，用于构建应用程序，共包含 23 个阶段。\n\n```xml\n<phases>\n  <!-- 验证项目是否正确，并且所有必要的信息可用于完成构建过程 -->\n  <phase>validate</phase>\n  <!-- 建立初始化状态，例如设置属性 -->\n  <phase>initialize</phase>\n  <!-- 生成要包含在编译阶段的源代码 -->\n  <phase>generate-sources</phase>\n  <!-- 处理源代码 -->\n  <phase>process-sources</phase>\n  <!-- 生成要包含在包中的资源 -->\n  <phase>generate-resources</phase>\n  <!-- 将资源复制并处理到目标目录中，为打包阶段做好准备。 -->\n  <phase>process-resources</phase>\n  <!-- 编译项目的源代码  -->\n  <phase>compile</phase>\n  <!-- 对编译生成的文件进行后处理，例如对 Java 类进行字节码增强/优化 -->\n  <phase>process-classes</phase>\n  <!-- 生成要包含在编译阶段的任何测试源代码 -->\n  <phase>generate-test-sources</phase>\n  <!-- 处理测试源代码 -->\n  <phase>process-test-sources</phase>\n  <!-- 生成要包含在编译阶段的测试源代码 -->\n  <phase>generate-test-resources</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-resources</phase>\n  <!-- 编译测试源代码 -->\n  <phase>test-compile</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-classes</phase>\n  <!-- 使用合适的单元测试框架（JUnit 就是其中之一）运行测试 -->\n  <phase>test</phase>\n  <!-- 在实际打包之前，执行任何的必要的操作为打包做准备 -->\n  <phase>prepare-package</phase>\n  <!-- 获取已编译的代码并将其打包成可分发的格式，例如 JAR、WAR 或 EAR 文件 -->\n  <phase>package</phase>\n  <!-- 在执行集成测试之前执行所需的操作。 例如，设置所需的环境 -->\n  <phase>pre-integration-test</phase>\n  <!-- 处理并在必要时部署软件包到集成测试可以运行的环境 -->\n  <phase>integration-test</phase>\n  <!-- 执行集成测试后执行所需的操作。 例如，清理环境  -->\n  <phase>post-integration-test</phase>\n  <!-- 运行任何检查以验证打的包是否有效并符合质量标准。 -->\n  <phase>verify</phase>\n  <!-- \t将包安装到本地仓库中，可以作为本地其他项目的依赖 -->\n  <phase>install</phase>\n  <!-- 将最终的项目包复制到远程仓库中与其他开发者和项目共享 -->\n  <phase>deploy</phase>\n</phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn test`命令的时候，会执行从 validate 到 test 的所有阶段，这也就解释了为什么执行测试的时候，项目的代码能够自动编译。\n\n### clean 生命周期\n\nclean 生命周期的目的是清理项目，共包含 3 个阶段：\n\n1. pre-clean\n2. clean\n3. post-clean\n\n```xml\n<phases>\n  <!--  执行一些需要在clean之前完成的工作 -->\n  <phase>pre-clean</phase>\n  <!--  移除所有上一次构建生成的文件 -->\n  <phase>clean</phase>\n  <!--  执行一些需要在clean之后立刻完成的工作 -->\n  <phase>post-clean</phase>\n</phases>\n<default-phases>\n  <clean>\n    org.apache.maven.plugins:maven-clean-plugin:2.5:clean\n  </clean>\n</default-phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn clean` 的时候，会执行 clean 生命周期中的 pre-clean 和 clean 阶段。\n\n### site 生命周期\n\nsite 生命周期的目的是建立和发布项目站点，共包含 4 个阶段：\n\n1. pre-site\n2. site\n3. post-site\n4. site-deploy\n\n```xml\n<phases>\n  <!--  执行一些需要在生成站点文档之前完成的工作 -->\n  <phase>pre-site</phase>\n  <!--  生成项目的站点文档作 -->\n  <phase>site</phase>\n  <!--  执行一些需要在生成站点文档之后完成的工作，并且为部署做准备 -->\n  <phase>post-site</phase>\n  <!--  将生成的站点文档部署到特定的服务器上 -->\n  <phase>site-deploy</phase>\n</phases>\n<default-phases>\n  <site>\n    org.apache.maven.plugins:maven-site-plugin:3.3:site\n  </site>\n  <site-deploy>\n    org.apache.maven.plugins:maven-site-plugin:3.3:deploy\n  </site-deploy>\n</default-phases>\n```\n\nMaven 能够基于 `pom.xml` 所包含的信息，自动生成一个友好的站点，方便团队交流和发布项目信息。\n\n## Maven 插件\n\nMaven 本质上是一个插件执行框架，所有的执行过程，都是由一个一个插件独立完成的。像咱们日常使用到的 install、clean、deploy 等命令，其实底层都是一个一个的 Maven 插件。关于 Maven 的核心插件可以参考官方的这篇文档：<https://maven.apache.org/plugins/index.html> 。\n\n本地默认插件路径: `${user.home}/.m2/repository/org/apache/maven/plugins`\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/maven-plugins.png)\n\n除了 Maven 自带的插件之外，还有一些三方提供的插件比如单测覆盖率插件 jacoco-maven-plugin、帮助开发检测代码中不合规范的地方的插件 maven-checkstyle-plugin、分析代码质量的 sonar-maven-plugin。并且，我们还可以自定义插件来满足自己的需求。\n\njacoco-maven-plugin 使用示例：\n\n```xml\n<build>\n  <plugins>\n    <plugin>\n      <groupId>org.jacoco</groupId>\n      <artifactId>jacoco-maven-plugin</artifactId>\n      <version>0.8.8</version>\n      <executions>\n        <execution>\n          <goals>\n            <goal>prepare-agent</goal>\n          </goals>\n        </execution>\n        <execution>\n          <id>generate-code-coverage-report</id>\n          <phase>test</phase>\n          <goals>\n            <goal>report</goal>\n          </goals>\n        </execution>\n      </executions>\n    </plugin>\n  </plugins>\n</build>\n```\n\n你可以将 Maven 插件理解为一组任务的集合，用户可以通过命令行直接运行指定插件的任务，也可以将插件任务挂载到构建生命周期，随着生命周期运行。\n\nMaven 插件被分为下面两种类型：\n\n- **Build plugins**：在构建时执行。\n- **Reporting plugins**：在网站生成过程中执行。\n\n## Maven 多模块管理\n\n多模块管理简单地来说就是将一个项目分为多个模块，每个模块只负责单一的功能实现。直观的表现就是一个 Maven 项目中不止有一个 `pom.xml` 文件，会在不同的目录中有多个 `pom.xml` 文件，进而实现多模块管理。\n\n多模块管理除了可以更加便于项目开发和管理，还有如下好处：\n\n1. 降低代码之间的耦合性（从类级别的耦合提升到 jar 包级别的耦合）；\n2. 减少重复，提升复用性；\n3. 每个模块都可以是自解释的（通过模块名或者模块文档）；\n4. 模块还规范了代码边界的划分，开发者很容易通过模块确定自己所负责的内容。\n\n多模块管理下，会有一个父模块，其他的都是子模块。父模块通常只有一个 `pom.xml`，没有其他内容。父模块的 `pom.xml` 一般只定义了各个依赖的版本号、包含哪些子模块以及插件有哪些。不过，要注意的是，如果依赖只在某个子项目中使用，则可以在子项目的 pom.xml 中直接引入，防止父 pom 的过于臃肿。\n\n如下图所示，Dubbo 项目就被分成了多个子模块比如 dubbo-common（公共逻辑模块）、dubbo-remoting（远程通讯模块）、dubbo-rpc（远程调用模块）。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/dubbo-maven-multi-module.png)\n\n## 文章推荐\n\n- [安全同学讲 Maven 间接依赖场景的仲裁机制 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/flniMiP-eu3JSBnswfd_Ew)\n- [高效使用 Java 构建工具｜ Maven 篇 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/Wvq7t2FC58jaCh4UFJ6GGQ)\n- [安全同学讲 Maven 重打包的故事 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/xsJkB0onUkakrVH0wejcIg)\n\n## 参考\n\n- 《Maven 实战》\n- Introduction to Repositories - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-repositories.html>\n- Introduction to the Build Lifecycle - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference>\n- Maven 依赖范围：<https://www.mvnbook.com/maven-dependency.html>\n- 解决 maven 依赖冲突，这篇就够了！：<https://www.cnblogs.com/qdhxhz/p/16363532.html>\n- Multi-Module Project with Maven：<https://www.baeldung.com/maven-multi-module>\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/docker/docker-intro.md":"---\ntitle: Docker 核心概念总结\ndescription: 梳理 Docker 的核心概念与容器/虚拟机差异，掌握镜像、容器与仓库的关系及在交付部署中的实际价值。\ncategory: 开发工具\ntag:\n  - Docker\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署\n---\n\n本文主要讲 Docker 的核心概念、运行模型和常见使用场景，不展开安装过程。安装、命令练习和本地服务启动可以看后面的 [Docker 实战](./docker-in-action.md)。\n\n## 容器介绍\n\nDocker 是常见的软件容器平台。想要搞懂 Docker，先要理解容器解决的到底是什么问题。\n\n### 什么是容器？\n\n#### 先来看看容器较为官方的解释\n\n**一句话概括容器：容器就是将软件打包成标准化单元，以用于开发、交付和部署。**\n\n- **容器镜像是轻量的、可执行的独立软件包** ，包含软件运行所需的所有内容：代码、运行时环境、系统工具、系统库和设置。\n- **容器化软件适用于基于 Linux 和 Windows 的应用，在任何环境中都能够始终如一地运行。**\n- **容器赋予了软件独立性**，使其免受外在环境差异（例如，开发和预演环境的差异）的影响，从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。\n\n#### 再来看看容器较为通俗的解释\n\n如果需要通俗地描述容器的话，我觉得容器就是一个存放东西的地方，就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。\n\n![认识容器](https://oss.javaguide.cn/github/javaguide/tools/docker/container.png)\n\n### 图解物理机、虚拟机与容器\n\n关于虚拟机与容器的对比在后面会详细介绍到，这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解(下面的图片来源于网络)。\n\n**物理机：**\n\n![物理机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E7%89%A9%E7%90%86%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**虚拟机：**\n\n![虚拟机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**容器：**\n\n![](https://oss.javaguide.cn/javaguide/image-20211110104003678.png)\n\n通过上面这三张抽象图，我们可以大概通过类比概括出：**容器虚拟化的是操作系统而不是硬件，容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。**\n\n### 容器 VS 虚拟机\n\n每当说起容器，我们不得不将其与虚拟机做一个比较。就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n简单来说：**容器和虚拟机具有相似的资源隔离和分配优势，但功能有所不同，因为容器虚拟化的是操作系统，而不是硬件，因此容器更容易移植，效率也更高。**\n\n传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![](https://oss.javaguide.cn/javaguide/2e2b95eebf60b6d03f6c1476f4d7c697.png)\n\n**容器和虚拟机的对比**：\n\n![](https://oss.javaguide.cn/javaguide/4ef8691d67eb1eb53217099d0a691eb5.png)\n\n- 容器是一个应用层抽象，用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行，共享操作系统内核，但各自作为独立的进程在用户空间中运行 。与虚拟机相比， **容器占用的空间较少**（容器镜像大小通常只有几十兆），**瞬间就能完成启动** 。\n\n- 虚拟机 (VM) 是一个物理硬件层抽象，用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源，因此 **占用大量空间** 。而且 VM **启动也十分缓慢** 。\n\n通过 Docker 官网，我们知道了这么多 Docker 的优势，但是大家也没有必要完全否定虚拟机技术，因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如，云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker 通常用于隔离不同的应用** ，例如前端，后端以及数据库。\n\n就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n![](https://oss.javaguide.cn/javaguide/056c87751b9dd7b56f4264240fe96d00.png)\n\n## Docker 介绍\n\n### 什么是 Docker？\n\n可以从下面几个角度理解 Docker：\n\n- **Docker 是一个软件容器平台。**\n- **Docker** 使用 Go 语言开发，基于 Linux 内核提供的 cgroups、namespaces，以及 UnionFS 等能力对进程进行封装隔离，属于操作系统层面的虚拟化技术。\n- Docker 能够把应用和运行依赖打包到镜像中，减少开发、测试、部署环境不一致带来的问题。\n- 用户可以方便地创建和使用容器，把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改，就像管理普通的代码一样。\n\n**Docker 思想**：\n\n- **集装箱**：就像海运中的集装箱一样，Docker 容器包含了应用程序及其所有依赖项，确保在任何环境中都能以相同的方式运行。\n- **标准化**：运输方式、存储方式、API 接口。\n- **隔离**：每个 Docker 容器都在自己的隔离环境中运行，与宿主机和其他容器隔离。\n\n### Docker 容器的特点\n\n- **轻量** : 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核；它们能够迅速启动，只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的，并共享一些公共文件。这样就能尽量降低磁盘用量，并能更快地下载镜像。\n- **标准** : Docker 容器基于开放式标准，能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。\n- **安全** : Docker 赋予应用的隔离性不仅限于彼此隔离，还独立于底层的基础设施。Docker 默认提供最强的隔离，因此应用出现问题，也只是单个容器的问题，而不会波及到整台机器。\n\n### 为什么要用 Docker ?\n\n- Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 “这段代码在我机器上没问题啊” 这类问题；——一致的运行环境\n- 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间\n- 避免公用的服务器，资源会容易受到其他用户的影响。——隔离性\n- 善于处理集中爆发的服务器使用压力；——弹性伸缩，快速扩展\n- 可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便\n- 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署\n\n---\n\n## Docker 基本概念\n\nDocker 中有非常重要的三个基本概念：镜像（Image）、容器（Container）和仓库（Repository）。\n\n理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-run.jpeg)\n\n### 镜像（Image）：一个特殊的文件系统\n\n**操作系统分为内核和用户空间**。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 root 文件系统。\n\n**Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。** 镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\nDocker 设计时，就充分利用 **Union FS** 的技术，将其设计为**分层存储的架构** 。镜像实际是由多层文件系统联合组成。\n\n**镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。** 比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n### 容器（Container）：镜像运行时的实体\n\n镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。\n\n**容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储，容器也是如此。**\n\n**容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。**\n\n按照 Docker 最佳实践的要求，**容器不应该向其存储层内写入业务数据**，容器存储层要尽量保持无状态。**需要持久化的文件写入，应该使用数据卷（Volume）或者绑定宿主目录**，这类读写会绕过容器存储层，直接落到宿主机或网络存储上，性能和稳定性更好。数据卷的生命周期独立于容器，容器删除后，数据卷不会自动删除。\n\n### 仓库（Repository）：集中存放镜像文件的地方\n\n镜像构建完成后，可以很容易的在当前宿主上运行，但是， **如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。**\n\n一个 Docker Registry 中可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应一个镜像。所以说：**镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。**\n\n通常，**一个仓库会包含同一个软件不同版本的镜像**，而**标签就常用于对应这个软件的各个版本**。我们可以通过 `<仓库名>:<标签>` 的格式指定具体镜像。如果不给出标签，将以 `latest` 作为默认标签。不过在生产环境中不建议依赖 `latest`，最好明确指定版本标签，便于回滚和排查问题。\n\n**这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念：**\n\n**Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n最常使用的 Registry 公开服务是官方的 **Docker Hub** ，这也是默认的 Registry，并拥有大量的高质量的官方镜像，网址为：[https://hub.docker.com/](https://hub.docker.com/ \"https://hub.docker.com/\") 。官方是这样介绍 Docker Hub 的：\n\n> Docker Hub 是 Docker 官方提供的一项服务，用于与您的团队查找和共享容器镜像。\n\n比如我们想要搜索自己想要的镜像：\n\n![利用 Docker Hub 搜索镜像](https://oss.javaguide.cn/github/javaguide/tools/docker/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png)\n\n在 Docker Hub 的搜索结果中，有几项关键的信息有助于我们选择合适的镜像：\n\n- **OFFICIAL Image**：代表镜像为 Docker 官方提供和维护，相对来说稳定性和安全性较高。\n- **Stars**：和点赞差不多的意思，类似 GitHub 的 Star。\n- **Downloads**：代表镜像被拉取的次数，基本上能够表示镜像被使用的频度。\n\n当然，除了直接通过 Docker Hub 网站搜索镜像这种方式外，我们还可以通过 `docker search` 这个命令搜索 Docker Hub 中的镜像，搜索的结果是一致的。\n\n```bash\n➜  ~ docker search mysql\nNAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED\nmysql                             MySQL is a widely used, open-source relation…   8763                [OK]\nmariadb                           MariaDB is a community-developed fork of MyS…   3073                [OK]\nmysql/mysql-server                Optimized MySQL Server Docker images. Create…   650                                     [OK]\n```\n\n在国内访问 **Docker Hub** 可能会比较慢，企业项目通常会结合公司内部镜像仓库或云厂商镜像仓库来做镜像缓存和分发。\n\n除了使用公开服务外，用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像，可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现，足以支持 Docker 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。\n\n### Image、Container 和 Repository 的关系\n\n下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系：\n\n![Docker 架构](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-regitstry.png)\n\n- Dockerfile 是一个文本文件，包含了一系列的指令和参数，用于定义如何构建一个 Docker 镜像。运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n- `docker pull` 命令可以从指定的 Registry/Hub 下载一个镜像到本地，默认使用 Docker Hub。\n- `docker run` 命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像，Docker 会先尝试从 Registry/Hub 拉取镜像。\n- `docker push` 命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n上面涉及到了一些 Docker 基本命令，后面的实战文章会详细介绍。\n\n### Build Ship and Run\n\nDocker 的概念基本上已经讲完，我们再来谈谈：Build, Ship, and Run。\n\n如果你搜索 Docker 官网，会发现如下的字样：**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么 Build, Ship, and Run 到底是在干什么呢？\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-ship-run.jpg)\n\n- **Build（构建镜像）**：镜像就像是集装箱包括文件以及运行环境等等资源。\n- **Ship（运输镜像）**：主机和仓库间运输，这里的仓库就像是超级码头一样。\n- **Run （运行镜像）**：运行的镜像就是一个容器，容器就是运行程序的地方。\n\nDocker 运行过程也就是去仓库把镜像拉到本地，然后用一条命令把镜像运行起来变成容器。所以，我们也常常将 Docker 称为码头工人或码头装卸工，这和 Docker 的中文翻译搬运工人如出一辙。\n\n## Docker 常见命令\n\n### 基本命令\n\n```bash\ndocker version # 查看 Docker 版本\ndocker images # 查看所有已下载镜像，等价于：docker image ls 命令\ndocker container ls # 查看所有容器\ndocker ps # 查看正在运行的容器\ndocker image prune # 清理没有被使用的镜像文件。-a/--all 会删除所有未被容器使用的镜像\n```\n\n### 拉取镜像\n\n`docker pull` 命令默认使用的 Registry 是 Docker Hub。当你执行 `docker pull` 命令而没有指定任何 Registry 地址时，Docker 会从 Docker Hub 拉取镜像。\n\n```bash\ndocker search mysql # 查看 MySQL 相关镜像\ndocker pull mysql:8.4 # 拉取 MySQL 镜像\ndocker image ls # 查看所有已下载镜像\n```\n\n### 构建镜像\n\n运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n\n```bash\n# image-name 是镜像名称，1.0.0 是镜像版本号或标签\ndocker build -t image-name:1.0.0 .\n```\n\n需要注意：Dockerfile 的文件名不必须为 Dockerfile，也不一定要放在构建上下文的根目录中。使用 `-f` 或 `--file` 选项，可以指定任何位置的任何文件作为 Dockerfile。当然，一般大家习惯性的会使用默认的文件名 `Dockerfile`，以及会将其置于镜像构建上下文目录中。\n\n### 删除镜像\n\n比如我们要删除已经下载的 MySQL 镜像。\n\n通过 `docker rmi [image]`（等价于 `docker image rm [image]`）删除镜像之前，首先要确保这个镜像没有被容器引用。可以通过标签名称或者镜像 ID 删除，也可以通过前面讲的 `docker ps` 命令查看是否有容器正在使用它。\n\n```shell\n➜  ~ docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES\nc4cd691d9f80        mysql:5.7           \"docker-entrypoint.s…\"   7 weeks ago         Up 12 days          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql\n```\n\n可以看到 `mysql:5.7` 正在被 ID 为 `c4cd691d9f80` 的容器引用，需要先通过 `docker stop c4cd691d9f80` 或者 `docker stop mysql` 暂停这个容器。\n\n然后查看 MySQL 镜像的 ID：\n\n```shell\n➜  ~ docker images\nREPOSITORY              TAG                 IMAGE ID            CREATED             SIZE\nmysql                   5.7                 f6509bac4980        3 months ago        373MB\n```\n\n通过 `IMAGE ID` 或者 `REPOSITORY:TAG` 即可删除：\n\n```shell\ndocker rmi f6509bac4980 # 或者 docker rmi mysql:5.7\n```\n\n### 镜像推送\n\n`docker push` 命令用于将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n```bash\n# 将镜像推送到私有镜像仓库 Harbor\n# harbor.example.com 是私有镜像仓库的地址，ubuntu 是镜像名称，18.04 是镜像版本标签\ndocker push harbor.example.com/ubuntu:18.04\n```\n\n镜像推送之前，要确保本地已经构建好需要推送的 Docker 镜像。另外，务必先登录到对应的镜像仓库。\n\n## Docker 数据管理\n\n在容器中管理数据主要有两种方式：\n\n1. 数据卷（Volumes）\n2. 挂载主机目录 (Bind mounts)\n\n![Docker 数据管理](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-data-management.png)\n\n数据卷是由 Docker 管理的数据存储区域，有如下这些特点：\n\n- 可以在容器之间共享和重用。\n- 即使容器被删除，数据卷中的数据也不会被自动删除，从而确保数据的持久性。\n- 对数据卷的修改会立马生效。\n- 对数据卷的更新，不会影响镜像。\n\n```bash\n# 创建一个数据卷\ndocker volume create my-vol\n# 查看所有的数据卷\ndocker volume ls\n# 查看数据卷的具体信息\ndocker volume inspect my-vol\n# 删除指定的数据卷\ndocker volume rm my-vol\n```\n\n在用 `docker run` 命令的时候，使用 `--mount` 标记来将一个或多个数据卷挂载到容器里。\n\n还可以通过 `--mount` 标记将宿主机上的文件或目录挂载到容器中，这使得容器可以直接访问宿主机的文件系统。Docker 挂载主机目录的默认权限是读写，用户也可以通过增加 `readonly` 指定为只读。\n\n## Docker Compose\n\n### 什么是 Docker Compose？有什么用？\n\nDocker Compose 是 Docker 官方提供的多容器应用定义和运行工具。通过 Compose，开发者可以使用一个 YAML 文件描述应用依赖的多个服务、网络、端口和数据卷，然后用一条命令启动或停止整组服务。\n\nDocker Compose 是开源项目，地址：<https://github.com/docker/compose>。\n\nDocker Compose 的核心功能：\n\n- **多容器管理**：允许用户在一个 YAML 文件中定义和管理多个容器。\n- **服务编排**：配置容器间的网络和依赖关系。\n- **一键启动与停止**：通过 `docker compose up` 和 `docker compose down` 等命令，可以轻松启动和停止整个应用。\n\nDocker Compose 简化了多容器应用程序的开发、测试和部署过程，提高了开发团队的生产力，同时降低了应用程序的部署复杂度和管理成本。\n\n### Docker Compose 文件基本结构\n\nDocker Compose 文件是 Docker Compose 工具的核心，用于定义和配置多容器 Docker 应用。这个文件通常命名为 `compose.yaml` 或 `docker-compose.yml`，采用 YAML（YAML Ain't Markup Language）格式编写。\n\nDocker Compose 文件基本结构如下：\n\n- **服务（services）：** 定义了应用中的每个容器（服务）。每个服务可以使用不同的镜像、环境设置和依赖关系。\n  - **镜像（image）：** 从指定的镜像中启动容器，可以是存储仓库、标签以及镜像 ID。\n  - **命令（command）：** 可选，覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本，常用于启动应用程序、执行初始化脚本等。\n  - **端口（ports）：** 可选，映射容器和宿主机的端口。\n  - **依赖（depends_on）：** 配置服务之间的启动依赖关系。例如后端服务依赖数据库服务时，可以先启动数据库，再启动后端服务。\n  - **环境变量（environment）：** 可选，设置服务运行所需的环境变量。\n  - **重启（restart）:** 可选，控制容器的重启策略。在容器退出时，根据指定的策略自动重启容器。\n  - **服务卷（volumes）:** 可选，定义服务使用的卷，用于数据持久化或在容器之间共享数据。\n  - **构建（build）：** 指定构建镜像的 Dockerfile 上下文路径，或者使用详细配置对象。\n- **网络（networks）：** 定义了容器间的网络连接。\n- **卷（volumes）：** 用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化。\n\n```yaml\nservices:\n  service-name-1:\n    image: nginx:stable\n    command: [\"nginx\", \"-g\", \"daemon off;\"]\n    environment:\n      TZ: Asia/Shanghai\n    volumes:\n      - web_data:/usr/share/nginx/html\n    networks:\n      - app_net\n    ports:\n      - \"8080:80\"\n    restart: unless-stopped\n    depends_on:\n      - service-name-2\n  service-name-2:\n    image: redis:7\n    networks:\n      - app_net\n\nvolumes:\n  web_data:\n\nnetworks:\n  app_net:\n```\n\n### Docker Compose 常见命令\n\n#### 启动\n\n`docker compose up` 会根据 Compose 文件中定义的服务创建并启动容器，并将它们连接到 Compose 创建的网络中。如果文件没有声明自定义网络，Compose 会自动创建默认网络。\n\n```bash\n# 在当前目录下寻找 compose.yaml 或 docker-compose.yml 文件，并根据其中定义的服务启动应用\ndocker compose up\n# 后台启动\ndocker compose up -d\n# 强制重新创建所有容器，即使它们已经存在\ndocker compose up --force-recreate\n# 重新构建镜像\ndocker compose up --build\n# 指定要启动的服务名称，而不是启动所有服务\n# 可以同时指定多个服务，用空格分隔。\ndocker compose up service-name\n```\n\n另外，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml up\n```\n\n#### 暂停\n\n`docker compose down` 用于停止并移除通过 `docker compose up` 启动的容器和网络。\n\n```bash\n# 在当前目录下寻找 Compose 文件\n# 根据其中定义移除启动的容器和网络\ndocker compose down\n# 停止容器但不移除\ndocker compose stop\n# 停止指定服务\ndocker compose stop service-name\n```\n\n同样地，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml down\n```\n\n#### 查看\n\n`docker compose ps` 用于查看通过 `docker compose up` 启动的所有容器的状态信息。\n\n```bash\n# 查看所有容器的状态信息\ndocker compose ps\n# 只显示服务名称\ndocker compose ps --services\n# 查看指定服务的容器\ndocker compose ps service-name\n```\n\n#### 其他\n\n| 命令                     | 介绍                   |\n| ------------------------ | ---------------------- |\n| `docker compose version` | 查看版本               |\n| `docker compose images`  | 列出所有容器使用的镜像 |\n| `docker compose kill`    | 强制停止服务的容器     |\n| `docker compose exec`    | 在容器中执行命令       |\n| `docker compose logs`    | 查看日志               |\n| `docker compose pause`   | 暂停服务               |\n| `docker compose unpause` | 恢复服务               |\n| `docker compose push`    | 推送服务镜像           |\n| `docker compose start`   | 启动当前停止的服务     |\n| `docker compose stop`    | 停止当前运行的服务     |\n| `docker compose rm`      | 删除已停止的服务容器   |\n| `docker compose top`     | 查看进程               |\n\n## Docker 底层原理\n\n首先，Docker 是基于轻量级虚拟化技术的软件，那什么是虚拟化技术呢？\n\n简单点来说，虚拟化技术可以这样定义：\n\n> 虚拟化技术是一种资源管理技术，是将计算机的各种[实体资源](https://zh.wikipedia.org/wiki/計算機科學)（[CPU](https://zh.wikipedia.org/wiki/CPU)、[内存](https://zh.wikipedia.org/wiki/内存)、[磁盘空间](https://zh.wikipedia.org/wiki/磁盘空间)、[网络适配器](https://zh.wikipedia.org/wiki/網路適配器)等），予以抽象、转换后呈现出来并可供分割、组合为一个或多个电脑配置环境。由此，打破实体结构间的不可切割的障碍，使用户可以比原本的配置更好的方式来应用这些电脑硬件资源。这些资源的新虚拟部分是不受现有资源的架设方式，地域或物理配置所限制。一般所指的虚拟化资源包括计算能力和数据存储。\n\nDocker 技术是基于 LXC（Linux container- Linux 容器）虚拟容器技术的。\n\n> LXC，其名称来自 Linux 软件容器（Linux Containers）的缩写，一种操作系统层虚拟化（Operating system–level virtualization）技术，为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器（Container），内含应用软件本身的代码，以及所需要的操作系统核心和库。通过统一的名字空间和共用 API 来分配不同软件容器的可用硬件资源，创造出应用程序的独立沙箱运行环境，使得 Linux 用户可以容易的创建和管理系统或应用容器。\n\nLXC 技术主要是借助 Linux 内核中提供的 CGroup 功能和 namespace 来实现的，通过 LXC 可以为软件提供一个独立的操作系统运行环境。\n\n**cgroup 和 namespace 介绍：**\n\n- **namespace 是 Linux 内核用来隔离内核资源的方式。** 通过 namespace 可以让一些进程只能看到与自己相关的一部分资源，而另外一些进程也只能看到与它们自己相关的资源，这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。\n\n  （以上关于 namespace 介绍内容来自<https://www.cnblogs.com/sparkdev/p/9365405.html> ，更多关于 namespace 的内容可以查看这篇文章 ）。\n\n- **CGroup 是 Control Groups 的缩写，是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。**\n\n  （以上关于 CGroup 介绍内容来自 <https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html> ，更多关于 CGroup 的内容可以查看这篇文章 ）。\n\n**cgroup 和 namespace 两者对比：**\n\n两者都是将进程进行分组，但是两者的作用还是有本质区别。namespace 是为了隔离进程组之间的资源，而 cgroup 是为了对一组进程进行统一的资源监控和限制。\n\n## 总结\n\n本文主要把 Docker 中的一些常见概念和命令做了详细的阐述。从零到上手实战可以看[Docker 从入门到上手干事](https://javaguide.cn/tools/docker/docker-in-action.html)这篇文章，内容非常详细！\n\n另外，再给大家推荐一本质量非常高的开源书籍[《Docker 从入门到实践》](https://yeasy.gitbook.io/docker_practice/introduction/why) ，这本书的内容非常新，毕竟书籍的内容是开源的，可以随时改进。\n\n![《Docker 从入门到实践》网站首页](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-getting-started-practice-website-homepage.png)\n\n## 参考\n\n- [Docker Compose：从零基础到实战应用的全面指南](https://juejin.cn/post/7306756690727747610)\n- [Linux Namespace 和 Cgroup](https://segmentfault.com/a/1190000009732550)\n- [LXC vs Docker: Why Docker is Better](https://www.upguard.com/articles/docker-vs-lxc \"LXC vs Docker: Why Docker is Better\")\n- [CGroup 介绍、应用实例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/gradle/gradle-core-concepts.md":"---\ntitle: Gradle 核心概念总结\ndescription: Gradle 是一个运行在 JVM 上的自动化构建工具，支持灵活的任务编排、依赖管理、插件扩展和多项目构建。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Gradle,Groovy,Gradle Wrapper,Gradle 包装器,Gradle 插件\n---\n\n> 这部分内容主要根据 Gradle 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\nGradle 这部分内容属于可选内容，可以根据自身需求决定是否学习。国内 Java 后端项目里 Maven 仍然更常见，但 Android、部分 Spring Boot 项目以及需要高度定制构建流程的项目中，Gradle 使用得也很多。\n\n## Gradle 介绍\n\nGradle 官方文档是这样介绍的 Gradle 的：\n\n> Gradle is an open-source [build automation](https://en.wikipedia.org/wiki/Build_automation) tool flexible enough to build almost any type of software. Gradle makes few assumptions about what you’re trying to build or how to build it. This makes Gradle particularly flexible.\n>\n> Gradle 是一个开源的构建自动化工具，它足够灵活，可以构建几乎任何类型的软件。Gradle 对你要构建什么或者如何构建它做了很少的假设。这使得 Gradle 特别灵活。\n\n简单来说，Gradle 就是一个运行在 JVM 上的自动化项目构建工具，用来帮助我们完成编译、测试、打包、发布等构建任务。\n\n对于开发者来说，Gradle 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\nGradle 构建脚本可以使用 Groovy DSL 或 Kotlin DSL 编写。现在新项目里 Kotlin DSL 也很常见，它的类型提示和 IDE 支持通常更好。\n\n## Groovy 介绍\n\nGradle 是运行在 JVM 上的一个程序，构建脚本可以使用 Groovy 或 Kotlin 编写。历史上很多 Gradle 示例使用 Groovy DSL，因此先了解一点 Groovy 语法对阅读老项目很有帮助。\n\nGroovy 是运行在 JVM 上的脚本语言，是基于 Java 扩展的动态语言，它的语法和 Java 非常的相似，可以使用 Java 的类库。Groovy 可以用于面向对象编程，也可以用作纯粹的脚本语言。在语言的设计上它吸纳了 Java、Python、Ruby 和 Smalltalk 语言的优秀特性，比如动态类型转换、闭包和元编程支持。\n\n我们可以用学习 Java 的方式去学习 Groovy ，学习成本相对来说还是比较低的，即使开发过程中忘记 Groovy 语法，也可以用 Java 语法继续编码。\n\n基于 JVM 的语言有很多种，比如 Groovy、Kotlin、Java、Scala，它们最终都会编译生成 Java 字节码文件并在 JVM 上运行。\n\n## Gradle 优势\n\nGradle 是新一代的构建系统，具有高效和灵活等诸多优势，广泛用于 Java 开发。不仅 Android 将其作为官方构建系统, 越来越多的 Java 项目比如 Spring Boot 也慢慢迁移到 Gradle。\n\n- 在灵活性上，Gradle 支持基于 Groovy 语言编写脚本，侧重于构建过程的灵活性，适合于构建复杂度较高的项目，可以完成非常复杂的构建。\n- 在粒度性上，Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的，在我们掌握了这一整套打包流程后，我们就可以通过去修改它的 Task 去动态改变其执行流程。\n- 在扩展性上，Gradle 支持插件机制，所以我们可以复用这些插件，就如同复用库一样简单方便。\n\n## Gradle Wrapper 介绍\n\nGradle 官方文档是这样介绍的 Gradle Wrapper 的：\n\n> The recommended way to execute any Gradle build is with the help of the Gradle Wrapper (in short just “Wrapper”). The Wrapper is a script that invokes a declared version of Gradle, downloading it beforehand if necessary. As a result, developers can get up and running with a Gradle project quickly without having to follow manual installation processes saving your company time and money.\n>\n> 执行 Gradle 构建的推荐方法是借助 Gradle Wrapper(简而言之就是“Wrapper”)。Wrapper 它是一个脚本，调用了已经声明的 Gradle 版本，如果需要的话，可以预先下载它。因此，开发人员可以快速启动并运行 Gradle 项目，而不必遵循手动安装过程，从而为公司节省时间和金钱。\n\n我们可以称 Gradle Wrapper 为 Gradle 包装器，它将 Gradle 再次包装，让所有的 Gradle 构建方法在 Gradle 包装器的帮助下运行。\n\nGradle Wrapper 的工作流程图如下（图源[Gradle Wrapper 官方文档介绍](https://docs.gradle.org/current/userguide/gradle_wrapper.html)）：\n\n![包装器工作流程](https://oss.javaguide.cn/github/javaguide/csdn/efa7a0006b04051e2b84cd116c6ccdfc.png)\n\n整个流程主要分为下面 3 步：\n\n1. 首先当我们刚创建的时候，如果指定的版本没有被下载，就先会去 Gradle 的服务器中下载对应版本的压缩包；\n2. 下载完成后需要先进行解压缩并且执行批处理文件；\n3. 后续项目每次构建都会重用这个解压过的 Gradle 版本。\n\nGradle Wrapper 会给我们带来下面这些好处：\n\n1. 在给定的 Gradle 版本上标准化项目，从而实现更可靠和健壮的构建。\n2. 可以让我们的电脑中不安装 Gradle 环境也可以运行 Gradle 项目。\n3. 为不同的用户和执行环境（例如 IDE 或持续集成服务器）提供新的 Gradle 版本就像更改 Wrapper 定义一样简单。\n\n### 生成 Gradle Wrapper\n\n如果想要首次生成 Gradle Wrapper，需要本地先有可用的 Gradle。Gradle 中已经内置了 Wrapper Task，在项目根目录执行 `gradle wrapper` 命令即可生成 Gradle Wrapper。\n\n执行命令 `gradle wrapper` 命令时可以指定一些参数来控制 wrapper 的生成。具体有如下两个配置参数：\n\n- `--gradle-version`：用于指定使用的 Gradle 版本。\n- `--gradle-distribution-url`：用于指定下载 Gradle 发行版的 URL，该值通常类似于 `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`。\n- `--gradle-distribution-sha256-sum`：用于指定发行版压缩包的 SHA-256 校验值，可以降低下载文件被篡改的风险。\n\n执行`gradle wrapper`命令之后，Gradle Wrapper 就生成完成了，项目根目录中生成如下文件：\n\n```plain\n├── gradle\n│   └── wrapper\n│       ├── gradle-wrapper.jar\n│       └── gradle-wrapper.properties\n├── gradlew\n└── gradlew.bat\n```\n\n每个文件的含义如下：\n\n- `gradle-wrapper.jar`：包含了 Gradle 运行时的逻辑代码。\n- `gradle-wrapper.properties`：定义了 Gradle 的版本号和 Gradle 运行时的行为属性。\n- `gradlew`：Linux/macOS 平台下，用于执行 Gradle 命令的包装器脚本。\n- `gradlew.bat`：Windows 平台下，用于执行 Gradle 命令的包装器脚本。\n\n`gradle-wrapper.properties` 文件的内容如下：\n\n```properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.0.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n```\n\n- `distributionBase`：Gradle 解包后存储的父目录。\n- `distributionPath`：`distributionBase`指定目录的子目录。`distributionBase+distributionPath`就是 Gradle 解包后的存放的具体目录。\n- `distributionUrl`：Gradle 指定版本的压缩包下载地址。\n- `zipStoreBase`：Gradle 压缩包下载后存储父目录。\n- `zipStorePath`：`zipStoreBase`指定目录的子目录。`zipStoreBase+zipStorePath`就是 Gradle 压缩包的存放位置。\n\n### 更新 Gradle Wrapper\n\n更新 Gradle Wrapper 有 2 种方式：\n\n1. 直接修改 `distributionUrl` 字段，然后执行 Gradle 命令。\n2. 执行 `./gradlew wrapper --gradle-version [version]`。\n\n下面的命令会将 Gradle 版本升级为 9.5.1。\n\n```shell\n./gradlew wrapper --gradle-version 9.5.1\n```\n\n`gradle-wrapper.properties` 文件中的 `distributionUrl` 属性也发生了改变。\n\n```properties\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.5.1-bin.zip\n```\n\n项目已经生成 Wrapper 后，日常构建优先使用 `./gradlew build`，而不是直接使用本机安装的 `gradle build`。这样可以确保团队成员和 CI 使用同一个 Gradle 版本。\n\n### 自定义 Gradle Wrapper\n\nGradle 已经内置了 Wrapper Task，因此构建 Gradle Wrapper 会生成 Gradle Wrapper 的属性文件，这个属性文件可以通过自定义 Wrapper Task 来设置。比如我们想要修改要下载的 Gradle 版本为 9.5.1，可以这么设置：\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n}\n```\n\n也可以设置 Gradle 发行版压缩包的下载地址和 Gradle 解包后的本地存储路径等配置。\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n    distributionUrl = '../../gradle-9.5.1-bin.zip'\n    distributionPath = 'wrapper/dists'\n}\n```\n\n`distributionUrl` 属性可以设置为本地的项目目录，你也可以设置为网络地址。\n\n## Gradle 任务\n\n在 Gradle 中，任务(Task)是构建执行的单个工作单元。\n\nGradle 的构建是基于 Task 进行的，当你运行项目的时候，实际就是在执行了一系列的 Task 比如编译 Java 源码的 Task、生成 jar 文件的 Task。\n\nTask 的声明方式如下（还有其他几种声明方式）：\n\n```groovy\n// 声明一个名字为 helloTask 的 Task\ntask helloTask{\n     doLast{\n       println \"Hello\"\n     }\n}\n```\n\n创建一个 Task 后，可以根据需要给 Task 添加不同的 Action，上面的“doLast”就是给队列尾增加一个 Action。\n\n```groovy\n //在Action 队列头部添加Action\n Task doFirst(Action<? super Task> action);\n Task doFirst(Closure action);\n\n //在Action 队列尾部添加Action\n Task doLast(Action<? super Task> action);\n Task doLast(Closure action);\n\n //删除所有的Action\n Task deleteAllActions();\n```\n\n一个 Task 中可以有多个 Acton，从队列头部开始向队列尾部执行 Acton。\n\nAction 代表的是一个个函数、方法，每个 Task 都是一堆 Action 按序组成的执行图。\n\nTask 声明依赖的关键字是`dependsOn`，支持声明一个或多个依赖：\n\n```groovy\ntask first {\n doLast {\n        println \"+++++first+++++\"\n    }\n}\ntask second {\n doLast {\n        println \"+++++second+++++\"\n    }\n}\n\n// 指定多个 task 依赖\ntask print(dependsOn :[second,first]) {\n doLast {\n      logger.quiet \"指定多个task依赖\"\n    }\n}\n\n// 指定一个 task 依赖\ntask third(dependsOn : print) {\n doLast {\n      println '+++++third+++++'\n    }\n}\n```\n\n执行 Task 之前，会先执行它的依赖 Task。\n\n我们还可以设置默认 Task，脚本中我们不调用默认 Task ，也会执行。\n\n```groovy\ndefaultTasks 'clean', 'run'\n\ntask clean {\n    doLast {\n        println 'Default Cleaning!'\n    }\n}\n\ntask run {\n    doLast {\n        println 'Default Running!'\n    }\n}\n```\n\nGradle 本身也内置了很多 Task 比如 copy（复制文件）、delete（删除文件）。\n\n```groovy\ntask deleteFile(type: Delete) {\n    delete \"C:\\\\Users\\\\guide\\\\Desktop\\\\test\"\n}\n```\n\n## Gradle 插件\n\nGradle 提供的是一套核心的构建机制，而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑，其本质上和 `.gradle` 文件是相同。你可以将 Gradle 插件看作是封装了一系列 Task 并执行的工具。\n\nGradle 插件主要分为两类：\n\n- 脚本插件：脚本插件就是一个普通的脚本文件，它可以被导入都其他构建脚本中。\n- 二进制插件 / 对象插件：在一个单独的插件模块中定义，其他模块通过 Plugin ID 应用插件。因为这种方式发布和复用更加友好，我们一般接触到的 Gradle 插件都是指二进制插件的形式。\n\n虽然 Gradle 插件与 .gradle 文件本质上没有区别，`.gradle` 文件也能实现 Gradle 插件类似的功能。但是，Gradle 插件使用了独立模块封装构建逻辑，无论是从开发开始使用来看，Gradle 插件的整体体验都更友好。\n\n- **逻辑复用：** 将相同的逻辑提供给多个相似项目复用，减少重复维护类似逻辑开销。当然 .gradle 文件也能做到逻辑复用，但 Gradle 插件的封装性更好；\n- **组件发布：** 可以将插件发布到 Maven 仓库进行管理，其他项目可以使用插件 ID 依赖。当然 .gradle 文件也可以放到一个远程路径被其他项目引用；\n- **构建配置：** Gradle 插件可以声明插件扩展来暴露可配置的属性，提供定制化能力。当然 .gradle 文件也可以做到，但实现会麻烦些。\n\n## Gradle 构建生命周期\n\nGradle 构建的生命周期有三个阶段：**初始化阶段，配置阶段**和**运行阶段**。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/dadbdf59fccd9a2ebf60a2d018541e52.png)\n\n在初始化阶段与配置阶段之间、配置阶段结束之后、执行阶段结束之后，我们都可以加一些定制化的 Hook。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/5c297ccc4dac83229ff3e19caee9d1d2.png)\n\n### 初始化阶段\n\nGradle 支持单项目和多项目构建。在初始化阶段，Gradle 确定哪些项目将参与构建，并为每个项目创建一个 [Project 实例](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html) 。本质上也就是执行 `settings.gradle` 脚本，从而读取整个项目中有多少个 Project 实例。\n\n### 配置阶段\n\n在配置阶段，Gradle 会解析每个工程的 `build.gradle` 文件，创建要执行的任务子集和确定各种任务之间的关系，以供执行阶段按照顺序执行，并对任务的做一些初始化配置。\n\n每个 `build.gradle` 对应一个 Project 对象，配置阶段执行的代码包括 `build.gradle` 中的各种语句、闭包以及 Task 中的配置语句。\n\n在配置阶段结束后，Gradle 会根据 Task 的依赖关系会创建一个 **有向无环图** 。\n\n### 运行阶段\n\n在运行阶段，Gradle 根据配置阶段创建和配置的要执行的任务子集，执行任务。\n\n## 参考\n\n- Gradle 官方文档：<https://docs.gradle.org/current/userguide/userguide.html>\n- Gradle 入门教程：<https://www.imooc.com/wiki/gradlebase>\n- Groovy 快速入门看这篇就够了：<https://cloud.tencent.com/developer/article/1358357>\n- 【Gradle】Gradle 的生命周期详解：<https://juejin.cn/post/7067719629874921508>\n- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)：<https://www.cnblogs.com/pengxurui/p/16281537.html>\n- Gradle 爬坑指南 -- 理解 Plugin、Task、构建流程：<https://juejin.cn/post/6889090530593112077>\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/docker/README.md":"---\ntitle: Docker 专题：容器、镜像、仓库、数据卷、网络与容器化部署\ndescription: Docker 面试与容器化学习路线，涵盖容器、镜像、仓库、Docker 引擎、数据卷、网络、常用命令和容器化部署实践。\ncategory: 开发工具\ntag:\n  - Docker\n  - 容器\n  - 部署\nsitemap:\n  changefreq: weekly\n  priority: 0.85\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发\n---\n\nDocker 是后端开发非常常见的容器化工具，常用于本地快速启动 MySQL、Redis、Kafka 等依赖服务，也常用于测试环境和部署交付。学习 Docker 时，要把核心概念和命令实践结合起来。\n\n## 适合谁看\n\n- 想快速理解 Docker 容器化基础的后端开发者。\n- 需要用 Docker 搭建本地开发环境、测试环境或依赖服务的同学。\n- 准备面试，需要讲清容器、镜像、数据卷、网络和容器与虚拟机区别的读者。\n- 已经会复制 Docker 命令，但不清楚镜像构建、容器生命周期和数据持久化的工程师。\n\n## 学习重点\n\n- 容器解决的是应用运行环境隔离和一致性问题。\n- 镜像是静态模板，容器是镜像运行后的实例，仓库用于分发和复用镜像。\n- Docker 常用命令要围绕镜像管理、容器生命周期、日志查看、端口映射和进入容器来掌握。\n- 数据卷用于数据持久化和宿主机目录挂载，网络用于容器之间和容器与外部服务通信。\n- Docker Compose 用于定义和运行多容器应用，适合本地开发环境和简单服务编排。\n- Docker 不是万能部署方案，生产环境还要结合镜像安全、资源限制、日志、监控和编排能力考虑。\n\n## 建议阅读顺序\n\n1. [Docker 核心概念总结](./docker-intro.md)：先理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。\n2. [Docker 实战](./docker-in-action.md)：通过实际命令练习镜像拉取、容器启动、端口映射、数据卷、日志查看和常见服务部署。\n3. 结合一个 Java 项目练习：把应用依赖的 MySQL、Redis 等服务用 Docker 启动起来，再观察日志、数据目录和端口映射。\n\n## 核心文章\n\n- [Docker 核心概念总结](./docker-intro.md)：讲解容器、镜像、仓库、Docker 引擎、Docker 架构、Docker Compose，以及 Docker 和虚拟机的区别。\n- [Docker 实战](./docker-in-action.md)：通过常用命令和实践场景理解镜像管理、容器管理、服务部署、本地环境搭建和常见排查。\n\n## 高频问题\n\n- Docker 主要解决什么问题？\n- 容器和虚拟机有什么区别？\n- 镜像和容器是什么关系？\n- Dockerfile、镜像、容器、仓库分别是什么？\n- 为什么容器删除后数据可能丢失？数据卷解决了什么问题？\n- 端口映射和容器网络分别解决什么问题？\n- 如何查看容器日志、进入容器、停止和删除容器？\n- `docker compose` 适合解决什么问题？和单独执行 `docker run` 有什么区别？\n- Docker 在开发、测试和部署环境里分别有什么价值？\n\n## 相关专题\n\n- [开发工具知识体系](../)\n- [Git 专题](../git/)\n- [Maven 专题](../maven/)\n- [高可用系统设计](../../high-availability/)\n\n<!-- @include: @article-footer.snippet.md -->\n"},"files":{"docs/tools/git/github-tips.md":"---\ntitle: GitHub 实用小技巧总结\ndescription: 汇总 GitHub 的高效使用技巧，包括个人主页、项目徽章、代码阅读、GitHub Actions、Explore/Trending 和开源协作提效方法。\ncategory: 开发工具\ntag:\n  - Git\nhead:\n  - - meta\n    - name: keywords\n      content: GitHub 技巧,个人主页,README,统计信息,开源贡献,GitHub Actions,代码阅读\n---\n\nGitHub 不只是代码托管平台。对开发者来说，它同时承担了项目展示、代码阅读、开源协作、自动化构建和个人主页的作用。这篇文章整理一些比较实用的 GitHub 使用技巧。\n\n## 一键生成 GitHub 简历与 GitHub 年报\n\n通过 [https://resume.github.io/](https://resume.github.io/) 这个网站你可以一键生成一个在线的 GitHub 简历。\n\n不过，简历里是否放 GitHub 链接要看账号内容质量。如果账号里有完整项目、持续维护记录、清晰 README 和比较规范的提交历史，GitHub 链接会加分；如果只有空仓库或者临时练习代码，就没必要强行放。生成后的效果如下图所示。\n\n![GitHub简历](https://oss.javaguide.cn/2020-11/image-20201108192205620.png)\n\n通过 <https://www.githubtrends.io/wrapped> 这个网站，你可以生成一份 GitHub 个人年报，这个年报会列举出你在这一年的项目贡献情况、最常使用的编程语言、详细的贡献信息。\n\n![](https://oss.javaguide.cn/github/dootask/image-20211226144607457.png)\n\n## 个性化 GitHub 首页\n\nGitHub 目前支持在个人主页自定义展示一些内容。展示效果如下图所示。\n\n![个性化首页展示效果](https://oss.javaguide.cn/java-guide-blog/image-20210616221212259.png)\n\n想要做到这样非常简单，你只需要创建一个和你的 GitHub 账户同名的仓库，然后自定义`README.md`的内容即可。\n\n展示在你主页的自定义内容就是`README.md`的内容（_不会 Markdown 语法的小伙伴自行面壁 5 分钟_）。\n\n![创建一个和你的GitHub账户同名的仓库](https://oss.javaguide.cn/java-guide-blog/image-20201107110309341.png)\n\n这个也是可以玩出花来的！比如说：通过 [github-readme-stats](https://hellogithub.com/periodical/statistics/click/?target=https://github.com/anuraghazra/github-readme-stats) 这个开源项目，你可以在 README 中展示动态生成的 GitHub 统计信息。展示效果如下图所示。\n\n![通过github-readme-stats动态生成GitHub统计信息 ](https://oss.javaguide.cn/java-guide-blog/image-20210616221312426.png)\n\n关于个性化首页这个就不多提了，感兴趣的小伙伴自行研究一下。\n\n## 自定义项目徽章\n\n你在 GitHub 上看到的项目徽章都是通过 [https://shields.io/](https://shields.io/) 这个网站生成的。我的 JavaGuide 这个项目的徽章如下图所示。\n\n![项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143136559.png)\n\n并且，你不光可以生成静态徽章，shield.io 还可以动态读取你项目的状态并生成对应的徽章。\n\n![自定义项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143502356.png)\n\n生成的描述项目状态的徽章效果如下图所示。\n\n![描述项目状态的徽章](https://oss.javaguide.cn/2020-11/image-20201107143752642.png)\n\n## 自动为项目添加贡献情况图标\n\n通过 repobeats 这个工具可以为 GitHub 项目添加如下图所示的项目贡献基本情况图表。\n\n![](https://oss.javaguide.cn/github/dootask/repobeats.png)\n\n地址：<https://repobeats.axiom.co/> 。\n\n## GitHub 表情\n\n![GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162254582.png)\n\n如果你想要在 GitHub 使用表情的话，可以在这里找找：[www.webfx.com/tools/emoji-cheat-sheet/](https://www.webfx.com/tools/emoji-cheat-sheet/)。\n\n![在线GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162432941.png)\n\n## 高效阅读 GitHub 项目的源代码\n\nGitHub Codespaces 可以提供类似 VS Code 的在线开发环境，适合临时阅读、调试或快速参与开源项目。对于大型项目或需要本地服务依赖的项目，还是建议 clone 到本地，用自己熟悉的 IDE 阅读和调试。\n\n简单介绍几种常用的 GitHub 项目源码阅读方式。\n\n### Chrome 插件 Octotree\n\n这个已经老生常谈了，是我最喜欢的一种方式。使用了 Octotree 之后网页侧边栏会按照树形结构展示项目，为我们带来 IDE 般的阅读源代码的感受。\n\n![Chrome插件Octotree](https://oss.javaguide.cn/2020-11/image-20201107144944798.png)\n\n### Sourcegraph\n\n不想将项目 clone 到本地时，也可以使用 Sourcegraph 这类代码搜索和阅读工具。Sourcegraph 支持跨仓库代码搜索、引用跳转等功能，阅读大型项目时比较有帮助。\n\n当你下载了这个插件之后，你的项目主页会多出一个小图标如下图所示。点击这个小图标即可在线阅读项目源代码。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107145749659.png)\n\n使用 Sourcegraph 阅读代码的效果类似下面这样，同样是树形结构展示代码，还支持类之间的跳转。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107150307314.png)\n\n### 克隆项目到本地\n\n先把项目克隆到本地，然后使用自己喜欢的 IDE 来阅读。想深入理解一个项目，首选这种方式。\n\n```bash\ngit clone https://github.com/Snailclimb/JavaGuide.git\n```\n\n## 扩展 GitHub 的功能\n\n**Enhanced GitHub** 可以让你的 GitHub 更好用。这个浏览器插件可以展示仓库大小、文件大小，并支持快速下载单个文件。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107160817672.png)\n\n## 自动为 Markdown 文件生成目录\n\n如果你想为 Markdown 文件生成目录，通过 VS Code 的 **Markdown Preview Enhanced** 这类插件就可以了。\n\n生成的目录效果如下图所示。你直接点击目录中的链接即可跳转到文章对应的位置，可以优化阅读体验。\n\n![](<https://oss.javaguide.cn/2020-11/iShot2020-11-07%2016.14.14%20(1).png>)\n\n不过，目前 GitHub 已经会为 Markdown 文件自动生成目录，只是需要通过页面上的目录按钮展开。\n\n![](https://oss.javaguide.cn/github/cosy/image-20211227093215005.png)\n\n## 善用 GitHub Explore\n\nGitHub 自带的 Explore 是一个非常强大且好用的功能，适合用来发现项目、主题和技术趋势。\n\n简单来说，GitHub Explore 可以提供下面这些服务：\n\n1. 可以根据你的个人兴趣为你推荐项目；\n2. GitHub Topics 按照类别/话题将一些项目进行了分类汇总。比如 [Data visualization](https://github.com/topics/data-visualization) 汇总了数据可视化相关的一些开源项目，[Awesome Lists](https://github.com/topics/awesome) 汇总了 Awesome 系列的仓库；\n3. 通过 GitHub Trending 我们可以看到最近比较热门的一些开源项目，我们可以按照语言类型以及时间维度对项目进行筛选；\n4. GitHub Collections 类似一个收藏夹集合。比如 [Teaching materials for computational social science](https://github.com/collections/teaching-computational-social-science) 这个收藏夹就汇总了计算机课程相关的开源资源，[Learn to Code](https://github.com/collections/learn-to-code) 这个收藏夹就汇总了对你学习编程有帮助的一些仓库；\n5. ……\n\n![](https://oss.javaguide.cn/github/javaguide/github-explore.png)\n\n## GitHub Actions 很强大\n\n你可以简单地将 GitHub Actions 理解为 GitHub 自带的自动化平台。通过 GitHub Actions，你可以直接在 GitHub 上完成构建、测试、部署、依赖扫描、定时任务等工作。\n\n关于 GitHub Actions 的详细介绍，推荐看一下阮一峰老师写的 [GitHub Actions 入门教程](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html) 。\n\nGitHub Actions 有一个官方市场，上面有很多别人提交的 Actions，可以直接复用。\n\n![](https://oss.javaguide.cn/github/javaguide/image-20211227100147433.png)\n\n## 后记\n\nGitHub 技巧不需要一次性全部记住。个人主页、项目徽章、代码阅读、Explore/Trending、GitHub Actions 这几块先用起来，就已经能覆盖大部分日常场景。\n\n另外，这篇文章没有展开讲 GitHub 搜索语法。实际使用中，关键词搜索、语言筛选、Star 数排序、更新时间筛选，往往比死记复杂语法更常用。\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/maven/maven-core-concepts.md":"---\ntitle: Maven 核心概念总结\ndescription: Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Maven坐标,Maven仓库,Maven生命周期,Maven多模块管理\n---\n\n> 这部分内容主要根据 Maven 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\n## Maven 介绍\n\n[Maven](https://github.com/apache/maven) 官方文档是这样介绍的 Maven 的：\n\n> Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.\n>\n> Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\n\n**什么是 POM？** 每一个 Maven 工程都有一个 `pom.xml` 文件，位于根目录中，包含项目构建生命周期的详细信息。通过 `pom.xml` 文件，我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。\n\n对于开发者来说，Maven 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\n关于 Maven 的基本使用这里就不介绍了，建议看看官网的 5 分钟上手 Maven 的教程：[Maven in 5 Minutes](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) 。\n\n## Maven 坐标\n\n项目中依赖的第三方库以及插件可统称为构件。每一个构件都可以使用 Maven 坐标唯一标识，坐标元素包括：\n\n- **groupId**(必须): 定义了当前 Maven 项目隶属的组织或公司。groupId 一般分为多段，通常情况下，第一段为域，第二段为公司名称。域又分为 org、com、cn 等，其中 org 为非营利组织，com 为商业组织，cn 表示中国。以 apache 开源社区的 tomcat 项目为例，这个项目的 groupId 是 org.apache，它的域是 org（因为 tomcat 是非营利项目），公司名称是 apache，artifactId 是 tomcat。\n- **artifactId**(必须)：定义了当前 Maven 项目的名称，项目的唯一的标识符，对应项目根目录的名称。\n- **version**(必须)：定义了 Maven 项目当前所处版本。\n- **packaging**（可选）：定义了 Maven 项目的打包方式（比如 jar，war...），默认使用 jar。\n- **classifier**(可选)：常用于区分从同一 POM 构建的具有不同内容的构件，可以是任意的字符串，附加在版本号之后。\n\n只要你提供正确的坐标，就能从 Maven 仓库中找到相应的构件供我们使用。\n\n举个例子（引入阿里巴巴开源的 EasyExcel）：\n\n```xml\n<dependency>\n    <groupId>com.alibaba</groupId>\n    <artifactId>easyexcel</artifactId>\n    <version>3.1.1</version>\n</dependency>\n```\n\n你可以在 <https://mvnrepository.com/> 这个网站上找到几乎所有可用的构件，如果你的项目使用的是 Maven 作为构建工具，那这个网站你一定会经常接触。\n\n![Maven 仓库](https://oss.javaguide.cn/github/javaguide/tools/maven/mvnrepository.com.png)\n\n## Maven 依赖\n\n如果使用 Maven 构建产生的构件（例如 Jar 文件）被其他的项目引用，那么该构件就是其他项目的依赖。\n\n### 依赖配置\n\n**配置信息示例**：\n\n```xml\n<project>\n    <dependencies>\n        <dependency>\n            <groupId></groupId>\n            <artifactId></artifactId>\n            <version></version>\n            <type>...</type>\n            <scope>...</scope>\n            <optional>...</optional>\n            <exclusions>\n                <exclusion>\n                  <groupId>...</groupId>\n                  <artifactId>...</artifactId>\n                </exclusion>\n          </exclusions>\n        </dependency>\n      </dependencies>\n</project>\n```\n\n**配置说明**：\n\n- dependencies：一个 pom.xml 文件中只能存在一个这样的标签，是用来管理依赖的总标签。\n- dependency：包含在 dependencies 标签中，可以有多个，每一个表示项目的一个依赖。\n- groupId,artifactId,version(必要)：依赖的基本坐标，对于任何一个依赖来说，基本坐标是最重要的，Maven 根据坐标才能找到需要的依赖。我们在上面解释过这些元素的具体意思，这里就不重复提了。\n- type(可选)：依赖的类型，对应于项目坐标定义的 packaging。大部分情况下，该元素不必声明，其默认值是 jar。\n- scope(可选)：依赖的范围，默认值是 compile。\n- optional(可选)：标记依赖是否可选\n- exclusions(可选)：用来排除传递性依赖,例如 jar 包冲突\n\n### 依赖范围\n\n**classpath** 用于指定 `.class` 文件存放的位置，类加载器会从该路径中加载所需的 `.class` 文件到内存中。\n\nMaven 在编译、执行测试、实际运行有着三套不同的 classpath：\n\n- **编译 classpath**：编译主代码有效\n- **测试 classpath**：编译、运行测试代码有效\n- **运行 classpath**：项目运行时有效\n\nMaven 的依赖范围如下：\n\n- **compile**：编译依赖范围（默认），使用此依赖范围对于编译、测试、运行三种都有效，即在编译、测试和运行的时候都要使用该依赖 Jar 包。\n- **test**：测试依赖范围，从字面意思就可以知道此依赖范围只能用于测试，而在编译和运行项目时无法使用此类依赖，典型的是 JUnit，它只用于编译测试代码和运行测试代码的时候才需要。\n- **provided**：此依赖范围，对于编译和测试有效，而对运行时无效。比如 `servlet-api.jar` 在 Tomcat 中已经提供了，我们只需要的是编译期提供而已。\n- **runtime**：运行时依赖范围，对于测试和运行有效，但是在编译主代码时无效，典型的就是 JDBC 驱动实现。\n- **system**：系统依赖范围，使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径，不依赖 Maven 仓库解析，所以可能会造成建构的不可移植。\n\n### 传递依赖性\n\n### 依赖冲突\n\n**1、对于 Maven 而言，同一个 groupId 同一个 artifactId 下，只能使用一个 version。**\n\n```xml\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.48</version>\n</dependency>\n<!-- 只会使用 1.0.49 这个版本的依赖 -->\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.49</version>\n</dependency>\n```\n\n若相同类型但版本不同的依赖存在于同一个 pom 文件，只会引入后一个声明的依赖。\n\n**2、项目的两个依赖同时引入了某个依赖。**\n\n举个例子，项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0)\n依赖链路二：A -> D -> X(2.0)\n```\n\n这两条依赖路径上有两个版本的 X，为了避免依赖重复，Maven 只会选择其中的一个进行解析。\n\n**哪个版本的 X 会被 Maven 解析使用呢?**\n\nMaven 在遇到这种问题的时候，会遵循 **路径最短优先** 和 **声明顺序优先** 两大原则。解决这个问题的过程也被称为 **Maven 依赖调解** 。\n\n**路径最短优先**\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0) // dist = 3\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n依赖链路二的路径最短，因此，X(2.0)会被解析使用。\n\n不过，你也可以发现。路径最短优先原则并不是通用的，像下面这种路径长度相等的情况就不能单单通过其解决了：\n\n```plain\n依赖链路一：A -> B -> X(1.0) // dist = 2\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n因此，Maven 又定义了声明顺序优先原则。\n\n依赖调解第一原则不能解决所有问题，比如这样的依赖关系：A->B->Y(1.0)、A-> C->Y(2.0)，Y(1.0)和 Y(2.0)的依赖路径长度是一样的，都为 2。Maven 定义了依赖调解的第二原则：\n\n**声明顺序优先**\n\n在依赖路径长度相等的前提下，在 `pom.xml` 中依赖声明的顺序决定了谁会被解析使用，顺序最前的那个依赖优胜。该例中，如果 B 的依赖声明在 D 之前，那么 X (1.0)就会被解析使用。\n\n```xml\n<!-- A pom.xml -->\n<dependencies>\n    ...\n    dependency B\n    ...\n    dependency D\n</dependencies>\n```\n\n### 排除依赖\n\n单纯依赖 Maven 来进行依赖调解，在很多情况下是不适用的，需要我们手动排除依赖。\n\n举个例子，当前项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n根据路径最短优先原则，X(1.0) 会被解析使用，也就是说实际用的是 1.0 版本的 X。\n\n但是！！！这会一些问题：如果 C 依赖用到了 1.5 版本的 X 中才有的一个类，运行项目就会报`NoClassDefFoundError`错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法，运行项目就会报`NoSuchMethodError`错误。\n\n现在知道为什么你的 Maven 项目总是会报`NoClassDefFoundError`和`NoSuchMethodError`错误了吧？\n\n**如何解决呢？** 我们可以通过`exclusion`标签手动将 X(1.0) 给排除。\n\n```xml\n<dependency>\n    ......\n    <exclusions>\n      <exclusion>\n        <artifactId>x</artifactId>\n        <groupId>org.apache.x</groupId>\n      </exclusion>\n    </exclusions>\n</dependency>\n```\n\n一般我们在解决依赖冲突的时候，都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。\n\n如果高版本修改了低版本的一些类或者方法的话，这个时候就不能直接保留高版本了，而是应该考虑优化上层依赖，比如升级上层依赖的版本。\n\n还是上面的例子：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n我们保留了 1.5 版本的 X，但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候，我们可以考虑升级 D 的版本到一个 X 兼容的版本。\n\n## Maven 仓库\n\n在 Maven 世界中，任何一个依赖、插件或者项目构建的输出，都可以称为 **构件** 。\n\n坐标和依赖是构件在 Maven 世界中的逻辑表示方式，构件的物理表示方式是文件，Maven 通过仓库来统一管理这些文件。 任何一个构件都有一组坐标唯一标识。有了仓库之后，无需手动引入构件，我们直接给定构件的坐标即可在 Maven 仓库中找到该构件。\n\nMaven 仓库分为：\n\n- **本地仓库**：运行 Maven 的计算机上的一个目录，它缓存远程下载的构件并包含尚未发布的临时构件。`settings.xml` 文件中可以看到 Maven 的本地仓库路径配置，默认本地仓库路径是在 `${user.home}/.m2/repository`。\n- **远程仓库**：官方或者其他组织维护的 Maven 仓库。\n\nMaven 远程仓库可以分为：\n\n- **中央仓库**：这个仓库是由 Maven 社区来维护的，里面存放了绝大多数开源软件的包，并且是作为 Maven 的默认配置，不需要开发者额外配置。另外为了方便查询，还提供了一个[查询地址](https://central.sonatype.com/)，开发者可以通过这个地址更快的搜索需要构件的坐标。\n- **私服**：私服是一种特殊的远程 Maven 仓库，它是架设在局域网内的仓库服务，私服一般被配置为互联网远程仓库的镜像，供局域网内的 Maven 用户使用。\n- **其他的公共仓库**：有一些公共仓库是为了加速访问（比如阿里云 Maven 镜像仓库）或者部分构件不存在于中央仓库中。\n\nMaven 依赖包寻找顺序：\n\n1. 先去本地仓库找寻，有的话，直接使用。\n2. 本地仓库没有找到的话，会去远程仓库找寻，下载包到本地仓库。\n3. 远程仓库没有找到的话，会报错。\n\n## Maven 生命周期\n\nMaven 的生命周期就是为了对所有的构建过程进行抽象和统一，包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。\n\nMaven 定义了 3 个生命周期`META-INF/plexus/components.xml`：\n\n- `default` 生命周期\n- `clean`生命周期\n- `site`生命周期\n\n这些生命周期是相互独立的，每个生命周期包含多个阶段(phase)。并且，这些阶段是有序的，也就是说，后面的阶段依赖于前面的阶段。当执行某个阶段的时候，会先执行它前面的阶段。\n\n执行 Maven 生命周期的命令格式如下：\n\n```bash\nmvn 阶段 [阶段2] ...[阶段n]\n```\n\n### default 生命周期\n\n`default`生命周期是在没有任何关联插件的情况下定义的，是 Maven 的主要生命周期，用于构建应用程序，共包含 23 个阶段。\n\n```xml\n<phases>\n  <!-- 验证项目是否正确，并且所有必要的信息可用于完成构建过程 -->\n  <phase>validate</phase>\n  <!-- 建立初始化状态，例如设置属性 -->\n  <phase>initialize</phase>\n  <!-- 生成要包含在编译阶段的源代码 -->\n  <phase>generate-sources</phase>\n  <!-- 处理源代码 -->\n  <phase>process-sources</phase>\n  <!-- 生成要包含在包中的资源 -->\n  <phase>generate-resources</phase>\n  <!-- 将资源复制并处理到目标目录中，为打包阶段做好准备。 -->\n  <phase>process-resources</phase>\n  <!-- 编译项目的源代码  -->\n  <phase>compile</phase>\n  <!-- 对编译生成的文件进行后处理，例如对 Java 类进行字节码增强/优化 -->\n  <phase>process-classes</phase>\n  <!-- 生成要包含在编译阶段的任何测试源代码 -->\n  <phase>generate-test-sources</phase>\n  <!-- 处理测试源代码 -->\n  <phase>process-test-sources</phase>\n  <!-- 生成要包含在编译阶段的测试源代码 -->\n  <phase>generate-test-resources</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-resources</phase>\n  <!-- 编译测试源代码 -->\n  <phase>test-compile</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-classes</phase>\n  <!-- 使用合适的单元测试框架（JUnit 就是其中之一）运行测试 -->\n  <phase>test</phase>\n  <!-- 在实际打包之前，执行任何的必要的操作为打包做准备 -->\n  <phase>prepare-package</phase>\n  <!-- 获取已编译的代码并将其打包成可分发的格式，例如 JAR、WAR 或 EAR 文件 -->\n  <phase>package</phase>\n  <!-- 在执行集成测试之前执行所需的操作。 例如，设置所需的环境 -->\n  <phase>pre-integration-test</phase>\n  <!-- 处理并在必要时部署软件包到集成测试可以运行的环境 -->\n  <phase>integration-test</phase>\n  <!-- 执行集成测试后执行所需的操作。 例如，清理环境  -->\n  <phase>post-integration-test</phase>\n  <!-- 运行任何检查以验证打的包是否有效并符合质量标准。 -->\n  <phase>verify</phase>\n  <!-- \t将包安装到本地仓库中，可以作为本地其他项目的依赖 -->\n  <phase>install</phase>\n  <!-- 将最终的项目包复制到远程仓库中与其他开发者和项目共享 -->\n  <phase>deploy</phase>\n</phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn test`命令的时候，会执行从 validate 到 test 的所有阶段，这也就解释了为什么执行测试的时候，项目的代码能够自动编译。\n\n### clean 生命周期\n\nclean 生命周期的目的是清理项目，共包含 3 个阶段：\n\n1. pre-clean\n2. clean\n3. post-clean\n\n```xml\n<phases>\n  <!--  执行一些需要在clean之前完成的工作 -->\n  <phase>pre-clean</phase>\n  <!--  移除所有上一次构建生成的文件 -->\n  <phase>clean</phase>\n  <!--  执行一些需要在clean之后立刻完成的工作 -->\n  <phase>post-clean</phase>\n</phases>\n<default-phases>\n  <clean>\n    org.apache.maven.plugins:maven-clean-plugin:2.5:clean\n  </clean>\n</default-phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn clean` 的时候，会执行 clean 生命周期中的 pre-clean 和 clean 阶段。\n\n### site 生命周期\n\nsite 生命周期的目的是建立和发布项目站点，共包含 4 个阶段：\n\n1. pre-site\n2. site\n3. post-site\n4. site-deploy\n\n```xml\n<phases>\n  <!--  执行一些需要在生成站点文档之前完成的工作 -->\n  <phase>pre-site</phase>\n  <!--  生成项目的站点文档作 -->\n  <phase>site</phase>\n  <!--  执行一些需要在生成站点文档之后完成的工作，并且为部署做准备 -->\n  <phase>post-site</phase>\n  <!--  将生成的站点文档部署到特定的服务器上 -->\n  <phase>site-deploy</phase>\n</phases>\n<default-phases>\n  <site>\n    org.apache.maven.plugins:maven-site-plugin:3.3:site\n  </site>\n  <site-deploy>\n    org.apache.maven.plugins:maven-site-plugin:3.3:deploy\n  </site-deploy>\n</default-phases>\n```\n\nMaven 能够基于 `pom.xml` 所包含的信息，自动生成一个友好的站点，方便团队交流和发布项目信息。\n\n## Maven 插件\n\nMaven 本质上是一个插件执行框架，所有的执行过程，都是由一个一个插件独立完成的。像咱们日常使用到的 install、clean、deploy 等命令，其实底层都是一个一个的 Maven 插件。关于 Maven 的核心插件可以参考官方的这篇文档：<https://maven.apache.org/plugins/index.html> 。\n\n本地默认插件路径: `${user.home}/.m2/repository/org/apache/maven/plugins`\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/maven-plugins.png)\n\n除了 Maven 自带的插件之外，还有一些三方提供的插件比如单测覆盖率插件 jacoco-maven-plugin、帮助开发检测代码中不合规范的地方的插件 maven-checkstyle-plugin、分析代码质量的 sonar-maven-plugin。并且，我们还可以自定义插件来满足自己的需求。\n\njacoco-maven-plugin 使用示例：\n\n```xml\n<build>\n  <plugins>\n    <plugin>\n      <groupId>org.jacoco</groupId>\n      <artifactId>jacoco-maven-plugin</artifactId>\n      <version>0.8.8</version>\n      <executions>\n        <execution>\n          <goals>\n            <goal>prepare-agent</goal>\n          </goals>\n        </execution>\n        <execution>\n          <id>generate-code-coverage-report</id>\n          <phase>test</phase>\n          <goals>\n            <goal>report</goal>\n          </goals>\n        </execution>\n      </executions>\n    </plugin>\n  </plugins>\n</build>\n```\n\n你可以将 Maven 插件理解为一组任务的集合，用户可以通过命令行直接运行指定插件的任务，也可以将插件任务挂载到构建生命周期，随着生命周期运行。\n\nMaven 插件被分为下面两种类型：\n\n- **Build plugins**：在构建时执行。\n- **Reporting plugins**：在网站生成过程中执行。\n\n## Maven 多模块管理\n\n多模块管理简单地来说就是将一个项目分为多个模块，每个模块只负责单一的功能实现。直观的表现就是一个 Maven 项目中不止有一个 `pom.xml` 文件，会在不同的目录中有多个 `pom.xml` 文件，进而实现多模块管理。\n\n多模块管理除了可以更加便于项目开发和管理，还有如下好处：\n\n1. 降低代码之间的耦合性（从类级别的耦合提升到 jar 包级别的耦合）；\n2. 减少重复，提升复用性；\n3. 每个模块都可以是自解释的（通过模块名或者模块文档）；\n4. 模块还规范了代码边界的划分，开发者很容易通过模块确定自己所负责的内容。\n\n多模块管理下，会有一个父模块，其他的都是子模块。父模块通常只有一个 `pom.xml`，没有其他内容。父模块的 `pom.xml` 一般只定义了各个依赖的版本号、包含哪些子模块以及插件有哪些。不过，要注意的是，如果依赖只在某个子项目中使用，则可以在子项目的 pom.xml 中直接引入，防止父 pom 的过于臃肿。\n\n如下图所示，Dubbo 项目就被分成了多个子模块比如 dubbo-common（公共逻辑模块）、dubbo-remoting（远程通讯模块）、dubbo-rpc（远程调用模块）。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/dubbo-maven-multi-module.png)\n\n## 文章推荐\n\n- [安全同学讲 Maven 间接依赖场景的仲裁机制 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/flniMiP-eu3JSBnswfd_Ew)\n- [高效使用 Java 构建工具｜ Maven 篇 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/Wvq7t2FC58jaCh4UFJ6GGQ)\n- [安全同学讲 Maven 重打包的故事 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/xsJkB0onUkakrVH0wejcIg)\n\n## 参考\n\n- 《Maven 实战》\n- Introduction to Repositories - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-repositories.html>\n- Introduction to the Build Lifecycle - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference>\n- Maven 依赖范围：<https://www.mvnbook.com/maven-dependency.html>\n- 解决 maven 依赖冲突，这篇就够了！：<https://www.cnblogs.com/qdhxhz/p/16363532.html>\n- Multi-Module Project with Maven：<https://www.baeldung.com/maven-multi-module>\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/docker/docker-intro.md":"---\ntitle: Docker 核心概念总结\ndescription: 梳理 Docker 的核心概念与容器/虚拟机差异，掌握镜像、容器与仓库的关系及在交付部署中的实际价值。\ncategory: 开发工具\ntag:\n  - Docker\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署\n---\n\n本文主要讲 Docker 的核心概念、运行模型和常见使用场景，不展开安装过程。安装、命令练习和本地服务启动可以看后面的 [Docker 实战](./docker-in-action.md)。\n\n## 容器介绍\n\nDocker 是常见的软件容器平台。想要搞懂 Docker，先要理解容器解决的到底是什么问题。\n\n### 什么是容器？\n\n#### 先来看看容器较为官方的解释\n\n**一句话概括容器：容器就是将软件打包成标准化单元，以用于开发、交付和部署。**\n\n- **容器镜像是轻量的、可执行的独立软件包** ，包含软件运行所需的所有内容：代码、运行时环境、系统工具、系统库和设置。\n- **容器化软件适用于基于 Linux 和 Windows 的应用，在任何环境中都能够始终如一地运行。**\n- **容器赋予了软件独立性**，使其免受外在环境差异（例如，开发和预演环境的差异）的影响，从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。\n\n#### 再来看看容器较为通俗的解释\n\n如果需要通俗地描述容器的话，我觉得容器就是一个存放东西的地方，就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。\n\n![认识容器](https://oss.javaguide.cn/github/javaguide/tools/docker/container.png)\n\n### 图解物理机、虚拟机与容器\n\n关于虚拟机与容器的对比在后面会详细介绍到，这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解(下面的图片来源于网络)。\n\n**物理机：**\n\n![物理机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E7%89%A9%E7%90%86%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**虚拟机：**\n\n![虚拟机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**容器：**\n\n![](https://oss.javaguide.cn/javaguide/image-20211110104003678.png)\n\n通过上面这三张抽象图，我们可以大概通过类比概括出：**容器虚拟化的是操作系统而不是硬件，容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。**\n\n### 容器 VS 虚拟机\n\n每当说起容器，我们不得不将其与虚拟机做一个比较。就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n简单来说：**容器和虚拟机具有相似的资源隔离和分配优势，但功能有所不同，因为容器虚拟化的是操作系统，而不是硬件，因此容器更容易移植，效率也更高。**\n\n传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![](https://oss.javaguide.cn/javaguide/2e2b95eebf60b6d03f6c1476f4d7c697.png)\n\n**容器和虚拟机的对比**：\n\n![](https://oss.javaguide.cn/javaguide/4ef8691d67eb1eb53217099d0a691eb5.png)\n\n- 容器是一个应用层抽象，用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行，共享操作系统内核，但各自作为独立的进程在用户空间中运行 。与虚拟机相比， **容器占用的空间较少**（容器镜像大小通常只有几十兆），**瞬间就能完成启动** 。\n\n- 虚拟机 (VM) 是一个物理硬件层抽象，用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源，因此 **占用大量空间** 。而且 VM **启动也十分缓慢** 。\n\n通过 Docker 官网，我们知道了这么多 Docker 的优势，但是大家也没有必要完全否定虚拟机技术，因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如，云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker 通常用于隔离不同的应用** ，例如前端，后端以及数据库。\n\n就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n![](https://oss.javaguide.cn/javaguide/056c87751b9dd7b56f4264240fe96d00.png)\n\n## Docker 介绍\n\n### 什么是 Docker？\n\n可以从下面几个角度理解 Docker：\n\n- **Docker 是一个软件容器平台。**\n- **Docker** 使用 Go 语言开发，基于 Linux 内核提供的 cgroups、namespaces，以及 UnionFS 等能力对进程进行封装隔离，属于操作系统层面的虚拟化技术。\n- Docker 能够把应用和运行依赖打包到镜像中，减少开发、测试、部署环境不一致带来的问题。\n- 用户可以方便地创建和使用容器，把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改，就像管理普通的代码一样。\n\n**Docker 思想**：\n\n- **集装箱**：就像海运中的集装箱一样，Docker 容器包含了应用程序及其所有依赖项，确保在任何环境中都能以相同的方式运行。\n- **标准化**：运输方式、存储方式、API 接口。\n- **隔离**：每个 Docker 容器都在自己的隔离环境中运行，与宿主机和其他容器隔离。\n\n### Docker 容器的特点\n\n- **轻量** : 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核；它们能够迅速启动，只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的，并共享一些公共文件。这样就能尽量降低磁盘用量，并能更快地下载镜像。\n- **标准** : Docker 容器基于开放式标准，能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。\n- **安全** : Docker 赋予应用的隔离性不仅限于彼此隔离，还独立于底层的基础设施。Docker 默认提供最强的隔离，因此应用出现问题，也只是单个容器的问题，而不会波及到整台机器。\n\n### 为什么要用 Docker ?\n\n- Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 “这段代码在我机器上没问题啊” 这类问题；——一致的运行环境\n- 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间\n- 避免公用的服务器，资源会容易受到其他用户的影响。——隔离性\n- 善于处理集中爆发的服务器使用压力；——弹性伸缩，快速扩展\n- 可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便\n- 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署\n\n---\n\n## Docker 基本概念\n\nDocker 中有非常重要的三个基本概念：镜像（Image）、容器（Container）和仓库（Repository）。\n\n理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-run.jpeg)\n\n### 镜像（Image）：一个特殊的文件系统\n\n**操作系统分为内核和用户空间**。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 root 文件系统。\n\n**Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。** 镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\nDocker 设计时，就充分利用 **Union FS** 的技术，将其设计为**分层存储的架构** 。镜像实际是由多层文件系统联合组成。\n\n**镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。** 比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n### 容器（Container）：镜像运行时的实体\n\n镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。\n\n**容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储，容器也是如此。**\n\n**容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。**\n\n按照 Docker 最佳实践的要求，**容器不应该向其存储层内写入业务数据**，容器存储层要尽量保持无状态。**需要持久化的文件写入，应该使用数据卷（Volume）或者绑定宿主目录**，这类读写会绕过容器存储层，直接落到宿主机或网络存储上，性能和稳定性更好。数据卷的生命周期独立于容器，容器删除后，数据卷不会自动删除。\n\n### 仓库（Repository）：集中存放镜像文件的地方\n\n镜像构建完成后，可以很容易的在当前宿主上运行，但是， **如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。**\n\n一个 Docker Registry 中可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应一个镜像。所以说：**镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。**\n\n通常，**一个仓库会包含同一个软件不同版本的镜像**，而**标签就常用于对应这个软件的各个版本**。我们可以通过 `<仓库名>:<标签>` 的格式指定具体镜像。如果不给出标签，将以 `latest` 作为默认标签。不过在生产环境中不建议依赖 `latest`，最好明确指定版本标签，便于回滚和排查问题。\n\n**这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念：**\n\n**Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n最常使用的 Registry 公开服务是官方的 **Docker Hub** ，这也是默认的 Registry，并拥有大量的高质量的官方镜像，网址为：[https://hub.docker.com/](https://hub.docker.com/ \"https://hub.docker.com/\") 。官方是这样介绍 Docker Hub 的：\n\n> Docker Hub 是 Docker 官方提供的一项服务，用于与您的团队查找和共享容器镜像。\n\n比如我们想要搜索自己想要的镜像：\n\n![利用 Docker Hub 搜索镜像](https://oss.javaguide.cn/github/javaguide/tools/docker/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png)\n\n在 Docker Hub 的搜索结果中，有几项关键的信息有助于我们选择合适的镜像：\n\n- **OFFICIAL Image**：代表镜像为 Docker 官方提供和维护，相对来说稳定性和安全性较高。\n- **Stars**：和点赞差不多的意思，类似 GitHub 的 Star。\n- **Downloads**：代表镜像被拉取的次数，基本上能够表示镜像被使用的频度。\n\n当然，除了直接通过 Docker Hub 网站搜索镜像这种方式外，我们还可以通过 `docker search` 这个命令搜索 Docker Hub 中的镜像，搜索的结果是一致的。\n\n```bash\n➜  ~ docker search mysql\nNAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED\nmysql                             MySQL is a widely used, open-source relation…   8763                [OK]\nmariadb                           MariaDB is a community-developed fork of MyS…   3073                [OK]\nmysql/mysql-server                Optimized MySQL Server Docker images. Create…   650                                     [OK]\n```\n\n在国内访问 **Docker Hub** 可能会比较慢，企业项目通常会结合公司内部镜像仓库或云厂商镜像仓库来做镜像缓存和分发。\n\n除了使用公开服务外，用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像，可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现，足以支持 Docker 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。\n\n### Image、Container 和 Repository 的关系\n\n下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系：\n\n![Docker 架构](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-regitstry.png)\n\n- Dockerfile 是一个文本文件，包含了一系列的指令和参数，用于定义如何构建一个 Docker 镜像。运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n- `docker pull` 命令可以从指定的 Registry/Hub 下载一个镜像到本地，默认使用 Docker Hub。\n- `docker run` 命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像，Docker 会先尝试从 Registry/Hub 拉取镜像。\n- `docker push` 命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n上面涉及到了一些 Docker 基本命令，后面的实战文章会详细介绍。\n\n### Build Ship and Run\n\nDocker 的概念基本上已经讲完，我们再来谈谈：Build, Ship, and Run。\n\n如果你搜索 Docker 官网，会发现如下的字样：**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么 Build, Ship, and Run 到底是在干什么呢？\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-ship-run.jpg)\n\n- **Build（构建镜像）**：镜像就像是集装箱包括文件以及运行环境等等资源。\n- **Ship（运输镜像）**：主机和仓库间运输，这里的仓库就像是超级码头一样。\n- **Run （运行镜像）**：运行的镜像就是一个容器，容器就是运行程序的地方。\n\nDocker 运行过程也就是去仓库把镜像拉到本地，然后用一条命令把镜像运行起来变成容器。所以，我们也常常将 Docker 称为码头工人或码头装卸工，这和 Docker 的中文翻译搬运工人如出一辙。\n\n## Docker 常见命令\n\n### 基本命令\n\n```bash\ndocker version # 查看 Docker 版本\ndocker images # 查看所有已下载镜像，等价于：docker image ls 命令\ndocker container ls # 查看所有容器\ndocker ps # 查看正在运行的容器\ndocker image prune # 清理没有被使用的镜像文件。-a/--all 会删除所有未被容器使用的镜像\n```\n\n### 拉取镜像\n\n`docker pull` 命令默认使用的 Registry 是 Docker Hub。当你执行 `docker pull` 命令而没有指定任何 Registry 地址时，Docker 会从 Docker Hub 拉取镜像。\n\n```bash\ndocker search mysql # 查看 MySQL 相关镜像\ndocker pull mysql:8.4 # 拉取 MySQL 镜像\ndocker image ls # 查看所有已下载镜像\n```\n\n### 构建镜像\n\n运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n\n```bash\n# image-name 是镜像名称，1.0.0 是镜像版本号或标签\ndocker build -t image-name:1.0.0 .\n```\n\n需要注意：Dockerfile 的文件名不必须为 Dockerfile，也不一定要放在构建上下文的根目录中。使用 `-f` 或 `--file` 选项，可以指定任何位置的任何文件作为 Dockerfile。当然，一般大家习惯性的会使用默认的文件名 `Dockerfile`，以及会将其置于镜像构建上下文目录中。\n\n### 删除镜像\n\n比如我们要删除已经下载的 MySQL 镜像。\n\n通过 `docker rmi [image]`（等价于 `docker image rm [image]`）删除镜像之前，首先要确保这个镜像没有被容器引用。可以通过标签名称或者镜像 ID 删除，也可以通过前面讲的 `docker ps` 命令查看是否有容器正在使用它。\n\n```shell\n➜  ~ docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES\nc4cd691d9f80        mysql:5.7           \"docker-entrypoint.s…\"   7 weeks ago         Up 12 days          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql\n```\n\n可以看到 `mysql:5.7` 正在被 ID 为 `c4cd691d9f80` 的容器引用，需要先通过 `docker stop c4cd691d9f80` 或者 `docker stop mysql` 暂停这个容器。\n\n然后查看 MySQL 镜像的 ID：\n\n```shell\n➜  ~ docker images\nREPOSITORY              TAG                 IMAGE ID            CREATED             SIZE\nmysql                   5.7                 f6509bac4980        3 months ago        373MB\n```\n\n通过 `IMAGE ID` 或者 `REPOSITORY:TAG` 即可删除：\n\n```shell\ndocker rmi f6509bac4980 # 或者 docker rmi mysql:5.7\n```\n\n### 镜像推送\n\n`docker push` 命令用于将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n```bash\n# 将镜像推送到私有镜像仓库 Harbor\n# harbor.example.com 是私有镜像仓库的地址，ubuntu 是镜像名称，18.04 是镜像版本标签\ndocker push harbor.example.com/ubuntu:18.04\n```\n\n镜像推送之前，要确保本地已经构建好需要推送的 Docker 镜像。另外，务必先登录到对应的镜像仓库。\n\n## Docker 数据管理\n\n在容器中管理数据主要有两种方式：\n\n1. 数据卷（Volumes）\n2. 挂载主机目录 (Bind mounts)\n\n![Docker 数据管理](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-data-management.png)\n\n数据卷是由 Docker 管理的数据存储区域，有如下这些特点：\n\n- 可以在容器之间共享和重用。\n- 即使容器被删除，数据卷中的数据也不会被自动删除，从而确保数据的持久性。\n- 对数据卷的修改会立马生效。\n- 对数据卷的更新，不会影响镜像。\n\n```bash\n# 创建一个数据卷\ndocker volume create my-vol\n# 查看所有的数据卷\ndocker volume ls\n# 查看数据卷的具体信息\ndocker volume inspect my-vol\n# 删除指定的数据卷\ndocker volume rm my-vol\n```\n\n在用 `docker run` 命令的时候，使用 `--mount` 标记来将一个或多个数据卷挂载到容器里。\n\n还可以通过 `--mount` 标记将宿主机上的文件或目录挂载到容器中，这使得容器可以直接访问宿主机的文件系统。Docker 挂载主机目录的默认权限是读写，用户也可以通过增加 `readonly` 指定为只读。\n\n## Docker Compose\n\n### 什么是 Docker Compose？有什么用？\n\nDocker Compose 是 Docker 官方提供的多容器应用定义和运行工具。通过 Compose，开发者可以使用一个 YAML 文件描述应用依赖的多个服务、网络、端口和数据卷，然后用一条命令启动或停止整组服务。\n\nDocker Compose 是开源项目，地址：<https://github.com/docker/compose>。\n\nDocker Compose 的核心功能：\n\n- **多容器管理**：允许用户在一个 YAML 文件中定义和管理多个容器。\n- **服务编排**：配置容器间的网络和依赖关系。\n- **一键启动与停止**：通过 `docker compose up` 和 `docker compose down` 等命令，可以轻松启动和停止整个应用。\n\nDocker Compose 简化了多容器应用程序的开发、测试和部署过程，提高了开发团队的生产力，同时降低了应用程序的部署复杂度和管理成本。\n\n### Docker Compose 文件基本结构\n\nDocker Compose 文件是 Docker Compose 工具的核心，用于定义和配置多容器 Docker 应用。这个文件通常命名为 `compose.yaml` 或 `docker-compose.yml`，采用 YAML（YAML Ain't Markup Language）格式编写。\n\nDocker Compose 文件基本结构如下：\n\n- **服务（services）：** 定义了应用中的每个容器（服务）。每个服务可以使用不同的镜像、环境设置和依赖关系。\n  - **镜像（image）：** 从指定的镜像中启动容器，可以是存储仓库、标签以及镜像 ID。\n  - **命令（command）：** 可选，覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本，常用于启动应用程序、执行初始化脚本等。\n  - **端口（ports）：** 可选，映射容器和宿主机的端口。\n  - **依赖（depends_on）：** 配置服务之间的启动依赖关系。例如后端服务依赖数据库服务时，可以先启动数据库，再启动后端服务。\n  - **环境变量（environment）：** 可选，设置服务运行所需的环境变量。\n  - **重启（restart）:** 可选，控制容器的重启策略。在容器退出时，根据指定的策略自动重启容器。\n  - **服务卷（volumes）:** 可选，定义服务使用的卷，用于数据持久化或在容器之间共享数据。\n  - **构建（build）：** 指定构建镜像的 Dockerfile 上下文路径，或者使用详细配置对象。\n- **网络（networks）：** 定义了容器间的网络连接。\n- **卷（volumes）：** 用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化。\n\n```yaml\nservices:\n  service-name-1:\n    image: nginx:stable\n    command: [\"nginx\", \"-g\", \"daemon off;\"]\n    environment:\n      TZ: Asia/Shanghai\n    volumes:\n      - web_data:/usr/share/nginx/html\n    networks:\n      - app_net\n    ports:\n      - \"8080:80\"\n    restart: unless-stopped\n    depends_on:\n      - service-name-2\n  service-name-2:\n    image: redis:7\n    networks:\n      - app_net\n\nvolumes:\n  web_data:\n\nnetworks:\n  app_net:\n```\n\n### Docker Compose 常见命令\n\n#### 启动\n\n`docker compose up` 会根据 Compose 文件中定义的服务创建并启动容器，并将它们连接到 Compose 创建的网络中。如果文件没有声明自定义网络，Compose 会自动创建默认网络。\n\n```bash\n# 在当前目录下寻找 compose.yaml 或 docker-compose.yml 文件，并根据其中定义的服务启动应用\ndocker compose up\n# 后台启动\ndocker compose up -d\n# 强制重新创建所有容器，即使它们已经存在\ndocker compose up --force-recreate\n# 重新构建镜像\ndocker compose up --build\n# 指定要启动的服务名称，而不是启动所有服务\n# 可以同时指定多个服务，用空格分隔。\ndocker compose up service-name\n```\n\n另外，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml up\n```\n\n#### 暂停\n\n`docker compose down` 用于停止并移除通过 `docker compose up` 启动的容器和网络。\n\n```bash\n# 在当前目录下寻找 Compose 文件\n# 根据其中定义移除启动的容器和网络\ndocker compose down\n# 停止容器但不移除\ndocker compose stop\n# 停止指定服务\ndocker compose stop service-name\n```\n\n同样地，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml down\n```\n\n#### 查看\n\n`docker compose ps` 用于查看通过 `docker compose up` 启动的所有容器的状态信息。\n\n```bash\n# 查看所有容器的状态信息\ndocker compose ps\n# 只显示服务名称\ndocker compose ps --services\n# 查看指定服务的容器\ndocker compose ps service-name\n```\n\n#### 其他\n\n| 命令                     | 介绍                   |\n| ------------------------ | ---------------------- |\n| `docker compose version` | 查看版本               |\n| `docker compose images`  | 列出所有容器使用的镜像 |\n| `docker compose kill`    | 强制停止服务的容器     |\n| `docker compose exec`    | 在容器中执行命令       |\n| `docker compose logs`    | 查看日志               |\n| `docker compose pause`   | 暂停服务               |\n| `docker compose unpause` | 恢复服务               |\n| `docker compose push`    | 推送服务镜像           |\n| `docker compose start`   | 启动当前停止的服务     |\n| `docker compose stop`    | 停止当前运行的服务     |\n| `docker compose rm`      | 删除已停止的服务容器   |\n| `docker compose top`     | 查看进程               |\n\n## Docker 底层原理\n\n首先，Docker 是基于轻量级虚拟化技术的软件，那什么是虚拟化技术呢？\n\n简单点来说，虚拟化技术可以这样定义：\n\n> 虚拟化技术是一种资源管理技术，是将计算机的各种[实体资源](https://zh.wikipedia.org/wiki/計算機科學)（[CPU](https://zh.wikipedia.org/wiki/CPU)、[内存](https://zh.wikipedia.org/wiki/内存)、[磁盘空间](https://zh.wikipedia.org/wiki/磁盘空间)、[网络适配器](https://zh.wikipedia.org/wiki/網路適配器)等），予以抽象、转换后呈现出来并可供分割、组合为一个或多个电脑配置环境。由此，打破实体结构间的不可切割的障碍，使用户可以比原本的配置更好的方式来应用这些电脑硬件资源。这些资源的新虚拟部分是不受现有资源的架设方式，地域或物理配置所限制。一般所指的虚拟化资源包括计算能力和数据存储。\n\nDocker 技术是基于 LXC（Linux container- Linux 容器）虚拟容器技术的。\n\n> LXC，其名称来自 Linux 软件容器（Linux Containers）的缩写，一种操作系统层虚拟化（Operating system–level virtualization）技术，为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器（Container），内含应用软件本身的代码，以及所需要的操作系统核心和库。通过统一的名字空间和共用 API 来分配不同软件容器的可用硬件资源，创造出应用程序的独立沙箱运行环境，使得 Linux 用户可以容易的创建和管理系统或应用容器。\n\nLXC 技术主要是借助 Linux 内核中提供的 CGroup 功能和 namespace 来实现的，通过 LXC 可以为软件提供一个独立的操作系统运行环境。\n\n**cgroup 和 namespace 介绍：**\n\n- **namespace 是 Linux 内核用来隔离内核资源的方式。** 通过 namespace 可以让一些进程只能看到与自己相关的一部分资源，而另外一些进程也只能看到与它们自己相关的资源，这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。\n\n  （以上关于 namespace 介绍内容来自<https://www.cnblogs.com/sparkdev/p/9365405.html> ，更多关于 namespace 的内容可以查看这篇文章 ）。\n\n- **CGroup 是 Control Groups 的缩写，是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。**\n\n  （以上关于 CGroup 介绍内容来自 <https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html> ，更多关于 CGroup 的内容可以查看这篇文章 ）。\n\n**cgroup 和 namespace 两者对比：**\n\n两者都是将进程进行分组，但是两者的作用还是有本质区别。namespace 是为了隔离进程组之间的资源，而 cgroup 是为了对一组进程进行统一的资源监控和限制。\n\n## 总结\n\n本文主要把 Docker 中的一些常见概念和命令做了详细的阐述。从零到上手实战可以看[Docker 从入门到上手干事](https://javaguide.cn/tools/docker/docker-in-action.html)这篇文章，内容非常详细！\n\n另外，再给大家推荐一本质量非常高的开源书籍[《Docker 从入门到实践》](https://yeasy.gitbook.io/docker_practice/introduction/why) ，这本书的内容非常新，毕竟书籍的内容是开源的，可以随时改进。\n\n![《Docker 从入门到实践》网站首页](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-getting-started-practice-website-homepage.png)\n\n## 参考\n\n- [Docker Compose：从零基础到实战应用的全面指南](https://juejin.cn/post/7306756690727747610)\n- [Linux Namespace 和 Cgroup](https://segmentfault.com/a/1190000009732550)\n- [LXC vs Docker: Why Docker is Better](https://www.upguard.com/articles/docker-vs-lxc \"LXC vs Docker: Why Docker is Better\")\n- [CGroup 介绍、应用实例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/gradle/gradle-core-concepts.md":"---\ntitle: Gradle 核心概念总结\ndescription: Gradle 是一个运行在 JVM 上的自动化构建工具，支持灵活的任务编排、依赖管理、插件扩展和多项目构建。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Gradle,Groovy,Gradle Wrapper,Gradle 包装器,Gradle 插件\n---\n\n> 这部分内容主要根据 Gradle 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\nGradle 这部分内容属于可选内容，可以根据自身需求决定是否学习。国内 Java 后端项目里 Maven 仍然更常见，但 Android、部分 Spring Boot 项目以及需要高度定制构建流程的项目中，Gradle 使用得也很多。\n\n## Gradle 介绍\n\nGradle 官方文档是这样介绍的 Gradle 的：\n\n> Gradle is an open-source [build automation](https://en.wikipedia.org/wiki/Build_automation) tool flexible enough to build almost any type of software. Gradle makes few assumptions about what you’re trying to build or how to build it. This makes Gradle particularly flexible.\n>\n> Gradle 是一个开源的构建自动化工具，它足够灵活，可以构建几乎任何类型的软件。Gradle 对你要构建什么或者如何构建它做了很少的假设。这使得 Gradle 特别灵活。\n\n简单来说，Gradle 就是一个运行在 JVM 上的自动化项目构建工具，用来帮助我们完成编译、测试、打包、发布等构建任务。\n\n对于开发者来说，Gradle 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\nGradle 构建脚本可以使用 Groovy DSL 或 Kotlin DSL 编写。现在新项目里 Kotlin DSL 也很常见，它的类型提示和 IDE 支持通常更好。\n\n## Groovy 介绍\n\nGradle 是运行在 JVM 上的一个程序，构建脚本可以使用 Groovy 或 Kotlin 编写。历史上很多 Gradle 示例使用 Groovy DSL，因此先了解一点 Groovy 语法对阅读老项目很有帮助。\n\nGroovy 是运行在 JVM 上的脚本语言，是基于 Java 扩展的动态语言，它的语法和 Java 非常的相似，可以使用 Java 的类库。Groovy 可以用于面向对象编程，也可以用作纯粹的脚本语言。在语言的设计上它吸纳了 Java、Python、Ruby 和 Smalltalk 语言的优秀特性，比如动态类型转换、闭包和元编程支持。\n\n我们可以用学习 Java 的方式去学习 Groovy ，学习成本相对来说还是比较低的，即使开发过程中忘记 Groovy 语法，也可以用 Java 语法继续编码。\n\n基于 JVM 的语言有很多种，比如 Groovy、Kotlin、Java、Scala，它们最终都会编译生成 Java 字节码文件并在 JVM 上运行。\n\n## Gradle 优势\n\nGradle 是新一代的构建系统，具有高效和灵活等诸多优势，广泛用于 Java 开发。不仅 Android 将其作为官方构建系统, 越来越多的 Java 项目比如 Spring Boot 也慢慢迁移到 Gradle。\n\n- 在灵活性上，Gradle 支持基于 Groovy 语言编写脚本，侧重于构建过程的灵活性，适合于构建复杂度较高的项目，可以完成非常复杂的构建。\n- 在粒度性上，Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的，在我们掌握了这一整套打包流程后，我们就可以通过去修改它的 Task 去动态改变其执行流程。\n- 在扩展性上，Gradle 支持插件机制，所以我们可以复用这些插件，就如同复用库一样简单方便。\n\n## Gradle Wrapper 介绍\n\nGradle 官方文档是这样介绍的 Gradle Wrapper 的：\n\n> The recommended way to execute any Gradle build is with the help of the Gradle Wrapper (in short just “Wrapper”). The Wrapper is a script that invokes a declared version of Gradle, downloading it beforehand if necessary. As a result, developers can get up and running with a Gradle project quickly without having to follow manual installation processes saving your company time and money.\n>\n> 执行 Gradle 构建的推荐方法是借助 Gradle Wrapper(简而言之就是“Wrapper”)。Wrapper 它是一个脚本，调用了已经声明的 Gradle 版本，如果需要的话，可以预先下载它。因此，开发人员可以快速启动并运行 Gradle 项目，而不必遵循手动安装过程，从而为公司节省时间和金钱。\n\n我们可以称 Gradle Wrapper 为 Gradle 包装器，它将 Gradle 再次包装，让所有的 Gradle 构建方法在 Gradle 包装器的帮助下运行。\n\nGradle Wrapper 的工作流程图如下（图源[Gradle Wrapper 官方文档介绍](https://docs.gradle.org/current/userguide/gradle_wrapper.html)）：\n\n![包装器工作流程](https://oss.javaguide.cn/github/javaguide/csdn/efa7a0006b04051e2b84cd116c6ccdfc.png)\n\n整个流程主要分为下面 3 步：\n\n1. 首先当我们刚创建的时候，如果指定的版本没有被下载，就先会去 Gradle 的服务器中下载对应版本的压缩包；\n2. 下载完成后需要先进行解压缩并且执行批处理文件；\n3. 后续项目每次构建都会重用这个解压过的 Gradle 版本。\n\nGradle Wrapper 会给我们带来下面这些好处：\n\n1. 在给定的 Gradle 版本上标准化项目，从而实现更可靠和健壮的构建。\n2. 可以让我们的电脑中不安装 Gradle 环境也可以运行 Gradle 项目。\n3. 为不同的用户和执行环境（例如 IDE 或持续集成服务器）提供新的 Gradle 版本就像更改 Wrapper 定义一样简单。\n\n### 生成 Gradle Wrapper\n\n如果想要首次生成 Gradle Wrapper，需要本地先有可用的 Gradle。Gradle 中已经内置了 Wrapper Task，在项目根目录执行 `gradle wrapper` 命令即可生成 Gradle Wrapper。\n\n执行命令 `gradle wrapper` 命令时可以指定一些参数来控制 wrapper 的生成。具体有如下两个配置参数：\n\n- `--gradle-version`：用于指定使用的 Gradle 版本。\n- `--gradle-distribution-url`：用于指定下载 Gradle 发行版的 URL，该值通常类似于 `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`。\n- `--gradle-distribution-sha256-sum`：用于指定发行版压缩包的 SHA-256 校验值，可以降低下载文件被篡改的风险。\n\n执行`gradle wrapper`命令之后，Gradle Wrapper 就生成完成了，项目根目录中生成如下文件：\n\n```plain\n├── gradle\n│   └── wrapper\n│       ├── gradle-wrapper.jar\n│       └── gradle-wrapper.properties\n├── gradlew\n└── gradlew.bat\n```\n\n每个文件的含义如下：\n\n- `gradle-wrapper.jar`：包含了 Gradle 运行时的逻辑代码。\n- `gradle-wrapper.properties`：定义了 Gradle 的版本号和 Gradle 运行时的行为属性。\n- `gradlew`：Linux/macOS 平台下，用于执行 Gradle 命令的包装器脚本。\n- `gradlew.bat`：Windows 平台下，用于执行 Gradle 命令的包装器脚本。\n\n`gradle-wrapper.properties` 文件的内容如下：\n\n```properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.0.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n```\n\n- `distributionBase`：Gradle 解包后存储的父目录。\n- `distributionPath`：`distributionBase`指定目录的子目录。`distributionBase+distributionPath`就是 Gradle 解包后的存放的具体目录。\n- `distributionUrl`：Gradle 指定版本的压缩包下载地址。\n- `zipStoreBase`：Gradle 压缩包下载后存储父目录。\n- `zipStorePath`：`zipStoreBase`指定目录的子目录。`zipStoreBase+zipStorePath`就是 Gradle 压缩包的存放位置。\n\n### 更新 Gradle Wrapper\n\n更新 Gradle Wrapper 有 2 种方式：\n\n1. 直接修改 `distributionUrl` 字段，然后执行 Gradle 命令。\n2. 执行 `./gradlew wrapper --gradle-version [version]`。\n\n下面的命令会将 Gradle 版本升级为 9.5.1。\n\n```shell\n./gradlew wrapper --gradle-version 9.5.1\n```\n\n`gradle-wrapper.properties` 文件中的 `distributionUrl` 属性也发生了改变。\n\n```properties\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.5.1-bin.zip\n```\n\n项目已经生成 Wrapper 后，日常构建优先使用 `./gradlew build`，而不是直接使用本机安装的 `gradle build`。这样可以确保团队成员和 CI 使用同一个 Gradle 版本。\n\n### 自定义 Gradle Wrapper\n\nGradle 已经内置了 Wrapper Task，因此构建 Gradle Wrapper 会生成 Gradle Wrapper 的属性文件，这个属性文件可以通过自定义 Wrapper Task 来设置。比如我们想要修改要下载的 Gradle 版本为 9.5.1，可以这么设置：\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n}\n```\n\n也可以设置 Gradle 发行版压缩包的下载地址和 Gradle 解包后的本地存储路径等配置。\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n    distributionUrl = '../../gradle-9.5.1-bin.zip'\n    distributionPath = 'wrapper/dists'\n}\n```\n\n`distributionUrl` 属性可以设置为本地的项目目录，你也可以设置为网络地址。\n\n## Gradle 任务\n\n在 Gradle 中，任务(Task)是构建执行的单个工作单元。\n\nGradle 的构建是基于 Task 进行的，当你运行项目的时候，实际就是在执行了一系列的 Task 比如编译 Java 源码的 Task、生成 jar 文件的 Task。\n\nTask 的声明方式如下（还有其他几种声明方式）：\n\n```groovy\n// 声明一个名字为 helloTask 的 Task\ntask helloTask{\n     doLast{\n       println \"Hello\"\n     }\n}\n```\n\n创建一个 Task 后，可以根据需要给 Task 添加不同的 Action，上面的“doLast”就是给队列尾增加一个 Action。\n\n```groovy\n //在Action 队列头部添加Action\n Task doFirst(Action<? super Task> action);\n Task doFirst(Closure action);\n\n //在Action 队列尾部添加Action\n Task doLast(Action<? super Task> action);\n Task doLast(Closure action);\n\n //删除所有的Action\n Task deleteAllActions();\n```\n\n一个 Task 中可以有多个 Acton，从队列头部开始向队列尾部执行 Acton。\n\nAction 代表的是一个个函数、方法，每个 Task 都是一堆 Action 按序组成的执行图。\n\nTask 声明依赖的关键字是`dependsOn`，支持声明一个或多个依赖：\n\n```groovy\ntask first {\n doLast {\n        println \"+++++first+++++\"\n    }\n}\ntask second {\n doLast {\n        println \"+++++second+++++\"\n    }\n}\n\n// 指定多个 task 依赖\ntask print(dependsOn :[second,first]) {\n doLast {\n      logger.quiet \"指定多个task依赖\"\n    }\n}\n\n// 指定一个 task 依赖\ntask third(dependsOn : print) {\n doLast {\n      println '+++++third+++++'\n    }\n}\n```\n\n执行 Task 之前，会先执行它的依赖 Task。\n\n我们还可以设置默认 Task，脚本中我们不调用默认 Task ，也会执行。\n\n```groovy\ndefaultTasks 'clean', 'run'\n\ntask clean {\n    doLast {\n        println 'Default Cleaning!'\n    }\n}\n\ntask run {\n    doLast {\n        println 'Default Running!'\n    }\n}\n```\n\nGradle 本身也内置了很多 Task 比如 copy（复制文件）、delete（删除文件）。\n\n```groovy\ntask deleteFile(type: Delete) {\n    delete \"C:\\\\Users\\\\guide\\\\Desktop\\\\test\"\n}\n```\n\n## Gradle 插件\n\nGradle 提供的是一套核心的构建机制，而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑，其本质上和 `.gradle` 文件是相同。你可以将 Gradle 插件看作是封装了一系列 Task 并执行的工具。\n\nGradle 插件主要分为两类：\n\n- 脚本插件：脚本插件就是一个普通的脚本文件，它可以被导入都其他构建脚本中。\n- 二进制插件 / 对象插件：在一个单独的插件模块中定义，其他模块通过 Plugin ID 应用插件。因为这种方式发布和复用更加友好，我们一般接触到的 Gradle 插件都是指二进制插件的形式。\n\n虽然 Gradle 插件与 .gradle 文件本质上没有区别，`.gradle` 文件也能实现 Gradle 插件类似的功能。但是，Gradle 插件使用了独立模块封装构建逻辑，无论是从开发开始使用来看，Gradle 插件的整体体验都更友好。\n\n- **逻辑复用：** 将相同的逻辑提供给多个相似项目复用，减少重复维护类似逻辑开销。当然 .gradle 文件也能做到逻辑复用，但 Gradle 插件的封装性更好；\n- **组件发布：** 可以将插件发布到 Maven 仓库进行管理，其他项目可以使用插件 ID 依赖。当然 .gradle 文件也可以放到一个远程路径被其他项目引用；\n- **构建配置：** Gradle 插件可以声明插件扩展来暴露可配置的属性，提供定制化能力。当然 .gradle 文件也可以做到，但实现会麻烦些。\n\n## Gradle 构建生命周期\n\nGradle 构建的生命周期有三个阶段：**初始化阶段，配置阶段**和**运行阶段**。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/dadbdf59fccd9a2ebf60a2d018541e52.png)\n\n在初始化阶段与配置阶段之间、配置阶段结束之后、执行阶段结束之后，我们都可以加一些定制化的 Hook。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/5c297ccc4dac83229ff3e19caee9d1d2.png)\n\n### 初始化阶段\n\nGradle 支持单项目和多项目构建。在初始化阶段，Gradle 确定哪些项目将参与构建，并为每个项目创建一个 [Project 实例](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html) 。本质上也就是执行 `settings.gradle` 脚本，从而读取整个项目中有多少个 Project 实例。\n\n### 配置阶段\n\n在配置阶段，Gradle 会解析每个工程的 `build.gradle` 文件，创建要执行的任务子集和确定各种任务之间的关系，以供执行阶段按照顺序执行，并对任务的做一些初始化配置。\n\n每个 `build.gradle` 对应一个 Project 对象，配置阶段执行的代码包括 `build.gradle` 中的各种语句、闭包以及 Task 中的配置语句。\n\n在配置阶段结束后，Gradle 会根据 Task 的依赖关系会创建一个 **有向无环图** 。\n\n### 运行阶段\n\n在运行阶段，Gradle 根据配置阶段创建和配置的要执行的任务子集，执行任务。\n\n## 参考\n\n- Gradle 官方文档：<https://docs.gradle.org/current/userguide/userguide.html>\n- Gradle 入门教程：<https://www.imooc.com/wiki/gradlebase>\n- Groovy 快速入门看这篇就够了：<https://cloud.tencent.com/developer/article/1358357>\n- 【Gradle】Gradle 的生命周期详解：<https://juejin.cn/post/7067719629874921508>\n- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)：<https://www.cnblogs.com/pengxurui/p/16281537.html>\n- Gradle 爬坑指南 -- 理解 Plugin、Task、构建流程：<https://juejin.cn/post/6889090530593112077>\n\n<!-- @include: @article-footer.snippet.md -->\n","docs/tools/docker/README.md":"---\ntitle: Docker 专题：容器、镜像、仓库、数据卷、网络与容器化部署\ndescription: Docker 面试与容器化学习路线，涵盖容器、镜像、仓库、Docker 引擎、数据卷、网络、常用命令和容器化部署实践。\ncategory: 开发工具\ntag:\n  - Docker\n  - 容器\n  - 部署\nsitemap:\n  changefreq: weekly\n  priority: 0.85\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发\n---\n\nDocker 是后端开发非常常见的容器化工具，常用于本地快速启动 MySQL、Redis、Kafka 等依赖服务，也常用于测试环境和部署交付。学习 Docker 时，要把核心概念和命令实践结合起来。\n\n## 适合谁看\n\n- 想快速理解 Docker 容器化基础的后端开发者。\n- 需要用 Docker 搭建本地开发环境、测试环境或依赖服务的同学。\n- 准备面试，需要讲清容器、镜像、数据卷、网络和容器与虚拟机区别的读者。\n- 已经会复制 Docker 命令，但不清楚镜像构建、容器生命周期和数据持久化的工程师。\n\n## 学习重点\n\n- 容器解决的是应用运行环境隔离和一致性问题。\n- 镜像是静态模板，容器是镜像运行后的实例，仓库用于分发和复用镜像。\n- Docker 常用命令要围绕镜像管理、容器生命周期、日志查看、端口映射和进入容器来掌握。\n- 数据卷用于数据持久化和宿主机目录挂载，网络用于容器之间和容器与外部服务通信。\n- Docker Compose 用于定义和运行多容器应用，适合本地开发环境和简单服务编排。\n- Docker 不是万能部署方案，生产环境还要结合镜像安全、资源限制、日志、监控和编排能力考虑。\n\n## 建议阅读顺序\n\n1. [Docker 核心概念总结](./docker-intro.md)：先理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。\n2. [Docker 实战](./docker-in-action.md)：通过实际命令练习镜像拉取、容器启动、端口映射、数据卷、日志查看和常见服务部署。\n3. 结合一个 Java 项目练习：把应用依赖的 MySQL、Redis 等服务用 Docker 启动起来，再观察日志、数据目录和端口映射。\n\n## 核心文章\n\n- [Docker 核心概念总结](./docker-intro.md)：讲解容器、镜像、仓库、Docker 引擎、Docker 架构、Docker Compose，以及 Docker 和虚拟机的区别。\n- [Docker 实战](./docker-in-action.md)：通过常用命令和实践场景理解镜像管理、容器管理、服务部署、本地环境搭建和常见排查。\n\n## 高频问题\n\n- Docker 主要解决什么问题？\n- 容器和虚拟机有什么区别？\n- 镜像和容器是什么关系？\n- Dockerfile、镜像、容器、仓库分别是什么？\n- 为什么容器删除后数据可能丢失？数据卷解决了什么问题？\n- 端口映射和容器网络分别解决什么问题？\n- 如何查看容器日志、进入容器、停止和删除容器？\n- `docker compose` 适合解决什么问题？和单独执行 `docker run` 有什么区别？\n- Docker 在开发、测试和部署环境里分别有什么价值？\n\n## 相关专题\n\n- [开发工具知识体系](../)\n- [Git 专题](../git/)\n- [Maven 专题](../maven/)\n- [高可用系统设计](../../high-availability/)\n\n<!-- @include: @article-footer.snippet.md -->\n"},"items":[{"name":"docker-intro.md","path":"docs/tools/docker/docker-intro.md","rawUrl":"https://raw.githubusercontent.com/Snailclimb/JavaGuide/HEAD/docs/tools/docker/docker-intro.md","title":"Docker Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Docker 核心概念总结\ndescription: 梳理 Docker 的核心概念与容器/虚拟机差异，掌握镜像、容器与仓库的关系及在交付部署中的实际价值。\ncategory: 开发工具\ntag:\n  - Docker\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署\n---\n\n本文主要讲 Docker 的核心概念、运行模型和常见使用场景，不展开安装过程。安装、命令练习和本地服务启动可以看后面的 [Docker 实战](./docker-in-action.md)。\n\n## 容器介绍\n\nDocker 是常见的软件容器平台。想要搞懂 Docker，先要理解容器解决的到底是什么问题。\n\n### 什么是容器？\n\n#### 先来看看容器较为官方的解释\n\n**一句话概括容器：容器就是将软件打包成标准化单元，以用于开发、交付和部署。**\n\n- **容器镜像是轻量的、可执行的独立软件包** ，包含软件运行所需的所有内容：代码、运行时环境、系统工具、系统库和设置。\n- **容器化软件适用于基于 Linux 和 Windows 的应用，在任何环境中都能够始终如一地运行。**\n- **容器赋予了软件独立性**，使其免受外在环境差异（例如，开发和预演环境的差异）的影响，从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。\n\n#### 再来看看容器较为通俗的解释\n\n如果需要通俗地描述容器的话，我觉得容器就是一个存放东西的地方，就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。\n\n![认识容器](https://oss.javaguide.cn/github/javaguide/tools/docker/container.png)\n\n### 图解物理机、虚拟机与容器\n\n关于虚拟机与容器的对比在后面会详细介绍到，这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解(下面的图片来源于网络)。\n\n**物理机：**\n\n![物理机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E7%89%A9%E7%90%86%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**虚拟机：**\n\n![虚拟机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**容器：**\n\n![](https://oss.javaguide.cn/javaguide/image-20211110104003678.png)\n\n通过上面这三张抽象图，我们可以大概通过类比概括出：**容器虚拟化的是操作系统而不是硬件，容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。**\n\n### 容器 VS 虚拟机\n\n每当说起容器，我们不得不将其与虚拟机做一个比较。就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n简单来说：**容器和虚拟机具有相似的资源隔离和分配优势，但功能有所不同，因为容器虚拟化的是操作系统，而不是硬件，因此容器更容易移植，效率也更高。**\n\n传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![](https://oss.javaguide.cn/javaguide/2e2b95eebf60b6d03f6c1476f4d7c697.png)\n\n**容器和虚拟机的对比**：\n\n![](https://oss.javaguide.cn/javaguide/4ef8691d67eb1eb53217099d0a691eb5.png)\n\n- 容器是一个应用层抽象，用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行，共享操作系统内核，但各自作为独立的进程在用户空间中运行 。与虚拟机相比， **容器占用的空间较少**（容器镜像大小通常只有几十兆），**瞬间就能完成启动** 。\n\n- 虚拟机 (VM) 是一个物理硬件层抽象，用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源，因此 **占用大量空间** 。而且 VM **启动也十分缓慢** 。\n\n通过 Docker 官网，我们知道了这么多 Docker 的优势，但是大家也没有必要完全否定虚拟机技术，因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如，云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker 通常用于隔离不同的应用** ，例如前端，后端以及数据库。\n\n就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n![](https://oss.javaguide.cn/javaguide/056c87751b9dd7b56f4264240fe96d00.png)\n\n## Docker 介绍\n\n### 什么是 Docker？\n\n可以从下面几个角度理解 Docker：\n\n- **Docker 是一个软件容器平台。**\n- **Docker** 使用 Go 语言开发，基于 Linux 内核提供的 cgroups、namespaces，以及 UnionFS 等能力对进程进行封装隔离，属于操作系统层面的虚拟化技术。\n- Docker 能够把应用和运行依赖打包到镜像中，减少开发、测试、部署环境不一致带来的问题。\n- 用户可以方便地创建和使用容器，把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改，就像管理普通的代码一样。\n\n**Docker 思想**：\n\n- **集装箱**：就像海运中的集装箱一样，Docker 容器包含了应用程序及其所有依赖项，确保在任何环境中都能以相同的方式运行。\n- **标准化**：运输方式、存储方式、API 接口。\n- **隔离**：每个 Docker 容器都在自己的隔离环境中运行，与宿主机和其他容器隔离。\n\n### Docker 容器的特点\n\n- **轻量** : 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核；它们能够迅速启动，只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的，并共享一些公共文件。这样就能尽量降低磁盘用量，并能更快地下载镜像。\n- **标准** : Docker 容器基于开放式标准，能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。\n- **安全** : Docker 赋予应用的隔离性不仅限于彼此隔离，还独立于底层的基础设施。Docker 默认提供最强的隔离，因此应用出现问题，也只是单个容器的问题，而不会波及到整台机器。\n\n### 为什么要用 Docker ?\n\n- Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 “这段代码在我机器上没问题啊” 这类问题；——一致的运行环境\n- 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间\n- 避免公用的服务器，资源会容易受到其他用户的影响。——隔离性\n- 善于处理集中爆发的服务器使用压力；——弹性伸缩，快速扩展\n- 可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便\n- 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署\n\n---\n\n## Docker 基本概念\n\nDocker 中有非常重要的三个基本概念：镜像（Image）、容器（Container）和仓库（Repository）。\n\n理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-run.jpeg)\n\n### 镜像（Image）：一个特殊的文件系统\n\n**操作系统分为内核和用户空间**。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 root 文件系统。\n\n**Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。** 镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\nDocker 设计时，就充分利用 **Union FS** 的技术，将其设计为**分层存储的架构** 。镜像实际是由多层文件系统联合组成。\n\n**镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。** 比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n### 容器（Container）：镜像运行时的实体\n\n镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。\n\n**容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储，容器也是如此。**\n\n**容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。**\n\n按照 Docker 最佳实践的要求，**容器不应该向其存储层内写入业务数据**，容器存储层要尽量保持无状态。**需要持久化的文件写入，应该使用数据卷（Volume）或者绑定宿主目录**，这类读写会绕过容器存储层，直接落到宿主机或网络存储上，性能和稳定性更好。数据卷的生命周期独立于容器，容器删除后，数据卷不会自动删除。\n\n### 仓库（Repository）：集中存放镜像文件的地方\n\n镜像构建完成后，可以很容易的在当前宿主上运行，但是， **如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。**\n\n一个 Docker Registry 中可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应一个镜像。所以说：**镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。**\n\n通常，**一个仓库会包含同一个软件不同版本的镜像**，而**标签就常用于对应这个软件的各个版本**。我们可以通过 `<仓库名>:<标签>` 的格式指定具体镜像。如果不给出标签，将以 `latest` 作为默认标签。不过在生产环境中不建议依赖 `latest`，最好明确指定版本标签，便于回滚和排查问题。\n\n**这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念：**\n\n**Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n最常使用的 Registry 公开服务是官方的 **Docker Hub** ，这也是默认的 Registry，并拥有大量的高质量的官方镜像，网址为：[https://hub.docker.com/](https://hub.docker.com/ \"https://hub.docker.com/\") 。官方是这样介绍 Docker Hub 的：\n\n> Docker Hub 是 Docker 官方提供的一项服务，用于与您的团队查找和共享容器镜像。\n\n比如我们想要搜索自己想要的镜像：\n\n![利用 Docker Hub 搜索镜像](https://oss.javaguide.cn/github/javaguide/tools/docker/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png)\n\n在 Docker Hub 的搜索结果中，有几项关键的信息有助于我们选择合适的镜像：\n\n- **OFFICIAL Image**：代表镜像为 Docker 官方提供和维护，相对来说稳定性和安全性较高。\n- **Stars**：和点赞差不多的意思，类似 GitHub 的 Star。\n- **Downloads**：代表镜像被拉取的次数，基本上能够表示镜像被使用的频度。\n\n当然，除了直接通过 Docker Hub 网站搜索镜像这种方式外，我们还可以通过 `docker search` 这个命令搜索 Docker Hub 中的镜像，搜索的结果是一致的。\n\n```bash\n➜  ~ docker search mysql\nNAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED\nmysql                             MySQL is a widely used, open-source relation…   8763                [OK]\nmariadb                           MariaDB is a community-developed fork of MyS…   3073                [OK]\nmysql/mysql-server                Optimized MySQL Server Docker images. Create…   650                                     [OK]\n```\n\n在国内访问 **Docker Hub** 可能会比较慢，企业项目通常会结合公司内部镜像仓库或云厂商镜像仓库来做镜像缓存和分发。\n\n除了使用公开服务外，用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像，可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现，足以支持 Docker 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。\n\n### Image、Container 和 Repository 的关系\n\n下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系：\n\n![Docker 架构](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-regitstry.png)\n\n- Dockerfile 是一个文本文件，包含了一系列的指令和参数，用于定义如何构建一个 Docker 镜像。运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n- `docker pull` 命令可以从指定的 Registry/Hub 下载一个镜像到本地，默认使用 Docker Hub。\n- `docker run` 命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像，Docker 会先尝试从 Registry/Hub 拉取镜像。\n- `docker push` 命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n上面涉及到了一些 Docker 基本命令，后面的实战文章会详细介绍。\n\n### Build Ship and Run\n\nDocker 的概念基本上已经讲完，我们再来谈谈：Build, Ship, and Run。\n\n如果你搜索 Docker 官网，会发现如下的字样：**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么 Build, Ship, and Run 到底是在干什么呢？\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-ship-run.jpg)\n\n- **Build（构建镜像）**：镜像就像是集装箱包括文件以及运行环境等等资源。\n- **Ship（运输镜像）**：主机和仓库间运输，这里的仓库就像是超级码头一样。\n- **Run （运行镜像）**：运行的镜像就是一个容器，容器就是运行程序的地方。\n\nDocker 运行过程也就是去仓库把镜像拉到本地，然后用一条命令把镜像运行起来变成容器。所以，我们也常常将 Docker 称为码头工人或码头装卸工，这和 Docker 的中文翻译搬运工人如出一辙。\n\n## Docker 常见命令\n\n### 基本命令\n\n```bash\ndocker version # 查看 Docker 版本\ndocker images # 查看所有已下载镜像，等价于：docker image ls 命令\ndocker container ls # 查看所有容器\ndocker ps # 查看正在运行的容器\ndocker image prune # 清理没有被使用的镜像文件。-a/--all 会删除所有未被容器使用的镜像\n```\n\n### 拉取镜像\n\n`docker pull` 命令默认使用的 Registry 是 Docker Hub。当你执行 `docker pull` 命令而没有指定任何 Registry 地址时，Docker 会从 Docker Hub 拉取镜像。\n\n```bash\ndocker search mysql # 查看 MySQL 相关镜像\ndocker pull mysql:8.4 # 拉取 MySQL 镜像\ndocker image ls # 查看所有已下载镜像\n```\n\n### 构建镜像\n\n运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n\n```bash\n# image-name 是镜像名称，1.0.0 是镜像版本号或标签\ndocker build -t image-name:1.0.0 .\n```\n\n需要注意：Dockerfile 的文件名不必须为 Dockerfile，也不一定要放在构建上下文的根目录中。使用 `-f` 或 `--file` 选项，可以指定任何位置的任何文件作为 Dockerfile。当然，一般大家习惯性的会使用默认的文件名 `Dockerfile`，以及会将其置于镜像构建上下文目录中。\n\n### 删除镜像\n\n比如我们要删除已经下载的 MySQL 镜像。\n\n通过 `docker rmi [image]`（等价于 `docker image rm [image]`）删除镜像之前，首先要确保这个镜像没有被容器引用。可以通过标签名称或者镜像 ID 删除，也可以通过前面讲的 `docker ps` 命令查看是否有容器正在使用它。\n\n```shell\n➜  ~ docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES\nc4cd691d9f80        mysql:5.7           \"docker-entrypoint.s…\"   7 weeks ago         Up 12 days          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql\n```\n\n可以看到 `mysql:5.7` 正在被 ID 为 `c4cd691d9f80` 的容器引用，需要先通过 `docker stop c4cd691d9f80` 或者 `docker stop mysql` 暂停这个容器。\n\n然后查看 MySQL 镜像的 ID：\n\n```shell\n➜  ~ docker images\nREPOSITORY              TAG                 IMAGE ID            CREATED             SIZE\nmysql                   5.7                 f6509bac4980        3 months ago        373MB\n```\n\n通过 `IMAGE ID` 或者 `REPOSITORY:TAG` 即可删除：\n\n```shell\ndocker rmi f6509bac4980 # 或者 docker rmi mysql:5.7\n```\n\n### 镜像推送\n\n`docker push` 命令用于将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n```bash\n# 将镜像推送到私有镜像仓库 Harbor\n# harbor.example.com 是私有镜像仓库的地址，ubuntu 是镜像名称，18.04 是镜像版本标签\ndocker push harbor.example.com/ubuntu:18.04\n```\n\n镜像推送之前，要确保本地已经构建好需要推送的 Docker 镜像。另外，务必先登录到对应的镜像仓库。\n\n## Docker 数据管理\n\n在容器中管理数据主要有两种方式：\n\n1. 数据卷（Volumes）\n2. 挂载主机目录 (Bind mounts)\n\n![Docker 数据管理](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-data-management.png)\n\n数据卷是由 Docker 管理的数据存储区域，有如下这些特点：\n\n- 可以在容器之间共享和重用。\n- 即使容器被删除，数据卷中的数据也不会被自动删除，从而确保数据的持久性。\n- 对数据卷的修改会立马生效。\n- 对数据卷的更新，不会影响镜像。\n\n```bash\n# 创建一个数据卷\ndocker volume create my-vol\n# 查看所有的数据卷\ndocker volume ls\n# 查看数据卷的具体信息\ndocker volume inspect my-vol\n# 删除指定的数据卷\ndocker volume rm my-vol\n```\n\n在用 `docker run` 命令的时候，使用 `--mount` 标记来将一个或多个数据卷挂载到容器里。\n\n还可以通过 `--mount` 标记将宿主机上的文件或目录挂载到容器中，这使得容器可以直接访问宿主机的文件系统。Docker 挂载主机目录的默认权限是读写，用户也可以通过增加 `readonly` 指定为只读。\n\n## Docker Compose\n\n### 什么是 Docker Compose？有什么用？\n\nDocker Compose 是 Docker 官方提供的多容器应用定义和运行工具。通过 Compose，开发者可以使用一个 YAML 文件描述应用依赖的多个服务、网络、端口和数据卷，然后用一条命令启动或停止整组服务。\n\nDocker Compose 是开源项目，地址：<https://github.com/docker/compose>。\n\nDocker Compose 的核心功能：\n\n- **多容器管理**：允许用户在一个 YAML 文件中定义和管理多个容器。\n- **服务编排**：配置容器间的网络和依赖关系。\n- **一键启动与停止**：通过 `docker compose up` 和 `docker compose down` 等命令，可以轻松启动和停止整个应用。\n\nDocker Compose 简化了多容器应用程序的开发、测试和部署过程，提高了开发团队的生产力，同时降低了应用程序的部署复杂度和管理成本。\n\n### Docker Compose 文件基本结构\n\nDocker Compose 文件是 Docker Compose 工具的核心，用于定义和配置多容器 Docker 应用。这个文件通常命名为 `compose.yaml` 或 `docker-compose.yml`，采用 YAML（YAML Ain't Markup Language）格式编写。\n\nDocker Compose 文件基本结构如下：\n\n- **服务（services）：** 定义了应用中的每个容器（服务）。每个服务可以使用不同的镜像、环境设置和依赖关系。\n  - **镜像（image）：** 从指定的镜像中启动容器，可以是存储仓库、标签以及镜像 ID。\n  - **命令（command）：** 可选，覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本，常用于启动应用程序、执行初始化脚本等。\n  - **端口（ports）：** 可选，映射容器和宿主机的端口。\n  - **依赖（depends_on）：** 配置服务之间的启动依赖关系。例如后端服务依赖数据库服务时，可以先启动数据库，再启动后端服务。\n  - **环境变量（environment）：** 可选，设置服务运行所需的环境变量。\n  - **重启（restart）:** 可选，控制容器的重启策略。在容器退出时，根据指定的策略自动重启容器。\n  - **服务卷（volumes）:** 可选，定义服务使用的卷，用于数据持久化或在容器之间共享数据。\n  - **构建（build）：** 指定构建镜像的 Dockerfile 上下文路径，或者使用详细配置对象。\n- **网络（networks）：** 定义了容器间的网络连接。\n- **卷（volumes）：** 用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化。\n\n```yaml\nservices:\n  service-name-1:\n    image: nginx:stable\n    command: [\"nginx\", \"-g\", \"daemon off;\"]\n    environment:\n      TZ: Asia/Shanghai\n    volumes:\n      - web_data:/usr/share/nginx/html\n    networks:\n      - app_net\n    ports:\n      - \"8080:80\"\n    restart: unless-stopped\n    depends_on:\n      - service-name-2\n  service-name-2:\n    image: redis:7\n    networks:\n      - app_net\n\nvolumes:\n  web_data:\n\nnetworks:\n  app_net:\n```\n\n### Docker Compose 常见命令\n\n#### 启动\n\n`docker compose up` 会根据 Compose 文件中定义的服务创建并启动容器，并将它们连接到 Compose 创建的网络中。如果文件没有声明自定义网络，Compose 会自动创建默认网络。\n\n```bash\n# 在当前目录下寻找 compose.yaml 或 docker-compose.yml 文件，并根据其中定义的服务启动应用\ndocker compose up\n# 后台启动\ndocker compose up -d\n# 强制重新创建所有容器，即使它们已经存在\ndocker compose up --force-recreate\n# 重新构建镜像\ndocker compose up --build\n# 指定要启动的服务名称，而不是启动所有服务\n# 可以同时指定多个服务，用空格分隔。\ndocker compose up service-name\n```\n\n另外，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml up\n```\n\n#### 暂停\n\n`docker compose down` 用于停止并移除通过 `docker compose up` 启动的容器和网络。\n\n```bash\n# 在当前目录下寻找 Compose 文件\n# 根据其中定义移除启动的容器和网络\ndocker compose down\n# 停止容器但不移除\ndocker compose stop\n# 停止指定服务\ndocker compose stop service-name\n```\n\n同样地，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml down\n```\n\n#### 查看\n\n`docker compose ps` 用于查看通过 `docker compose up` 启动的所有容器的状态信息。\n\n```bash\n# 查看所有容器的状态信息\ndocker compose ps\n# 只显示服务名称\ndocker compose ps --services\n# 查看指定服务的容器\ndocker compose ps service-name\n```\n\n#### 其他\n\n| 命令                     | 介绍                   |\n| ------------------------ | ---------------------- |\n| `docker compose version` | 查看版本               |\n| `docker compose images`  | 列出所有容器使用的镜像 |\n| `docker compose kill`    | 强制停止服务的容器     |\n| `docker compose exec`    | 在容器中执行命令       |\n| `docker compose logs`    | 查看日志               |\n| `docker compose pause`   | 暂停服务               |\n| `docker compose unpause` | 恢复服务               |\n| `docker compose push`    | 推送服务镜像           |\n| `docker compose start`   | 启动当前停止的服务     |\n| `docker compose stop`    | 停止当前运行的服务     |\n| `docker compose rm`      | 删除已停止的服务容器   |\n| `docker compose top`     | 查看进程               |\n\n## Docker 底层原理\n\n首先，Docker 是基于轻量级虚拟化技术的软件，那什么是虚拟化技术呢？\n\n简单点来说，虚拟化技术可以这样定义：\n\n> 虚拟化技术是一种资源管理技术，是将计算机的各种[实体资源](https://zh.wikipedia.org/wiki/計算機科學)（[CPU](https://zh.wikipedia.org/wiki/CPU)、[内存](https://zh.wikipedia.org/wiki/内存)、[磁盘空间](https://zh.wikipedia.org/wiki/磁盘空间)、[网络适配器](https://zh.wikipedia.org/wiki/網路適配器)等），予以抽象、转换后呈现出来并可供分割、组合为一个或多个电脑配置环境。由此，打破实体结构间的不可切割的障碍，使用户可以比原本的配置更好的方式来应用这些电脑硬件资源。这些资源的新虚拟部分是不受现有资源的架设方式，地域或物理配置所限制。一般所指的虚拟化资源包括计算能力和数据存储。\n\nDocker 技术是基于 LXC（Linux container- Linux 容器）虚拟容器技术的。\n\n> LXC，其名称来自 Linux 软件容器（Linux Containers）的缩写，一种操作系统层虚拟化（Operating system–level virtualization）技术，为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器（Container），内含应用软件本身的代码，以及所需要的操作系统核心和库。通过统一的名字空间和共用 API 来分配不同软件容器的可用硬件资源，创造出应用程序的独立沙箱运行环境，使得 Linux 用户可以容易的创建和管理系统或应用容器。\n\nLXC 技术主要是借助 Linux 内核中提供的 CGroup 功能和 namespace 来实现的，通过 LXC 可以为软件提供一个独立的操作系统运行环境。\n\n**cgroup 和 namespace 介绍：**\n\n- **namespace 是 Linux 内核用来隔离内核资源的方式。** 通过 namespace 可以让一些进程只能看到与自己相关的一部分资源，而另外一些进程也只能看到与它们自己相关的资源，这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。\n\n  （以上关于 namespace 介绍内容来自<https://www.cnblogs.com/sparkdev/p/9365405.html> ，更多关于 namespace 的内容可以查看这篇文章 ）。\n\n- **CGroup 是 Control Groups 的缩写，是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。**\n\n  （以上关于 CGroup 介绍内容来自 <https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html> ，更多关于 CGroup 的内容可以查看这篇文章 ）。\n\n**cgroup 和 namespace 两者对比：**\n\n两者都是将进程进行分组，但是两者的作用还是有本质区别。namespace 是为了隔离进程组之间的资源，而 cgroup 是为了对一组进程进行统一的资源监控和限制。\n\n## 总结\n\n本文主要把 Docker 中的一些常见概念和命令做了详细的阐述。从零到上手实战可以看[Docker 从入门到上手干事](https://javaguide.cn/tools/docker/docker-in-action.html)这篇文章，内容非常详细！\n\n另外，再给大家推荐一本质量非常高的开源书籍[《Docker 从入门到实践》](https://yeasy.gitbook.io/docker_practice/introduction/why) ，这本书的内容非常新，毕竟书籍的内容是开源的，可以随时改进。\n\n![《Docker 从入门到实践》网站首页](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-getting-started-practice-website-homepage.png)\n\n## 参考\n\n- [Docker Compose：从零基础到实战应用的全面指南](https://juejin.cn/post/7306756690727747610)\n- [Linux Namespace 和 Cgroup](https://segmentfault.com/a/1190000009732550)\n- [LXC vs Docker: Why Docker is Better](https://www.upguard.com/articles/docker-vs-lxc \"LXC vs Docker: Why Docker is Better\")\n- [CGroup 介绍、应用实例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)\n\n<!-- @include: @article-footer.snippet.md -->\n","frontmatter":{"title":"Docker 核心概念总结","description":"梳理 Docker 的核心概念与容器/虚拟机差异，掌握镜像、容器与仓库的关系及在交付部署中的实际价值。","category":"开发工具","tag":["Docker"],"head":[["meta",{"name":"keywords","content":"Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署"}]]},"isInternal":false,"tokens":9847,"sizeBytes":30240},{"name":"github-tips.md","path":"docs/tools/git/github-tips.md","rawUrl":"https://raw.githubusercontent.com/Snailclimb/JavaGuide/HEAD/docs/tools/git/github-tips.md","title":"Git Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: GitHub 实用小技巧总结\ndescription: 汇总 GitHub 的高效使用技巧，包括个人主页、项目徽章、代码阅读、GitHub Actions、Explore/Trending 和开源协作提效方法。\ncategory: 开发工具\ntag:\n  - Git\nhead:\n  - - meta\n    - name: keywords\n      content: GitHub 技巧,个人主页,README,统计信息,开源贡献,GitHub Actions,代码阅读\n---\n\nGitHub 不只是代码托管平台。对开发者来说，它同时承担了项目展示、代码阅读、开源协作、自动化构建和个人主页的作用。这篇文章整理一些比较实用的 GitHub 使用技巧。\n\n## 一键生成 GitHub 简历与 GitHub 年报\n\n通过 [https://resume.github.io/](https://resume.github.io/) 这个网站你可以一键生成一个在线的 GitHub 简历。\n\n不过，简历里是否放 GitHub 链接要看账号内容质量。如果账号里有完整项目、持续维护记录、清晰 README 和比较规范的提交历史，GitHub 链接会加分；如果只有空仓库或者临时练习代码，就没必要强行放。生成后的效果如下图所示。\n\n![GitHub简历](https://oss.javaguide.cn/2020-11/image-20201108192205620.png)\n\n通过 <https://www.githubtrends.io/wrapped> 这个网站，你可以生成一份 GitHub 个人年报，这个年报会列举出你在这一年的项目贡献情况、最常使用的编程语言、详细的贡献信息。\n\n![](https://oss.javaguide.cn/github/dootask/image-20211226144607457.png)\n\n## 个性化 GitHub 首页\n\nGitHub 目前支持在个人主页自定义展示一些内容。展示效果如下图所示。\n\n![个性化首页展示效果](https://oss.javaguide.cn/java-guide-blog/image-20210616221212259.png)\n\n想要做到这样非常简单，你只需要创建一个和你的 GitHub 账户同名的仓库，然后自定义`README.md`的内容即可。\n\n展示在你主页的自定义内容就是`README.md`的内容（_不会 Markdown 语法的小伙伴自行面壁 5 分钟_）。\n\n![创建一个和你的GitHub账户同名的仓库](https://oss.javaguide.cn/java-guide-blog/image-20201107110309341.png)\n\n这个也是可以玩出花来的！比如说：通过 [github-readme-stats](https://hellogithub.com/periodical/statistics/click/?target=https://github.com/anuraghazra/github-readme-stats) 这个开源项目，你可以在 README 中展示动态生成的 GitHub 统计信息。展示效果如下图所示。\n\n![通过github-readme-stats动态生成GitHub统计信息 ](https://oss.javaguide.cn/java-guide-blog/image-20210616221312426.png)\n\n关于个性化首页这个就不多提了，感兴趣的小伙伴自行研究一下。\n\n## 自定义项目徽章\n\n你在 GitHub 上看到的项目徽章都是通过 [https://shields.io/](https://shields.io/) 这个网站生成的。我的 JavaGuide 这个项目的徽章如下图所示。\n\n![项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143136559.png)\n\n并且，你不光可以生成静态徽章，shield.io 还可以动态读取你项目的状态并生成对应的徽章。\n\n![自定义项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143502356.png)\n\n生成的描述项目状态的徽章效果如下图所示。\n\n![描述项目状态的徽章](https://oss.javaguide.cn/2020-11/image-20201107143752642.png)\n\n## 自动为项目添加贡献情况图标\n\n通过 repobeats 这个工具可以为 GitHub 项目添加如下图所示的项目贡献基本情况图表。\n\n![](https://oss.javaguide.cn/github/dootask/repobeats.png)\n\n地址：<https://repobeats.axiom.co/> 。\n\n## GitHub 表情\n\n![GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162254582.png)\n\n如果你想要在 GitHub 使用表情的话，可以在这里找找：[www.webfx.com/tools/emoji-cheat-sheet/](https://www.webfx.com/tools/emoji-cheat-sheet/)。\n\n![在线GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162432941.png)\n\n## 高效阅读 GitHub 项目的源代码\n\nGitHub Codespaces 可以提供类似 VS Code 的在线开发环境，适合临时阅读、调试或快速参与开源项目。对于大型项目或需要本地服务依赖的项目，还是建议 clone 到本地，用自己熟悉的 IDE 阅读和调试。\n\n简单介绍几种常用的 GitHub 项目源码阅读方式。\n\n### Chrome 插件 Octotree\n\n这个已经老生常谈了，是我最喜欢的一种方式。使用了 Octotree 之后网页侧边栏会按照树形结构展示项目，为我们带来 IDE 般的阅读源代码的感受。\n\n![Chrome插件Octotree](https://oss.javaguide.cn/2020-11/image-20201107144944798.png)\n\n### Sourcegraph\n\n不想将项目 clone 到本地时，也可以使用 Sourcegraph 这类代码搜索和阅读工具。Sourcegraph 支持跨仓库代码搜索、引用跳转等功能，阅读大型项目时比较有帮助。\n\n当你下载了这个插件之后，你的项目主页会多出一个小图标如下图所示。点击这个小图标即可在线阅读项目源代码。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107145749659.png)\n\n使用 Sourcegraph 阅读代码的效果类似下面这样，同样是树形结构展示代码，还支持类之间的跳转。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107150307314.png)\n\n### 克隆项目到本地\n\n先把项目克隆到本地，然后使用自己喜欢的 IDE 来阅读。想深入理解一个项目，首选这种方式。\n\n```bash\ngit clone https://github.com/Snailclimb/JavaGuide.git\n```\n\n## 扩展 GitHub 的功能\n\n**Enhanced GitHub** 可以让你的 GitHub 更好用。这个浏览器插件可以展示仓库大小、文件大小，并支持快速下载单个文件。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107160817672.png)\n\n## 自动为 Markdown 文件生成目录\n\n如果你想为 Markdown 文件生成目录，通过 VS Code 的 **Markdown Preview Enhanced** 这类插件就可以了。\n\n生成的目录效果如下图所示。你直接点击目录中的链接即可跳转到文章对应的位置，可以优化阅读体验。\n\n![](<https://oss.javaguide.cn/2020-11/iShot2020-11-07%2016.14.14%20(1).png>)\n\n不过，目前 GitHub 已经会为 Markdown 文件自动生成目录，只是需要通过页面上的目录按钮展开。\n\n![](https://oss.javaguide.cn/github/cosy/image-20211227093215005.png)\n\n## 善用 GitHub Explore\n\nGitHub 自带的 Explore 是一个非常强大且好用的功能，适合用来发现项目、主题和技术趋势。\n\n简单来说，GitHub Explore 可以提供下面这些服务：\n\n1. 可以根据你的个人兴趣为你推荐项目；\n2. GitHub Topics 按照类别/话题将一些项目进行了分类汇总。比如 [Data visualization](https://github.com/topics/data-visualization) 汇总了数据可视化相关的一些开源项目，[Awesome Lists](https://github.com/topics/awesome) 汇总了 Awesome 系列的仓库；\n3. 通过 GitHub Trending 我们可以看到最近比较热门的一些开源项目，我们可以按照语言类型以及时间维度对项目进行筛选；\n4. GitHub Collections 类似一个收藏夹集合。比如 [Teaching materials for computational social science](https://github.com/collections/teaching-computational-social-science) 这个收藏夹就汇总了计算机课程相关的开源资源，[Learn to Code](https://github.com/collections/learn-to-code) 这个收藏夹就汇总了对你学习编程有帮助的一些仓库；\n5. ……\n\n![](https://oss.javaguide.cn/github/javaguide/github-explore.png)\n\n## GitHub Actions 很强大\n\n你可以简单地将 GitHub Actions 理解为 GitHub 自带的自动化平台。通过 GitHub Actions，你可以直接在 GitHub 上完成构建、测试、部署、依赖扫描、定时任务等工作。\n\n关于 GitHub Actions 的详细介绍，推荐看一下阮一峰老师写的 [GitHub Actions 入门教程](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html) 。\n\nGitHub Actions 有一个官方市场，上面有很多别人提交的 Actions，可以直接复用。\n\n![](https://oss.javaguide.cn/github/javaguide/image-20211227100147433.png)\n\n## 后记\n\nGitHub 技巧不需要一次性全部记住。个人主页、项目徽章、代码阅读、Explore/Trending、GitHub Actions 这几块先用起来，就已经能覆盖大部分日常场景。\n\n另外，这篇文章没有展开讲 GitHub 搜索语法。实际使用中，关键词搜索、语言筛选、Star 数排序、更新时间筛选，往往比死记复杂语法更常用。\n\n<!-- @include: @article-footer.snippet.md -->\n","frontmatter":{"title":"GitHub 实用小技巧总结","description":"汇总 GitHub 的高效使用技巧，包括个人主页、项目徽章、代码阅读、GitHub Actions、Explore/Trending 和开源协作提效方法。","category":"开发工具","tag":["Git"],"head":[["meta",{"name":"keywords","content":"GitHub 技巧,个人主页,README,统计信息,开源贡献,GitHub Actions,代码阅读"}]]},"isInternal":false,"tokens":2692,"sizeBytes":8473},{"name":"gradle-core-concepts.md","path":"docs/tools/gradle/gradle-core-concepts.md","rawUrl":"https://raw.githubusercontent.com/Snailclimb/JavaGuide/HEAD/docs/tools/gradle/gradle-core-concepts.md","title":"Gradle Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Gradle 核心概念总结\ndescription: Gradle 是一个运行在 JVM 上的自动化构建工具，支持灵活的任务编排、依赖管理、插件扩展和多项目构建。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Gradle,Groovy,Gradle Wrapper,Gradle 包装器,Gradle 插件\n---\n\n> 这部分内容主要根据 Gradle 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\nGradle 这部分内容属于可选内容，可以根据自身需求决定是否学习。国内 Java 后端项目里 Maven 仍然更常见，但 Android、部分 Spring Boot 项目以及需要高度定制构建流程的项目中，Gradle 使用得也很多。\n\n## Gradle 介绍\n\nGradle 官方文档是这样介绍的 Gradle 的：\n\n> Gradle is an open-source [build automation](https://en.wikipedia.org/wiki/Build_automation) tool flexible enough to build almost any type of software. Gradle makes few assumptions about what you’re trying to build or how to build it. This makes Gradle particularly flexible.\n>\n> Gradle 是一个开源的构建自动化工具，它足够灵活，可以构建几乎任何类型的软件。Gradle 对你要构建什么或者如何构建它做了很少的假设。这使得 Gradle 特别灵活。\n\n简单来说，Gradle 就是一个运行在 JVM 上的自动化项目构建工具，用来帮助我们完成编译、测试、打包、发布等构建任务。\n\n对于开发者来说，Gradle 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\nGradle 构建脚本可以使用 Groovy DSL 或 Kotlin DSL 编写。现在新项目里 Kotlin DSL 也很常见，它的类型提示和 IDE 支持通常更好。\n\n## Groovy 介绍\n\nGradle 是运行在 JVM 上的一个程序，构建脚本可以使用 Groovy 或 Kotlin 编写。历史上很多 Gradle 示例使用 Groovy DSL，因此先了解一点 Groovy 语法对阅读老项目很有帮助。\n\nGroovy 是运行在 JVM 上的脚本语言，是基于 Java 扩展的动态语言，它的语法和 Java 非常的相似，可以使用 Java 的类库。Groovy 可以用于面向对象编程，也可以用作纯粹的脚本语言。在语言的设计上它吸纳了 Java、Python、Ruby 和 Smalltalk 语言的优秀特性，比如动态类型转换、闭包和元编程支持。\n\n我们可以用学习 Java 的方式去学习 Groovy ，学习成本相对来说还是比较低的，即使开发过程中忘记 Groovy 语法，也可以用 Java 语法继续编码。\n\n基于 JVM 的语言有很多种，比如 Groovy、Kotlin、Java、Scala，它们最终都会编译生成 Java 字节码文件并在 JVM 上运行。\n\n## Gradle 优势\n\nGradle 是新一代的构建系统，具有高效和灵活等诸多优势，广泛用于 Java 开发。不仅 Android 将其作为官方构建系统, 越来越多的 Java 项目比如 Spring Boot 也慢慢迁移到 Gradle。\n\n- 在灵活性上，Gradle 支持基于 Groovy 语言编写脚本，侧重于构建过程的灵活性，适合于构建复杂度较高的项目，可以完成非常复杂的构建。\n- 在粒度性上，Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的，在我们掌握了这一整套打包流程后，我们就可以通过去修改它的 Task 去动态改变其执行流程。\n- 在扩展性上，Gradle 支持插件机制，所以我们可以复用这些插件，就如同复用库一样简单方便。\n\n## Gradle Wrapper 介绍\n\nGradle 官方文档是这样介绍的 Gradle Wrapper 的：\n\n> The recommended way to execute any Gradle build is with the help of the Gradle Wrapper (in short just “Wrapper”). The Wrapper is a script that invokes a declared version of Gradle, downloading it beforehand if necessary. As a result, developers can get up and running with a Gradle project quickly without having to follow manual installation processes saving your company time and money.\n>\n> 执行 Gradle 构建的推荐方法是借助 Gradle Wrapper(简而言之就是“Wrapper”)。Wrapper 它是一个脚本，调用了已经声明的 Gradle 版本，如果需要的话，可以预先下载它。因此，开发人员可以快速启动并运行 Gradle 项目，而不必遵循手动安装过程，从而为公司节省时间和金钱。\n\n我们可以称 Gradle Wrapper 为 Gradle 包装器，它将 Gradle 再次包装，让所有的 Gradle 构建方法在 Gradle 包装器的帮助下运行。\n\nGradle Wrapper 的工作流程图如下（图源[Gradle Wrapper 官方文档介绍](https://docs.gradle.org/current/userguide/gradle_wrapper.html)）：\n\n![包装器工作流程](https://oss.javaguide.cn/github/javaguide/csdn/efa7a0006b04051e2b84cd116c6ccdfc.png)\n\n整个流程主要分为下面 3 步：\n\n1. 首先当我们刚创建的时候，如果指定的版本没有被下载，就先会去 Gradle 的服务器中下载对应版本的压缩包；\n2. 下载完成后需要先进行解压缩并且执行批处理文件；\n3. 后续项目每次构建都会重用这个解压过的 Gradle 版本。\n\nGradle Wrapper 会给我们带来下面这些好处：\n\n1. 在给定的 Gradle 版本上标准化项目，从而实现更可靠和健壮的构建。\n2. 可以让我们的电脑中不安装 Gradle 环境也可以运行 Gradle 项目。\n3. 为不同的用户和执行环境（例如 IDE 或持续集成服务器）提供新的 Gradle 版本就像更改 Wrapper 定义一样简单。\n\n### 生成 Gradle Wrapper\n\n如果想要首次生成 Gradle Wrapper，需要本地先有可用的 Gradle。Gradle 中已经内置了 Wrapper Task，在项目根目录执行 `gradle wrapper` 命令即可生成 Gradle Wrapper。\n\n执行命令 `gradle wrapper` 命令时可以指定一些参数来控制 wrapper 的生成。具体有如下两个配置参数：\n\n- `--gradle-version`：用于指定使用的 Gradle 版本。\n- `--gradle-distribution-url`：用于指定下载 Gradle 发行版的 URL，该值通常类似于 `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`。\n- `--gradle-distribution-sha256-sum`：用于指定发行版压缩包的 SHA-256 校验值，可以降低下载文件被篡改的风险。\n\n执行`gradle wrapper`命令之后，Gradle Wrapper 就生成完成了，项目根目录中生成如下文件：\n\n```plain\n├── gradle\n│   └── wrapper\n│       ├── gradle-wrapper.jar\n│       └── gradle-wrapper.properties\n├── gradlew\n└── gradlew.bat\n```\n\n每个文件的含义如下：\n\n- `gradle-wrapper.jar`：包含了 Gradle 运行时的逻辑代码。\n- `gradle-wrapper.properties`：定义了 Gradle 的版本号和 Gradle 运行时的行为属性。\n- `gradlew`：Linux/macOS 平台下，用于执行 Gradle 命令的包装器脚本。\n- `gradlew.bat`：Windows 平台下，用于执行 Gradle 命令的包装器脚本。\n\n`gradle-wrapper.properties` 文件的内容如下：\n\n```properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.0.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n```\n\n- `distributionBase`：Gradle 解包后存储的父目录。\n- `distributionPath`：`distributionBase`指定目录的子目录。`distributionBase+distributionPath`就是 Gradle 解包后的存放的具体目录。\n- `distributionUrl`：Gradle 指定版本的压缩包下载地址。\n- `zipStoreBase`：Gradle 压缩包下载后存储父目录。\n- `zipStorePath`：`zipStoreBase`指定目录的子目录。`zipStoreBase+zipStorePath`就是 Gradle 压缩包的存放位置。\n\n### 更新 Gradle Wrapper\n\n更新 Gradle Wrapper 有 2 种方式：\n\n1. 直接修改 `distributionUrl` 字段，然后执行 Gradle 命令。\n2. 执行 `./gradlew wrapper --gradle-version [version]`。\n\n下面的命令会将 Gradle 版本升级为 9.5.1。\n\n```shell\n./gradlew wrapper --gradle-version 9.5.1\n```\n\n`gradle-wrapper.properties` 文件中的 `distributionUrl` 属性也发生了改变。\n\n```properties\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.5.1-bin.zip\n```\n\n项目已经生成 Wrapper 后，日常构建优先使用 `./gradlew build`，而不是直接使用本机安装的 `gradle build`。这样可以确保团队成员和 CI 使用同一个 Gradle 版本。\n\n### 自定义 Gradle Wrapper\n\nGradle 已经内置了 Wrapper Task，因此构建 Gradle Wrapper 会生成 Gradle Wrapper 的属性文件，这个属性文件可以通过自定义 Wrapper Task 来设置。比如我们想要修改要下载的 Gradle 版本为 9.5.1，可以这么设置：\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n}\n```\n\n也可以设置 Gradle 发行版压缩包的下载地址和 Gradle 解包后的本地存储路径等配置。\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n    distributionUrl = '../../gradle-9.5.1-bin.zip'\n    distributionPath = 'wrapper/dists'\n}\n```\n\n`distributionUrl` 属性可以设置为本地的项目目录，你也可以设置为网络地址。\n\n## Gradle 任务\n\n在 Gradle 中，任务(Task)是构建执行的单个工作单元。\n\nGradle 的构建是基于 Task 进行的，当你运行项目的时候，实际就是在执行了一系列的 Task 比如编译 Java 源码的 Task、生成 jar 文件的 Task。\n\nTask 的声明方式如下（还有其他几种声明方式）：\n\n```groovy\n// 声明一个名字为 helloTask 的 Task\ntask helloTask{\n     doLast{\n       println \"Hello\"\n     }\n}\n```\n\n创建一个 Task 后，可以根据需要给 Task 添加不同的 Action，上面的“doLast”就是给队列尾增加一个 Action。\n\n```groovy\n //在Action 队列头部添加Action\n Task doFirst(Action<? super Task> action);\n Task doFirst(Closure action);\n\n //在Action 队列尾部添加Action\n Task doLast(Action<? super Task> action);\n Task doLast(Closure action);\n\n //删除所有的Action\n Task deleteAllActions();\n```\n\n一个 Task 中可以有多个 Acton，从队列头部开始向队列尾部执行 Acton。\n\nAction 代表的是一个个函数、方法，每个 Task 都是一堆 Action 按序组成的执行图。\n\nTask 声明依赖的关键字是`dependsOn`，支持声明一个或多个依赖：\n\n```groovy\ntask first {\n doLast {\n        println \"+++++first+++++\"\n    }\n}\ntask second {\n doLast {\n        println \"+++++second+++++\"\n    }\n}\n\n// 指定多个 task 依赖\ntask print(dependsOn :[second,first]) {\n doLast {\n      logger.quiet \"指定多个task依赖\"\n    }\n}\n\n// 指定一个 task 依赖\ntask third(dependsOn : print) {\n doLast {\n      println '+++++third+++++'\n    }\n}\n```\n\n执行 Task 之前，会先执行它的依赖 Task。\n\n我们还可以设置默认 Task，脚本中我们不调用默认 Task ，也会执行。\n\n```groovy\ndefaultTasks 'clean', 'run'\n\ntask clean {\n    doLast {\n        println 'Default Cleaning!'\n    }\n}\n\ntask run {\n    doLast {\n        println 'Default Running!'\n    }\n}\n```\n\nGradle 本身也内置了很多 Task 比如 copy（复制文件）、delete（删除文件）。\n\n```groovy\ntask deleteFile(type: Delete) {\n    delete \"C:\\\\Users\\\\guide\\\\Desktop\\\\test\"\n}\n```\n\n## Gradle 插件\n\nGradle 提供的是一套核心的构建机制，而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑，其本质上和 `.gradle` 文件是相同。你可以将 Gradle 插件看作是封装了一系列 Task 并执行的工具。\n\nGradle 插件主要分为两类：\n\n- 脚本插件：脚本插件就是一个普通的脚本文件，它可以被导入都其他构建脚本中。\n- 二进制插件 / 对象插件：在一个单独的插件模块中定义，其他模块通过 Plugin ID 应用插件。因为这种方式发布和复用更加友好，我们一般接触到的 Gradle 插件都是指二进制插件的形式。\n\n虽然 Gradle 插件与 .gradle 文件本质上没有区别，`.gradle` 文件也能实现 Gradle 插件类似的功能。但是，Gradle 插件使用了独立模块封装构建逻辑，无论是从开发开始使用来看，Gradle 插件的整体体验都更友好。\n\n- **逻辑复用：** 将相同的逻辑提供给多个相似项目复用，减少重复维护类似逻辑开销。当然 .gradle 文件也能做到逻辑复用，但 Gradle 插件的封装性更好；\n- **组件发布：** 可以将插件发布到 Maven 仓库进行管理，其他项目可以使用插件 ID 依赖。当然 .gradle 文件也可以放到一个远程路径被其他项目引用；\n- **构建配置：** Gradle 插件可以声明插件扩展来暴露可配置的属性，提供定制化能力。当然 .gradle 文件也可以做到，但实现会麻烦些。\n\n## Gradle 构建生命周期\n\nGradle 构建的生命周期有三个阶段：**初始化阶段，配置阶段**和**运行阶段**。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/dadbdf59fccd9a2ebf60a2d018541e52.png)\n\n在初始化阶段与配置阶段之间、配置阶段结束之后、执行阶段结束之后，我们都可以加一些定制化的 Hook。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/5c297ccc4dac83229ff3e19caee9d1d2.png)\n\n### 初始化阶段\n\nGradle 支持单项目和多项目构建。在初始化阶段，Gradle 确定哪些项目将参与构建，并为每个项目创建一个 [Project 实例](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html) 。本质上也就是执行 `settings.gradle` 脚本，从而读取整个项目中有多少个 Project 实例。\n\n### 配置阶段\n\n在配置阶段，Gradle 会解析每个工程的 `build.gradle` 文件，创建要执行的任务子集和确定各种任务之间的关系，以供执行阶段按照顺序执行，并对任务的做一些初始化配置。\n\n每个 `build.gradle` 对应一个 Project 对象，配置阶段执行的代码包括 `build.gradle` 中的各种语句、闭包以及 Task 中的配置语句。\n\n在配置阶段结束后，Gradle 会根据 Task 的依赖关系会创建一个 **有向无环图** 。\n\n### 运行阶段\n\n在运行阶段，Gradle 根据配置阶段创建和配置的要执行的任务子集，执行任务。\n\n## 参考\n\n- Gradle 官方文档：<https://docs.gradle.org/current/userguide/userguide.html>\n- Gradle 入门教程：<https://www.imooc.com/wiki/gradlebase>\n- Groovy 快速入门看这篇就够了：<https://cloud.tencent.com/developer/article/1358357>\n- 【Gradle】Gradle 的生命周期详解：<https://juejin.cn/post/7067719629874921508>\n- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)：<https://www.cnblogs.com/pengxurui/p/16281537.html>\n- Gradle 爬坑指南 -- 理解 Plugin、Task、构建流程：<https://juejin.cn/post/6889090530593112077>\n\n<!-- @include: @article-footer.snippet.md -->\n","frontmatter":{"title":"Gradle 核心概念总结","description":"Gradle 是一个运行在 JVM 上的自动化构建工具，支持灵活的任务编排、依赖管理、插件扩展和多项目构建。","category":"开发工具","head":[["meta",{"name":"keywords","content":"Gradle,Groovy,Gradle Wrapper,Gradle 包装器,Gradle 插件"}]]},"isInternal":false,"tokens":4730,"sizeBytes":14962},{"name":"maven-core-concepts.md","path":"docs/tools/maven/maven-core-concepts.md","rawUrl":"https://raw.githubusercontent.com/Snailclimb/JavaGuide/HEAD/docs/tools/maven/maven-core-concepts.md","title":"Maven Skill","category":"anthropic-skill","format":"markdown","content":"---\ntitle: Maven 核心概念总结\ndescription: Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Maven坐标,Maven仓库,Maven生命周期,Maven多模块管理\n---\n\n> 这部分内容主要根据 Maven 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\n## Maven 介绍\n\n[Maven](https://github.com/apache/maven) 官方文档是这样介绍的 Maven 的：\n\n> Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.\n>\n> Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\n\n**什么是 POM？** 每一个 Maven 工程都有一个 `pom.xml` 文件，位于根目录中，包含项目构建生命周期的详细信息。通过 `pom.xml` 文件，我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。\n\n对于开发者来说，Maven 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\n关于 Maven 的基本使用这里就不介绍了，建议看看官网的 5 分钟上手 Maven 的教程：[Maven in 5 Minutes](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) 。\n\n## Maven 坐标\n\n项目中依赖的第三方库以及插件可统称为构件。每一个构件都可以使用 Maven 坐标唯一标识，坐标元素包括：\n\n- **groupId**(必须): 定义了当前 Maven 项目隶属的组织或公司。groupId 一般分为多段，通常情况下，第一段为域，第二段为公司名称。域又分为 org、com、cn 等，其中 org 为非营利组织，com 为商业组织，cn 表示中国。以 apache 开源社区的 tomcat 项目为例，这个项目的 groupId 是 org.apache，它的域是 org（因为 tomcat 是非营利项目），公司名称是 apache，artifactId 是 tomcat。\n- **artifactId**(必须)：定义了当前 Maven 项目的名称，项目的唯一的标识符，对应项目根目录的名称。\n- **version**(必须)：定义了 Maven 项目当前所处版本。\n- **packaging**（可选）：定义了 Maven 项目的打包方式（比如 jar，war...），默认使用 jar。\n- **classifier**(可选)：常用于区分从同一 POM 构建的具有不同内容的构件，可以是任意的字符串，附加在版本号之后。\n\n只要你提供正确的坐标，就能从 Maven 仓库中找到相应的构件供我们使用。\n\n举个例子（引入阿里巴巴开源的 EasyExcel）：\n\n```xml\n<dependency>\n    <groupId>com.alibaba</groupId>\n    <artifactId>easyexcel</artifactId>\n    <version>3.1.1</version>\n</dependency>\n```\n\n你可以在 <https://mvnrepository.com/> 这个网站上找到几乎所有可用的构件，如果你的项目使用的是 Maven 作为构建工具，那这个网站你一定会经常接触。\n\n![Maven 仓库](https://oss.javaguide.cn/github/javaguide/tools/maven/mvnrepository.com.png)\n\n## Maven 依赖\n\n如果使用 Maven 构建产生的构件（例如 Jar 文件）被其他的项目引用，那么该构件就是其他项目的依赖。\n\n### 依赖配置\n\n**配置信息示例**：\n\n```xml\n<project>\n    <dependencies>\n        <dependency>\n            <groupId></groupId>\n            <artifactId></artifactId>\n            <version></version>\n            <type>...</type>\n            <scope>...</scope>\n            <optional>...</optional>\n            <exclusions>\n                <exclusion>\n                  <groupId>...</groupId>\n                  <artifactId>...</artifactId>\n                </exclusion>\n          </exclusions>\n        </dependency>\n      </dependencies>\n</project>\n```\n\n**配置说明**：\n\n- dependencies：一个 pom.xml 文件中只能存在一个这样的标签，是用来管理依赖的总标签。\n- dependency：包含在 dependencies 标签中，可以有多个，每一个表示项目的一个依赖。\n- groupId,artifactId,version(必要)：依赖的基本坐标，对于任何一个依赖来说，基本坐标是最重要的，Maven 根据坐标才能找到需要的依赖。我们在上面解释过这些元素的具体意思，这里就不重复提了。\n- type(可选)：依赖的类型，对应于项目坐标定义的 packaging。大部分情况下，该元素不必声明，其默认值是 jar。\n- scope(可选)：依赖的范围，默认值是 compile。\n- optional(可选)：标记依赖是否可选\n- exclusions(可选)：用来排除传递性依赖,例如 jar 包冲突\n\n### 依赖范围\n\n**classpath** 用于指定 `.class` 文件存放的位置，类加载器会从该路径中加载所需的 `.class` 文件到内存中。\n\nMaven 在编译、执行测试、实际运行有着三套不同的 classpath：\n\n- **编译 classpath**：编译主代码有效\n- **测试 classpath**：编译、运行测试代码有效\n- **运行 classpath**：项目运行时有效\n\nMaven 的依赖范围如下：\n\n- **compile**：编译依赖范围（默认），使用此依赖范围对于编译、测试、运行三种都有效，即在编译、测试和运行的时候都要使用该依赖 Jar 包。\n- **test**：测试依赖范围，从字面意思就可以知道此依赖范围只能用于测试，而在编译和运行项目时无法使用此类依赖，典型的是 JUnit，它只用于编译测试代码和运行测试代码的时候才需要。\n- **provided**：此依赖范围，对于编译和测试有效，而对运行时无效。比如 `servlet-api.jar` 在 Tomcat 中已经提供了，我们只需要的是编译期提供而已。\n- **runtime**：运行时依赖范围，对于测试和运行有效，但是在编译主代码时无效，典型的就是 JDBC 驱动实现。\n- **system**：系统依赖范围，使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径，不依赖 Maven 仓库解析，所以可能会造成建构的不可移植。\n\n### 传递依赖性\n\n### 依赖冲突\n\n**1、对于 Maven 而言，同一个 groupId 同一个 artifactId 下，只能使用一个 version。**\n\n```xml\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.48</version>\n</dependency>\n<!-- 只会使用 1.0.49 这个版本的依赖 -->\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.49</version>\n</dependency>\n```\n\n若相同类型但版本不同的依赖存在于同一个 pom 文件，只会引入后一个声明的依赖。\n\n**2、项目的两个依赖同时引入了某个依赖。**\n\n举个例子，项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0)\n依赖链路二：A -> D -> X(2.0)\n```\n\n这两条依赖路径上有两个版本的 X，为了避免依赖重复，Maven 只会选择其中的一个进行解析。\n\n**哪个版本的 X 会被 Maven 解析使用呢?**\n\nMaven 在遇到这种问题的时候，会遵循 **路径最短优先** 和 **声明顺序优先** 两大原则。解决这个问题的过程也被称为 **Maven 依赖调解** 。\n\n**路径最短优先**\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0) // dist = 3\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n依赖链路二的路径最短，因此，X(2.0)会被解析使用。\n\n不过，你也可以发现。路径最短优先原则并不是通用的，像下面这种路径长度相等的情况就不能单单通过其解决了：\n\n```plain\n依赖链路一：A -> B -> X(1.0) // dist = 2\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n因此，Maven 又定义了声明顺序优先原则。\n\n依赖调解第一原则不能解决所有问题，比如这样的依赖关系：A->B->Y(1.0)、A-> C->Y(2.0)，Y(1.0)和 Y(2.0)的依赖路径长度是一样的，都为 2。Maven 定义了依赖调解的第二原则：\n\n**声明顺序优先**\n\n在依赖路径长度相等的前提下，在 `pom.xml` 中依赖声明的顺序决定了谁会被解析使用，顺序最前的那个依赖优胜。该例中，如果 B 的依赖声明在 D 之前，那么 X (1.0)就会被解析使用。\n\n```xml\n<!-- A pom.xml -->\n<dependencies>\n    ...\n    dependency B\n    ...\n    dependency D\n</dependencies>\n```\n\n### 排除依赖\n\n单纯依赖 Maven 来进行依赖调解，在很多情况下是不适用的，需要我们手动排除依赖。\n\n举个例子，当前项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n根据路径最短优先原则，X(1.0) 会被解析使用，也就是说实际用的是 1.0 版本的 X。\n\n但是！！！这会一些问题：如果 C 依赖用到了 1.5 版本的 X 中才有的一个类，运行项目就会报`NoClassDefFoundError`错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法，运行项目就会报`NoSuchMethodError`错误。\n\n现在知道为什么你的 Maven 项目总是会报`NoClassDefFoundError`和`NoSuchMethodError`错误了吧？\n\n**如何解决呢？** 我们可以通过`exclusion`标签手动将 X(1.0) 给排除。\n\n```xml\n<dependency>\n    ......\n    <exclusions>\n      <exclusion>\n        <artifactId>x</artifactId>\n        <groupId>org.apache.x</groupId>\n      </exclusion>\n    </exclusions>\n</dependency>\n```\n\n一般我们在解决依赖冲突的时候，都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。\n\n如果高版本修改了低版本的一些类或者方法的话，这个时候就不能直接保留高版本了，而是应该考虑优化上层依赖，比如升级上层依赖的版本。\n\n还是上面的例子：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n我们保留了 1.5 版本的 X，但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候，我们可以考虑升级 D 的版本到一个 X 兼容的版本。\n\n## Maven 仓库\n\n在 Maven 世界中，任何一个依赖、插件或者项目构建的输出，都可以称为 **构件** 。\n\n坐标和依赖是构件在 Maven 世界中的逻辑表示方式，构件的物理表示方式是文件，Maven 通过仓库来统一管理这些文件。 任何一个构件都有一组坐标唯一标识。有了仓库之后，无需手动引入构件，我们直接给定构件的坐标即可在 Maven 仓库中找到该构件。\n\nMaven 仓库分为：\n\n- **本地仓库**：运行 Maven 的计算机上的一个目录，它缓存远程下载的构件并包含尚未发布的临时构件。`settings.xml` 文件中可以看到 Maven 的本地仓库路径配置，默认本地仓库路径是在 `${user.home}/.m2/repository`。\n- **远程仓库**：官方或者其他组织维护的 Maven 仓库。\n\nMaven 远程仓库可以分为：\n\n- **中央仓库**：这个仓库是由 Maven 社区来维护的，里面存放了绝大多数开源软件的包，并且是作为 Maven 的默认配置，不需要开发者额外配置。另外为了方便查询，还提供了一个[查询地址](https://central.sonatype.com/)，开发者可以通过这个地址更快的搜索需要构件的坐标。\n- **私服**：私服是一种特殊的远程 Maven 仓库，它是架设在局域网内的仓库服务，私服一般被配置为互联网远程仓库的镜像，供局域网内的 Maven 用户使用。\n- **其他的公共仓库**：有一些公共仓库是为了加速访问（比如阿里云 Maven 镜像仓库）或者部分构件不存在于中央仓库中。\n\nMaven 依赖包寻找顺序：\n\n1. 先去本地仓库找寻，有的话，直接使用。\n2. 本地仓库没有找到的话，会去远程仓库找寻，下载包到本地仓库。\n3. 远程仓库没有找到的话，会报错。\n\n## Maven 生命周期\n\nMaven 的生命周期就是为了对所有的构建过程进行抽象和统一，包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。\n\nMaven 定义了 3 个生命周期`META-INF/plexus/components.xml`：\n\n- `default` 生命周期\n- `clean`生命周期\n- `site`生命周期\n\n这些生命周期是相互独立的，每个生命周期包含多个阶段(phase)。并且，这些阶段是有序的，也就是说，后面的阶段依赖于前面的阶段。当执行某个阶段的时候，会先执行它前面的阶段。\n\n执行 Maven 生命周期的命令格式如下：\n\n```bash\nmvn 阶段 [阶段2] ...[阶段n]\n```\n\n### default 生命周期\n\n`default`生命周期是在没有任何关联插件的情况下定义的，是 Maven 的主要生命周期，用于构建应用程序，共包含 23 个阶段。\n\n```xml\n<phases>\n  <!-- 验证项目是否正确，并且所有必要的信息可用于完成构建过程 -->\n  <phase>validate</phase>\n  <!-- 建立初始化状态，例如设置属性 -->\n  <phase>initialize</phase>\n  <!-- 生成要包含在编译阶段的源代码 -->\n  <phase>generate-sources</phase>\n  <!-- 处理源代码 -->\n  <phase>process-sources</phase>\n  <!-- 生成要包含在包中的资源 -->\n  <phase>generate-resources</phase>\n  <!-- 将资源复制并处理到目标目录中，为打包阶段做好准备。 -->\n  <phase>process-resources</phase>\n  <!-- 编译项目的源代码  -->\n  <phase>compile</phase>\n  <!-- 对编译生成的文件进行后处理，例如对 Java 类进行字节码增强/优化 -->\n  <phase>process-classes</phase>\n  <!-- 生成要包含在编译阶段的任何测试源代码 -->\n  <phase>generate-test-sources</phase>\n  <!-- 处理测试源代码 -->\n  <phase>process-test-sources</phase>\n  <!-- 生成要包含在编译阶段的测试源代码 -->\n  <phase>generate-test-resources</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-resources</phase>\n  <!-- 编译测试源代码 -->\n  <phase>test-compile</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-classes</phase>\n  <!-- 使用合适的单元测试框架（JUnit 就是其中之一）运行测试 -->\n  <phase>test</phase>\n  <!-- 在实际打包之前，执行任何的必要的操作为打包做准备 -->\n  <phase>prepare-package</phase>\n  <!-- 获取已编译的代码并将其打包成可分发的格式，例如 JAR、WAR 或 EAR 文件 -->\n  <phase>package</phase>\n  <!-- 在执行集成测试之前执行所需的操作。 例如，设置所需的环境 -->\n  <phase>pre-integration-test</phase>\n  <!-- 处理并在必要时部署软件包到集成测试可以运行的环境 -->\n  <phase>integration-test</phase>\n  <!-- 执行集成测试后执行所需的操作。 例如，清理环境  -->\n  <phase>post-integration-test</phase>\n  <!-- 运行任何检查以验证打的包是否有效并符合质量标准。 -->\n  <phase>verify</phase>\n  <!-- \t将包安装到本地仓库中，可以作为本地其他项目的依赖 -->\n  <phase>install</phase>\n  <!-- 将最终的项目包复制到远程仓库中与其他开发者和项目共享 -->\n  <phase>deploy</phase>\n</phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn test`命令的时候，会执行从 validate 到 test 的所有阶段，这也就解释了为什么执行测试的时候，项目的代码能够自动编译。\n\n### clean 生命周期\n\nclean 生命周期的目的是清理项目，共包含 3 个阶段：\n\n1. pre-clean\n2. clean\n3. post-clean\n\n```xml\n<phases>\n  <!--  执行一些需要在clean之前完成的工作 -->\n  <phase>pre-clean</phase>\n  <!--  移除所有上一次构建生成的文件 -->\n  <phase>clean</phase>\n  <!--  执行一些需要在clean之后立刻完成的工作 -->\n  <phase>post-clean</phase>\n</phases>\n<default-phases>\n  <clean>\n    org.apache.maven.plugins:maven-clean-plugin:2.5:clean\n  </clean>\n</default-phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn clean` 的时候，会执行 clean 生命周期中的 pre-clean 和 clean 阶段。\n\n### site 生命周期\n\nsite 生命周期的目的是建立和发布项目站点，共包含 4 个阶段：\n\n1. pre-site\n2. site\n3. post-site\n4. site-deploy\n\n```xml\n<phases>\n  <!--  执行一些需要在生成站点文档之前完成的工作 -->\n  <phase>pre-site</phase>\n  <!--  生成项目的站点文档作 -->\n  <phase>site</phase>\n  <!--  执行一些需要在生成站点文档之后完成的工作，并且为部署做准备 -->\n  <phase>post-site</phase>\n  <!--  将生成的站点文档部署到特定的服务器上 -->\n  <phase>site-deploy</phase>\n</phases>\n<default-phases>\n  <site>\n    org.apache.maven.plugins:maven-site-plugin:3.3:site\n  </site>\n  <site-deploy>\n    org.apache.maven.plugins:maven-site-plugin:3.3:deploy\n  </site-deploy>\n</default-phases>\n```\n\nMaven 能够基于 `pom.xml` 所包含的信息，自动生成一个友好的站点，方便团队交流和发布项目信息。\n\n## Maven 插件\n\nMaven 本质上是一个插件执行框架，所有的执行过程，都是由一个一个插件独立完成的。像咱们日常使用到的 install、clean、deploy 等命令，其实底层都是一个一个的 Maven 插件。关于 Maven 的核心插件可以参考官方的这篇文档：<https://maven.apache.org/plugins/index.html> 。\n\n本地默认插件路径: `${user.home}/.m2/repository/org/apache/maven/plugins`\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/maven-plugins.png)\n\n除了 Maven 自带的插件之外，还有一些三方提供的插件比如单测覆盖率插件 jacoco-maven-plugin、帮助开发检测代码中不合规范的地方的插件 maven-checkstyle-plugin、分析代码质量的 sonar-maven-plugin。并且，我们还可以自定义插件来满足自己的需求。\n\njacoco-maven-plugin 使用示例：\n\n```xml\n<build>\n  <plugins>\n    <plugin>\n      <groupId>org.jacoco</groupId>\n      <artifactId>jacoco-maven-plugin</artifactId>\n      <version>0.8.8</version>\n      <executions>\n        <execution>\n          <goals>\n            <goal>prepare-agent</goal>\n          </goals>\n        </execution>\n        <execution>\n          <id>generate-code-coverage-report</id>\n          <phase>test</phase>\n          <goals>\n            <goal>report</goal>\n          </goals>\n        </execution>\n      </executions>\n    </plugin>\n  </plugins>\n</build>\n```\n\n你可以将 Maven 插件理解为一组任务的集合，用户可以通过命令行直接运行指定插件的任务，也可以将插件任务挂载到构建生命周期，随着生命周期运行。\n\nMaven 插件被分为下面两种类型：\n\n- **Build plugins**：在构建时执行。\n- **Reporting plugins**：在网站生成过程中执行。\n\n## Maven 多模块管理\n\n多模块管理简单地来说就是将一个项目分为多个模块，每个模块只负责单一的功能实现。直观的表现就是一个 Maven 项目中不止有一个 `pom.xml` 文件，会在不同的目录中有多个 `pom.xml` 文件，进而实现多模块管理。\n\n多模块管理除了可以更加便于项目开发和管理，还有如下好处：\n\n1. 降低代码之间的耦合性（从类级别的耦合提升到 jar 包级别的耦合）；\n2. 减少重复，提升复用性；\n3. 每个模块都可以是自解释的（通过模块名或者模块文档）；\n4. 模块还规范了代码边界的划分，开发者很容易通过模块确定自己所负责的内容。\n\n多模块管理下，会有一个父模块，其他的都是子模块。父模块通常只有一个 `pom.xml`，没有其他内容。父模块的 `pom.xml` 一般只定义了各个依赖的版本号、包含哪些子模块以及插件有哪些。不过，要注意的是，如果依赖只在某个子项目中使用，则可以在子项目的 pom.xml 中直接引入，防止父 pom 的过于臃肿。\n\n如下图所示，Dubbo 项目就被分成了多个子模块比如 dubbo-common（公共逻辑模块）、dubbo-remoting（远程通讯模块）、dubbo-rpc（远程调用模块）。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/dubbo-maven-multi-module.png)\n\n## 文章推荐\n\n- [安全同学讲 Maven 间接依赖场景的仲裁机制 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/flniMiP-eu3JSBnswfd_Ew)\n- [高效使用 Java 构建工具｜ Maven 篇 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/Wvq7t2FC58jaCh4UFJ6GGQ)\n- [安全同学讲 Maven 重打包的故事 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/xsJkB0onUkakrVH0wejcIg)\n\n## 参考\n\n- 《Maven 实战》\n- Introduction to Repositories - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-repositories.html>\n- Introduction to the Build Lifecycle - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference>\n- Maven 依赖范围：<https://www.mvnbook.com/maven-dependency.html>\n- 解决 maven 依赖冲突，这篇就够了！：<https://www.cnblogs.com/qdhxhz/p/16363532.html>\n- Multi-Module Project with Maven：<https://www.baeldung.com/maven-multi-module>\n\n<!-- @include: @article-footer.snippet.md -->\n","frontmatter":{"title":"Maven 核心概念总结","description":"Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。","category":"开发工具","head":[["meta",{"name":"keywords","content":"Maven坐标,Maven仓库,Maven生命周期,Maven多模块管理"}]]},"isInternal":false,"tokens":7174,"sizeBytes":21782},{"name":"README.md","path":"docs/tools/docker/README.md","rawUrl":"https://raw.githubusercontent.com/Snailclimb/JavaGuide/HEAD/docs/tools/docker/README.md","title":"docker Documentation","category":"plugin-manifest","format":"markdown","content":"---\ntitle: Docker 专题：容器、镜像、仓库、数据卷、网络与容器化部署\ndescription: Docker 面试与容器化学习路线，涵盖容器、镜像、仓库、Docker 引擎、数据卷、网络、常用命令和容器化部署实践。\ncategory: 开发工具\ntag:\n  - Docker\n  - 容器\n  - 部署\nsitemap:\n  changefreq: weekly\n  priority: 0.85\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发\n---\n\nDocker 是后端开发非常常见的容器化工具，常用于本地快速启动 MySQL、Redis、Kafka 等依赖服务，也常用于测试环境和部署交付。学习 Docker 时，要把核心概念和命令实践结合起来。\n\n## 适合谁看\n\n- 想快速理解 Docker 容器化基础的后端开发者。\n- 需要用 Docker 搭建本地开发环境、测试环境或依赖服务的同学。\n- 准备面试，需要讲清容器、镜像、数据卷、网络和容器与虚拟机区别的读者。\n- 已经会复制 Docker 命令，但不清楚镜像构建、容器生命周期和数据持久化的工程师。\n\n## 学习重点\n\n- 容器解决的是应用运行环境隔离和一致性问题。\n- 镜像是静态模板，容器是镜像运行后的实例，仓库用于分发和复用镜像。\n- Docker 常用命令要围绕镜像管理、容器生命周期、日志查看、端口映射和进入容器来掌握。\n- 数据卷用于数据持久化和宿主机目录挂载，网络用于容器之间和容器与外部服务通信。\n- Docker Compose 用于定义和运行多容器应用，适合本地开发环境和简单服务编排。\n- Docker 不是万能部署方案，生产环境还要结合镜像安全、资源限制、日志、监控和编排能力考虑。\n\n## 建议阅读顺序\n\n1. [Docker 核心概念总结](./docker-intro.md)：先理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。\n2. [Docker 实战](./docker-in-action.md)：通过实际命令练习镜像拉取、容器启动、端口映射、数据卷、日志查看和常见服务部署。\n3. 结合一个 Java 项目练习：把应用依赖的 MySQL、Redis 等服务用 Docker 启动起来，再观察日志、数据目录和端口映射。\n\n## 核心文章\n\n- [Docker 核心概念总结](./docker-intro.md)：讲解容器、镜像、仓库、Docker 引擎、Docker 架构、Docker Compose，以及 Docker 和虚拟机的区别。\n- [Docker 实战](./docker-in-action.md)：通过常用命令和实践场景理解镜像管理、容器管理、服务部署、本地环境搭建和常见排查。\n\n## 高频问题\n\n- Docker 主要解决什么问题？\n- 容器和虚拟机有什么区别？\n- 镜像和容器是什么关系？\n- Dockerfile、镜像、容器、仓库分别是什么？\n- 为什么容器删除后数据可能丢失？数据卷解决了什么问题？\n- 端口映射和容器网络分别解决什么问题？\n- 如何查看容器日志、进入容器、停止和删除容器？\n- `docker compose` 适合解决什么问题？和单独执行 `docker run` 有什么区别？\n- Docker 在开发、测试和部署环境里分别有什么价值？\n\n## 相关专题\n\n- [开发工具知识体系](../)\n- [Git 专题](../git/)\n- [Maven 专题](../maven/)\n- [高可用系统设计](../../high-availability/)\n\n<!-- @include: @article-footer.snippet.md -->\n","frontmatter":{"title":"Docker 专题：容器、镜像、仓库、数据卷、网络与容器化部署","description":"Docker 面试与容器化学习路线，涵盖容器、镜像、仓库、Docker 引擎、数据卷、网络、常用命令和容器化部署实践。","category":"开发工具","tag":["Docker","容器","部署"],"sitemap":{"changefreq":"weekly","priority":0.85},"head":[["meta",{"name":"keywords","content":"Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发"}]]},"isInternal":false,"tokens":1258,"sizeBytes":3390}],"systemPromptSnippet":"<yakaai_skills repo=\"Snailclimb/JavaGuide\">\n<!-- File: docs/tools/docker/docker-intro.md (Tokens: ~9847 | Category: anthropic-skill) -->\n---\ntitle: Docker 核心概念总结\ndescription: 梳理 Docker 的核心概念与容器/虚拟机差异，掌握镜像、容器与仓库的关系及在交付部署中的实际价值。\ncategory: 开发工具\ntag:\n  - Docker\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,引擎,隔离,虚拟机对比,部署\n---\n\n本文主要讲 Docker 的核心概念、运行模型和常见使用场景，不展开安装过程。安装、命令练习和本地服务启动可以看后面的 [Docker 实战](./docker-in-action.md)。\n\n## 容器介绍\n\nDocker 是常见的软件容器平台。想要搞懂 Docker，先要理解容器解决的到底是什么问题。\n\n### 什么是容器？\n\n#### 先来看看容器较为官方的解释\n\n**一句话概括容器：容器就是将软件打包成标准化单元，以用于开发、交付和部署。**\n\n- **容器镜像是轻量的、可执行的独立软件包** ，包含软件运行所需的所有内容：代码、运行时环境、系统工具、系统库和设置。\n- **容器化软件适用于基于 Linux 和 Windows 的应用，在任何环境中都能够始终如一地运行。**\n- **容器赋予了软件独立性**，使其免受外在环境差异（例如，开发和预演环境的差异）的影响，从而有助于减少团队间在相同基础设施上运行不同软件时的冲突。\n\n#### 再来看看容器较为通俗的解释\n\n如果需要通俗地描述容器的话，我觉得容器就是一个存放东西的地方，就像书包可以装各种文具、衣柜可以放各种衣服、鞋架可以放各种鞋子一样。我们现在所说的容器存放的东西可能更偏向于应用比如网站、程序甚至是系统环境。\n\n![认识容器](https://oss.javaguide.cn/github/javaguide/tools/docker/container.png)\n\n### 图解物理机、虚拟机与容器\n\n关于虚拟机与容器的对比在后面会详细介绍到，这里只是通过网上的图片加深大家对于物理机、虚拟机与容器这三者的理解(下面的图片来源于网络)。\n\n**物理机：**\n\n![物理机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E7%89%A9%E7%90%86%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**虚拟机：**\n\n![虚拟机](https://oss.javaguide.cn/github/javaguide/tools/docker/%E8%99%9A%E6%8B%9F%E6%9C%BA%E5%9B%BE%E8%A7%A3.jpeg)\n\n**容器：**\n\n![](https://oss.javaguide.cn/javaguide/image-20211110104003678.png)\n\n通过上面这三张抽象图，我们可以大概通过类比概括出：**容器虚拟化的是操作系统而不是硬件，容器之间是共享同一套操作系统资源的。虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统。因此容器的隔离级别会稍低一些。**\n\n### 容器 VS 虚拟机\n\n每当说起容器，我们不得不将其与虚拟机做一个比较。就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n简单来说：**容器和虚拟机具有相似的资源隔离和分配优势，但功能有所不同，因为容器虚拟化的是操作系统，而不是硬件，因此容器更容易移植，效率也更高。**\n\n传统虚拟机技术是虚拟出一套硬件后，在其上运行一个完整操作系统，在该系统上再运行所需应用进程；而容器内的应用进程直接运行于宿主的内核，容器内没有自己的内核，而且也没有进行硬件虚拟。因此容器要比传统虚拟机更为轻便。\n\n![](https://oss.javaguide.cn/javaguide/2e2b95eebf60b6d03f6c1476f4d7c697.png)\n\n**容器和虚拟机的对比**：\n\n![](https://oss.javaguide.cn/javaguide/4ef8691d67eb1eb53217099d0a691eb5.png)\n\n- 容器是一个应用层抽象，用于将代码和依赖资源打包在一起。 多个容器可以在同一台机器上运行，共享操作系统内核，但各自作为独立的进程在用户空间中运行 。与虚拟机相比， **容器占用的空间较少**（容器镜像大小通常只有几十兆），**瞬间就能完成启动** 。\n\n- 虚拟机 (VM) 是一个物理硬件层抽象，用于将一台服务器变成多台服务器。管理程序允许多个 VM 在一台机器上运行。每个 VM 都包含一整套操作系统、一个或多个应用、必要的二进制文件和库资源，因此 **占用大量空间** 。而且 VM **启动也十分缓慢** 。\n\n通过 Docker 官网，我们知道了这么多 Docker 的优势，但是大家也没有必要完全否定虚拟机技术，因为两者有不同的使用场景。**虚拟机更擅长于彻底隔离整个运行环境**。例如，云服务提供商通常采用虚拟机技术隔离不同的用户。而 **Docker 通常用于隔离不同的应用** ，例如前端，后端以及数据库。\n\n就我而言，对于两者无所谓谁会取代谁，而是两者可以和谐共存。\n\n![](https://oss.javaguide.cn/javaguide/056c87751b9dd7b56f4264240fe96d00.png)\n\n## Docker 介绍\n\n### 什么是 Docker？\n\n可以从下面几个角度理解 Docker：\n\n- **Docker 是一个软件容器平台。**\n- **Docker** 使用 Go 语言开发，基于 Linux 内核提供的 cgroups、namespaces，以及 UnionFS 等能力对进程进行封装隔离，属于操作系统层面的虚拟化技术。\n- Docker 能够把应用和运行依赖打包到镜像中，减少开发、测试、部署环境不一致带来的问题。\n- 用户可以方便地创建和使用容器，把自己的应用放入容器。容器还可以进行版本管理、复制、分享、修改，就像管理普通的代码一样。\n\n**Docker 思想**：\n\n- **集装箱**：就像海运中的集装箱一样，Docker 容器包含了应用程序及其所有依赖项，确保在任何环境中都能以相同的方式运行。\n- **标准化**：运输方式、存储方式、API 接口。\n- **隔离**：每个 Docker 容器都在自己的隔离环境中运行，与宿主机和其他容器隔离。\n\n### Docker 容器的特点\n\n- **轻量** : 在一台机器上运行的多个 Docker 容器可以共享这台机器的操作系统内核；它们能够迅速启动，只需占用很少的计算和内存资源。镜像是通过文件系统层进行构造的，并共享一些公共文件。这样就能尽量降低磁盘用量，并能更快地下载镜像。\n- **标准** : Docker 容器基于开放式标准，能够在所有主流 Linux 版本、Microsoft Windows 以及包括 VM、裸机服务器和云在内的任何基础设施上运行。\n- **安全** : Docker 赋予应用的隔离性不仅限于彼此隔离，还独立于底层的基础设施。Docker 默认提供最强的隔离，因此应用出现问题，也只是单个容器的问题，而不会波及到整台机器。\n\n### 为什么要用 Docker ?\n\n- Docker 的镜像提供了除内核外完整的运行时环境，确保了应用运行环境一致性，从而不会再出现 “这段代码在我机器上没问题啊” 这类问题；——一致的运行环境\n- 可以做到秒级、甚至毫秒级的启动时间。大大的节约了开发、测试、部署的时间。——更快速的启动时间\n- 避免公用的服务器，资源会容易受到其他用户的影响。——隔离性\n- 善于处理集中爆发的服务器使用压力；——弹性伸缩，快速扩展\n- 可以很轻易的将在一个平台上运行的应用，迁移到另一个平台上，而不用担心运行环境的变化导致应用无法正常运行的情况。——迁移方便\n- 使用 Docker 可以通过定制应用镜像来实现持续集成、持续交付、部署。——持续交付和部署\n\n---\n\n## Docker 基本概念\n\nDocker 中有非常重要的三个基本概念：镜像（Image）、容器（Container）和仓库（Repository）。\n\n理解了这三个概念，就理解了 Docker 的整个生命周期。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-run.jpeg)\n\n### 镜像（Image）：一个特殊的文件系统\n\n**操作系统分为内核和用户空间**。对于 Linux 而言，内核启动后，会挂载 root 文件系统为其提供用户空间支持。而 Docker 镜像（Image），就相当于是一个 root 文件系统。\n\n**Docker 镜像是一个特殊的文件系统，除了提供容器运行时所需的程序、库、资源、配置等文件外，还包含了一些为运行时准备的一些配置参数（如匿名卷、环境变量、用户等）。** 镜像不包含任何动态数据，其内容在构建之后也不会被改变。\n\nDocker 设计时，就充分利用 **Union FS** 的技术，将其设计为**分层存储的架构** 。镜像实际是由多层文件系统联合组成。\n\n**镜像构建时，会一层层构建，前一层是后一层的基础。每一层构建完就不会再发生改变，后一层上的任何改变只发生在自己这一层。** 比如，删除前一层文件的操作，实际不是真的删除前一层的文件，而是仅在当前层标记为该文件已删除。在最终容器运行的时候，虽然不会看到这个文件，但是实际上该文件会一直跟随镜像。因此，在构建镜像的时候，需要额外小心，每一层尽量只包含该层需要添加的东西，任何额外的东西应该在该层构建结束前清理掉。\n\n分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层，然后进一步添加新的层，以定制自己所需的内容，构建新的镜像。\n\n### 容器（Container）：镜像运行时的实体\n\n镜像（Image）和容器（Container）的关系，就像是面向对象程序设计中的 类 和 实例 一样，镜像是静态的定义，**容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等** 。\n\n**容器的实质是进程，但与直接在宿主执行的进程不同，容器进程运行于属于自己的独立的 命名空间。前面讲过镜像使用的是分层存储，容器也是如此。**\n\n**容器存储层的生存周期和容器一样，容器消亡时，容器存储层也随之消亡。因此，任何保存于容器存储层的信息都会随容器删除而丢失。**\n\n按照 Docker 最佳实践的要求，**容器不应该向其存储层内写入业务数据**，容器存储层要尽量保持无状态。**需要持久化的文件写入，应该使用数据卷（Volume）或者绑定宿主目录**，这类读写会绕过容器存储层，直接落到宿主机或网络存储上，性能和稳定性更好。数据卷的生命周期独立于容器，容器删除后，数据卷不会自动删除。\n\n### 仓库（Repository）：集中存放镜像文件的地方\n\n镜像构建完成后，可以很容易的在当前宿主上运行，但是， **如果需要在其它服务器上使用这个镜像，我们就需要一个集中的存储、分发镜像的服务，Docker Registry 就是这样的服务。**\n\n一个 Docker Registry 中可以包含多个仓库（Repository）；每个仓库可以包含多个标签（Tag）；每个标签对应一个镜像。所以说：**镜像仓库是 Docker 用来集中存放镜像文件的地方类似于我们之前常用的代码仓库。**\n\n通常，**一个仓库会包含同一个软件不同版本的镜像**，而**标签就常用于对应这个软件的各个版本**。我们可以通过 `<仓库名>:<标签>` 的格式指定具体镜像。如果不给出标签，将以 `latest` 作为默认标签。不过在生产环境中不建议依赖 `latest`，最好明确指定版本标签，便于回滚和排查问题。\n\n**这里补充一下 Docker Registry 公开服务和私有 Docker Registry 的概念：**\n\n**Docker Registry 公开服务** 是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像，并可能提供收费服务供用户管理私有镜像。\n\n最常使用的 Registry 公开服务是官方的 **Docker Hub** ，这也是默认的 Registry，并拥有大量的高质量的官方镜像，网址为：[https://hub.docker.com/](https://hub.docker.com/ \"https://hub.docker.com/\") 。官方是这样介绍 Docker Hub 的：\n\n> Docker Hub 是 Docker 官方提供的一项服务，用于与您的团队查找和共享容器镜像。\n\n比如我们想要搜索自己想要的镜像：\n\n![利用 Docker Hub 搜索镜像](https://oss.javaguide.cn/github/javaguide/tools/docker/Screen%20Shot%202019-11-04%20at%208.21.39%20PM.png)\n\n在 Docker Hub 的搜索结果中，有几项关键的信息有助于我们选择合适的镜像：\n\n- **OFFICIAL Image**：代表镜像为 Docker 官方提供和维护，相对来说稳定性和安全性较高。\n- **Stars**：和点赞差不多的意思，类似 GitHub 的 Star。\n- **Downloads**：代表镜像被拉取的次数，基本上能够表示镜像被使用的频度。\n\n当然，除了直接通过 Docker Hub 网站搜索镜像这种方式外，我们还可以通过 `docker search` 这个命令搜索 Docker Hub 中的镜像，搜索的结果是一致的。\n\n```bash\n➜  ~ docker search mysql\nNAME                              DESCRIPTION                                     STARS               OFFICIAL            AUTOMATED\nmysql                             MySQL is a widely used, open-source relation…   8763                [OK]\nmariadb                           MariaDB is a community-developed fork of MyS…   3073                [OK]\nmysql/mysql-server                Optimized MySQL Server Docker images. Create…   650                                     [OK]\n```\n\n在国内访问 **Docker Hub** 可能会比较慢，企业项目通常会结合公司内部镜像仓库或云厂商镜像仓库来做镜像缓存和分发。\n\n除了使用公开服务外，用户还可以在 **本地搭建私有 Docker Registry** 。Docker 官方提供了 Docker Registry 镜像，可以直接使用做为私有 Registry 服务。开源的 Docker Registry 镜像只提供了 Docker Registry API 的服务端实现，足以支持 Docker 命令，不影响使用。但不包含图形界面，以及镜像维护、用户管理、访问控制等高级功能。\n\n### Image、Container 和 Repository 的关系\n\n下面这一张图很形象地展示了 Image、Container、Repository 和 Registry/Hub 这四者的关系：\n\n![Docker 架构](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-regitstry.png)\n\n- Dockerfile 是一个文本文件，包含了一系列的指令和参数，用于定义如何构建一个 Docker 镜像。运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n- `docker pull` 命令可以从指定的 Registry/Hub 下载一个镜像到本地，默认使用 Docker Hub。\n- `docker run` 命令可以从本地镜像创建一个新的容器并启动它。如果本地没有镜像，Docker 会先尝试从 Registry/Hub 拉取镜像。\n- `docker push` 命令可以将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n上面涉及到了一些 Docker 基本命令，后面的实战文章会详细介绍。\n\n### Build Ship and Run\n\nDocker 的概念基本上已经讲完，我们再来谈谈：Build, Ship, and Run。\n\n如果你搜索 Docker 官网，会发现如下的字样：**“Docker - Build, Ship, and Run Any App, Anywhere”**。那么 Build, Ship, and Run 到底是在干什么呢？\n\n![](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-build-ship-run.jpg)\n\n- **Build（构建镜像）**：镜像就像是集装箱包括文件以及运行环境等等资源。\n- **Ship（运输镜像）**：主机和仓库间运输，这里的仓库就像是超级码头一样。\n- **Run （运行镜像）**：运行的镜像就是一个容器，容器就是运行程序的地方。\n\nDocker 运行过程也就是去仓库把镜像拉到本地，然后用一条命令把镜像运行起来变成容器。所以，我们也常常将 Docker 称为码头工人或码头装卸工，这和 Docker 的中文翻译搬运工人如出一辙。\n\n## Docker 常见命令\n\n### 基本命令\n\n```bash\ndocker version # 查看 Docker 版本\ndocker images # 查看所有已下载镜像，等价于：docker image ls 命令\ndocker container ls # 查看所有容器\ndocker ps # 查看正在运行的容器\ndocker image prune # 清理没有被使用的镜像文件。-a/--all 会删除所有未被容器使用的镜像\n```\n\n### 拉取镜像\n\n`docker pull` 命令默认使用的 Registry 是 Docker Hub。当你执行 `docker pull` 命令而没有指定任何 Registry 地址时，Docker 会从 Docker Hub 拉取镜像。\n\n```bash\ndocker search mysql # 查看 MySQL 相关镜像\ndocker pull mysql:8.4 # 拉取 MySQL 镜像\ndocker image ls # 查看所有已下载镜像\n```\n\n### 构建镜像\n\n运行 `docker build`命令并指定一个 Dockerfile 时，Docker 会读取 Dockerfile 中的指令，逐步构建一个新的镜像，并将其保存在本地。\n\n```bash\n# image-name 是镜像名称，1.0.0 是镜像版本号或标签\ndocker build -t image-name:1.0.0 .\n```\n\n需要注意：Dockerfile 的文件名不必须为 Dockerfile，也不一定要放在构建上下文的根目录中。使用 `-f` 或 `--file` 选项，可以指定任何位置的任何文件作为 Dockerfile。当然，一般大家习惯性的会使用默认的文件名 `Dockerfile`，以及会将其置于镜像构建上下文目录中。\n\n### 删除镜像\n\n比如我们要删除已经下载的 MySQL 镜像。\n\n通过 `docker rmi [image]`（等价于 `docker image rm [image]`）删除镜像之前，首先要确保这个镜像没有被容器引用。可以通过标签名称或者镜像 ID 删除，也可以通过前面讲的 `docker ps` 命令查看是否有容器正在使用它。\n\n```shell\n➜  ~ docker ps\nCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                               NAMES\nc4cd691d9f80        mysql:5.7           \"docker-entrypoint.s…\"   7 weeks ago         Up 12 days          0.0.0.0:3306->3306/tcp, 33060/tcp   mysql\n```\n\n可以看到 `mysql:5.7` 正在被 ID 为 `c4cd691d9f80` 的容器引用，需要先通过 `docker stop c4cd691d9f80` 或者 `docker stop mysql` 暂停这个容器。\n\n然后查看 MySQL 镜像的 ID：\n\n```shell\n➜  ~ docker images\nREPOSITORY              TAG                 IMAGE ID            CREATED             SIZE\nmysql                   5.7                 f6509bac4980        3 months ago        373MB\n```\n\n通过 `IMAGE ID` 或者 `REPOSITORY:TAG` 即可删除：\n\n```shell\ndocker rmi f6509bac4980 # 或者 docker rmi mysql:5.7\n```\n\n### 镜像推送\n\n`docker push` 命令用于将本地的 Docker 镜像上传到指定的 Registry/Hub。\n\n```bash\n# 将镜像推送到私有镜像仓库 Harbor\n# harbor.example.com 是私有镜像仓库的地址，ubuntu 是镜像名称，18.04 是镜像版本标签\ndocker push harbor.example.com/ubuntu:18.04\n```\n\n镜像推送之前，要确保本地已经构建好需要推送的 Docker 镜像。另外，务必先登录到对应的镜像仓库。\n\n## Docker 数据管理\n\n在容器中管理数据主要有两种方式：\n\n1. 数据卷（Volumes）\n2. 挂载主机目录 (Bind mounts)\n\n![Docker 数据管理](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-data-management.png)\n\n数据卷是由 Docker 管理的数据存储区域，有如下这些特点：\n\n- 可以在容器之间共享和重用。\n- 即使容器被删除，数据卷中的数据也不会被自动删除，从而确保数据的持久性。\n- 对数据卷的修改会立马生效。\n- 对数据卷的更新，不会影响镜像。\n\n```bash\n# 创建一个数据卷\ndocker volume create my-vol\n# 查看所有的数据卷\ndocker volume ls\n# 查看数据卷的具体信息\ndocker volume inspect my-vol\n# 删除指定的数据卷\ndocker volume rm my-vol\n```\n\n在用 `docker run` 命令的时候，使用 `--mount` 标记来将一个或多个数据卷挂载到容器里。\n\n还可以通过 `--mount` 标记将宿主机上的文件或目录挂载到容器中，这使得容器可以直接访问宿主机的文件系统。Docker 挂载主机目录的默认权限是读写，用户也可以通过增加 `readonly` 指定为只读。\n\n## Docker Compose\n\n### 什么是 Docker Compose？有什么用？\n\nDocker Compose 是 Docker 官方提供的多容器应用定义和运行工具。通过 Compose，开发者可以使用一个 YAML 文件描述应用依赖的多个服务、网络、端口和数据卷，然后用一条命令启动或停止整组服务。\n\nDocker Compose 是开源项目，地址：<https://github.com/docker/compose>。\n\nDocker Compose 的核心功能：\n\n- **多容器管理**：允许用户在一个 YAML 文件中定义和管理多个容器。\n- **服务编排**：配置容器间的网络和依赖关系。\n- **一键启动与停止**：通过 `docker compose up` 和 `docker compose down` 等命令，可以轻松启动和停止整个应用。\n\nDocker Compose 简化了多容器应用程序的开发、测试和部署过程，提高了开发团队的生产力，同时降低了应用程序的部署复杂度和管理成本。\n\n### Docker Compose 文件基本结构\n\nDocker Compose 文件是 Docker Compose 工具的核心，用于定义和配置多容器 Docker 应用。这个文件通常命名为 `compose.yaml` 或 `docker-compose.yml`，采用 YAML（YAML Ain't Markup Language）格式编写。\n\nDocker Compose 文件基本结构如下：\n\n- **服务（services）：** 定义了应用中的每个容器（服务）。每个服务可以使用不同的镜像、环境设置和依赖关系。\n  - **镜像（image）：** 从指定的镜像中启动容器，可以是存储仓库、标签以及镜像 ID。\n  - **命令（command）：** 可选，覆盖容器启动后默认执行的命令。在启动服务时运行特定的命令或脚本，常用于启动应用程序、执行初始化脚本等。\n  - **端口（ports）：** 可选，映射容器和宿主机的端口。\n  - **依赖（depends_on）：** 配置服务之间的启动依赖关系。例如后端服务依赖数据库服务时，可以先启动数据库，再启动后端服务。\n  - **环境变量（environment）：** 可选，设置服务运行所需的环境变量。\n  - **重启（restart）:** 可选，控制容器的重启策略。在容器退出时，根据指定的策略自动重启容器。\n  - **服务卷（volumes）:** 可选，定义服务使用的卷，用于数据持久化或在容器之间共享数据。\n  - **构建（build）：** 指定构建镜像的 Dockerfile 上下文路径，或者使用详细配置对象。\n- **网络（networks）：** 定义了容器间的网络连接。\n- **卷（volumes）：** 用于数据持久化和共享的数据卷定义。常用于数据库存储、配置文件、日志等数据的持久化。\n\n```yaml\nservices:\n  service-name-1:\n    image: nginx:stable\n    command: [\"nginx\", \"-g\", \"daemon off;\"]\n    environment:\n      TZ: Asia/Shanghai\n    volumes:\n      - web_data:/usr/share/nginx/html\n    networks:\n      - app_net\n    ports:\n      - \"8080:80\"\n    restart: unless-stopped\n    depends_on:\n      - service-name-2\n  service-name-2:\n    image: redis:7\n    networks:\n      - app_net\n\nvolumes:\n  web_data:\n\nnetworks:\n  app_net:\n```\n\n### Docker Compose 常见命令\n\n#### 启动\n\n`docker compose up` 会根据 Compose 文件中定义的服务创建并启动容器，并将它们连接到 Compose 创建的网络中。如果文件没有声明自定义网络，Compose 会自动创建默认网络。\n\n```bash\n# 在当前目录下寻找 compose.yaml 或 docker-compose.yml 文件，并根据其中定义的服务启动应用\ndocker compose up\n# 后台启动\ndocker compose up -d\n# 强制重新创建所有容器，即使它们已经存在\ndocker compose up --force-recreate\n# 重新构建镜像\ndocker compose up --build\n# 指定要启动的服务名称，而不是启动所有服务\n# 可以同时指定多个服务，用空格分隔。\ndocker compose up service-name\n```\n\n另外，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml up\n```\n\n#### 暂停\n\n`docker compose down` 用于停止并移除通过 `docker compose up` 启动的容器和网络。\n\n```bash\n# 在当前目录下寻找 Compose 文件\n# 根据其中定义移除启动的容器和网络\ndocker compose down\n# 停止容器但不移除\ndocker compose stop\n# 停止指定服务\ndocker compose stop service-name\n```\n\n同样地，如果 Compose 文件名称不是 `compose.yaml` 或 `docker-compose.yml`，可以通过 `-f` 参数指定。\n\n```bash\ndocker compose -f compose.prod.yaml down\n```\n\n#### 查看\n\n`docker compose ps` 用于查看通过 `docker compose up` 启动的所有容器的状态信息。\n\n```bash\n# 查看所有容器的状态信息\ndocker compose ps\n# 只显示服务名称\ndocker compose ps --services\n# 查看指定服务的容器\ndocker compose ps service-name\n```\n\n#### 其他\n\n| 命令                     | 介绍                   |\n| ------------------------ | ---------------------- |\n| `docker compose version` | 查看版本               |\n| `docker compose images`  | 列出所有容器使用的镜像 |\n| `docker compose kill`    | 强制停止服务的容器     |\n| `docker compose exec`    | 在容器中执行命令       |\n| `docker compose logs`    | 查看日志               |\n| `docker compose pause`   | 暂停服务               |\n| `docker compose unpause` | 恢复服务               |\n| `docker compose push`    | 推送服务镜像           |\n| `docker compose start`   | 启动当前停止的服务     |\n| `docker compose stop`    | 停止当前运行的服务     |\n| `docker compose rm`      | 删除已停止的服务容器   |\n| `docker compose top`     | 查看进程               |\n\n## Docker 底层原理\n\n首先，Docker 是基于轻量级虚拟化技术的软件，那什么是虚拟化技术呢？\n\n简单点来说，虚拟化技术可以这样定义：\n\n> 虚拟化技术是一种资源管理技术，是将计算机的各种[实体资源](https://zh.wikipedia.org/wiki/計算機科學)（[CPU](https://zh.wikipedia.org/wiki/CPU)、[内存](https://zh.wikipedia.org/wiki/内存)、[磁盘空间](https://zh.wikipedia.org/wiki/磁盘空间)、[网络适配器](https://zh.wikipedia.org/wiki/網路適配器)等），予以抽象、转换后呈现出来并可供分割、组合为一个或多个电脑配置环境。由此，打破实体结构间的不可切割的障碍，使用户可以比原本的配置更好的方式来应用这些电脑硬件资源。这些资源的新虚拟部分是不受现有资源的架设方式，地域或物理配置所限制。一般所指的虚拟化资源包括计算能力和数据存储。\n\nDocker 技术是基于 LXC（Linux container- Linux 容器）虚拟容器技术的。\n\n> LXC，其名称来自 Linux 软件容器（Linux Containers）的缩写，一种操作系统层虚拟化（Operating system–level virtualization）技术，为 Linux 内核容器功能的一个用户空间接口。它将应用软件系统打包成一个软件容器（Container），内含应用软件本身的代码，以及所需要的操作系统核心和库。通过统一的名字空间和共用 API 来分配不同软件容器的可用硬件资源，创造出应用程序的独立沙箱运行环境，使得 Linux 用户可以容易的创建和管理系统或应用容器。\n\nLXC 技术主要是借助 Linux 内核中提供的 CGroup 功能和 namespace 来实现的，通过 LXC 可以为软件提供一个独立的操作系统运行环境。\n\n**cgroup 和 namespace 介绍：**\n\n- **namespace 是 Linux 内核用来隔离内核资源的方式。** 通过 namespace 可以让一些进程只能看到与自己相关的一部分资源，而另外一些进程也只能看到与它们自己相关的资源，这两拨进程根本就感觉不到对方的存在。具体的实现方式是把一个或多个进程的相关资源指定在同一个 namespace 中。Linux namespaces 是对全局系统资源的一种封装隔离，使得处于不同 namespace 的进程拥有独立的全局系统资源，改变一个 namespace 中的系统资源只会影响当前 namespace 里的进程，对其他 namespace 中的进程没有影响。\n\n  （以上关于 namespace 介绍内容来自<https://www.cnblogs.com/sparkdev/p/9365405.html> ，更多关于 namespace 的内容可以查看这篇文章 ）。\n\n- **CGroup 是 Control Groups 的缩写，是 Linux 内核提供的一种可以限制、记录、隔离进程组 (process groups) 所使用的物理资源 (如 cpu memory i/o 等等) 的机制。**\n\n  （以上关于 CGroup 介绍内容来自 <https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html> ，更多关于 CGroup 的内容可以查看这篇文章 ）。\n\n**cgroup 和 namespace 两者对比：**\n\n两者都是将进程进行分组，但是两者的作用还是有本质区别。namespace 是为了隔离进程组之间的资源，而 cgroup 是为了对一组进程进行统一的资源监控和限制。\n\n## 总结\n\n本文主要把 Docker 中的一些常见概念和命令做了详细的阐述。从零到上手实战可以看[Docker 从入门到上手干事](https://javaguide.cn/tools/docker/docker-in-action.html)这篇文章，内容非常详细！\n\n另外，再给大家推荐一本质量非常高的开源书籍[《Docker 从入门到实践》](https://yeasy.gitbook.io/docker_practice/introduction/why) ，这本书的内容非常新，毕竟书籍的内容是开源的，可以随时改进。\n\n![《Docker 从入门到实践》网站首页](https://oss.javaguide.cn/github/javaguide/tools/docker/docker-getting-started-practice-website-homepage.png)\n\n## 参考\n\n- [Docker Compose：从零基础到实战应用的全面指南](https://juejin.cn/post/7306756690727747610)\n- [Linux Namespace 和 Cgroup](https://segmentfault.com/a/1190000009732550)\n- [LXC vs Docker: Why Docker is Better](https://www.upguard.com/articles/docker-vs-lxc \"LXC vs Docker: Why Docker is Better\")\n- [CGroup 介绍、应用实例及原理描述](https://www.ibm.com/developerworks/cn/linux/1506_cgroup/index.html)\n\n<!-- @include: @article-footer.snippet.md -->\n\n<!-- File: docs/tools/git/github-tips.md (Tokens: ~2692 | Category: anthropic-skill) -->\n---\ntitle: GitHub 实用小技巧总结\ndescription: 汇总 GitHub 的高效使用技巧，包括个人主页、项目徽章、代码阅读、GitHub Actions、Explore/Trending 和开源协作提效方法。\ncategory: 开发工具\ntag:\n  - Git\nhead:\n  - - meta\n    - name: keywords\n      content: GitHub 技巧,个人主页,README,统计信息,开源贡献,GitHub Actions,代码阅读\n---\n\nGitHub 不只是代码托管平台。对开发者来说，它同时承担了项目展示、代码阅读、开源协作、自动化构建和个人主页的作用。这篇文章整理一些比较实用的 GitHub 使用技巧。\n\n## 一键生成 GitHub 简历与 GitHub 年报\n\n通过 [https://resume.github.io/](https://resume.github.io/) 这个网站你可以一键生成一个在线的 GitHub 简历。\n\n不过，简历里是否放 GitHub 链接要看账号内容质量。如果账号里有完整项目、持续维护记录、清晰 README 和比较规范的提交历史，GitHub 链接会加分；如果只有空仓库或者临时练习代码，就没必要强行放。生成后的效果如下图所示。\n\n![GitHub简历](https://oss.javaguide.cn/2020-11/image-20201108192205620.png)\n\n通过 <https://www.githubtrends.io/wrapped> 这个网站，你可以生成一份 GitHub 个人年报，这个年报会列举出你在这一年的项目贡献情况、最常使用的编程语言、详细的贡献信息。\n\n![](https://oss.javaguide.cn/github/dootask/image-20211226144607457.png)\n\n## 个性化 GitHub 首页\n\nGitHub 目前支持在个人主页自定义展示一些内容。展示效果如下图所示。\n\n![个性化首页展示效果](https://oss.javaguide.cn/java-guide-blog/image-20210616221212259.png)\n\n想要做到这样非常简单，你只需要创建一个和你的 GitHub 账户同名的仓库，然后自定义`README.md`的内容即可。\n\n展示在你主页的自定义内容就是`README.md`的内容（_不会 Markdown 语法的小伙伴自行面壁 5 分钟_）。\n\n![创建一个和你的GitHub账户同名的仓库](https://oss.javaguide.cn/java-guide-blog/image-20201107110309341.png)\n\n这个也是可以玩出花来的！比如说：通过 [github-readme-stats](https://hellogithub.com/periodical/statistics/click/?target=https://github.com/anuraghazra/github-readme-stats) 这个开源项目，你可以在 README 中展示动态生成的 GitHub 统计信息。展示效果如下图所示。\n\n![通过github-readme-stats动态生成GitHub统计信息 ](https://oss.javaguide.cn/java-guide-blog/image-20210616221312426.png)\n\n关于个性化首页这个就不多提了，感兴趣的小伙伴自行研究一下。\n\n## 自定义项目徽章\n\n你在 GitHub 上看到的项目徽章都是通过 [https://shields.io/](https://shields.io/) 这个网站生成的。我的 JavaGuide 这个项目的徽章如下图所示。\n\n![项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143136559.png)\n\n并且，你不光可以生成静态徽章，shield.io 还可以动态读取你项目的状态并生成对应的徽章。\n\n![自定义项目徽章](https://oss.javaguide.cn/2020-11/image-20201107143502356.png)\n\n生成的描述项目状态的徽章效果如下图所示。\n\n![描述项目状态的徽章](https://oss.javaguide.cn/2020-11/image-20201107143752642.png)\n\n## 自动为项目添加贡献情况图标\n\n通过 repobeats 这个工具可以为 GitHub 项目添加如下图所示的项目贡献基本情况图表。\n\n![](https://oss.javaguide.cn/github/dootask/repobeats.png)\n\n地址：<https://repobeats.axiom.co/> 。\n\n## GitHub 表情\n\n![GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162254582.png)\n\n如果你想要在 GitHub 使用表情的话，可以在这里找找：[www.webfx.com/tools/emoji-cheat-sheet/](https://www.webfx.com/tools/emoji-cheat-sheet/)。\n\n![在线GitHub表情](https://oss.javaguide.cn/2020-11/image-20201107162432941.png)\n\n## 高效阅读 GitHub 项目的源代码\n\nGitHub Codespaces 可以提供类似 VS Code 的在线开发环境，适合临时阅读、调试或快速参与开源项目。对于大型项目或需要本地服务依赖的项目，还是建议 clone 到本地，用自己熟悉的 IDE 阅读和调试。\n\n简单介绍几种常用的 GitHub 项目源码阅读方式。\n\n### Chrome 插件 Octotree\n\n这个已经老生常谈了，是我最喜欢的一种方式。使用了 Octotree 之后网页侧边栏会按照树形结构展示项目，为我们带来 IDE 般的阅读源代码的感受。\n\n![Chrome插件Octotree](https://oss.javaguide.cn/2020-11/image-20201107144944798.png)\n\n### Sourcegraph\n\n不想将项目 clone 到本地时，也可以使用 Sourcegraph 这类代码搜索和阅读工具。Sourcegraph 支持跨仓库代码搜索、引用跳转等功能，阅读大型项目时比较有帮助。\n\n当你下载了这个插件之后，你的项目主页会多出一个小图标如下图所示。点击这个小图标即可在线阅读项目源代码。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107145749659.png)\n\n使用 Sourcegraph 阅读代码的效果类似下面这样，同样是树形结构展示代码，还支持类之间的跳转。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107150307314.png)\n\n### 克隆项目到本地\n\n先把项目克隆到本地，然后使用自己喜欢的 IDE 来阅读。想深入理解一个项目，首选这种方式。\n\n```bash\ngit clone https://github.com/Snailclimb/JavaGuide.git\n```\n\n## 扩展 GitHub 的功能\n\n**Enhanced GitHub** 可以让你的 GitHub 更好用。这个浏览器插件可以展示仓库大小、文件大小，并支持快速下载单个文件。\n\n![](https://oss.javaguide.cn/2020-11/image-20201107160817672.png)\n\n## 自动为 Markdown 文件生成目录\n\n如果你想为 Markdown 文件生成目录，通过 VS Code 的 **Markdown Preview Enhanced** 这类插件就可以了。\n\n生成的目录效果如下图所示。你直接点击目录中的链接即可跳转到文章对应的位置，可以优化阅读体验。\n\n![](<https://oss.javaguide.cn/2020-11/iShot2020-11-07%2016.14.14%20(1).png>)\n\n不过，目前 GitHub 已经会为 Markdown 文件自动生成目录，只是需要通过页面上的目录按钮展开。\n\n![](https://oss.javaguide.cn/github/cosy/image-20211227093215005.png)\n\n## 善用 GitHub Explore\n\nGitHub 自带的 Explore 是一个非常强大且好用的功能，适合用来发现项目、主题和技术趋势。\n\n简单来说，GitHub Explore 可以提供下面这些服务：\n\n1. 可以根据你的个人兴趣为你推荐项目；\n2. GitHub Topics 按照类别/话题将一些项目进行了分类汇总。比如 [Data visualization](https://github.com/topics/data-visualization) 汇总了数据可视化相关的一些开源项目，[Awesome Lists](https://github.com/topics/awesome) 汇总了 Awesome 系列的仓库；\n3. 通过 GitHub Trending 我们可以看到最近比较热门的一些开源项目，我们可以按照语言类型以及时间维度对项目进行筛选；\n4. GitHub Collections 类似一个收藏夹集合。比如 [Teaching materials for computational social science](https://github.com/collections/teaching-computational-social-science) 这个收藏夹就汇总了计算机课程相关的开源资源，[Learn to Code](https://github.com/collections/learn-to-code) 这个收藏夹就汇总了对你学习编程有帮助的一些仓库；\n5. ……\n\n![](https://oss.javaguide.cn/github/javaguide/github-explore.png)\n\n## GitHub Actions 很强大\n\n你可以简单地将 GitHub Actions 理解为 GitHub 自带的自动化平台。通过 GitHub Actions，你可以直接在 GitHub 上完成构建、测试、部署、依赖扫描、定时任务等工作。\n\n关于 GitHub Actions 的详细介绍，推荐看一下阮一峰老师写的 [GitHub Actions 入门教程](https://www.ruanyifeng.com/blog/2019/09/getting-started-with-github-actions.html) 。\n\nGitHub Actions 有一个官方市场，上面有很多别人提交的 Actions，可以直接复用。\n\n![](https://oss.javaguide.cn/github/javaguide/image-20211227100147433.png)\n\n## 后记\n\nGitHub 技巧不需要一次性全部记住。个人主页、项目徽章、代码阅读、Explore/Trending、GitHub Actions 这几块先用起来，就已经能覆盖大部分日常场景。\n\n另外，这篇文章没有展开讲 GitHub 搜索语法。实际使用中，关键词搜索、语言筛选、Star 数排序、更新时间筛选，往往比死记复杂语法更常用。\n\n<!-- @include: @article-footer.snippet.md -->\n\n<!-- File: docs/tools/gradle/gradle-core-concepts.md (Tokens: ~4730 | Category: anthropic-skill) -->\n---\ntitle: Gradle 核心概念总结\ndescription: Gradle 是一个运行在 JVM 上的自动化构建工具，支持灵活的任务编排、依赖管理、插件扩展和多项目构建。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Gradle,Groovy,Gradle Wrapper,Gradle 包装器,Gradle 插件\n---\n\n> 这部分内容主要根据 Gradle 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\nGradle 这部分内容属于可选内容，可以根据自身需求决定是否学习。国内 Java 后端项目里 Maven 仍然更常见，但 Android、部分 Spring Boot 项目以及需要高度定制构建流程的项目中，Gradle 使用得也很多。\n\n## Gradle 介绍\n\nGradle 官方文档是这样介绍的 Gradle 的：\n\n> Gradle is an open-source [build automation](https://en.wikipedia.org/wiki/Build_automation) tool flexible enough to build almost any type of software. Gradle makes few assumptions about what you’re trying to build or how to build it. This makes Gradle particularly flexible.\n>\n> Gradle 是一个开源的构建自动化工具，它足够灵活，可以构建几乎任何类型的软件。Gradle 对你要构建什么或者如何构建它做了很少的假设。这使得 Gradle 特别灵活。\n\n简单来说，Gradle 就是一个运行在 JVM 上的自动化项目构建工具，用来帮助我们完成编译、测试、打包、发布等构建任务。\n\n对于开发者来说，Gradle 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\nGradle 构建脚本可以使用 Groovy DSL 或 Kotlin DSL 编写。现在新项目里 Kotlin DSL 也很常见，它的类型提示和 IDE 支持通常更好。\n\n## Groovy 介绍\n\nGradle 是运行在 JVM 上的一个程序，构建脚本可以使用 Groovy 或 Kotlin 编写。历史上很多 Gradle 示例使用 Groovy DSL，因此先了解一点 Groovy 语法对阅读老项目很有帮助。\n\nGroovy 是运行在 JVM 上的脚本语言，是基于 Java 扩展的动态语言，它的语法和 Java 非常的相似，可以使用 Java 的类库。Groovy 可以用于面向对象编程，也可以用作纯粹的脚本语言。在语言的设计上它吸纳了 Java、Python、Ruby 和 Smalltalk 语言的优秀特性，比如动态类型转换、闭包和元编程支持。\n\n我们可以用学习 Java 的方式去学习 Groovy ，学习成本相对来说还是比较低的，即使开发过程中忘记 Groovy 语法，也可以用 Java 语法继续编码。\n\n基于 JVM 的语言有很多种，比如 Groovy、Kotlin、Java、Scala，它们最终都会编译生成 Java 字节码文件并在 JVM 上运行。\n\n## Gradle 优势\n\nGradle 是新一代的构建系统，具有高效和灵活等诸多优势，广泛用于 Java 开发。不仅 Android 将其作为官方构建系统, 越来越多的 Java 项目比如 Spring Boot 也慢慢迁移到 Gradle。\n\n- 在灵活性上，Gradle 支持基于 Groovy 语言编写脚本，侧重于构建过程的灵活性，适合于构建复杂度较高的项目，可以完成非常复杂的构建。\n- 在粒度性上，Gradle 构建的粒度细化到了每一个 task 之中。并且它所有的 Task 源码都是开源的，在我们掌握了这一整套打包流程后，我们就可以通过去修改它的 Task 去动态改变其执行流程。\n- 在扩展性上，Gradle 支持插件机制，所以我们可以复用这些插件，就如同复用库一样简单方便。\n\n## Gradle Wrapper 介绍\n\nGradle 官方文档是这样介绍的 Gradle Wrapper 的：\n\n> The recommended way to execute any Gradle build is with the help of the Gradle Wrapper (in short just “Wrapper”). The Wrapper is a script that invokes a declared version of Gradle, downloading it beforehand if necessary. As a result, developers can get up and running with a Gradle project quickly without having to follow manual installation processes saving your company time and money.\n>\n> 执行 Gradle 构建的推荐方法是借助 Gradle Wrapper(简而言之就是“Wrapper”)。Wrapper 它是一个脚本，调用了已经声明的 Gradle 版本，如果需要的话，可以预先下载它。因此，开发人员可以快速启动并运行 Gradle 项目，而不必遵循手动安装过程，从而为公司节省时间和金钱。\n\n我们可以称 Gradle Wrapper 为 Gradle 包装器，它将 Gradle 再次包装，让所有的 Gradle 构建方法在 Gradle 包装器的帮助下运行。\n\nGradle Wrapper 的工作流程图如下（图源[Gradle Wrapper 官方文档介绍](https://docs.gradle.org/current/userguide/gradle_wrapper.html)）：\n\n![包装器工作流程](https://oss.javaguide.cn/github/javaguide/csdn/efa7a0006b04051e2b84cd116c6ccdfc.png)\n\n整个流程主要分为下面 3 步：\n\n1. 首先当我们刚创建的时候，如果指定的版本没有被下载，就先会去 Gradle 的服务器中下载对应版本的压缩包；\n2. 下载完成后需要先进行解压缩并且执行批处理文件；\n3. 后续项目每次构建都会重用这个解压过的 Gradle 版本。\n\nGradle Wrapper 会给我们带来下面这些好处：\n\n1. 在给定的 Gradle 版本上标准化项目，从而实现更可靠和健壮的构建。\n2. 可以让我们的电脑中不安装 Gradle 环境也可以运行 Gradle 项目。\n3. 为不同的用户和执行环境（例如 IDE 或持续集成服务器）提供新的 Gradle 版本就像更改 Wrapper 定义一样简单。\n\n### 生成 Gradle Wrapper\n\n如果想要首次生成 Gradle Wrapper，需要本地先有可用的 Gradle。Gradle 中已经内置了 Wrapper Task，在项目根目录执行 `gradle wrapper` 命令即可生成 Gradle Wrapper。\n\n执行命令 `gradle wrapper` 命令时可以指定一些参数来控制 wrapper 的生成。具体有如下两个配置参数：\n\n- `--gradle-version`：用于指定使用的 Gradle 版本。\n- `--gradle-distribution-url`：用于指定下载 Gradle 发行版的 URL，该值通常类似于 `https://services.gradle.org/distributions/gradle-${gradleVersion}-bin.zip`。\n- `--gradle-distribution-sha256-sum`：用于指定发行版压缩包的 SHA-256 校验值，可以降低下载文件被篡改的风险。\n\n执行`gradle wrapper`命令之后，Gradle Wrapper 就生成完成了，项目根目录中生成如下文件：\n\n```plain\n├── gradle\n│   └── wrapper\n│       ├── gradle-wrapper.jar\n│       └── gradle-wrapper.properties\n├── gradlew\n└── gradlew.bat\n```\n\n每个文件的含义如下：\n\n- `gradle-wrapper.jar`：包含了 Gradle 运行时的逻辑代码。\n- `gradle-wrapper.properties`：定义了 Gradle 的版本号和 Gradle 运行时的行为属性。\n- `gradlew`：Linux/macOS 平台下，用于执行 Gradle 命令的包装器脚本。\n- `gradlew.bat`：Windows 平台下，用于执行 Gradle 命令的包装器脚本。\n\n`gradle-wrapper.properties` 文件的内容如下：\n\n```properties\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-6.0.1-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n```\n\n- `distributionBase`：Gradle 解包后存储的父目录。\n- `distributionPath`：`distributionBase`指定目录的子目录。`distributionBase+distributionPath`就是 Gradle 解包后的存放的具体目录。\n- `distributionUrl`：Gradle 指定版本的压缩包下载地址。\n- `zipStoreBase`：Gradle 压缩包下载后存储父目录。\n- `zipStorePath`：`zipStoreBase`指定目录的子目录。`zipStoreBase+zipStorePath`就是 Gradle 压缩包的存放位置。\n\n### 更新 Gradle Wrapper\n\n更新 Gradle Wrapper 有 2 种方式：\n\n1. 直接修改 `distributionUrl` 字段，然后执行 Gradle 命令。\n2. 执行 `./gradlew wrapper --gradle-version [version]`。\n\n下面的命令会将 Gradle 版本升级为 9.5.1。\n\n```shell\n./gradlew wrapper --gradle-version 9.5.1\n```\n\n`gradle-wrapper.properties` 文件中的 `distributionUrl` 属性也发生了改变。\n\n```properties\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-9.5.1-bin.zip\n```\n\n项目已经生成 Wrapper 后，日常构建优先使用 `./gradlew build`，而不是直接使用本机安装的 `gradle build`。这样可以确保团队成员和 CI 使用同一个 Gradle 版本。\n\n### 自定义 Gradle Wrapper\n\nGradle 已经内置了 Wrapper Task，因此构建 Gradle Wrapper 会生成 Gradle Wrapper 的属性文件，这个属性文件可以通过自定义 Wrapper Task 来设置。比如我们想要修改要下载的 Gradle 版本为 9.5.1，可以这么设置：\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n}\n```\n\n也可以设置 Gradle 发行版压缩包的下载地址和 Gradle 解包后的本地存储路径等配置。\n\n```groovy\ntasks.wrapper {\n    gradleVersion = '9.5.1'\n    distributionUrl = '../../gradle-9.5.1-bin.zip'\n    distributionPath = 'wrapper/dists'\n}\n```\n\n`distributionUrl` 属性可以设置为本地的项目目录，你也可以设置为网络地址。\n\n## Gradle 任务\n\n在 Gradle 中，任务(Task)是构建执行的单个工作单元。\n\nGradle 的构建是基于 Task 进行的，当你运行项目的时候，实际就是在执行了一系列的 Task 比如编译 Java 源码的 Task、生成 jar 文件的 Task。\n\nTask 的声明方式如下（还有其他几种声明方式）：\n\n```groovy\n// 声明一个名字为 helloTask 的 Task\ntask helloTask{\n     doLast{\n       println \"Hello\"\n     }\n}\n```\n\n创建一个 Task 后，可以根据需要给 Task 添加不同的 Action，上面的“doLast”就是给队列尾增加一个 Action。\n\n```groovy\n //在Action 队列头部添加Action\n Task doFirst(Action<? super Task> action);\n Task doFirst(Closure action);\n\n //在Action 队列尾部添加Action\n Task doLast(Action<? super Task> action);\n Task doLast(Closure action);\n\n //删除所有的Action\n Task deleteAllActions();\n```\n\n一个 Task 中可以有多个 Acton，从队列头部开始向队列尾部执行 Acton。\n\nAction 代表的是一个个函数、方法，每个 Task 都是一堆 Action 按序组成的执行图。\n\nTask 声明依赖的关键字是`dependsOn`，支持声明一个或多个依赖：\n\n```groovy\ntask first {\n doLast {\n        println \"+++++first+++++\"\n    }\n}\ntask second {\n doLast {\n        println \"+++++second+++++\"\n    }\n}\n\n// 指定多个 task 依赖\ntask print(dependsOn :[second,first]) {\n doLast {\n      logger.quiet \"指定多个task依赖\"\n    }\n}\n\n// 指定一个 task 依赖\ntask third(dependsOn : print) {\n doLast {\n      println '+++++third+++++'\n    }\n}\n```\n\n执行 Task 之前，会先执行它的依赖 Task。\n\n我们还可以设置默认 Task，脚本中我们不调用默认 Task ，也会执行。\n\n```groovy\ndefaultTasks 'clean', 'run'\n\ntask clean {\n    doLast {\n        println 'Default Cleaning!'\n    }\n}\n\ntask run {\n    doLast {\n        println 'Default Running!'\n    }\n}\n```\n\nGradle 本身也内置了很多 Task 比如 copy（复制文件）、delete（删除文件）。\n\n```groovy\ntask deleteFile(type: Delete) {\n    delete \"C:\\\\Users\\\\guide\\\\Desktop\\\\test\"\n}\n```\n\n## Gradle 插件\n\nGradle 提供的是一套核心的构建机制，而 Gradle 插件则是运行在这套机制上的一些具体构建逻辑，其本质上和 `.gradle` 文件是相同。你可以将 Gradle 插件看作是封装了一系列 Task 并执行的工具。\n\nGradle 插件主要分为两类：\n\n- 脚本插件：脚本插件就是一个普通的脚本文件，它可以被导入都其他构建脚本中。\n- 二进制插件 / 对象插件：在一个单独的插件模块中定义，其他模块通过 Plugin ID 应用插件。因为这种方式发布和复用更加友好，我们一般接触到的 Gradle 插件都是指二进制插件的形式。\n\n虽然 Gradle 插件与 .gradle 文件本质上没有区别，`.gradle` 文件也能实现 Gradle 插件类似的功能。但是，Gradle 插件使用了独立模块封装构建逻辑，无论是从开发开始使用来看，Gradle 插件的整体体验都更友好。\n\n- **逻辑复用：** 将相同的逻辑提供给多个相似项目复用，减少重复维护类似逻辑开销。当然 .gradle 文件也能做到逻辑复用，但 Gradle 插件的封装性更好；\n- **组件发布：** 可以将插件发布到 Maven 仓库进行管理，其他项目可以使用插件 ID 依赖。当然 .gradle 文件也可以放到一个远程路径被其他项目引用；\n- **构建配置：** Gradle 插件可以声明插件扩展来暴露可配置的属性，提供定制化能力。当然 .gradle 文件也可以做到，但实现会麻烦些。\n\n## Gradle 构建生命周期\n\nGradle 构建的生命周期有三个阶段：**初始化阶段，配置阶段**和**运行阶段**。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/dadbdf59fccd9a2ebf60a2d018541e52.png)\n\n在初始化阶段与配置阶段之间、配置阶段结束之后、执行阶段结束之后，我们都可以加一些定制化的 Hook。\n\n![](https://oss.javaguide.cn/github/javaguide/csdn/5c297ccc4dac83229ff3e19caee9d1d2.png)\n\n### 初始化阶段\n\nGradle 支持单项目和多项目构建。在初始化阶段，Gradle 确定哪些项目将参与构建，并为每个项目创建一个 [Project 实例](https://docs.gradle.org/current/dsl/org.gradle.api.Project.html) 。本质上也就是执行 `settings.gradle` 脚本，从而读取整个项目中有多少个 Project 实例。\n\n### 配置阶段\n\n在配置阶段，Gradle 会解析每个工程的 `build.gradle` 文件，创建要执行的任务子集和确定各种任务之间的关系，以供执行阶段按照顺序执行，并对任务的做一些初始化配置。\n\n每个 `build.gradle` 对应一个 Project 对象，配置阶段执行的代码包括 `build.gradle` 中的各种语句、闭包以及 Task 中的配置语句。\n\n在配置阶段结束后，Gradle 会根据 Task 的依赖关系会创建一个 **有向无环图** 。\n\n### 运行阶段\n\n在运行阶段，Gradle 根据配置阶段创建和配置的要执行的任务子集，执行任务。\n\n## 参考\n\n- Gradle 官方文档：<https://docs.gradle.org/current/userguide/userguide.html>\n- Gradle 入门教程：<https://www.imooc.com/wiki/gradlebase>\n- Groovy 快速入门看这篇就够了：<https://cloud.tencent.com/developer/article/1358357>\n- 【Gradle】Gradle 的生命周期详解：<https://juejin.cn/post/7067719629874921508>\n- 手把手带你自定义 Gradle 插件 —— Gradle 系列(2)：<https://www.cnblogs.com/pengxurui/p/16281537.html>\n- Gradle 爬坑指南 -- 理解 Plugin、Task、构建流程：<https://juejin.cn/post/6889090530593112077>\n\n<!-- @include: @article-footer.snippet.md -->\n\n<!-- File: docs/tools/maven/maven-core-concepts.md (Tokens: ~7174 | Category: anthropic-skill) -->\n---\ntitle: Maven 核心概念总结\ndescription: Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\ncategory: 开发工具\nhead:\n  - - meta\n    - name: keywords\n      content: Maven坐标,Maven仓库,Maven生命周期,Maven多模块管理\n---\n\n> 这部分内容主要根据 Maven 官方文档整理，做了对应的删减，主要保留比较重要的部分，不涉及实战，主要是一些重要概念的介绍。\n\n## Maven 介绍\n\n[Maven](https://github.com/apache/maven) 官方文档是这样介绍的 Maven 的：\n\n> Apache Maven is a software project management and comprehension tool. Based on the concept of a project object model (POM), Maven can manage a project's build, reporting and documentation from a central piece of information.\n>\n> Apache Maven 的本质是一个软件项目管理和理解工具。基于项目对象模型 (Project Object Model，POM) 的概念，Maven 可以从一条中心信息管理项目的构建、报告和文档。\n\n**什么是 POM？** 每一个 Maven 工程都有一个 `pom.xml` 文件，位于根目录中，包含项目构建生命周期的详细信息。通过 `pom.xml` 文件，我们可以定义项目的坐标、项目依赖、项目信息、插件信息等等配置。\n\n对于开发者来说，Maven 的主要作用主要有 3 个：\n\n1. **项目构建**：提供标准的、跨平台的自动化项目构建方式。\n2. **依赖管理**：方便快捷的管理项目依赖的资源（jar 包），避免资源间的版本冲突问题。\n3. **统一开发结构**：提供标准的、统一的项目结构。\n\n关于 Maven 的基本使用这里就不介绍了，建议看看官网的 5 分钟上手 Maven 的教程：[Maven in 5 Minutes](https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html) 。\n\n## Maven 坐标\n\n项目中依赖的第三方库以及插件可统称为构件。每一个构件都可以使用 Maven 坐标唯一标识，坐标元素包括：\n\n- **groupId**(必须): 定义了当前 Maven 项目隶属的组织或公司。groupId 一般分为多段，通常情况下，第一段为域，第二段为公司名称。域又分为 org、com、cn 等，其中 org 为非营利组织，com 为商业组织，cn 表示中国。以 apache 开源社区的 tomcat 项目为例，这个项目的 groupId 是 org.apache，它的域是 org（因为 tomcat 是非营利项目），公司名称是 apache，artifactId 是 tomcat。\n- **artifactId**(必须)：定义了当前 Maven 项目的名称，项目的唯一的标识符，对应项目根目录的名称。\n- **version**(必须)：定义了 Maven 项目当前所处版本。\n- **packaging**（可选）：定义了 Maven 项目的打包方式（比如 jar，war...），默认使用 jar。\n- **classifier**(可选)：常用于区分从同一 POM 构建的具有不同内容的构件，可以是任意的字符串，附加在版本号之后。\n\n只要你提供正确的坐标，就能从 Maven 仓库中找到相应的构件供我们使用。\n\n举个例子（引入阿里巴巴开源的 EasyExcel）：\n\n```xml\n<dependency>\n    <groupId>com.alibaba</groupId>\n    <artifactId>easyexcel</artifactId>\n    <version>3.1.1</version>\n</dependency>\n```\n\n你可以在 <https://mvnrepository.com/> 这个网站上找到几乎所有可用的构件，如果你的项目使用的是 Maven 作为构建工具，那这个网站你一定会经常接触。\n\n![Maven 仓库](https://oss.javaguide.cn/github/javaguide/tools/maven/mvnrepository.com.png)\n\n## Maven 依赖\n\n如果使用 Maven 构建产生的构件（例如 Jar 文件）被其他的项目引用，那么该构件就是其他项目的依赖。\n\n### 依赖配置\n\n**配置信息示例**：\n\n```xml\n<project>\n    <dependencies>\n        <dependency>\n            <groupId></groupId>\n            <artifactId></artifactId>\n            <version></version>\n            <type>...</type>\n            <scope>...</scope>\n            <optional>...</optional>\n            <exclusions>\n                <exclusion>\n                  <groupId>...</groupId>\n                  <artifactId>...</artifactId>\n                </exclusion>\n          </exclusions>\n        </dependency>\n      </dependencies>\n</project>\n```\n\n**配置说明**：\n\n- dependencies：一个 pom.xml 文件中只能存在一个这样的标签，是用来管理依赖的总标签。\n- dependency：包含在 dependencies 标签中，可以有多个，每一个表示项目的一个依赖。\n- groupId,artifactId,version(必要)：依赖的基本坐标，对于任何一个依赖来说，基本坐标是最重要的，Maven 根据坐标才能找到需要的依赖。我们在上面解释过这些元素的具体意思，这里就不重复提了。\n- type(可选)：依赖的类型，对应于项目坐标定义的 packaging。大部分情况下，该元素不必声明，其默认值是 jar。\n- scope(可选)：依赖的范围，默认值是 compile。\n- optional(可选)：标记依赖是否可选\n- exclusions(可选)：用来排除传递性依赖,例如 jar 包冲突\n\n### 依赖范围\n\n**classpath** 用于指定 `.class` 文件存放的位置，类加载器会从该路径中加载所需的 `.class` 文件到内存中。\n\nMaven 在编译、执行测试、实际运行有着三套不同的 classpath：\n\n- **编译 classpath**：编译主代码有效\n- **测试 classpath**：编译、运行测试代码有效\n- **运行 classpath**：项目运行时有效\n\nMaven 的依赖范围如下：\n\n- **compile**：编译依赖范围（默认），使用此依赖范围对于编译、测试、运行三种都有效，即在编译、测试和运行的时候都要使用该依赖 Jar 包。\n- **test**：测试依赖范围，从字面意思就可以知道此依赖范围只能用于测试，而在编译和运行项目时无法使用此类依赖，典型的是 JUnit，它只用于编译测试代码和运行测试代码的时候才需要。\n- **provided**：此依赖范围，对于编译和测试有效，而对运行时无效。比如 `servlet-api.jar` 在 Tomcat 中已经提供了，我们只需要的是编译期提供而已。\n- **runtime**：运行时依赖范围，对于测试和运行有效，但是在编译主代码时无效，典型的就是 JDBC 驱动实现。\n- **system**：系统依赖范围，使用 system 范围的依赖时必须通过 systemPath 元素显示地指定依赖文件的路径，不依赖 Maven 仓库解析，所以可能会造成建构的不可移植。\n\n### 传递依赖性\n\n### 依赖冲突\n\n**1、对于 Maven 而言，同一个 groupId 同一个 artifactId 下，只能使用一个 version。**\n\n```xml\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.48</version>\n</dependency>\n<!-- 只会使用 1.0.49 这个版本的依赖 -->\n<dependency>\n    <groupId>in.hocg.boot</groupId>\n    <artifactId>mybatis-plus-spring-boot-starter</artifactId>\n    <version>1.0.49</version>\n</dependency>\n```\n\n若相同类型但版本不同的依赖存在于同一个 pom 文件，只会引入后一个声明的依赖。\n\n**2、项目的两个依赖同时引入了某个依赖。**\n\n举个例子，项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0)\n依赖链路二：A -> D -> X(2.0)\n```\n\n这两条依赖路径上有两个版本的 X，为了避免依赖重复，Maven 只会选择其中的一个进行解析。\n\n**哪个版本的 X 会被 Maven 解析使用呢?**\n\nMaven 在遇到这种问题的时候，会遵循 **路径最短优先** 和 **声明顺序优先** 两大原则。解决这个问题的过程也被称为 **Maven 依赖调解** 。\n\n**路径最短优先**\n\n```plain\n依赖链路一：A -> B -> C -> X(1.0) // dist = 3\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n依赖链路二的路径最短，因此，X(2.0)会被解析使用。\n\n不过，你也可以发现。路径最短优先原则并不是通用的，像下面这种路径长度相等的情况就不能单单通过其解决了：\n\n```plain\n依赖链路一：A -> B -> X(1.0) // dist = 2\n依赖链路二：A -> D -> X(2.0) // dist = 2\n```\n\n因此，Maven 又定义了声明顺序优先原则。\n\n依赖调解第一原则不能解决所有问题，比如这样的依赖关系：A->B->Y(1.0)、A-> C->Y(2.0)，Y(1.0)和 Y(2.0)的依赖路径长度是一样的，都为 2。Maven 定义了依赖调解的第二原则：\n\n**声明顺序优先**\n\n在依赖路径长度相等的前提下，在 `pom.xml` 中依赖声明的顺序决定了谁会被解析使用，顺序最前的那个依赖优胜。该例中，如果 B 的依赖声明在 D 之前，那么 X (1.0)就会被解析使用。\n\n```xml\n<!-- A pom.xml -->\n<dependencies>\n    ...\n    dependency B\n    ...\n    dependency D\n</dependencies>\n```\n\n### 排除依赖\n\n单纯依赖 Maven 来进行依赖调解，在很多情况下是不适用的，需要我们手动排除依赖。\n\n举个例子，当前项目存在下面这样的依赖关系：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n根据路径最短优先原则，X(1.0) 会被解析使用，也就是说实际用的是 1.0 版本的 X。\n\n但是！！！这会一些问题：如果 C 依赖用到了 1.5 版本的 X 中才有的一个类，运行项目就会报`NoClassDefFoundError`错误。如果 C 依赖用到了 1.5 版本的 X 中才有的一个方法，运行项目就会报`NoSuchMethodError`错误。\n\n现在知道为什么你的 Maven 项目总是会报`NoClassDefFoundError`和`NoSuchMethodError`错误了吧？\n\n**如何解决呢？** 我们可以通过`exclusion`标签手动将 X(1.0) 给排除。\n\n```xml\n<dependency>\n    ......\n    <exclusions>\n      <exclusion>\n        <artifactId>x</artifactId>\n        <groupId>org.apache.x</groupId>\n      </exclusion>\n    </exclusions>\n</dependency>\n```\n\n一般我们在解决依赖冲突的时候，都会优先保留版本较高的。这是因为大部分 jar 在升级的时候都会做到向下兼容。\n\n如果高版本修改了低版本的一些类或者方法的话，这个时候就不能直接保留高版本了，而是应该考虑优化上层依赖，比如升级上层依赖的版本。\n\n还是上面的例子：\n\n```plain\n依赖链路一：A -> B -> C -> X(1.5) // dist = 3\n依赖链路二：A -> D -> X(1.0) // dist = 2\n```\n\n我们保留了 1.5 版本的 X，但是这个版本的 X 删除了 1.0 版本中的某些类。这个时候，我们可以考虑升级 D 的版本到一个 X 兼容的版本。\n\n## Maven 仓库\n\n在 Maven 世界中，任何一个依赖、插件或者项目构建的输出，都可以称为 **构件** 。\n\n坐标和依赖是构件在 Maven 世界中的逻辑表示方式，构件的物理表示方式是文件，Maven 通过仓库来统一管理这些文件。 任何一个构件都有一组坐标唯一标识。有了仓库之后，无需手动引入构件，我们直接给定构件的坐标即可在 Maven 仓库中找到该构件。\n\nMaven 仓库分为：\n\n- **本地仓库**：运行 Maven 的计算机上的一个目录，它缓存远程下载的构件并包含尚未发布的临时构件。`settings.xml` 文件中可以看到 Maven 的本地仓库路径配置，默认本地仓库路径是在 `${user.home}/.m2/repository`。\n- **远程仓库**：官方或者其他组织维护的 Maven 仓库。\n\nMaven 远程仓库可以分为：\n\n- **中央仓库**：这个仓库是由 Maven 社区来维护的，里面存放了绝大多数开源软件的包，并且是作为 Maven 的默认配置，不需要开发者额外配置。另外为了方便查询，还提供了一个[查询地址](https://central.sonatype.com/)，开发者可以通过这个地址更快的搜索需要构件的坐标。\n- **私服**：私服是一种特殊的远程 Maven 仓库，它是架设在局域网内的仓库服务，私服一般被配置为互联网远程仓库的镜像，供局域网内的 Maven 用户使用。\n- **其他的公共仓库**：有一些公共仓库是为了加速访问（比如阿里云 Maven 镜像仓库）或者部分构件不存在于中央仓库中。\n\nMaven 依赖包寻找顺序：\n\n1. 先去本地仓库找寻，有的话，直接使用。\n2. 本地仓库没有找到的话，会去远程仓库找寻，下载包到本地仓库。\n3. 远程仓库没有找到的话，会报错。\n\n## Maven 生命周期\n\nMaven 的生命周期就是为了对所有的构建过程进行抽象和统一，包含了项目的清理、初始化、编译、测试、打包、集成测试、验证、部署和站点生成等几乎所有构建步骤。\n\nMaven 定义了 3 个生命周期`META-INF/plexus/components.xml`：\n\n- `default` 生命周期\n- `clean`生命周期\n- `site`生命周期\n\n这些生命周期是相互独立的，每个生命周期包含多个阶段(phase)。并且，这些阶段是有序的，也就是说，后面的阶段依赖于前面的阶段。当执行某个阶段的时候，会先执行它前面的阶段。\n\n执行 Maven 生命周期的命令格式如下：\n\n```bash\nmvn 阶段 [阶段2] ...[阶段n]\n```\n\n### default 生命周期\n\n`default`生命周期是在没有任何关联插件的情况下定义的，是 Maven 的主要生命周期，用于构建应用程序，共包含 23 个阶段。\n\n```xml\n<phases>\n  <!-- 验证项目是否正确，并且所有必要的信息可用于完成构建过程 -->\n  <phase>validate</phase>\n  <!-- 建立初始化状态，例如设置属性 -->\n  <phase>initialize</phase>\n  <!-- 生成要包含在编译阶段的源代码 -->\n  <phase>generate-sources</phase>\n  <!-- 处理源代码 -->\n  <phase>process-sources</phase>\n  <!-- 生成要包含在包中的资源 -->\n  <phase>generate-resources</phase>\n  <!-- 将资源复制并处理到目标目录中，为打包阶段做好准备。 -->\n  <phase>process-resources</phase>\n  <!-- 编译项目的源代码  -->\n  <phase>compile</phase>\n  <!-- 对编译生成的文件进行后处理，例如对 Java 类进行字节码增强/优化 -->\n  <phase>process-classes</phase>\n  <!-- 生成要包含在编译阶段的任何测试源代码 -->\n  <phase>generate-test-sources</phase>\n  <!-- 处理测试源代码 -->\n  <phase>process-test-sources</phase>\n  <!-- 生成要包含在编译阶段的测试源代码 -->\n  <phase>generate-test-resources</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-resources</phase>\n  <!-- 编译测试源代码 -->\n  <phase>test-compile</phase>\n  <!-- 处理从测试代码文件编译生成的文件 -->\n  <phase>process-test-classes</phase>\n  <!-- 使用合适的单元测试框架（JUnit 就是其中之一）运行测试 -->\n  <phase>test</phase>\n  <!-- 在实际打包之前，执行任何的必要的操作为打包做准备 -->\n  <phase>prepare-package</phase>\n  <!-- 获取已编译的代码并将其打包成可分发的格式，例如 JAR、WAR 或 EAR 文件 -->\n  <phase>package</phase>\n  <!-- 在执行集成测试之前执行所需的操作。 例如，设置所需的环境 -->\n  <phase>pre-integration-test</phase>\n  <!-- 处理并在必要时部署软件包到集成测试可以运行的环境 -->\n  <phase>integration-test</phase>\n  <!-- 执行集成测试后执行所需的操作。 例如，清理环境  -->\n  <phase>post-integration-test</phase>\n  <!-- 运行任何检查以验证打的包是否有效并符合质量标准。 -->\n  <phase>verify</phase>\n  <!-- \t将包安装到本地仓库中，可以作为本地其他项目的依赖 -->\n  <phase>install</phase>\n  <!-- 将最终的项目包复制到远程仓库中与其他开发者和项目共享 -->\n  <phase>deploy</phase>\n</phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn test`命令的时候，会执行从 validate 到 test 的所有阶段，这也就解释了为什么执行测试的时候，项目的代码能够自动编译。\n\n### clean 生命周期\n\nclean 生命周期的目的是清理项目，共包含 3 个阶段：\n\n1. pre-clean\n2. clean\n3. post-clean\n\n```xml\n<phases>\n  <!--  执行一些需要在clean之前完成的工作 -->\n  <phase>pre-clean</phase>\n  <!--  移除所有上一次构建生成的文件 -->\n  <phase>clean</phase>\n  <!--  执行一些需要在clean之后立刻完成的工作 -->\n  <phase>post-clean</phase>\n</phases>\n<default-phases>\n  <clean>\n    org.apache.maven.plugins:maven-clean-plugin:2.5:clean\n  </clean>\n</default-phases>\n```\n\n根据前面提到的阶段间依赖关系理论，当我们执行 `mvn clean` 的时候，会执行 clean 生命周期中的 pre-clean 和 clean 阶段。\n\n### site 生命周期\n\nsite 生命周期的目的是建立和发布项目站点，共包含 4 个阶段：\n\n1. pre-site\n2. site\n3. post-site\n4. site-deploy\n\n```xml\n<phases>\n  <!--  执行一些需要在生成站点文档之前完成的工作 -->\n  <phase>pre-site</phase>\n  <!--  生成项目的站点文档作 -->\n  <phase>site</phase>\n  <!--  执行一些需要在生成站点文档之后完成的工作，并且为部署做准备 -->\n  <phase>post-site</phase>\n  <!--  将生成的站点文档部署到特定的服务器上 -->\n  <phase>site-deploy</phase>\n</phases>\n<default-phases>\n  <site>\n    org.apache.maven.plugins:maven-site-plugin:3.3:site\n  </site>\n  <site-deploy>\n    org.apache.maven.plugins:maven-site-plugin:3.3:deploy\n  </site-deploy>\n</default-phases>\n```\n\nMaven 能够基于 `pom.xml` 所包含的信息，自动生成一个友好的站点，方便团队交流和发布项目信息。\n\n## Maven 插件\n\nMaven 本质上是一个插件执行框架，所有的执行过程，都是由一个一个插件独立完成的。像咱们日常使用到的 install、clean、deploy 等命令，其实底层都是一个一个的 Maven 插件。关于 Maven 的核心插件可以参考官方的这篇文档：<https://maven.apache.org/plugins/index.html> 。\n\n本地默认插件路径: `${user.home}/.m2/repository/org/apache/maven/plugins`\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/maven-plugins.png)\n\n除了 Maven 自带的插件之外，还有一些三方提供的插件比如单测覆盖率插件 jacoco-maven-plugin、帮助开发检测代码中不合规范的地方的插件 maven-checkstyle-plugin、分析代码质量的 sonar-maven-plugin。并且，我们还可以自定义插件来满足自己的需求。\n\njacoco-maven-plugin 使用示例：\n\n```xml\n<build>\n  <plugins>\n    <plugin>\n      <groupId>org.jacoco</groupId>\n      <artifactId>jacoco-maven-plugin</artifactId>\n      <version>0.8.8</version>\n      <executions>\n        <execution>\n          <goals>\n            <goal>prepare-agent</goal>\n          </goals>\n        </execution>\n        <execution>\n          <id>generate-code-coverage-report</id>\n          <phase>test</phase>\n          <goals>\n            <goal>report</goal>\n          </goals>\n        </execution>\n      </executions>\n    </plugin>\n  </plugins>\n</build>\n```\n\n你可以将 Maven 插件理解为一组任务的集合，用户可以通过命令行直接运行指定插件的任务，也可以将插件任务挂载到构建生命周期，随着生命周期运行。\n\nMaven 插件被分为下面两种类型：\n\n- **Build plugins**：在构建时执行。\n- **Reporting plugins**：在网站生成过程中执行。\n\n## Maven 多模块管理\n\n多模块管理简单地来说就是将一个项目分为多个模块，每个模块只负责单一的功能实现。直观的表现就是一个 Maven 项目中不止有一个 `pom.xml` 文件，会在不同的目录中有多个 `pom.xml` 文件，进而实现多模块管理。\n\n多模块管理除了可以更加便于项目开发和管理，还有如下好处：\n\n1. 降低代码之间的耦合性（从类级别的耦合提升到 jar 包级别的耦合）；\n2. 减少重复，提升复用性；\n3. 每个模块都可以是自解释的（通过模块名或者模块文档）；\n4. 模块还规范了代码边界的划分，开发者很容易通过模块确定自己所负责的内容。\n\n多模块管理下，会有一个父模块，其他的都是子模块。父模块通常只有一个 `pom.xml`，没有其他内容。父模块的 `pom.xml` 一般只定义了各个依赖的版本号、包含哪些子模块以及插件有哪些。不过，要注意的是，如果依赖只在某个子项目中使用，则可以在子项目的 pom.xml 中直接引入，防止父 pom 的过于臃肿。\n\n如下图所示，Dubbo 项目就被分成了多个子模块比如 dubbo-common（公共逻辑模块）、dubbo-remoting（远程通讯模块）、dubbo-rpc（远程调用模块）。\n\n![](https://oss.javaguide.cn/github/javaguide/tools/maven/dubbo-maven-multi-module.png)\n\n## 文章推荐\n\n- [安全同学讲 Maven 间接依赖场景的仲裁机制 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/flniMiP-eu3JSBnswfd_Ew)\n- [高效使用 Java 构建工具｜ Maven 篇 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/Wvq7t2FC58jaCh4UFJ6GGQ)\n- [安全同学讲 Maven 重打包的故事 - 阿里开发者 - 2022](https://mp.weixin.qq.com/s/xsJkB0onUkakrVH0wejcIg)\n\n## 参考\n\n- 《Maven 实战》\n- Introduction to Repositories - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-repositories.html>\n- Introduction to the Build Lifecycle - Maven 官方文档：<https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html#Lifecycle_Reference>\n- Maven 依赖范围：<https://www.mvnbook.com/maven-dependency.html>\n- 解决 maven 依赖冲突，这篇就够了！：<https://www.cnblogs.com/qdhxhz/p/16363532.html>\n- Multi-Module Project with Maven：<https://www.baeldung.com/maven-multi-module>\n\n<!-- @include: @article-footer.snippet.md -->\n\n<!-- File: docs/tools/docker/README.md (Tokens: ~1258 | Category: plugin-manifest) -->\n---\ntitle: Docker 专题：容器、镜像、仓库、数据卷、网络与容器化部署\ndescription: Docker 面试与容器化学习路线，涵盖容器、镜像、仓库、Docker 引擎、数据卷、网络、常用命令和容器化部署实践。\ncategory: 开发工具\ntag:\n  - Docker\n  - 容器\n  - 部署\nsitemap:\n  changefreq: weekly\n  priority: 0.85\nhead:\n  - - meta\n    - name: keywords\n      content: Docker,容器,镜像,仓库,Docker引擎,数据卷,网络,容器化部署,环境一致性,后端开发\n---\n\nDocker 是后端开发非常常见的容器化工具，常用于本地快速启动 MySQL、Redis、Kafka 等依赖服务，也常用于测试环境和部署交付。学习 Docker 时，要把核心概念和命令实践结合起来。\n\n## 适合谁看\n\n- 想快速理解 Docker 容器化基础的后端开发者。\n- 需要用 Docker 搭建本地开发环境、测试环境或依赖服务的同学。\n- 准备面试，需要讲清容器、镜像、数据卷、网络和容器与虚拟机区别的读者。\n- 已经会复制 Docker 命令，但不清楚镜像构建、容器生命周期和数据持久化的工程师。\n\n## 学习重点\n\n- 容器解决的是应用运行环境隔离和一致性问题。\n- 镜像是静态模板，容器是镜像运行后的实例，仓库用于分发和复用镜像。\n- Docker 常用命令要围绕镜像管理、容器生命周期、日志查看、端口映射和进入容器来掌握。\n- 数据卷用于数据持久化和宿主机目录挂载，网络用于容器之间和容器与外部服务通信。\n- Docker Compose 用于定义和运行多容器应用，适合本地开发环境和简单服务编排。\n- Docker 不是万能部署方案，生产环境还要结合镜像安全、资源限制、日志、监控和编排能力考虑。\n\n## 建议阅读顺序\n\n1. [Docker 核心概念总结](./docker-intro.md)：先理解容器、镜像、仓库、Docker 引擎以及容器和虚拟机的区别。\n2. [Docker 实战](./docker-in-action.md)：通过实际命令练习镜像拉取、容器启动、端口映射、数据卷、日志查看和常见服务部署。\n3. 结合一个 Java 项目练习：把应用依赖的 MySQL、Redis 等服务用 Docker 启动起来，再观察日志、数据目录和端口映射。\n\n## 核心文章\n\n- [Docker 核心概念总结](./docker-intro.md)：讲解容器、镜像、仓库、Docker 引擎、Docker 架构、Docker Compose，以及 Docker 和虚拟机的区别。\n- [Docker 实战](./docker-in-action.md)：通过常用命令和实践场景理解镜像管理、容器管理、服务部署、本地环境搭建和常见排查。\n\n## 高频问题\n\n- Docker 主要解决什么问题？\n- 容器和虚拟机有什么区别？\n- 镜像和容器是什么关系？\n- Dockerfile、镜像、容器、仓库分别是什么？\n- 为什么容器删除后数据可能丢失？数据卷解决了什么问题？\n- 端口映射和容器网络分别解决什么问题？\n- 如何查看容器日志、进入容器、停止和删除容器？\n- `docker compose` 适合解决什么问题？和单独执行 `docker run` 有什么区别？\n- Docker 在开发、测试和部署环境里分别有什么价值？\n\n## 相关专题\n\n- [开发工具知识体系](../)\n- [Git 专题](../git/)\n- [Maven 专题](../maven/)\n- [高可用系统设计](../../high-availability/)\n\n<!-- @include: @article-footer.snippet.md -->\n</yakaai_skills>"}