一个简洁的博客网站:http://lss-coding.top,欢迎大家来访
学习娱乐导航页:http://miss123.top/
Maven 是一个 Apache 软件基金会组织维护的一款专门为 Java 项目提供构建和依赖管理支持的工具。
官网:https://maven.apache.org/index.html
在程序开发中,越来越多的框架诞生,或者说框架的复杂程度也越来越高,项目中所需要依赖的 jar 包越来越多。在一个项目中的一个模块可能就需要依赖上百个 jar 包都是非常正常的。
要想获取这些 jar 包我们可以通过官网去获得,但是官网都是英文的界面,并且不一定能直接的找到下载的入口。还有一种可能就是从网上下载别的小伙伴上传的 jar 包,问题就是这些个 jar 可能打包的不规范,在使用过程中会出现各种各样的问题。而使用 Maven 之后,依赖对应的 jar 包能够自动进行下载、方便、快捷并且规范。
框架中使用的 jar 包,不仅数量庞大,而且彼此之间都是有着千丝万缕的关系的。依赖关系程度相当复杂,已经上升到了完全不能靠人工手动解决的程度。另外就是 jar 包在使用的过程中可能会产生冲突,进一步增加了我们在 jar 包使用过程中的难度。
**构建:**Java 项目开发过程中,构建指的是使用原材料生产产品的过程
构建过程中包含的主要环节:
**依赖:**如果 A 工程里面用到了 B 工程的类、接口、配置文件等这样的资源,那么我们就可以说 A 依赖 B。例如:
依赖管理中要解决的具体问题:
没有注意的构建
可以不适用 Maven,但是项目必须是要进行构建的。当我们使用 IDEA 进行开发时,构建是 IDEA 替我们进行完成的。
脱离 IDE 环境仍需要进行构建
管理规模庞大的 jar 包,需要专门的工具
脱离 IDE 环境执行构建操作,需要专门的工具
下载解压安装
下载地址:https://maven.apache.org/download.cgi
下载完成之后解压即可
配置Maven 本地仓库
在解压中找到 conf/setting.xml 配置文件,修改下载 jar 包的位置
配置下载镜像仓库
Maven 下载 jar 包默认访问的境外的中央仓库,而国外的网站速度很慢。改成 阿里云提供的镜像仓库,访问国内的网站,可以让 Maven 下载 jar 包的时候速度更快。在 conf/setting.xml 配置文件中的 mirror 标签配置
<mirror>
<id>nexus-aliyunid>
<mirrorOf>*mirrorOf>
<name>Nexus aliyunname>
<url>http://maven.aliyun.com/nexus/content/groups/publicurl>
mirror>
配置 JDK 基础版本
如果按照默认配置运行,Java 工程使用的默认版本是 1.5,修改配置,在 conf/setting.xml 文件中的 profile 标签中修改
<profiles>
<profile>
<id>jdk-1.8id>
<activation>
<activeByDefault>trueactiveByDefault>
<jdk>1.8jdk>
activation>
<repositories>
<maven.compiler.source>1.8maven.compiler.source>
<maven.compiler.target>1.8maven.compiler.target>
<maven.compiler.compilerVersion>1.8maven.compiler.compilerVersion>
repositories>
profile>
profiles>
使用三个向量在 Maven 仓库中唯一定位到一个 jar 包
坐标:
<dependency>
<groupId>mysqlgroupId>
<artifactId>mysql-connector-javaartifactId>
<version>8.0.28version>
dependency>
上面坐标对应的 jar 包在 Maven 本地仓库中的位置
Maven 本地的仓库根目录\mysql\mysql-connector-java\8.0.28\mysql-connector-java.8.0.28
<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>4.0.0modelVersion>
<groupId>com.lssgroupId>
<artifactId>demo01artifactId>
<version>1.0-SNAPSHOTversion>
<packaging>jarpackaging>
<name>demo01name>
<url>http://maven.apache.orgurl>
<properties>
<project.build.sourceEncoding>UTF-8project.build.sourceEncoding>
properties>
<dependencies>
<dependency>
<groupId>junitgroupId>
<artifactId>junitartifactId>
<version>4.12version>
<scope>testscope>
dependency>
dependencies>
project>
POM
POM:Project Object Model,项目对象的模型。和 POM 类似的是 DOM,文档对象模型,他们都是模型化思想的具体体现
模块化思想:POM 表示将工程抽象为一个模型,再用程序中的对象来描述这个模型。这样我们就可以用程序来管理项目了。我们在开发过程中,最基本的做法就是将现实生活中的事物抽象为模型,然后封装模型相关的数据作为一个对象,这样就可以在程序中计算与实现事物相关的数据。
对应的配置文件:POM 理念集中体现在 Maven 工程根目录下的 pom.xml 配置文件中。所以 pom.xml 配置文件就是 Maven 工程的核心配置文件。
目录结构
这些目录在 超级 pom 里面定义好的,还有一个 target 目录专门存放构建操作输出的结果。
Maven 为了构建过程能够尽可能自动化完成,所以必须约定目录结构的作用。例如:Maven 执行编译操作,必须先去 java 源程序目录读取 Java 源代码,然后执行编译,最后把编译结果存放到 target 目录
约定大于配置:Maven 对于目录结构这个问题,没有采用配置的方式,而是基于约定。这样会让我们在开发过程中非常方便。如果每次创建 Maven 工程后,还需要针对各个目录的 位置进行详细的配置,那肯定非常麻烦。目前开发领域技术发展趋势:约定大于配置,配置大于编码。
构建项目:mvn archetype:generate
清理操作:mvn clean
主程序编译:mvn compile
测试程序编译:mvn test-compile
测试操作:mvn test
打包操作:mvn package
安装:mvn install
查看依赖jar包列表:mvn dependency:list
创建 Web 工程:mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-webapp -DarchetypeVersion=1.4
明确:从来只有 Web 工程依赖 Java 工程,没有反过来 Java 工程依赖 Web 工程。本质上来说,Web 工程依赖的 Java 工程其实就是 Web 工程里导入的 jar 包。最终 Java 工程会变成 jar 包,放在 Web 工程的 WEB-INF/lib 目录下。
<dependency>
<groupId>com.lssgroupId>
<artifactId>demo01artifactId>
<version>1.0-SNAPSHOTversion>
dependency>
标签的位置:dependencies/dependency/scop
标签的可选值:compile/test/provided/system/runtime/import
main目录 | test 目录 | 开发过程 | 部署到服务器 | |
---|---|---|---|---|
compile | 有 | 有 | 有 | 有 |
test | 无 | 有 | 有 | 无 |
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 中
<dependency>
<groupId>org.springframework.cloudgroupId>
<artifactId>spring-cloud-dependenciesartifactId>
<version>Hoxton.SR9version>
<type>pomtype>
<scope>importscope>
dependency>
system
在 Windows 系统环境下开发,想要导入 D 判断下第一个 jar 包到我们的项目中,此时可以将依赖配置为 system范围
该方式引入的依赖完全不具有可移植性,所以不要使用。他引入的包只能是在本地的,部署上线是不行的。
<dependency>
<groupId>com.lssgroupId>
<artifactId>demo01artifactId>
<version>1.0version>
<systemPath>D:\java\maven\demo01.jarsystemPath>
<scope>systemscope>
dependency>
runtime
专门用于编译时不需要,但是运行时需要的 jar 包。比如:编辑时我们根据接口调用方法,但是实际运行时需要的是接口的实现类。
<dependency>
<groupId>com.lssgroupId>
<artifactId>demo01artifactId>
<scope>runtimescope>
<optional>trueoptional>
dependency>
A 依赖 B,B 依赖 C,那么在 A 没有配置对 C 的依赖情况下, A 里面能不能直接使用C ?
传递的原则
在 A 依赖 B,B 依赖C 的前提下,C 是否能够传递到 A,取决于 B 依赖 C 时使用的依赖范围。
当 A 依赖 B,B 依赖 C 而且 C 可以传递到 A 的时候,A 不想要 C,需要在 A 里面把 C 排除掉。而往往这种情况都是为了避免 jar 包之间的冲突。所以配置依赖的排除其实就是阻止某些 jar 包的传递。因为这样的 jar 包传递过来会和其他 jar 包冲突。
<dependency>
<groupId>com.lssgroupId>
<artifactId>demo01artifactId>
<version>1.0-SNAPSHOPversion>
<scope>compilescope>
<exclusions>
<exclusion>
<groupId>org.apache.commonsgroupId>
<artifactId>commons-pool2artifactId>
exclusion>
exclusions>
dependency>
Maven 工程之间,A 工程继承 B工程
本质上是 A 工程的 pom.xml 中的配置继承了 B 工程中的 pom.xml 的配置。
在父工程中统一管理项目中的依赖信息,具体来说就是依赖信息的版本。
背景:
需求:
通过在父工程中为整个项目维护依赖信息的组合既保证了整个项目使用规范、准确的 jar 包;又能够将以往的经验沉淀下来,节约时间和精力。
父工程的打包方式必须是 pom
<parent>
<groupId>groupId>
<artifactId>artifactId>
<version>version>
parent>
<packaging>pompackaging>
<modules>
<module>module01module>
<module>module02module>
<module>module03module>
modules>
<properties>
<标签名>版本号标签名>
properties>
<dependencyManagement>
<dependencies>
<dependency>
<version>${标签名}version>
dependency>
dependencies>
dependencyManagement>
编写一套符合要求、开发各种功能都正常工作的依赖组合并不容易。如果公司里面已经有人总结了成熟的组合方案,那么再开发新项目时,如果不适用原有的依赖,而是重新摸索,会浪费大量的时间。为了提高效率,我们可以使用工程继承的机制,让成熟的依赖组合方案能够保留下来。如上图所示:公司级的父工程中管理的就是成熟的依赖组合方案,各个新项目、子项目各取所需即可。
使用一个 “总工程” 将各个 “模块工程” 汇集起来,作为一个整体对应完整的项目。
概念的对应关系:
从继承角度来看:
从聚合关系角度来看:
好处:
<modules>
<module>module01module>
<module>module02module>
<module>module03module>
modules>
**注意:**循环依赖问题,如果 A 工程依赖 B 工程,B 工程依赖 C 工程,C 工程又反过来依赖 A 工程,那么在执行构建操作时会报错误:The projects in the reactor contain a cyclic reference;
为了构建过程自动化完成,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将最终的包复制到远程的仓库,以让其他开发人员与项目共享或部署到服务器上运行。 |
从 Spring-boot-starter 的 pom 文件可以看到:除了坐标标签、dependencies 标签之外,还有 description、url、organization、licenses、developers、scm、issueManagement 等这些描述项目信息的标签。
所以从项目管理的角度来看,Maven 提供了如下功能:
在上面的介绍中,Maven 在构建过程中有很多默认的设定。例如:源文件存放目录、测试源文件存放的目录、构建输出的目录…等。但是其实这些要素都是被 Maven 定义过的。定义的位置就是:超级 pom
Super POM 是 Maven 的默认 POM。除非明确设置,否则所有 POM 都扩展 Super POM,这意味着 Super POM 中指定的配置由您为项目创建的 POM 继承。
所以我们自己的 POM 即使没有明确指定一个父工程(父POM),其实也是默认继承了超级 POM。就好比 Java 类默认继承了 Object 类。
和 Java 类一样,POM 之间其实也是单继承的。如果我们给一个 POM 指定了父 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:隐含的一层,但是实际上真正生效的一层。
在实际使用 Maven 的过程中,发现 build 标签有时候有,有时候没,怎么回事?其实通过有效 POM 我们能够看到,build 标签的相关配置其实一直都在,只是在我们需要定制构建过程的时候才会通过配置 build 标签覆盖默认值或补充配置。这一点我们可以通过打印有效 POM 来看到。
本质来说:我们配置的 build 标签都是对超级 POM 配置的叠加。那我们又为什么要在默认配置的基础上叠加呢?很简单,在默认配置无法满足需求的时候定制构建过程。
build 标签的子标签大致分为三个主体部分:
目录名 | 作用 |
---|---|
sourceDirectory | 主体源程序存放目录 |
scriptSourceDirectory | 脚本源程序存放目录 |
testSourceDirectory | 测试源程序存放目录 |
outputDirectory | 主体源程序编译结果输出目录 |
testOutputDirectory | 测试源程序编译结果输出目录 |
resources | 主体资源文件存放目录 |
testResources | 测试资源文件存放目录 |
directory | 构建结果输出目录 |
备用插件管理
pluginManagement 标签存放着几个极少用到的插件:
通过 pluginManagement 标签管理起来的插件就像 dependencyManagement 一样,子工程使用时可以省略版本号,起到在父工程中统一管理版本的效果。
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
<version>2.6.2version>
plugin>
<build>
<plugins>
<plugin>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-maven-pluginartifactId>
plugin>
plugins>
build>
plugins 标签存放的是默认声明周期中实际会用到的插件,这些插件都不陌生,所以抛开插件本身不谈,看看plugin 标签的结构:
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-pluginartifactId>
<version>3.1version>
<executions>
<execution>
<id>default-compileid>
<phase>compilephase>
<goals>
<goal>compilegoal>
goals>
execution>
<execution>
<id>default-testCompileid>
<phase>test-compilephase>
<goals>
<goal>testCompilegoal>
goals>
execution>
executions>
plugin>
plugins>
build>
坐标部分:
artifactId 和 version 标签定义了插件的坐标,作为 Maven 的自带插件这里省略了 groupId。
执行部分
exeutions 标签内可以配置多个 execution 标签,executions 标签内:
在插件目标的执行过程中可以进行配置,例如maven-site-plugin 插件的 site 目标:
configuration 标签内进行配置时使用的标签是插件本身自定义的。
<execution>
<id>default-testCompileid>
<phase>test-compilephase>
<goals>
<goal>testCompilegoal>
goals>
<configuration>
<outputDirectory>输出路径outputDirectory>
<reportPlugins>
<reportPlugin>
<groupId>org.apache.maven.pluginsgroupId>
<artifactId>maven-project-info-reports-pluginartifactId>
reportPlugin>
reportPlugins>
configuration>
execution>
每个插件能够做那些设置都是各个插件自己规定的,不能一概而论。
在当前项目依赖其他 jar 包的时候,可能其他两个包里面有不同版本的两个相同的 jar ,这个时候就需要进行仲裁了
最短路径原则
下图中,对于 pro25-module-a 来说,Maven 会采纳 1.2.12 版本
路径相同时先声明者有限
下图中,采用那个版本,取决于 pro29-module-x 中,对 pro30-module-y 和 pro31-module-z 两个模块的依赖哪一个先声明。
下载: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
查看状态
编订依赖列表的程序员经常会面对 jar 包冲突的问题,初次设定一组依赖,因为尚未经过验证,所以确实有可能存在各种问题,需要做有针对性的调整。那么谁来做?我们不希望看到的是团队中每个程序员都需要自己去找依赖,即使是做同一个项目,每个模块也各加各的依赖,没有统一管理。那前人踩过的坑,后人还要再踩一遍。而且大家用的依赖有很多细节都不一样,版本五花八门,这就让事情变得更加的复杂。
所以虽然初期需要根据项目开发和实际运行情况对依赖管理配置不断调整,最终确定一个各方面都 OK 的版本。但是一旦确定下来,放在父工程中做依赖管理,各个子模块各取所需,这样基本上就能很好的避免问题的扩散。
即使开发中遇到了新问题,也可以回到源头检查、调整 dependencyManagement 配置的列表一一而不是每个模块都要改。
由于实际开发时我们往往都会整合使用很多大型框架,所以一个项目中哪怕只是一个模块也会涉足到大量 jar 包。数以百计的 jar 包要彼此协调、精密配合才能保证程序正常运行。而规模如此庞大的 jar 包组合在一起难免会有磕磕碰碰。最关键的是由于 jar 包冲突所导致的问题非常诡异。
一般来说,由于我们自己编写代码、配置文件写错所导致的问题通常能够在异常信息中看到我们自己类的全类名或配置文件的所在路径。如果整个错误信息中完全没有我们负责的部分,全部是框架、第三方工具包里面的类报错,这往往就是 jar 包的问题所引起的。
而具体的表现形式中,主要体现为找不到类或找不到方法。
抛出异常:找不到类
抛出异常:找不到方法
程序找不到符合预期的方法,这种情况多见于通过反射调用方法,所以经常会导致:
没报错但是结果不对
两个 jar 包中的类分别实现了一个接口,这本来是很正常的。但是问题在于:由于没有注意命名规范,两个不同实现类恰巧是同一个名字
例子:项目中部分模块使用了 log4j 打印日志;其他模块使用 logback,编译运行都不会冲突,但是会引起日志服务降级,让你的 log 配置文件失效。比如:你指定了 error 级别输出,但是冲突就会导致 info、debug 都在输出。
很多情况下常用框架之间的整合容易出现的冲突问题都有人总结过了,拿抛出的异常搜索一下基本上就可以直接找到对应的 jar 包。
不管具体使用的是什么工具,基本思路无非是两步:
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.pluginsgroupId>
<artifactId>maven-enforcer-pluginartifactId>
<version>1.4.1version>
<executions>
<execution>
<id>enforce-dependenciesid>
<phase>validatephase>
<goals>
<goal>display-infogoal>
<goal>enforcegoal>
goals>
execution>
executions>
<dependencies>
<dependency>
<groupId>org.codehaus.mojogroupId>
<artifactId>extra-enforcer-rulesartifactId>
<version>1.0-beta-4version>
dependency>
dependencies>
<configuration>
<rules>
<banDuplicateClasses>
<findAllDuplicates>truefindAllDuplicates>
banDuplicateClasses>
rules>
configuration>
plugin>
plugins>
pluginManagement>
build>
在 Terminal 中执行命令:mvn clean package enforcer:enforce
石器时代
依赖管理:最开始的时候如果需要依赖第三方的 jar 包,需要把 jar 放到 lib 目录中,如果 jar 包多了不好管理,很容易出现版本冲突的问题,每个项目需要使用到同一个 jar 包都得拷贝一份到项目中,很占用存储空间。繁琐!
测试:每个功能都需要书写测试类,在 main 中写测试非常麻烦,能不写一般都是不会写,就算写了也是很简单的测试而已
打包:通过IDE 打包然后上传到服务器或者放入依赖的项目中,非常的繁琐。
上传:通过一些文件上传工具上传 jar 包到项目中
以上这些操作都是比较繁琐的,但是很多操作都不能省略,像这种重复而又没有技术含量的操作非常的无聊,所以就有了构建工具的出现。
工业时代
使用构建工具
依赖管理:可以做依赖的管理操作,将 jar 包统一管理起来,更加的清晰和方便,而且仅仅是依赖管理,没有拷贝 jar 包到项目中。
自动化:可以自动测试、打包、发布。
机器能做的事,绝不自己手动去做,大大提高开发效率。
Ant(Apache Ant)
软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于 Java 环境中的软件开发
Maven
从 Ant 中借用了绝大多数构建任务,其突出的是依赖管理和项目发布
Gradle
使用 Groovy 语言构建脚本,不再像 Maven 一样使用 XML
一个开源的项目自动化构建工具,建立在 Apache Ant 和 Apache Maven 概念的基础上,并引入了基于 Groovy的特定领域语言(DSL),而不再使用 XML 形式管理构建脚本
DSL(Domain Specific Language)定义:针对某一个领域,具有受限表达性的一种计算机程序设计语言。只针对一个领域做出来的简洁语言,而非为了通用而设计。
Groovy 是基于 Java 虚拟机的一种敏捷的动态语言,它是一种成熟的 OOP 编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。
下载地址:https://groovy.apache.org/download.html
下载好之后进行解压
进入到 bin 目录打开 cmd 窗口
输入命令 groovy -v
查看版本信息和依赖的 JVM 的信息
可以打开 Groovy 自带的一个控制台
通过命令 groovyConsole
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
}
}
package com.lss
// 以脚本的形式来编写代码
// 使用脚本的方式来创建学生对象
Student student = new Student()
// 调用 getter/setter 方法
student.setName("张山")
println student.getName()
// 使用点的方式进行赋值和获取字段值
student.email = "[email protected]"
println student.email
// 调用无权限修饰符的字段的 getter/setter
student.setAge(18)
println student.getAge()
// 调用具名构造器
Student student1 = new Student("name": "小白", email: "xiaobia")
println student1.getName()
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
闭包简单理解就是 {} 括起来的代码块,跟方法类似,可带参数和不带参数
闭包可以赋给一个变量也可以做参数传递给一个方法,在方法中调用闭包
{
[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}"
}
Gradle 下载地址:https://gradle.org/releases/
下载好之后进行解压缩,然后配置环境变量
然后通过命令 gradle -v
检测环境是否配置成功
IDEA 中自带了 gradel 和 groovy 插件,所以可以直接使用,创建项目的时候直接选择即可
本人使用的 IDEA 版本是 2020.3.4 版本,所以创建好项目之后在 file -> setting -> Build,Exec… -> BuildTools -> Gradle 中配置本地的 Gradle 环境
在 main 文件夹下的 java 文件夹下创建一个类,编写一段代码,然后点击右侧的的 Gradle 中的 build 构建项目
在左侧可以看到 build.gradle
文件,该文件是项目的核心配置文件,相当于 maven 的 pom.xml
通过点击右侧的 build 按钮可以打包构建项目
在控制栏输入命令就可以执行这个打包好的项目,下面有乱码问题,后续解决
创建的时候选择 Web
选项
构建脚本指的就是 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 也是先执行被依赖的任务。
一个 project 代表一个正在构建的组件(jar/war文件),当构建开始时,Gradle 会基于 build.gradle 实例化一个 org.gradle.api.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其他常用配置:
总之所有的配置都会被封装到 Project 对象中。
每个任务在构建执行过程中会被封装成 org.gradle.api.Task 对象,主要包括任务的动作和任务依赖,任务动作定义了一个原子操作,可以定义依赖其他任务、动作顺序和执行条件。
任务主要操作动作:
在 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 时就执行。
任务是 Gradle 构建中的两个基本概念之一,而任务的定义和使用有多种形式。
// 定义任务语法
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 中的代码
// 只有调用任务的时候才会执行
// 定义任务时参数依赖
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.times {val ->
task "tk${val}" doFirst {
println "The task is task${val}"
}
}
task t5{
ext.myProperties = "The properties Task"
doFirst {
println "t5 execute ${myProperties}"
}
}
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 方法,所有在初始化阶段无法回调到。
// 项目构建之前的钩子方法
gradle.settingsEvaluated {
println "初始化阶段 settingsEvaluated"
}
gradle.projectsLoaded {
println "初始化阶段 projectsLoaded"
}
gradle.beforeProject {
println "初始化阶段 beforeProject"
}
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"
}
从上面的图上可以看到,每个任务的执行都会有一个钩子函数进行调用
jar 包的标志
在 build.gradle 文件中的 dependencies 中指明依赖的 jar 包
jar 包存放的位置
公共仓库(中央仓库)
Gradle 没有自己的中央仓库,可配置使用 Maven的中央仓库,mavenCentral/jcenter
私有仓库
配置从本地 maven 仓库中获取依赖的 jar 包,不远程加载 jar 包,使用 mavenLocal
自定义 maven 仓库
自定义仓库来源,一般指向公司的 Maven 私服,普遍做法
文件仓库
本地机器上的文件路径,一般不使用,没有什么意义,因为构建工具的目的就是去除本机的影响,可以在任何地方公用同一份仓库数据,跟本机关联上就没有太大的意义。
比如:A 依赖 B,如果 C 依赖 A,那么 C 依赖 B
就是因为依赖的传递性,所以才会有版本的冲突问题
Gradle 的自动化依赖管理流程
Gradle 工具从远程仓库下载 jar 包到本地仓库,Gradle 工具需要依赖配置文件,如果同一个 jar 经常使用会被存入到依赖缓存中。
在 build.gradle 中的 dependencies 中配置依赖,依赖分为4中
依赖配置:
**compile:**配置依赖的 jar ,测试代码编译和运行以及源码运行一定存在
**runtime:**配置依赖的 jar,只有源码运行和测试运行存在
**testCompile:**配置依赖的 jar ,测试代码的编译和运行存在
**testRuntime:**配置依赖的 jar ,只有测试代码的运行存在
以上四种配置选用的主要判断依据是是否仅是运行阶段需要依赖或是是否仅是测试阶段需要依赖。
管理依赖的最重要的环节就是传递性依赖过程中存在的版本冲突的问题处理。
在之前的手动管理依赖过程中经常遇到版本冲突问题,版本一冲突程序就无法正常运行。
而作为版本管理工具就应该拥有解决此问题的功能
由于传递性依赖的特点,两个不同jar包会被依赖进来,这样就会产生版本冲突的问题。
解决方法:
对比 Maven ,是按照最短路径原则和优先声明原则来处理版本冲突问题的。
在 Gradle 中,默认自动解决版本冲突的方案是选用版本较高者(顺应项目都是向下兼容)。
可以使用其他的方式解决冲突问题
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 包。自定配置需要的版本。
dependencies {
compile(group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final'){
transitive = false
}
}
一般需要自动依赖子项目来减少工作量,所以不推荐使用
configurations.all {
resolutionStrategy{
force 'org.slf4j:slf4j-api:1.7.24'
}
}
configurations.all {
resolutionStrategy{
// 修改 gradle 不自动处理版本冲突问题
failOnVersionConflict()
}
}
上面配置完成之后,如果存在依赖 jar 包版本冲突问题,Gradle 将不再自动处理,构建时会抛出异常。
此操作可查看有那些 jar 存在版本冲突问题。
一个比较复杂的项目往往都是分为几个小的项目协同完成的,这就涉及到多个项目的构建,而多项目构建则需要把一个大项目进行 “项目模块化”。通过模块的互相协作完成整个功能。
多项目构建模块划分和依赖关系
划分和关系搭建如下图
脚本配置范围和项目关系
再之前使用 Maven 的多项目构建时,一般需要一个 root 项目来管理所有的模块。Grandle 也一样用 root 项目来统一管理所有的模块。
所有项目(包括 root项目)的公用配置再 allprojects 中配置,所有子模块的公用配置可以在 subprojects 中类配置,build.gradle 针对项目配置项都可以配置在 allprojects/subproject 中。
根据上面的关系图创建相应的模块
在 comment 中创建两个项目 core 和 model
与上面方式一样
在 comment 的 setting.gradle 文件中可以看到模块的信息
dependencies {
// core 依赖 model 子模块
compile project(":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 (“:模块名”)
项目发布可以将自己写好的模块发布给别人去使用,也可以发布到公司的公共仓库以供依赖的项目使用。
发布实现流程:
添加 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"
}
}
}
}
在 gradle 中,嵌入式 Tomcat 使用 gradle-tomcat-plugin
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}"
}
// 单一配置
// tomcat.httpPort=8081 // 配置 htpp端口号
// tomcat.contextPath="/" 配置上下文路径
// 统一配置
tomcat {
httpPort = 8081 // 配置 htpp 端口号
contextPath = '/' // 配置上下文路径
}
学习参考视频
- Maven 部分:https://www.bilibili.com/video/BV12q4y147e4?p=1
- Gradle 部分:https://www.bilibili.com/video/BV1J4411C7Q5?p=1