(一)Maven
1. 了解 Maven
1.1 简介
Maven 是一个 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。
官网:maven.apache.org/index.html
在程序开发中,越来越多的框架诞生,或者说框架的复杂程度也越来越高,项目中所需要依赖的 jar 包越来越多。在一个项目中的一个模块可能就需要依赖上百个 jar 包都是非常正常的。
要想获取这些 jar 包我们可以通过官网去获得,但是官网都是英文的界面,并且不一定能直接的找到下载的入口。还有一种可能就是从网上下载别的小伙伴上传的 jar 包,问题就是这些个 jar 可能打包的不规范,在使用过程中会出现各种各样的问题。而使用 Maven 之后,依赖对应的 jar 包能够自动进行下载、方便、快捷并且规范。
框架中使用的 jar 包,不仅数量庞大,而且彼此之间都是有着千丝万缕的关系的。依赖关系程度相当复杂,已经上升到了完全不能靠人工手动解决的程度。另外就是 jar 包在使用的过程中可能会产生冲突,进一步增加了我们在 jar 包使用过程中的难度。
构建: Java 项目开发过程中,构建指的是使用原材料生产产品的过程
- 原材料:java 源代码、基于 Html 的 Thymeleaf 文件、图片、配置文件......
- 产品:一个可以在服务器上运行的项目
构建过程中包含的主要环节:
-
清理:删除上一次构建的结果,为下一次构建做好准备
-
编译:Java 源程序编译成 *.class 字节码文件
-
测试:运行提前准备好的测试程序
-
报告:针对刚才测试的结果生成一个全面的信息
-
打包
- Java 功能:jar 包
- Web 工程:wra 包
-
安装:把一个 Maven 工程经过打包操作生成的 jar 包或wra包存入 Maven 仓库
-
部署:
- jar 包:把一个 jar 包部署到 Nexus 私服服务器上。
- war 包:借助相关 Maven 插件,将 war 包部署到 Tomcat 服务器上。
依赖: 如果 A 工程里面用到了 B 工程的类、接口、配置文件等这样的资源,那么我们就可以说 A 依赖 B。例如:
- junit-4.12 依赖 hamcrest-core-1.3
- thymeleaf-3.0.12.RELEASE依赖ognl-3.1.26 等等
依赖管理中要解决的具体问题:
- jar 包的下载:使用 Maven 之后,jar 包会从规范的远程仓库下载到本地
- jar 包之间的依赖:通过依赖的传递性自动完成
- jar 包之间的冲突:通过对依赖的配置进行调整,让某些 jar 包不会被导入
1.2 构建管理工具
-
没有注意的构建
可以不适用 Maven,但是项目必须是要进行构建的。当我们使用 IDEA 进行开发时,构建是 IDEA 替我们进行完成的。
-
脱离 IDE 环境仍需要进行构建
管理规模庞大的 jar 包,需要专门的工具
脱离 IDE 环境执行构建操作,需要专门的工具
1.3 Maven 的工作机制
1.4 安装配置
-
下载解压安装
下载地址:maven.apache.org/download.cg…
下载完成之后解压即可
-
配置Maven 本地仓库
在解压中找到 conf/setting.xml 配置文件,修改下载 jar 包的位置
-
配置下载镜像仓库
Maven 下载 jar 包默认访问的境外的中央仓库,而国外的网站速度很慢。改成 阿里云提供的镜像仓库,访问国内的网站,可以让 Maven 下载 jar 包的时候速度更快。在 conf/setting.xml 配置文件中的 mirror 标签配置
<mirror>
<id>nexus-aliyun</id>
<mirrorOf>*</mirrorOf>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public</url>
</mirror>
-
配置 JDK 基础版本
如果按照默认配置运行,Java 工程使用的默认版本是 1.5,修改配置,在 conf/setting.xml 文件中的 profile 标签中修改
<profiles>
<profile>
<id>jdk-1.8</id>
<activation>
<activeByDefault>true</activeByDefault>
<jdk>1.8</jdk>
</activation>
<repositories>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<maven.compiler.compilerVersion>1.8</maven.compiler.compilerVersion>
</repositories>
</profile>
</profiles>
- 在系统属性中配置环境变量
2. 常见标签介绍
2.1 向量坐标说明
使用三个向量在 Maven 仓库中唯一定位到一个 jar 包
-
groupId:公司或组织 Id,公司或者组织域名的倒序,通常也会加上项目名称
- 例如:com.lss.maven
-
artifactld:一个项目或是项目中的一个模块 id,模块的名称,将来作为 Maven 工程的工程名
- 例如:model01
-
version:版本号,模块的版本号,根据自己的需要设定
- 例如:SNAPSHOP 表示快照版本,正在迭代过程中,不稳定的版本
- 例如:RELEASE 表示正式版本
2.2 坐标和jar包路径关系
坐标:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
</dependency>
上面坐标对应的 jar 包在 Maven 本地仓库中的位置
Maven 本地的仓库根目录\mysql\mysql-connector-java\8.0.28\mysql-connector-java.8.0.28
- pom.xml 文件
<!-- 跟标签:project,表示对当前工程进行配置、管理 -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<!-- modelVersion 标签:从 Maven2 开始就固定是 4.0.0-->
<!-- 代表当前 pom.xml 所采用的标签结构 -->
<modelVersion>4.0.0</modelVersion>
<!-- 坐标信息 -->
<!-- 表示公司或组织开发的某一个项目 -->
<groupId>com.lss</groupId>
<!-- 代表项目下的某一个模块 -->
<artifactId>demo01</artifactId>
<!-- 代表当前模块的版本 -->
<version>1.0-SNAPSHOT</version>
<!-- packaging 标签:打包方式 -->
<!-- 取值 jar:生成 jar 包,说明这是一个 Java 工程 -->
<!-- 取值 war:生成 war 包,说明这是一个 Web 工程 -->
<!-- 取值 pom:说明这个工程是用来管理其他工程的工程 -->
<packaging>jar</packaging>
<name>demo01</name>
<url>http://maven.apache.org</url>
<!-- 在 Maven 中定义属性值 -->
<properties>
<!-- 在构建过程中读取源码时使用的字符集 -->
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<!-- dependencies 标签:配置依赖信息,可以包括多个 dependency 子标签 -->
<dependencies>
<!-- dependency 标签:配置一个具体的依赖信息 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<!-- scope 标签:配置当前依赖的范围 -->
<scope>test</scope>
</dependency>
</dependencies>
</project>
2.3 Maven 核心概念
-
POM
POM:Project Object Model,项目对象的模型。和 POM 类似的是 DOM,文档对象模型,他们都是模型化思想的具体体现
模块化思想:POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与实现事物相关的数据。
对应的配置文件:POM 理念集中体现在 Maven 工程根目录下的 pom.xml 配置文件中。所以 pom.xml 配置文件就是 Maven 工程的核心配置文件。
-
目录结构
这些目录在 超级 pom 里面定义好的,还有一个 target 目录专门存放构建操作输出的结果。
Maven 为了构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如:Maven 执行编译操作,必须先去 java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放到 target 目录
约定大于配置:Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的 位置进行详细的配置,那肯定非常麻烦。目前开发领域技术发展趋势:约定大于配置,配置大于编码。
3. 常用命令
-
构建项目:mvn archetype:generate
-
清理操作:mvn clean
- 删除target 目录
-
主程序编译:mvn compile
- 存放目录:target/classes
-
测试程序编译:mvn test-compile
- 存放目录:target/classes
-
测试操作:mvn test
- 存放目录:target/surefire-reports
-
打包操作:mvn package
- 在target 目录下会生成一个 .jar 包文件
-
安装:mvn install
- 执行完成后会在 mavne根路径\groupId\找到相应的 artifactId.jar 的文件,安装的效果就是将本地构建过程中生成的 jar包存入 Maven 本地仓库。这个 jar 包在 Maven 仓库中的路径是根据坐标生成的。安装操作还会将 pom.xml 文件转换为 XXX.pom 文件一起存入本地仓库。所以我们在 Maven 的本地仓库中想看一个 jar 包原始 pom.xml 文件时,查看对应的 XXX.pom 文件即可,他们是名字发生了改变,本质是同一个文件。
-
查看依赖jar包列表:mvn dependency:list
-
创建 Web 工程:mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4
4. 基础概念
明确:从来只有 Web 工程依赖 Java 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下。
<!-- 配置对 Java 工程的依赖 -->
<dependency>
<!-- 通过指定被依赖工程的坐标完成依赖 -->
<groupId>com.lss</groupId>
<artifactId>demo01</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
4.1 依赖范围
标签的位置:dependencies/dependency/scop
标签的可选值:compile/test/provided/system/runtime/import
- compile 和 test 对比
| main目录 | test 目录 | 开发过程 | 部署到服务器 | |
|---|---|---|---|---|
| compile | 有 | 有 | 有 | 有 |
| test | 无 | 有 | 有 | 无 |
- compile 和 provided 对比
| main目录 | test 目录 | 开发过程 | 部署到服务器 | |
|---|---|---|---|---|
| compile | 有 | 有 | 有 | 有 |
| test | 有 | 有 | 有 | 无 |
compile:通常使用的第三方框架的 jar 包这样在项目实际运行时真正要用到的 jar 包都是以 compile 范围进行依赖的。比如 SSM框架所需 jar 包
test:测试过程中使用的 jar 包,以 test 范围依赖进来。比如 junit。
provided:在开发过程中需要用到的“服务器上的jar包”通常以 provided 范围依赖进来。比如 servlet-api、jsp-api。而这个范围的 jar 包之所以不需要参与部署、不放进 war 包,就是避免和服务器上已有的同类 jar 包产生冲突,同时减轻服务器的负担。说白了就是“服务器上已经有了,不用带了”
-
import
管理依赖最基本的办法就是继承父工程,但是和 Java 类一样,Maven 也是单继承的。如果不同体系的依赖信息封装在不同 POM 中了,没办法继承多个父工程怎么办?这时就可以使用 import 依赖范围。例如 SpringCloud
import 依赖范围使用要求:1. 打包类型必须是pom,必须放在 dependencyManagement 中
<!-- SpringCloud 依赖导入 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR9</version>
<type>pom</type>
<scope>import</scope>
</dependency>
-
system
在 Windows 系统环境下开发,想要导入 D 判断下第一个 jar 包到我们的项目中,此时可以将依赖配置为 system范围
该方式引入的依赖完全不具有可移植性,所以不要使用。他引入的包只能是在本地的,部署上线是不行的。
<dependency>
<groupId>com.lss</groupId>
<artifactId>demo01</artifactId>
<version>1.0</version>
<systemPath>D:\java\maven\demo01.jar</systemPath>
<scope>system</scope>
</dependency>
-
runtime
专门用于编译时不需要,但是运行时需要的 jar 包。比如:编辑时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。
<dependency>
<groupId>com.lss</groupId>
<artifactId>demo01</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
4.2 依赖的传递性
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖情况下, A 里面能不能直接使用C ?
传递的原则
在 A 依赖 B,B 依赖C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
- B 依赖 C 时使用 compile 范围:可以传递
- B 依赖 C 时使用 test 或 provided 范围:不能传递,所以需要这样的 jar 包时,就必须在需要的地方明确配置依赖才可以。
4.3 依赖的排除
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
<dependency>
<groupId>com.lss</groupId>
<artifactId>demo01</artifactId>
<version>1.0-SNAPSHOP</version>
<scope>compile</scope>
<!-- 使用 excludes 标签配置依赖排除 -->
<exclusions>
<!-- 在 exclude 标签中配置一个具体的排除 -->
<exclusion>
<!-- 指定要排除的依赖的坐标(不需要写version)-->
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</exclusion>
</exclusions>
</dependency>
4.4 继承
Maven 工程之间,A 工程继承 B工程
- B工程 :父工程
- A 工程:子工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中的 pom.xml 的配置。
在父工程中统一管理项目中的依赖信息,具体来说就是依赖信息的版本。
背景:
- 对一个比较大型的项目进行了模块拆分。
- 一个 project 下面,创建了很多个 module。
- 每一个 module 都需要配置自己的依赖信息。
需求:
- 在每一个 module 中各自维护各自的依赖信息很容易发生出入,不易统一管理。
- 使用同一个框架内的不同 jar 包,他们应该是同一个版本,所以整个项目中使用的框架版本需要统一。
- 使用框架时需要的 jar 包组合(或者说依赖信息组合)需要经过长期摸索和反复调试,最终确定一个可用组合。这个耗费很大精力总结出来的方案不应该在新的的项目中重新摸索。
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。
父工程的打包方式必须是 pom
<!-- parent 标签给当前工程配置父工程 -->
<parent>
<!-- 通过指定父工程的坐标找到父工程 -->
<groupId></groupId>
<artifactId></artifactId>
<version></version>
</parent>
<!-- 如果子工程的 groupId 和 version 和父工程的一样,则子工程可以只保留 artifactId -->
<!-- 当前工程作为父工程,要去管理其他工程,打包方式必须是 pom -->
<packaging>pom</packaging>
<!-- 聚合配置 -->
<modules>
<module>module01</module>
<module>module02</module>
<module>module03</module>
</modules>
<properties>
<!-- 创建自定义的属性标签 -->
<标签名>版本号</标签名>
</properties>
<!-- 在父工程中统一管理依赖信息 -->
<!-- 注意:即使在父工程配置了对依赖的管理,子工程需要使用具体哪一个依赖还是要明确配置
1. 如果子工程中省略 version,则采用的就是 父工程的版本号
2. 否则,子工程版本覆盖父工程的版本 -->
<dependencyManagement>
<dependencies>
<dependency>
<!-- 父工程中通过引用属性表达式设定版本号,这样版本号就成了一个动态值 -->
<version>${标签名}</version>
</dependency>
</dependencies>
</dependencyManagement>
编写一套符合要求、开发各种功能都正常工作的依赖组合并不容易。如果公司里面已经有人总结了成熟的组合方案,那么再开发新项目时,如果不适用原有的依赖,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。如上图所示:公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子项目各取所需即可。
4.5 聚合
使用一个 “总工程” 将各个 “模块工程” 汇集起来,作为一个整体对应完整的项目。
- 项目:整体
- 模块:部分
概念的对应关系:
从继承角度来看:
- 父工程
- 子工程
从聚合关系角度来看:
- 总工程
- 模块工程
好处:
- 一键执行 Maven 命令:很多构建命令i都可以在 “总工程” 中一键执行,以 mvn install 命令为例:Maven 要求有父工程时先安装父工程;有依赖的工程时,先安装被依赖的工程。我们自己考虑这些规则会很麻烦。但是工程聚合之后,在总工程执行 mvn install 可以一键完成安装,而且会自动按照正确的顺序执行。
- 配置聚合之后,各个模块工程会在总工程中展示一个列表,让项目中的各个模块一目了然。
<!-- 聚合配置 -->
<modules>
<module>module01</module>
<module>module02</module>
<module>module03</module>
</modules>
注意: 循环依赖问题,如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报错误:The projects in the reactor contain a cyclic reference;
4.6 生命周期
为了构建过程自动化完成,Maven 设定了三个生命周期,生命周期中的每一个环节对应构建过程中一个操作。
| 生命周期名称 | 作用 | 各个环节 |
|---|---|---|
| Clean | 清理操作相关 | pre-clean clean post-clean |
| Site | 生成站点相关 | pre-sit site post-site deploy-site |
| default | 主要构建过程 | validate generate-sources process-sources generate-resources process-resources复制并处理资源文件,至目标目录,准备打包。 compile编译项目的源代码 process-classes generate-test-sources process-test-sources generate-test-resources process-test-resources复制并处理资源文件,至目标测试目录 test-compile编译测试源代码 process-test-classes test使用合适的单元测试框架运行测试。这些测试代码不会被打包或部署 prepare-package package接受编译好的代码,打包成可发布的格式,如jar pre-integration-test integration-test post-integration-test verify install将包安装至本地仓库,以让其他项目依赖 deploy将最终的包复制到远程的仓库,以让其他开发人员与项目共享或部署到服务器上运行。 |
4.7 其他标签简介
从 Spring-boot-starter 的 pom 文件可以看到:除了坐标标签、dependencies 标签之外,还有 description、url、organization、licenses、developers、scm、issueManagement 等这些描述项目信息的标签。
所以从项目管理的角度来看,Maven 提供了如下功能:
-
项目对象模型(pom):将整个项目本身抽象、封装为应用程序中的一个对象,以便于管理和操作。
-
全局性构建逻辑重用:Maven 对整个构建过程进行封装之后,程序员只需要指定配置信息即可完成构建。让构建过程从 Ant的编程式升级到了 Maven 的声明式。
-
构建的标准集合:在 Maven 提供的标准框架体系内,所有的构建都可以按照统一的规范生成和使用。
-
构建关系定义:Maven 定义了构建之间的三种基本关系,让大型应用系统可以使用 Maven 来进行管理
- 继承关系:通过从上到下的继承关系,将各个子构件中的重复信息提取到父构件中统一管理
- 聚合关系:将多个构件聚合为一个整体,便于统一操作
- 依赖关系:Maven 定义了依赖的范围、依赖的传递、依赖的排除、版本仲裁机制等一系列规范和标准,让大型项目可以有序容纳数百甚至更多依赖
-
插件目标系统:Maven 核心程序定义抽象的声明周期,然后将插件的目标绑定到生命周期中的特定阶段,实现了标准和具体实现解耦合,让 Maven 程序极具扩展性。
-
项目描述信息的维护:我们不仅可以在 pom 中声明项目描述信息,更可以将整个项目相关信息收集起来生成 HTML 页面组成一个可以直接访问的站点。这些项目描述信息包括:
- 公司或组织信息
- 项目许可证
- 开发成员信息
- issue管理信息
- SCM 信息
4.8 超级 POM
在上面的介绍中,Maven 在构建过程中有很多默认的设定。例如:源文件存放目录、测试源文件存放的目录、构建输出的目录...等。但是其实这些要素都是被 Maven 定义过的。定义的位置就是:超级 pom
Super POM 是 Maven 的默认 POM。除非明确设置,否则所有 POM 都扩展 Super POM,这意味着 Super POM 中指定的配置由您为项目创建的 POM 继承。
所以我们自己的 POM 即使没有明确指定一个父工程(父POM),其实也是默认继承了超级 POM。就好比 Java 类默认继承了 Object 类。
4.9 父 POM
和 Java 类一样,POM 之间其实也是单继承的。如果我们给一个 POM 指定了父 POM,那么继承关系如下:
4.10 有效 POM
在 POM 的继承关系中,子 POM 可以覆盖父 POM 中的配置;如果子 POM 没有覆盖,那么父 POM 中的配置将会被继承。按照这个原则,继承关系中的所有 POM 叠加在一起,就得到了一个最终生效的 POM。显然 Maven 实际运行过程中,执行构建操作就是按照这个最终生效的 POM 来运行的,这个最终生效的 POM 就是有效 POM,英文:effective POM
可以使用命令:mvn help:effective-pom 命令查看
总结
综上所述,平时我们使用和配置的 POM 其实大致是由四个层次组成的:
- 超级 POM:所有 POM 默认继承,只是有直接和间接之分。
- 父 POM:这一层可能没有,可能有一层,也可能有很多层。
- 当前 pom.xml 配置的 POM:我们最多关注和最多使用的一层。
- 有效 POM:隐含的一层,但是实际上真正生效的一层。
5. build 标签详解
在实际使用 Maven 的过程中,发现 build 标签有时候有,有时候没,怎么回事?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
本质来说:我们配置的 build 标签都是对超级 POM 配置的叠加。那我们又为什么要在默认配置的基础上叠加呢?很简单,在默认配置无法满足需求的时候定制构建过程。
build 标签的子标签大致分为三个主体部分:
- 定义约定的目录结构
| 目录名 | 作用 |
|---|---|
| sourceDirectory | 主体源程序存放目录 |
| scriptSourceDirectory | 脚本源程序存放目录 |
| testSourceDirectory | 测试源程序存放目录 |
| outputDirectory | 主体源程序编译结果输出目录 |
| testOutputDirectory | 测试源程序编译结果输出目录 |
| resources | 主体资源文件存放目录 |
| testResources | 测试资源文件存放目录 |
| directory | 构建结果输出目录 |
-
备用插件管理
pluginManagement 标签存放着几个极少用到的插件:
- maven-arturen-plugin
- maven-assembly-plugin
- maven-dependency-plugin
- maven-release-plugin
通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。
- 被 spring-boot-dependencies 管理的插件信息:
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.6.2</version>
</plugin>
- 子工程使用的插件信息
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
- 生命周期插件
plugins 标签存放的是默认声明周期中实际会用到的插件,这些插件都不陌生,所以抛开插件本身不谈,看看plugin 标签的结构:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<executions>
<execution>
<id>default-compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
-
坐标部分:
artifactId 和 version 标签定义了插件的坐标,作为 Maven 的自带插件这里省略了 groupId。
-
执行部分
exeutions 标签内可以配置多个 execution 标签,executions 标签内:
-
id:指定唯一标识
-
phase:关联的声明周期阶段
-
goals/goal:关键指定生命周期的目标
- goals 标签中可以配置多个 goal 标签,标识一个生命周期环节可以对应当前插件的多个目标。
-
在插件目标的执行过程中可以进行配置,例如maven-site-plugin 插件的 site 目标:
configuration 标签内进行配置时使用的标签是插件本身自定义的。
<execution>
<id>default-testCompile</id>
<phase>test-compile</phase>
<goals>
<goal>testCompile</goal>
</goals>
<configuration>
<outputDirectory>输出路径</outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>
</reportPlugin>
</reportPlugins>
</configuration>
</execution>
每个插件能够做那些设置都是各个插件自己规定的,不能一概而论。
6. 其他内容
6.1 版本仲裁
在当前项目依赖其他 jar 包的时候,可能其他两个包里面有不同版本的两个相同的 jar ,这个时候就需要进行仲裁了
-
最短路径原则
下图中,对于 pro25-module-a 来说,Maven 会采纳 1.2.12 版本
-
路径相同时先声明者有限
下图中,采用那个版本,取决于 pro29-module-x 中,对 pro30-module-y 和 pro31-module-z 两个模块的依赖哪一个先声明。
6.2 搭建私服 Nexus 仓库
下载:wget https://download.sonatype.com/nexus/3/nexus-3.38.1-01-unix.tar.gz
解压缩,进入到 bin 目录下通过命令启动 tar -zxvf nexus-3.25.1-04-unix.tar.gz
./nexus start 启动
./nexus status 查看状态
6.3 jar 包冲突
6.3.1 面对 jar 包冲突
编订依赖列表的程序员经常会面对 jar 包冲突的问题,初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做?我们不希望看到的是团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本五花八门,这就让事情变得更加的复杂。
所以虽然初期需要根据项目开发和实际运行情况对依赖管理配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。
即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表一一而不是每个模块都要改。
6.3.2 表现形式
由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉足到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异。
一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。
而具体的表现形式中,主要体现为找不到类或找不到方法。
-
抛出异常:找不到类
- java.lang.ClassNotFoundException:编译过程中找不到类
- java.lang.NoClassDefFoundError:运行过程中找不到类
- java.lang.LinkageError:不同类加载器分别加载的多个类有相同的全限定名
-
抛出异常:找不到方法
程序找不到符合预期的方法,这种情况多见于通过反射调用方法,所以经常会导致:
- java.lang.NoSuchMethodError
-
没报错但是结果不对
两个 jar 包中的类分别实现了一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字
例子:项目中部分模块使用了 log4j 打印日志;其他模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的 log 配置文件失效。比如:你指定了 error 级别输出,但是冲突就会导致 info、debug 都在输出。
6.3.3 本质
- 同一个 jar 包的不同版本
- 不同 jar 包中包含同类名
6.3.4 解决方法
很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。
不管具体使用的是什么工具,基本思路无非是两步:
- 第一步:把彼此冲突的 jar 包找到
- 第二步:在冲突的 jar 包中选定一个。具体通过 exclusions 排除依赖,或者明确声明依赖。
-
IDEA 的 Maven Helper 插件
这个插件是 IDEA 中安装的插件,不是 Maven 插件。他能够给我们罗列出来同一个 jar 包的不同版本,以及它们的来源。但是对不同 jar 包中同名的类没有方法。
在 IDEA 中 setting-plugin 中安装插件
-
Maven 的 enforcer 插件
使用 Maven 的 enforcer 插件既可以检测同一个 jar 包的不同版本,又可以检测不同 jar 包中同名的类。
在 pom.xml 中引入
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>1.4.1</version>
<executions>
<execution>
<id>enforce-dependencies</id>
<phase>validate</phase>
<goals>
<goal>display-info</goal>
<goal>enforce</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.codehaus.mojo</groupId>
<artifactId>extra-enforcer-rules</artifactId>
<version>1.0-beta-4</version>
</dependency>
</dependencies>
<configuration>
<rules>
<banDuplicateClasses>
<findAllDuplicates>true</findAllDuplicates>
</banDuplicateClasses>
</rules>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
在 Terminal 中执行命令:mvn clean package enforcer:enforce
以上是 Maven 部分内容,以下是 Gradle 部分类型
(二)Gradle
1. 介绍
1.1 项目构建历史
-
石器时代
依赖管理:最开始的时候如果需要依赖第三方的 jar 包,需要把 jar 放到 lib 目录中,如果 jar 包多了不好管理,很容易出现版本冲突的问题,每个项目需要使用到同一个 jar 包都得拷贝一份到项目中,很占用存储空间。繁琐!
测试:每个功能都需要书写测试类,在 main 中写测试非常麻烦,能不写一般都是不会写,就算写了也是很简单的测试而已
打包:通过IDE 打包然后上传到服务器或者放入依赖的项目中,非常的繁琐。
上传:通过一些文件上传工具上传 jar 包到项目中
以上这些操作都是比较繁琐的,但是很多操作都不能省略,像这种重复而又没有技术含量的操作非常的无聊,所以就有了构建工具的出现。
-
工业时代
使用构建工具
依赖管理:可以做依赖的管理操作,将 jar 包统一管理起来,更加的清晰和方便,而且仅仅是依赖管理,没有拷贝 jar 包到项目中。
自动化:可以自动测试、打包、发布。
机器能做的事,绝不自己手动去做,大大提高开发效率。
1.2 主流的构建工具
-
Ant(Apache Ant)
软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于 Java 环境中的软件开发
-
Maven
从 Ant 中借用了绝大多数构建任务,其突出的是依赖管理和项目发布
-
Gradle
使用 Groovy 语言构建脚本,不再像 Maven 一样使用 XML
1.3 什么是 Gradle
一个开源的项目自动化构建工具,建立在 Apache Ant 和 Apache Maven 概念的基础上,并引入了基于 Groovy的特定领域语言(DSL),而不再使用 XML 形式管理构建脚本
DSL(Domain Specific Language)定义:针对某一个领域,具有受限表达性的一种计算机程序设计语言。只针对一个领域做出来的简洁语言,而非为了通用而设计。
2. Groovy 介绍
Groovy 是基于 Java 虚拟机的一种敏捷的动态语言,它是一种成熟的 OOP 编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。
2.1 相较于 Java
- Groovy 完全兼容 Java 语法,可以做脚本也可以做类
- 分号是可选的,一般不加分号,以换行作为结束
- 类、方法、字段都是公共的,没有访问权限限制
- 默认生成具名(名值对)参数构造器,key:value
- 字段不定义访问权限时,编辑器自动给字段添加 getter/setter 方法
- 字段可以使用点来获取,无访问权限的也可使用 getter/setter 来操作
- 方法可省略 return 关 键字,自动检索最后一行的结果作为返回值
- 控制比较不会有 NullPointerException 异常抛出
2.2 Groovy 的高级特性
- assert断言:可以用 assert 代替之前 Java 的断言语句
- 可选类型:可使用类 JavaScript 的弱类型,可以使用 def 来标识任意类型
- 方法调用:调用带参数方法时可以省略括号
- 字符串定义:字符串定义有三种方式,单引号、双引号、三个单引号
- 集合 API:集合的定义和使用更加简单,API 和 Java 有所不同,但兼容 Java API
- 闭包:Groovy 的一大特性,跟方法类似的代码块,可赋值给一个变量也可以作为参数传递给一个方法,像普通方法一样调用。
2.3 环境配置
下载地址:groovy.apache.org/download.ht…
下载好之后进行解压
进入到 bin 目录打开 cmd 窗口
输入命令 groovy -v 查看版本信息和依赖的 JVM 的信息
可以打开 Groovy 自带的一个控制台
通过命令 groovyConsole
2.4 IDEA 编写代码
2.4.1 创建 Groovy 工程
2.4.2 创建测试类代码
- Student 类
package com.lss
// 不用权限修饰符,默认使用的 public
class Student {
private String name // 可省略分号
private String email // 省略 getter/setter
int age // 无权限修饰符时默认生成 getter/setter
String getName() {
name // 可省略 return
}
void setName(String name) {
this.name = name
}
}
- App 类
package com.lss
// 以脚本的形式来编写代码
// 使用脚本的方式来创建学生对象
Student student = new Student()
// 调用 getter/setter 方法
student.setName("张山")
println student.getName()
// 使用点的方式进行赋值和获取字段值
student.email = "zhangshan@qq.com"
println student.email
// 调用无权限修饰符的字段的 getter/setter
student.setAge(18)
println student.getAge()
// 调用具名构造器
Student student1 = new Student("name": "小白", email: "xiaobia")
println student1.getName()
2.4.3 基本语法定义
package com.lss
println "------ 基本语法定义 ------"
// 变量的声明
def name = "小李"
age = 18
// 调用带参数的方法时可以省略括号
println name + " " + age
// 断言 assert
// assert age = 10
println "------ 字符串的定义 ------"
str1 = '小张' // 定义普通的字符串
str2 = "名字是:${str1}" // 可以引用变量
str3 = '''name:小赵
age:18''' // 按照格式定义字符串
println str1
println str2
println str3
println "------ 集合定义 ------"
// list 集合,使用 [] 来声明集合
def list = ["数据1", "数据2", "数据3"]
// 给 list 集合添加元素
list.add("数据4")
list << "数据5"
println list
// Map 映射,使用 [key:value] 方式定义
def map = ['name': '你好', 'age': '11']
map.put('birthday','2011-01-01')
// 使用点key的方式赋值
map.gender = 'famale'
println map
2.4.4 闭包
闭包简单理解就是 {} 括起来的代码块,跟方法类似,可带参数和不带参数
闭包可以赋给一个变量也可以做参数传递给一个方法,在方法中调用闭包
- 定义格式
{
[param1, param2...->]
执行体
}
[] 括起来的内容表示可有可无
- 调用:需要使用变量来接受再调用
def fun = {[param1,param2...->] 执行体}
fun([param]) 或者 fun.call([param])
println "------ 闭包定义 ------"
def c1 = {
println "不带参数的闭包"
}
def c2 = {
val ->
println "带参数:${val}"
}
// 定义方法形参指定类型接受不能带参数的闭包方法
def method1 (Closure closure) {
closure()
}
// 定义方法形参无指定类型,接受参数闭包的方法
def method2 (closure) {
closure("小子")
}
// 调用方法传入已定义好的闭包
method1 c1
method2 c2
// 调用方法时直接定义新的闭包作为实际参数
method1({
println "闭包method1"
})
method1{
println "method1省略括号"
}
method2{
val ->
println "新定义的闭包:${val}"
}
3. Gradle 创建项目
3.1 环境配置
Gradle 下载地址:gradle.org/releases/
下载好之后进行解压缩,然后配置环境变量
然后通过命令 gradle -v 检测环境是否配置成功
3.2 创建一个 Java 项目
IDEA 中自带了 gradel 和 groovy 插件,所以可以直接使用,创建项目的时候直接选择即可
本人使用的 IDEA 版本是 2020.3.4 版本,所以创建好项目之后在 file -> setting -> Build,Exec... -> BuildTools -> Gradle 中配置本地的 Gradle 环境
在 main 文件夹下的 java 文件夹下创建一个类,编写一段代码,然后点击右侧的的 Gradle 中的 build 构建项目
在左侧可以看到 build.gradle 文件,该文件是项目的核心配置文件,相当于 maven 的 pom.xml
通过点击右侧的 build 按钮可以打包构建项目
在控制栏输入命令就可以执行这个打包好的项目,下面有乱码问题,后续解决
3.3 创建一个 Web 项目
创建的时候选择 Web 选项
4. 构建脚本
构建脚本指的就是 build.gradle 文件
介绍: Gradle 构建脚本中最重要的两个概念是 project 和 Task,任何一个 Gradle 构建都由一个或者多个 project 组成每个 project 包括许多的构建部分,可以是一个 jar 包,也可以是一个 web 应用,也可以是多个 jar 的整合,可以部署应用和搭建环境。
每个 project 由一个或多个 Task 组成,每个 Task 表示在构建执行过程中的一个原子操作。如编译、打包、生成 javadoc、发布到仓库等操作。
Project 和 Project 以及 Project 和 Task 以及 Task 和 Task 之间关系如下:
Project 依赖 Project2,所以需要先构建 Project2,Project 2中有三个任务 Task FGH,由于依赖关系,先执行 TaskG 再执行 Task F 再执行 Task H.Project 中的 Task 也是先执行被依赖的任务。
4.1 Project 对象
一个 project 代表一个正在构建的组件(jar/war文件),当构建开始时,Gradle 会基于 build.gradle 实例化一个 org.gradle.api.Project 对象,并通过 project 变量来隐式调用其成员
- Project 属性
| 名字 | 类型 | 默认值 |
|---|---|---|
| project | Project | project实例 |
| group | Object | 项目分组:未指定 |
| name | String | 项目目录名 |
| version | Object | 项目版本:未指定 |
| path | String | 项目绝对路径 |
| description | String | 项目描述 |
| projectDir | File | 包含生成脚本目录 |
| buildDir | File | projectDir/build |
| ant | AntBuilder | AntBuilder实例 |
Project 中常用的属性有 project(隐式使用),group,name,version
Project其他常用配置:
- plugins,apply plugin 用来引入插件使用
- dependencies 依赖配置
- repositories 仓库配置
- task 任务书写
- ext,gradle.properties Project 中属性的其他配置方法
总之所有的配置都会被封装到 Project 对象中。
4.2 task 任务
每个任务在构建执行过程中会被封装成 org.gradle.api.Task 对象,主要包括任务的动作和任务依赖,任务动作定义了一个原子操作,可以定义依赖其他任务、动作顺序和执行条件。
任务主要操作动作:
- dependsOn:依赖相关操作
- doFirst:任务执行之前执行的方法
- doLast:任务执行之后执行的方法
在 build.gradle 文件中编写如下代码:
task t1 {
println "t1 execute..."
}
task t2(dependsOn: "t1") {
doFirst {
println "execute First..."
}
println "t2 execute..."
doLast {
println "execute Last..."
}
}
然后点击右侧的 build 按钮
从输出结果我们可以看到,两个方法都执行了,但是 doFirst 和 doLast 方法中的内容不执行,此时是在构建状态,并没有去执行某一个任务
当我们点击右侧的 other下的 t2 的时候
从结果可以看出两个方法都执行了
结论:直接定义在任务下的代码会在配置 project 时执行,其他时候不执行,就算依赖也不执行。只有在 doFirst 或 doLast 中配置id操作才会在调用任务或依赖执行时调用。所以以后自定义任务执行代码需要写在 doFirst 或 doLast 中,除非先构建 Project 时就执行。
4.3 自定义任务
任务是 Gradle 构建中的两个基本概念之一,而任务的定义和使用有多种形式。
4.3.1 语法
// 定义任务语法
task tName1 {
println "直接带闭包的定义方式"
}
task tName2() {
println "带括号的定义方式"
}
// 以上代码只会在构建 Project 时执行,build,其他方式不执行
// 如果需要在任务调用时执行动作代码,需要将代码定义到 doFirst 或者 doLast 中
// 常见定义方式
task t1{
// 任务调用前执行
doFirst {
println "t1 execute first"
}
println "t1 execute"
}
task t2{
// 任务调用后执行
doLast {
println "t2 execute last"
}
println "t2 execute"
}
// doLast 的简写,任务调用后执行
task t3<<{
println "t3"
}
// 配置 Project 时不会执行 doFirst 或 doLast 中的代码
// 只有调用任务的时候才会执行
4.3.2 任务依赖配置
- 定义任务参数依赖
// 定义任务时参数依赖
task tsk1{
doFirst {
println "tsk1"
}
}
task tsk2(dependsOn: "tsk1"){
doLast {
println "tsk2"
}
}
- 任务内部依赖
// 任务内部依赖
task tsk1{
doFirst {
println "tsk1"
}
}
task tsk2{
dependsOn(tsk1)
doLast {
println "tsk2"
}
}
- 外部添加依赖
// 任务外部依赖
task tsk1{
doFirst {
println "tsk1"
}
}
task tsk2{
doLast {
println "tsk2"
}
}
tsk2.dependsOn tsk1
4.3.3 动态任务
4.times {val ->
task "tk${val}" doFirst {
println "The task is task${val}"
}
}
4.3.4 任务属性自定义
task t5{
ext.myProperties = "The properties Task"
doFirst {
println "t5 execute ${myProperties}"
}
}
5. Gradle 生命周期
Gradle 的生命周期分为三个阶段
第一阶段:初始化阶段
通过 setting.gradle 判断有哪些项目需要初始化,加载所有需要初始化的项目的 build.gradle 文件并为每个项目创建 project 对象
第二阶段:配置阶段
执行各项目下的 build.gradle 脚本,完成 project 的配置,并且构造 Task 任务依赖关系图以便在执行阶段按照依赖关系执行 Task 中的配置代码
// 配置代码
task configCode{
println "config Code"
}
第三阶段:执行阶段
通过配置阶段的 Task 图,按顺序执行需要执行的任务中的动作代码,就是执行任务中的 doFirst 或者 doLast 中的代码
// 动作代码,任务调用的才会执行的代码
task executeCode <<{
println "execute Code"
}
注意:
- 初始化阶段的钩子方法和 gradle.beforeProject() 只能定义在 setting.gradle 或 init.gradle 脚本中,执行 build.gradle 时已经有了 project 对象,且执行前就调用了 beforeProject 钩子方法
- gradle.buildStarted() 钩子方法无法执行到,在初始化前就已经调用 buildStarted 方法,所有在初始化阶段无法回调到。
- setting.gradle
// 项目构建之前的钩子方法
gradle.settingsEvaluated {
println "初始化阶段 settingsEvaluated"
}
gradle.projectsLoaded {
println "初始化阶段 projectsLoaded"
}
gradle.beforeProject {
println "初始化阶段 beforeProject"
}
- build.gradle
task t1{
println "t1 configuration"
doFirst{
println "执行了 doFirst 方法"
}
doLast {
println "执行了 doLast 方法"
}
}
gradle.afterProject {
println "配置阶段 afterProject"
}
project.beforeEvaluate {
println "配置阶段 beforeEvaluate"
}
gradle.projectsEvaluated {
println "配置阶段 projectsEvaluated"
}
project.afterEvaluate {
println "配置阶段 afterEvaluate"
}
gradle.taskGraph.whenReady {
println "配置阶段 taskGraph.whenReady"
}
gradle.taskGraph.beforeTask {
println "执行阶段 taskGraph.beforeTask"
}
gradle.taskGraph.afterTask {
println "执行阶段 taskGraph.afterTask"
}
gradle.buildFinished {
println "执行阶段 buildFinished"
}
从上面的图上可以看到,每个任务的执行都会有一个钩子函数进行调用
6. 依赖管理
6.1 坐标
jar 包的标志
- group:指明 jar 包所在的分组
- name:指明 jar 包的名称
- version:指明 jar 包的版本
在 build.gradle 文件中的 dependencies 中指明依赖的 jar 包
6.2 仓库
jar 包存放的位置
-
公共仓库(中央仓库)
Gradle 没有自己的中央仓库,可配置使用 Maven的中央仓库,mavenCentral/jcenter
-
私有仓库
配置从本地 maven 仓库中获取依赖的 jar 包,不远程加载 jar 包,使用 mavenLocal
-
自定义 maven 仓库
自定义仓库来源,一般指向公司的 Maven 私服,普遍做法
-
文件仓库
本地机器上的文件路径,一般不使用,没有什么意义,因为构建工具的目的就是去除本机的影响,可以在任何地方公用同一份仓库数据,跟本机关联上就没有太大的意义。
6.3 依赖传递性
比如:A 依赖 B,如果 C 依赖 A,那么 C 依赖 B
就是因为依赖的传递性,所以才会有版本的冲突问题
-
Gradle 的自动化依赖管理流程
Gradle 工具从远程仓库下载 jar 包到本地仓库,Gradle 工具需要依赖配置文件,如果同一个 jar 经常使用会被存入到依赖缓存中。
6.4 依赖阶段配置
在 build.gradle 中的 dependencies 中配置依赖,依赖分为4中
- 源码依赖:compile,runtime
- 测试依赖:testCompile,runtime
依赖配置:
compile: 配置依赖的 jar ,测试代码编译和运行以及源码运行一定存在
runtime: 配置依赖的 jar,只有源码运行和测试运行存在
testCompile: 配置依赖的 jar ,测试代码的编译和运行存在
testRuntime: 配置依赖的 jar ,只有测试代码的运行存在
以上四种配置选用的主要判断依据是是否仅是运行阶段需要依赖或是是否仅是测试阶段需要依赖。
7. 版本冲突
管理依赖的最重要的环节就是传递性依赖过程中存在的版本冲突的问题处理。
在之前的手动管理依赖过程中经常遇到版本冲突问题,版本一冲突程序就无法正常运行。
而作为版本管理工具就应该拥有解决此问题的功能
由于传递性依赖的特点,两个不同jar包会被依赖进来,这样就会产生版本冲突的问题。
解决方法:
对比 Maven ,是按照最短路径原则和优先声明原则来处理版本冲突问题的。
在 Gradle 中,默认自动解决版本冲突的方案是选用版本较高者(顺应项目都是向下兼容)。
可以使用其他的方式解决冲突问题
- 排除某个 jar 包的传递性依赖
dependencies {
compile(group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final') {
// module 是 jar 的name
exclude group: "org.slf4j", module: "slf4j-api"
}
}
以上配置指定了项目中不再依赖任何版本的 slf4j-api 的 jar 包。自定配置需要的版本。
- 排除所有 jar 的传递性依赖(不推荐使用)
dependencies {
compile(group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final'){
transitive = false
}
}
一般需要自动依赖子项目来减少工作量,所以不推荐使用
- 手动指定某个 jar 包版本
configurations.all {
resolutionStrategy{
force 'org.slf4j:slf4j-api:1.7.24'
}
}
- 配置以下策略,不检查冲突问题
configurations.all {
resolutionStrategy{
// 修改 gradle 不自动处理版本冲突问题
failOnVersionConflict()
}
}
上面配置完成之后,如果存在依赖 jar 包版本冲突问题,Gradle 将不再自动处理,构建时会抛出异常。
此操作可查看有那些 jar 存在版本冲突问题。
8. 多项目构建
一个比较复杂的项目往往都是分为几个小的项目协同完成的,这就涉及到多个项目的构建,而多项目构建则需要把一个大项目进行 “项目模块化”。通过模块的互相协作完成整个功能。
8.1 前期准备
-
多项目构建模块划分和依赖关系
划分和关系搭建如下图
-
脚本配置范围和项目关系
再之前使用 Maven 的多项目构建时,一般需要一个 root 项目来管理所有的模块。Grandle 也一样用 root 项目来统一管理所有的模块。
所有项目(包括 root项目)的公用配置再 allprojects 中配置,所有子模块的公用配置可以在 subprojects 中类配置,build.gradle 针对项目配置项都可以配置在 allprojects/subproject 中。
-
实现需求
- 所有的项目都需要使用 Java 插件,web 项目也需要依赖 Java 环境
- web 子项目需打包为 war 包
- 统一配置公共属性,例如:group,version
- 统一管理资源库
- 通用依赖配置,例如 logback 日志功能的引入
8.2 创建项目
根据上面的关系图创建相应的模块
- 创建一个空的 gradle 项目 comment
-
在 comment 中创建两个项目 core 和 model
与上面方式一样
- 创建两个 web 项目 admin 和 web
在 comment 的 setting.gradle 文件中可以看到模块的信息
8.3 修改依赖信息
- core 依赖 model
dependencies {
// core 依赖 model 子模块
compile project(":model")
}
- admin 和 web 依赖 core 和 model 模块,因为 core 依赖了 model,所以根据依赖传递性 core 包括 model
dependencies {
compile project(":core")
}
-
所有项目使用 Java 插件,web 项目使用 war
在 comment 的 build.gradle 文件中配置公用的 java 插件和版本,删除子模块中的相关内容
// 总的一些配置信息如下 comment 的 buld.gradle
// 配置统一的信息,包括 root 项目
allprojects {
// 统一引入 java 插件和版本指定
apply plugin : "java"
sourceCompatibility = 1.8
// 统一配置公共资源,例如:group、version 这是整个项目的信息
group 'com.lss'
version '1.0-SNAPSHOT'
}
// 配置公用的资源库
subprojects {
// 放到上面的 allprojects 中也可以
repositories {
mavenCentral()
}
// 通用依赖配置,例如 logback 日志功能的引入
dependencies {
compile 'ch.qos.logback:logback-classic:1.2.3'
}
}
// 其他各个模块的 build.gradle
// admin 模块,web部分
plugins {
id 'war'
}
dependencies {
compile project(":core")
}
// web 模块,web部分
plugins {
id 'war'
}
dependencies {
compile project(":core")
}
// core 模块
plugins {
}
dependencies {
// core 依赖 model 子模块
compile project(":model")
}
// model 模块
plugins {
id 'java'
}
dependencies {
}
特点:
- comment 配置公共依赖
- 其他子模块只需要配置其独自特殊的依赖即可
- 构建单独子模块只能构建本身和其依赖模块
- 构建 comment 项目会构建所有的模块项目
在 root 项目中 build.gradle 中使用 allprojects/subprojects 来做公共的配置,所有项目使用 java,web项目用 war
属性配置文件的抽取(gradle.properties)
项目的依赖使用 project (":模块名")
9. 项目发布
项目发布可以将自己写好的模块发布给别人去使用,也可以发布到公司的公共仓库以供依赖的项目使用。
- 由 Gradle 将项目打包和创建 metadata 文件
- 将要求发布到本地仓库或者远程仓库
发布实现流程:
添加 maven-publish 插件,配置发布任务,执行发布
apply plugin :'maven-publish'
// 配置发布任务
publishing {
publications {
// publishProject 为自定义名称,可写多个发布任务
publishProject(MavenPublication) {
from components.java // 发布 jar 包
// from components.war // 发布 war 包
}
}
// 配置发布到哪里
repositories {
maven {
// 指定要上传到 maven 私服仓库
url = ""
// 认证用户和密码
credentials {
username="root"
password="admin"
}
}
}
}
- generatePomFileForPublishProjectPublication:生成 pom 文件
- publish:发布到 repositories 中指定的仓库(一般为 Maven 私服)
- publishPublishPrjectPublicationToMavenLocal:执行 publishProject 指定操作到指定仓库
- publishPublishProjectPublicationToRepository:执行 publishProject 中的操作并发布到指定仓库(私服)
- publishToMavenLocal:执行所有发布任务中的操作发布到本地 maven 仓库
10. 配置 Tomcat 插件
在 gradle 中,嵌入式 Tomcat 使用 gradle-tomcat-plugin
- 在admin模块的 build.gradle 文件中添加插件
buildscript {
repositories {
jcenter()
}
dependencies {
classpath "com.bmuschko:gradle-tomcat-plugin:2.5"
}
}
// 注意:buildscript 需要配置在所有的 plugins 配置之前,可以 apply plugin 引入插件
- 引入插件库
apply plugin: "com.bmuschko.tomcat"
// 此插件用 apply 引入可以跟其他插件引入放一块,如果用的是 plugins 来引入就只能放在 buildscript 之后
- 配置依赖信息
dependencies {
compile project(":core")
// 配置依赖 Tomcat 版本
def tomcatVersion = '8.0.42'
tomcat "org.apache.tomcat.embed:tomcat-embed-core:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-logging-juli:${tomcatVersion}",
"org.apache.tomcat.embed:tomcat-embed-jasper:${tomcatVersion}"
}
- tomcatJasper:调用 jsp 转化器,将 jsp 转为类并编译为 class 文件
- tomcatRun:启动 tomcat 服务器部署项目
- tomcatRunWar:启动 tomcat 服务器部署 war
- tomcatStop:关闭 tomcat 服务器
- 常用配置修改
// 单一配置
// tomcat.httpPort=8081 // 配置 htpp端口号
// tomcat.contextPath="/" 配置上下文路径
// 统一配置
tomcat {
httpPort = 8081 // 配置 htpp 端口号
contextPath = '/' // 配置上下文路径
}
学习参考视频
- Maven 部分:www.bilibili.com/video/BV12q…
- Gradle 部分:www.bilibili.com/video/BV1J4…