| Gradle文章目录 | |
|---|---|
| Java之Gradle【IDEA版】入门到精通(上)(一篇文章精通系列)【安装+基本使用+项目创建+项目部署+文件操作+依赖管理+插件使用】 | |
| Java之Gradle【IDEA版】入门到精通(下)(一篇文章精通系列)【创建 Springboot 项目+基于 ssm 多模块项目案例+微服务实战】 |
Gradle 是一款Google 推出的基于 JVM、通用灵活的项目构建工具,支持 Maven,JCenter 多种第三方仓库;支持传递性依赖管理、废弃了繁杂的xml 文件,转而使用简洁的、支持多种语言(例如:java、groovy 等)的 build 脚本文件。

官网地址: https://gradle.org/
学习Gradle 的原因:
Gradle官方下载安装教程页面:https://gradle.org/install/
点击releases page.

点击:complete (checksums)


解压






GRADLE_HOME
和
对应刚刚安装的位置路径




GRADLE_USER_HOME
和
对应Maven仓库的位置

gradle -v 或者 gradle --version
通过gradle -v或者 gradle --version检测是否安装成功
Gradle 7.4.2安装成功的提示文本

Gradle 项目默认目录结构和Maven 项目的目录结构一致
都是基于约定大于配置【Convention Over Configuration】。其完整项目目录结构如下所示:

Tips:
通过https://start.spring.io/,脚手架创建项目


创建gradle02目录

进入gradle02目录,在地址栏当中输入cmd


输入
gradle init





输入no


下面直接回车项目名称使用默认文件夹名称


创建成功

Gradle 常用命令说明:
| 常用gradle指令 | 作用 |
|---|---|
| gradle clean | 清空build目录 |
| gradle classes | 编译业务代码和配置文件 |
| gradle test | 编译测试代码,生成测试报告 |
| gradle build | 构建项目 |
| gradle build -x test | 跳过测试构建 |
需要注意的是:gradle 的指令要在含有build.gradle 的目录执行。
Gradle 自带的Maven 源地址是国外的,该Maven 源在国内的访问速度是很慢的,除非使用了特别的手段。
一般情况下,我们建议使用国内的第三方开放的Maven 源或企业内部自建Maven 源。
认识init.d 文件夹
我们可以在gradle 的init.d 目录下创建以.gradle 结尾的文件,.gradle 文件可以实现在build 开始之前执行,所以你可以在这个文件配置一些你想预先加载的操作。
在init.d 文件夹创建init.gradle 文件

allprojects {
repositories {
mavenLocal()
maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
maven { name "Bstek" ; url "https://nexus.bsdn.org/content/groups/public/" }
mavenCentral()
}
buildscript {
repositories {
maven { name "Alibaba" ; url 'https://maven.aliyun.com/repository/public' }
maven { name "Bstek" ; url 'https://nexus.bsdn.org/content/groups/public/' }
maven { name "M2" ; url 'https://plugins.gradle.org/m2/' }
}
}
}

设置M2_HOME的配置


拓展说明
拓展 1:启用init.gradle 文件的方法有:
1.在命令行指定文件,例如:gradle --init-script yourdir/init.gradle -q taskName。你可以多次输入此命令来指定多个init文件2.把init.gradle文件放到 USER_HOME/.gradle/ 目录下
USER_HOME/.gradle/init.d/ 目录下GRADLE_HOME/init.d/ 目录下拓展 2:仓库地址说明
mavenLocal(): 指定使用maven本地仓库,而本地仓库在配置maven时settings文件指定的仓库位置。
如E:/repository,gradle 查找jar包顺序如下:USER_HOME/.m2/settings.xml >> M2_HOME/conf/settings.xml >> USER_HOME/.m2/repository
maven { url 地址},指定maven仓库,一般用私有仓库地址或其它的第三方库【比如阿里镜像仓库地址】。
mavenCentral():这是Maven的中央仓库,无需配置,直接声明就可以使用。
jcenter():JCenter中央仓库,实际也是是用的maven搭建的,但相比Maven仓库更友好,通过CDN分发,并且支持https访问,在新版本中已经废弃了,替换为了mavenCentral()。
总之, gradle可以通过指定仓库地址为本地maven仓库地址和远程仓库地址相结合的方式,避免每次都会去远程仓库下载依赖库。
这种方式也有一定的问题,如果本地maven仓库有这个依赖,就会从直接加载本地依赖,如果本地仓库没有该依赖,那么还是会从远程下载。
但是下载的jar不是存储在本地maven仓库中,
而是放在自己的缓存目录中,默认在USER_HOME/.gradle/caches目录,
当然如果我们配置过GRADLE_USER_HOME环境变量,
则会放在GRADLE_USER_HOME/caches目录,那么可不可以将gradle caches指向maven repository。
我们说这是不行的,caches下载文件不是按照maven仓库中存放的方式。
拓展 3:阿里云仓库地址请参考:https://developer.aliyun.com/mvn/guide


Gradle Wrapper 实际上就是对 Gradle 的一层包装,用于解决实际开发中可能会遇到的不同的项目需要不同版本的 Gradle
问题。例如:把自己的代码共享给其他人使用,可能出现如下情况:
这时候,我们就可以考虑使用 Gradle Wrapper 了。这也是官方建议使用 Gradle Wrapper 的原因。实际上有了 Gradle Wrapper 之后,我们本地是可以不配置 Gradle 的,下载Gradle 项目后,使用 gradle 项目自带的wrapper 操作也是可以的。
那如何使用Gradle Wrapper 呢?
项目中的gradlew、gradlew.cmd脚本用的就是wrapper中规定的gradle版本。参见源码
而我们上面提到的gradle指令用的是本地gradle,所以gradle指令和gradlew指令所使用的gradle版本有可能是不一样的。
gradlew、gradlew.cmd的使用方式与gradle使用方式完全一致,只不过把gradle指令换成了gradlew指令。
当然,我们也可在终端执行 gradlew 指令时,指定指定一些参数,来控制 Wrapper 的生成,比如依赖的版本等,如下:
| 参数名 | 说明 |
|---|---|
| –gradle-version | 用于指定使用的Gradle版本 |
| –gradle-distribution-url | 用于指定下载Gradle发行版的url地址 |

具体操作如下所示 :
gradle wrapper --gradle-version=7.4.2:升级wrapper版本号
只是修改gradle.properties中wrapper版本,
未实际下载
gradle wrapper --gradle-version 7.4.2 --distribution-type all :关联源码用
GradleWrapper 的执行流程:
./gradlew build 命令的时候,gradlew 会读取 gradle-wrapper.properties 文件的配置信息
GRADLE_USER_HOME目录下的wrapper/dists目录中)GRADLE_USER_HOME目录下的caches目录中),下载再使用相同版本的gradle就不用下载了./gradlew 所有命令都是使用指定的 gradle 版本。如下图所示:




gradle-wrapper.properties 文件解读:
| 字段名 | 说明 |
|---|---|
| distributionBase | 下载的Gradle压缩包解压后存储的主目录 |
| distributionPath | 相对于distributionBase的解压后的Gradle压缩包的路径 |
| zipStoreBase | 同distributionBase,只不过是存放zip压缩包的 |
| zipStorePath | 同distributionPath,只不过是存放zip压缩包的 |
| distributionUrl | Gradle发行版压缩包的下载地址 |

注意:前面提到的 GRALE_USER_HOME 环境变量用于这里的Gradle Wrapper 下载的特定版本的gradle 存储目录。
如果我们没有配置过GRALE_USER_HOME 环境变量,默认在当前用户家目录下的.gradle 文件夹中。
那什么时候选择使用 gradle wrapper、什么时候选择使用本地gradle?
下载别人的项目或者使用操作以前自己写的不同版本的gradle项目时:用Gradle wrapper,也即:gradlew
什么时候使用本地gradle?
新建一个项目时: 使用gradle指令即可。
具体整合:
第一步:创建由Gradle 管理的项目







特别提示 1:使得在Terminal 中执行以gradlew 开头命令和操作图形化的IDEA 使用Gradle 版本不一定是同一个版本哦。
1.Terminal中以gradlew开头指令用的是Wrapper规定的gradle版本,wrapper中规定版本默认和idea插件中规定的版本一致。
2.而图形化的IDEA使用Gradle是本地安装的哦。
特别提示
2:目前只能是在创建项目时重新设置本地gradle,创建新项目需要重新去改。


特别提示3:当 我 们 在 gradle.build 文 件 添 加 依 赖 之 后 , 这 些 依 赖 会 在 下 载 到GRADLE_USER_HOME/caches/modules-2/files-2.1 目录下面,所以这里的 GRADLE_USER_HOME 相当于 Gradle 的本地仓库,当然也可以如下方式找到jar 包位置。

,无法自己选择创建项目是普通 java 工程还是 web 工程了【IDEA 旧版本是可以的】
,所以我们如果想创建 web 工程,只需要自己在 src/main/目录下添加webapp/WEB-INF/web.xml 及页面即可。






plugins {
id 'war'
}
group 'com.itbluebox.gradle'
version '1.0-SNAPSHOT'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework:spring-beans:4.1.7.RELEASE'
implementation 'org.springframework:spring-web:4.1.7.RELEASE'
implementation 'org.springframework:spring-webmvc:4.1.7.RELEASE'
implementation 'org.springframework:spring-tx:4.1.7.RELEASE'
implementation 'org.springframework:spring-test:4.0.5.RELEASE'
implementation 'org.springframework:spring-jdbc:4.1.7.RELEASE'
implementation 'org.mybatis:mybatis-spring:1.2.3'
implementation 'org.mybatis:mybatis:3.3.0'
implementation 'mysql:mysql-connector-java:8.0.27'
implementation 'com.alibaba:druid:1.0.15'
implementation "com.fasterxml.jackson.core:jackson-databind:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-annotations:2.2.3"
implementation "com.fasterxml.jackson.core:jackson-core:2.2.3"
implementation 'org.aspectj:aspectjweaver:1.8.6'
implementation 'log4j:log4j:1.2.17'
implementation 'org.slf4j:slf4j-api:1.7.25'
implementation 'jstl:jstl:1.2'
compileOnly 'javax.servlet:servlet-api:2.5'
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}




<web-app version="2.4"
xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<context-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:applicationContext.xmlparam-value>
context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListenerlistener-class>
listener>
<servlet>
<servlet-name>DispatcherServletservlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
<init-param>
<param-name>contextConfigLocationparam-name>
<param-value>classpath:springmvc.xmlparam-value>
init-param>
servlet>
<servlet-mapping>
<servlet-name>DispatcherServletservlet-name>
<url-pattern>/url-pattern>
servlet-mapping>
<filter>
<filter-name>CharacterEncodingFilterfilter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
<init-param>
<param-name>encodingparam-name>
<param-value>UTF-8param-value>
init-param>
filter>
<filter-mapping>
<filter-name>CharacterEncodingFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
<filter>
<filter-name>HiddenHttpMethodFilterfilter-name>
<filter-class>org.springframework.web.filter.HiddenHttpMethodFilterfilter-class>
filter>
<filter-mapping>
<filter-name>HiddenHttpMethodFilterfilter-name>
<url-pattern>/*url-pattern>
filter-mapping>
web-app>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<context:component-scan base-package="com.itbluebox">
<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:exclude-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
context:component-scan>
<context:property-placeholder location="classpath:jdbc.properties">context:property-placeholder>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="username" value="${jdbc.userName}">property>
<property name="password" value="${jdbc.password}">property>
<property name="url" value="${jdbc.jdbcUrl}">property>
<property name="driverClassName" value="${jdbc.driverClass}">property>
bean>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource">property>
bean>
<tx:annotation-driven transaction-manager="transactionManager">tx:annotation-driven>
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource">property>
<property name="configLocation" value="classpath:mybatis-config.xml">property>
bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.itbluebox.dao"/>
bean>
beans>

jdbc.jdbcUrl=jdbc:mysql://localhost:3306/test?rewriteBatchedStatements=true
jdbc.driverClass=com.mysql.cj.jdbc.Driver
jdbc.userName=root
jdbc.password=root

DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
configuration>

<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
<context:component-scan base-package="com.itbluebox" use-default-filters="false">
<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
<context:include-filter type="annotation" expression="org.springframework.web.bind.annotation.ControllerAdvice"/>
context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/"/>
<property name="suffix" value=".jsp"/>
bean>
<mvc:default-servlet-handler/>
<mvc:annotation-driven/>
beans>







添加一些数据





public class Admin {
private Integer id;
private String username;
private String email;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
@Override
public String toString() {
return "Admin{" +
"id=" + id +
", username='" + username + '\'' +
", email='" + email + '\'' +
'}';
}
}



public interface AdminMapper {
List<Admin> getAdminList();
}



@Service
public class AdminService {
@Autowired
private AdminMapper adminMapper;
@Transactional
public List<Admin> getAdminList(){
return adminMapper.getAdminList();
}
}



@Controller
@RequestMapping("/admin")
public class AdminController {
@Autowired
private AdminService adminService;
@RequestMapping("/list")
@ResponseBody
public List<Admin> getAdminList() {
System.out.println("dada");
return adminService.getAdminList();
}
}




DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.itbluebox.dao.AdminMapper">
<select id="getAdminList" resultType="com.itbluebox.bean.Admin">
select id,username,email from admin
select>
mapper>
当我们将一个 java 项目打成war 包之后,就需要部署到服务器运行,这里有两种方式:
● 我们将项目打成 war 包,部署到本地tomcat 运行:演示

选择安装好的Tomcat








http://localhost:8080/ssm/admin/list

Gretty 是一个功能丰富的 gradle 插件,用于在嵌入的 servlet 容器上运行 web 应用程序,让项目开发和部署更加简单。目前Gretty 插件已经作为 gradle 的核心库使用了,Gretty 其核心功能为:
第一步:引入 Gretty 插件
plugins {
id 'war'
id 'org.gretty' version '2.2.0'
}
第二步:指定maven 仓库
repositories {
//指定jcenter仓库,一定要放在前面
jcenter()
mavenCentral()
}
第三步:针对Gretty 插件的设置
gretty {
httpPort = 8888
contextPath = "/web"
debugPort = 5005 // default
debugSuspend = true // default
httpsEnabled = true
managedClassReload=true // 修改了类之后重新加载
//servletContainer = 'tomcat8' //如果不指定默认的servlet容器,支持tomcat7/8,默认是使用的是Jetty服务器
httpsPort = 4431
}

第四步:执行Gretty 插件

gradle appRun
如 果 大 家 想 进 一 步 了 解 的 属 性 配 置 , 比 如 Gretty 热 部 署 等 设 置 , 欢 迎 参 考 其 官 方 文 档
http://akhikhl.github.io/gretty-doc/Gretty-configuration.html。
测试任务自动检测并执行测试源集中的所有单元测试。测试执行完成后会生成一个报告。支持JUnit 和 TestNG 测试。

Gradle 对于Junit4.x 支持
dependencies {
testImplementation group: 'junit' ,name: 'junit', version: '4.12'
}
test {
useJUnit()
}
Gradle 对于Junit5.x 版本支持

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
}
test {
useJUnitPlatform()
}
注意:无论是 Junt4.x 版本还是Junit5.x 版本,我们只需在 build.gradle 目录下执行gradle test 指令,gradle 就会帮我们执行所有的加了@Test 注解的测试,并生成测试报告。




public class AppTest {
@Test
public void testMethod1(){
System.out.println("test itbluebox method1");
}
}

生成测试包



enabled值为FALSE的时候跳过测试类,为true的时候不跳过测试类
enabled(false) //跳过测试类






public class AppTest {
@Test
public void testMethod2(){
System.out.println("test itbluebox method2");
}
}

include('com/itbluebox/bj/')
或者
include('com/itbluebox/bj/**')





//include('com/itbluebox/bj/')
exclude("com/itbluebox/bj/**") //设置排除某个包下的测试类




为了让大家快速的入门gradle,本章将从整体构建脚本的角度介绍:
● 什么是 setting 文件,它有什么作用;
● 说明什么是build 文件,它又有什么作用
● 我们可以创建多少个 build
● project 和task,他们有什么作用;又是什么关系,如何配置
● 项目的生命周期
● 项目发布
● 使用Gradle 创建SpringBoot 项目等
Gradle 项目的生命周期分为三大阶段: Initialization -> Configuration -> Execution.
每个阶段都有自己的职责,具体如下图所示:

● Initialization 阶段主要目的是初始化构建, 它又分为两个子过程,一个是执行 Init Script,另一个是执行 Setting Script。
● init.gradle 文件会在每个项目 build 之前被调用,用于做一些初始化的操作,它主要有如下作用:
○ 配置内部的仓库信息(如公司的 maven 仓库信息);
○ 配置一些全局属性;
○ 配置用户名及密码信息(如公司仓库的用户名和密码信息)。
● Setting Script 则更重要, 它初始化了一次构建所参与的所有模块。
● Configuration 阶段:这个阶段开始加载项目中所有模块的 Build Script。所谓 “加载” 就是执行 build.gradle 中的语句, 根据脚本代码创建对应的 task, 最终根据所有 task 生成由 Task 组成的有向无环图(Directed Acyclic Graphs),如下:

从而构成如下有向无环树:

Execution 阶段:这个阶段会根据上个阶段构建好的有向无环图,按着顺序执行 Task【Action 动作】。
首先对 settings 文件的几点说明:
1、作用:主要是在项目初始化阶段确定一下引入哪些工程需要加入到项目构建中,为构建项目工程树做准备。
2、工程树:gradle 中有工程树的概念,类似于 maven 中的project 与module。

3、内容:里面主要定义了当前 gradle 项目及子 project 的项目名称
4、位置:必须放在根工程目录下。
5、名字:为settings.gradle 文件,不能发生变化
6、对应实例:与 org.gradle.api.initialization.Settings 实例是一一对应的关系。每个项目只有一个settings 文件。
7、关注:作为开发者我们只需要关注该文件中的include 方法即可。使用相对路径【 : 】引入子工程。
8.一个子工程只有在setting 文件中配置了才会被 gradle 识别,这样在构建的时候才会被包含进去。案例如下所示:
//根工程项目名
rootProject.name = 'root'
//包含的子工程名称
include 'subject01'
include 'subject02'
include 'subject03'
//包含的子工程下的子工程名称
include 'subject01:subproject011'
include 'subject01:subproject012'
项目名称中 “:” 代表项目的分隔符, 类似路径中的 “/”. 如果以 “:” 开头则表示相对于 root project 。然后 Gradle 会为每个带有 build.gradle 脚本文件的工程构建一个与之对应的 Project 对象。
项目实质上是 Task 对象的集合。一个 Task 表示一个逻辑上较为独立的执行过程,比如编译Java 源代码,拷贝文件, 打包Jar 文件,甚至可以是执行一个系统命令。另外,一个 Task 可以读取和设置Project 的Property 以完成特定的操作。
可参考官方文档:https://docs.gradle.org/current/userguide/tutorial_using_tasks.html
让我们来先看一个例子:

task task1{
//定义任务的配置段:在项目的生命周期的配置阶段
println "最简单的任务"
//配置任务的行为:在执行阶段执行,doFirst会在doLast执行之前执行
doFirst(){
println "task1 doFirst"
}
doLast(){
println "task1 doLast"
}
}
在文件所在的目录执行命令: gradle -i task1。
#日志选项 看详细信息
gradle -i
提示 1 :task 的配置段是在配置阶段完成
提示 2 :task 的doFirst、doLast 方法是执行阶段完成,并且doFirst 在doLast 执行之前执行。
提示 3:区分任务的配置段和任务的行为,任务的配置段在配置阶段执行,任务的行为在执行阶段执行


案例如下:doFirst、doLast 两个方法可以在任务内部定义,也可以在任务外部定义

def map=new HashMap<String,Object>();
//action属性可以设置为闭包,设置task自身的行为
map.put("action",{println "taskD.."})
task(map,"a"){
description 'taskA description '
group "atguigu"
//在task内部定义doFirst、doLast行为
doFirst {
def name = 'doFirst..'
println name
}
doLast {
def name = 'doLast..'
println name
}
}
//在task外部定义doFirst、doLast行为
a.doFirst {
println it.description
}
a.doLast {
println it.group
}
执行gradle clean

输入:gradle a

底层原理分析:

无论是定义任务自身的 action,还是添加的doLast、doFirst 方法,其实底层都被放入到一个Action 的List 中了,最初这个 action List 是空的,当我们设置了 action【任务自身的行为】,它先将action 添加到列表中,此时列表中只有一个action,后续执行doFirst 的时候doFirst 在action 前面添加,执行 doLast 的时候doLast 在action 后面添加。doFirst 永远添加在actions List 的第一位,保证添加的Action 在现有的 action List 元素的最前面;
doLast 永远都是在action List 末尾添加,保证其添加的Action 在现有的action List 元素的最后面。
一个往前面添加,一个往后面添加,最后这个action List 就按顺序形成了doFirst、doSelf、doLast 三部分的 Actions,就达到 doFirst、doSelf、doLast 三部分的 Actions 顺序执行的目的。
提示 1:其中<<代表doLast,在gradle5.x 版本之后就废弃,不能使用了,如下所示:
task hello << {
println 'Hello world!'
}
Task 之间的依赖关系可以在以下几部分设置:
方式一:参数方式依赖

task A {
doLast {
println "TaskA.."
}
}
task 'B' {
doLast {
println "TaskB.."
}
}
//参数方式依赖: dependsOn后面用冒号
task 'C'(dependsOn: ['A', 'B']) {
doLast {
println "TaskC.."
}
}
运行测试
gradle C

方式二:内部依赖

//参数方式依赖
task 'C' {
//内部依赖:dependsOn后面用 = 号
dependsOn= [A,B]
doLast {
println "TaskC.."
}
}
gradle clean

gradle C

方式三:外部依赖

task A {
doLast {
println "TaskA.."
}
}
task 'B' {
doLast {
println "TaskB.."
}
}
//参数方式依赖: dependsOn后面用冒号
task 'C'() {
doLast {
println "TaskC.."
}
}
C.dependsOn = ['A', 'B']
gradle clean

gradle C

C.dependsOn(B,'A')
当然:task 也支持跨项目依赖
在subproject01 工程的 build.gradle 文件中定义:
task A {
doLast {
println "TaskA.."
}
}
在subproject02 工程的 build.gradle 文件中定义:
task B{
dependsOn(":subproject01:A") //依赖根工程下的subject01中的任务A :跨项目依赖。
doLast {
println "TaskB.."
}
}
测试:gradle B ,控制台显示如下

拓展 1:当一个 Task 依赖多个Task 的时候,被依赖的Task 之间如果没有依赖关系,那么它们的执行顺序是随机的,并无影响。
拓展 2:重复依赖的任务只会执行一次,比如:
A->B、C
B->C
任务A 依赖任务 B 和任务 C、任务 B 依赖C 任务。执行任务A 的时候,显然任务C 被重复依赖了,C 只会执行一次。
任务执行语法:gradle [taskName...] [--option-name...]。
| 分类 | 解释 |
|---|---|
| 常见的任务(*) | ![]() |
| 项目报告相关任务 | ![]() |
| 调试相关选项 | ![]() |
| 性能选项:【备注: 在gradle.properties 中指定这些选项中的许多选项,因此不需要命令行标志】 | ![]() |
| 守护进程选项 | ![]() |
| 日志选项 | ![]() |
| 其它(*) | ![]() |
gradle run :运行一个服务,需要application 插件支持,并且指定了主启动类才能运行切换回项目2

定义main方法

指定main方法所在类的路径

运行


或者
gradle -w run

gradle run

显示项目目录结构
gradle projects

切换回web项目

gradle task 列出所选项目【当前 project,不包含父、子】的已分配给任务组的那些任务。gradle task


指定对应的任务分组

group "abc"
可以看到对应的分组信息


gradle tasks --all :列出所选项目的所有任务。
gradle tasks --group="build setup":列出所选项目中指定分组中的任务。 gradle tasks --group="abc"

gradle help --task someTask :显示某个任务的详细信息gradle help --task B

gradle dependencies :查看整个项目的依赖信息,以依赖树的方式显示

gradle properties 列出所选项目的属性列表
gradle --help 列出所有命令
gradle init --type pom :将maven 项目转换为gradle 项目(根目录执行)


gradle init --type pom


修改成功


gradle [taskName] :执行自定义任务在gradle-web项目当中

task connectionTask {
doLast {
println "TaskA.."
}
}
gradle cT

更 详 细 请 参 考 官 方 文 档 : https://docs.gradle.org/current/userguide/command_line_interface.html#sec:command_line_executing_tasks
拓展:gradle 任务名是缩写: 任务名支持驼峰式命名风格的任务名缩写,如:connectTask 简写为:cT,执行任务 gradle cT。
拓展 1:前面提到的Gradle 指令本质:一个个的task[任务], Gradle 中所有操作都是基于任务完成的。

拓展 2:gradle 默认各指令之间相互的依赖关系:

任务定义方式,总体分为两大类:一种是通过 Project 中的task()方法,另一种是通过tasks 对象的 create 或者register 方法。

task('A',{//任务名称,闭包都作为参数println "taskA..."
println "taskA..."
})
task('B'){//闭包作为最后一个参数可以直接从括号中拿出来println "taskB..."
println "taskB..."
}
task C{//groovy语法支持省略方法括号:上面三种本质是一种println "taskC..."
println "taskC..."
}
def map=new HashMap<String,Object>();
map.put("action",{
println "taskD.."
}) //action属性可以设置为闭包
task(map,"D");
tasks.create('E'){//使用tasks的create方法
println "taskE.."
}
tasks.register('f'){ //注:register执行的是延迟创建。也即只有当task被需要使用的时候才会被创建。
println "taskF "
}
gradle build

gradle f

当然:我们也可以在定义任务的同时指定任务的属性,具体属性有:

在定义任务时也可以给任务分配属性:
定义任务的时候可以直接指定任务属性,也可以给已有的任务动态分配属性:

//①.F是任务名,前面通过具名参数给map的属性赋值,以参数方式指定任务的属性信息
task(group: "itbluebox",description: "this is task B","F")
//②.H是任务名,定义任务的同时,在内部直接指定属性信息
task("H") {
group("itbluebox")
description("this is the task H")
}
//③.Y是任务名,给已有的任务 在外部直接指定属性信息
task "y"{}
y.group="itbluebox"
clean.group("itbluebox") //案例:给已有的clean任务重新指定组信息
可以在 idea 中看到:
上面自定义的那几个任务和 gradle 自带的 clean 任务已经跑到:itbluebox组了。


前面我们定义的task 都是DefaultTask 类型的,如果要完成某些具体的操作完全需要我们自己去编写gradle 脚本,
势必有些麻烦,
那有没有一些现成的任务类型可以使用呢?
有的,Gradle 官网给出了一些现成的任务类型帮助我们快速完成想要的任务,
我们只需要在创建任务的时候,指定当前任务的类型即可,
然后即可使用这种类型中的属性和API 方法了。
| 常见任务类型 | 该类型任务的作用 |
|---|---|
| Delete | 删除文件或目录 |
| Copy | 将文件复制到目标目录中。此任务还可以在复制时重命名和筛选文件。 |
| CreateStartScripts | 创建启动脚本 |
| Exec | 执行命令行进程 |
| GenerateMavenPom | 生成 Maven 模块描述符(POM)文件。 |
| GradleBuild | 执行 Gradle 构建 |
| Jar | 组装 JAR 归档文件 |
| JavaCompile | 编译 Java 源文件 |
| Javadoc | 为 Java 类 生 成 HTML API 文 档 |
| PublishToMavenRepository | 将 MavenPublication 发布到 mavenartifactrepostal。 |
| Tar | 组装 TAR 存档文件 |
| Test | 执行 JUnit (3.8.x、4.x 或 5.x)或 TestNG 测试。 |
| Upload | 将 Configuration 的构件上传到一组存储库。 |
| War | 组装 WAR 档案。 |
| Zip | 组装 ZIP 归档文件。默认是压缩 ZIP 的内容。 |

tasks.register('myClean',Delete){
delete buildDir
}
执行下面命令删除build目录
gradle myClean

自定义 Task 类型

def myTask=task MyDefinitionTask (type: CustomTask)
myTask.doFirst(){
println "task 执行之前 执行的 doFirst方法"
}
myTask.doLast(){
println "task 执行之后 执行的 doLast方法"
}
class CustomTask extends DefaultTask {
//@TaskAction表示Task本身要执行的方法@TaskAction
def doSelf(){
println "Task 自身 在执行的in doSelf"
}
}
测试:gradle MyDefinitionTask

放开注释内容

测试:gradle MyDefinitionTask

在 Gradle 中,有三种方式可以指定 Task 执行顺序:
1、dependsOn 强依赖方式
2、通过 Task 输入输出
3、通过 API 指定执行顺序
详细请参考官网:https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
gradle 的强大功能不仅仅用于定义任务的功能。例如,可以使用它在循环中注册同一类型的多个任务
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "I'm task number $counter"
}
}
}
一旦注册了任务,就可以通过 API 访问它们。例如,您可以使用它在运行时动态地向任务添加依赖项。Ant 不允许这样的事情发生。
构建 4 个任务,但是任务 0 必须依赖于任务 2 和 3,那么代表任务 2 和 3 需要在任务 0 之前优先加载。具体测试如下:
4.times { counter ->
tasks.register("task$counter") {
doLast {
println "I'm task number $counter"
}
}
}
tasks.named('task0') {
dependsOn('task2', 'task3')
}




每个任务都有一个 enabled 默认为的标志 true。将其设置为 false 阻止执行任何任务动作。禁用的任务将标记为“跳过”。

task2.enabled = false



每个任务都有一个 timeout 可用于限制其执行时间的属性。
当任务达到超时时,其任务执行线程将被中断。该任务将被标记为失败。终结器任务仍将运行。
如果 --continue 使用,其他任务可以在此之后继续运行。
不响应中断的任务无法超时。
Gradle 的所有内置任务均会及时响应超时
删除之前的内容,添加如下内容

task a() {
doLast {
Thread.sleep(1000)
println "当前任务a执行了"
}
timeout = Duration.ofMillis(500)
}
task b() {
doLast {
println "当前任务b执行了"
}
}
在控制台使用:
gradle a b 测试会发现执行a 的时候,由于a 执行超时,抛出异常,所以没有继续往下执行【b 也没执行】。

然后在控制台使用: gradle a b -continue,测试会发现a 虽然执行失败,但是 b 还是执行了。


task itbluebox {
doLast {
println "itbluebox dolast"
}
}
//根据任务名查找
tasks.findByName("itbluebox").doFirst({println "itbluebox1:北京 "})
tasks.getByName("itbluebox").doFirst({println "itbluebox2:深圳 "})
//根据任务路径查找【相对路径】
tasks.findByPath(":itbluebox").doFirst({println "itbluebox3:上海"}) tasks.getByPath(":itbluebox").doFirst({println "itbluebox4:武汉 "})
gradle itbluebox

当我们执行、依赖一个不存在的任务时,
Gradle 会执行失败,报错误信息。
那我们能否对其进行改进,当执行一个不存在的任务时,
不是报错而是打印提示信息呢?

task hello {
doLast {
println 'hello itbluebox 的粉丝们'
}
}
tasks.addRule("对该规则的一个描述,便于调试、查看等"){
String taskName -> task(taskName) {
doLast {
println "该${taskName}任务不存在,请查证后再执行"
}
}
}
测试: 使用
gradle abc hello
进行测试,此时当 abc 任务不存在时,
也不会报异常【不中断执行】
而是提示自定义的规则信息,
继续执行 hello 任务。

此外,它还可以根据不同的规则动态创建需要的任务等情况。
断言就是一个条件表达式。Task 有一个 onlyIf 方法。
它接受一个闭包作为参数,如果该闭包返回 true 则该任务执行, 否则跳过。
这有很多用途,比如控制程序哪些情况下打什么包,什么时候执行单元测试,
什么情况下执行单元测试的时候不执行网络测试等。具体案例如下所示:
task hello {
doLast {
println 'hello itbluebox的粉丝们'
}
}
hello.onlyIf { !project.hasProperty('fensi') }

测试
gradle hello

通过-P 为Project 添加fensi 属性
gradle hello -Pfensi

Gradle 允许您定义一个或多个在没有指定其他任务时执行的默认任务。代码如下所示:
defaultTasks 声明默认任务
defaultTasks 'myClean', 'myRun'
tasks.register('myClean'){
doLast {
println 'Default Cleaning!'
}
}
tasks.register('myRun') {
doLast {
println 'Default Running!'
}
}
tasks.register('other') {
doLast {
println "I'm not a default task!"
}
}

gradle -q

几种常见的文件操作方式:
● 本地文件
● 文件集合
● 文件树
● 文件拷贝
● 归档文件
各种文件操作类型的详细介绍如下所示:
使用 Project.file(java.lang.Object)方法,
通过指定文件的相对路径或绝对路径来对文件的操作,
其中相对路径为相对当前project[根project 或者子project]的目录。
其实使用 Project.file(java.lang.Object)方法创建的 File 对象就是 Java 中的 File 对象,
我们可以使用它就像在 Java 中使用一样。
示例代码如下:
//使用相对路径
File configFile = file('src/conf.xml')
configFile.createNewFile();



创建成功

// 使用绝对路径
File configFile = file('D:\\conf.xml');
configFile.createNewFile();

运行成功

创建成功

new File// new File
File configFile = new File("itbluebox.xml")
configFile.createNewFile();


运行成功并创建成功

// 使用一个文件对象
configFile = new File('src/config.xml')
println(configFile.exists())
文 件 集 合 就 是 一 组 文 件 的 列 表 , 在 Gradle 中 , 文 件 集 合 用 FileCollection 接 口 表 示 。 我 们 可 以 使 用。

//方式2.通过文件集合的方式
FileCollection fileCollection = files('src/text1.xml',['src/text2.xml','src/text3.xml'])
fileCollection.forEach(item->{
println item.name
})


创建文件

//方式2.通过文件集合的方式
FileCollection fileCollection = files('src/text1.xml',['src/text2.xml','src/text3.xml'])
fileCollection.forEach(item->{
item.createNewFile()
})

创建成功


Set set = fileCollection.files // 把文件集合转换为java中的Set类型
for (final def i in set){
println i.exists()
}



Set set2 = fileCollection as Set
for (final def i in set2){
println i.exists()
}



List list = fileCollection as List
for (final def i in list){
println i.exists()
}


println "\nAdd file set\n"
def union = fileCollection + files('src/text4.xml') // 添加或者删除一个集合
union.forEach(){File it ->
println it.name
}
println "\nMinus file set\n"
def minus = fileCollection - files('src/text1.xml')
minus.forEach(){File it ->
println it.name
}



对于文件集合我们可以遍历它;
也可以把它转换成java 类型;
同时还能使用+来添加一个集合,或使用-来删除集合。
文件树是有层级结构的文件集合,
一个文件树它可以代表一个目录结构或一 ZIP 压缩包中的内容结构。
文件树是从文件集合继承过来的,所以文件树具有文件集合所有的功能。
我们可以使用 Project.fileTree(java.util.Map)方法来创建文件树对象,
还可以使用过虑条件来包含或排除相关文件。
示例代码如下:

ConfigurableFileTree configurableFileTree = fileTree('src/main')
configurableFileTree.forEach(item-> {
println item.name
})


include 指定对应筛选文件("**/*.java"筛选以.java后缀的文件)

ConfigurableFileTree configurableFileTree = fileTree('src/main')
configurableFileTree.include("**/*.java").forEach(item-> {
println item.name
})


exclude排除不符合条件



tree = fileTree('src/main').include('**/*.java')// 第一种方式:使用路径创建文件树对象,同时指定包含的文件
//第二种方式:通过闭包创建文件树:
tree = fileTree('src/main') {
include '**/*.java'
}
tree = fileTree(dir: 'src/main', include: '**/*.java')
//第三种方式:通过路径和闭包创建文件树:具名参数给map传值
tree = fileTree(dir: 'src/main', includes: ['**/*.java', '**/*.xml', '**/*.txt'], exclude: '**/*test*/**')
tree.each {File file -> // 遍历文件树的所有文件
println file println file.name
}
我们可以使用 Copy 任务来拷贝文件,通过它可以过虑指定拷贝内容,
还能对文件进行重命名操作等。
Copy 任务必须指定一组需要拷贝的文件和拷贝到的目录,
这里使用CopySpec.from(java.lang.Object[])方法指定原文件;
使用CopySpec.into(java.lang.Object)方法指定目标目录。
示例代码如下
task copyTask(type: Copy) {
from 'src/main/resources' into 'build/config'
}
from()方法接受的参数和文件集合时files()一样。
当参数为一个目录时,该目录下所有的文件都会被拷贝到指定目录下(目录自身不会被拷贝);
当参数为一个文件时,该文件会被拷贝到指定目录;
如果参数指定的文件不存在,就会被忽略;
当参数为一个 Zip 压缩文件,该压缩文件的内容会被拷贝到指定目录。
into()方法接受的参数与本地文件时 file()一样。 示例代码如下

task copyTask(type: Copy) {
// 拷贝src/main/webapp目录下所有的文件
from 'src/main/webapp'
// 拷贝单独的一个文件
from 'src/staging/index.html'
// 从Zip压缩文件中拷贝内容
from zipTree('src/main/assets.zip')
// 拷贝到的目标目录
into 'build/explodedWar'
}


在拷贝文件的时候还可以添加过虑条件来指定包含或排除的文件,示例如下:

task copyTask(type: Copy) {
// 拷贝src/main/目录下所有的文件
from 'src/main/'
include("**/*.java","**/*.jsp","**/*.xml")
exclude("**/*.jsp")
// 拷贝到的目标目录
into 'build/explodedWar'
}


运行成功

在拷贝文件的时候还可以对文件进行重命名操作,示例如下:

task copyTask(type: Copy) {
// 拷贝src/main/目录下所有的文件
from 'src/main/java/com/itbluebox/bean/Admin.java'
// 使用一个闭包方式重命名文件
rename {
String fileName -> fileName.replace('Admin', 'AdminTest')
}
// 拷贝到的目标目录
into 'build/explodedWar'
}




在上面的例子中我们都是使用Copy 任务来完成拷贝功能的,那么有没有另外一种方式呢?答案是肯定的,那就是
Project.copy(org.gradle.api.Action)方法。下面示例展示了 copy()方法的使用方式:
task copyMethod {
doLast {
copy {
from 'src/main/webapp'
into 'build/explodedWar'
include '**/*.html' include '**/*.jsp'
}
}
}
或者使用project 对象的copy 方法:

copy {
//相对路径或者绝对路径
from file('src/main/resources/jdbc.properties') //file也可以换成new File()
into this.buildDir.absolutePath
}

执行gradle build 指令即可。去 build 目录的本地磁盘查看,就能看到。


通常一个项目会有很多的 Jar 包,我们希望把项目打包成一个 WAR,ZIP 或 TAR 包进行发布,这时我们就可以使用Zip,Tar,Jar,War 和Ear 任务来实现,不过它们的用法都一样,所以在这里我只介绍Zip 任务的示例。首先,创建一个 Zip 压缩文件,并指定压缩文件名称,如下代码所示:

apply plugin: 'java' version=1.0
task myZip(type: Zip) {
from 'src/main‘
into ‘build’ //保存到build目录中
baseName = 'myGame'
}
println myZip.archiveName




执行命令gradle -q myZip,输出结果为:

压缩为tar



最后,我们可以使用 Project.zipTree(java.lang.Object)和 Project.tarTree(java.lang.Object)方法来创建访问 Zip 压缩包的文件树对象,示例代码如下:
// 使用zipTree
FileTree zip = zipTree('someFile.zip')
// 使用tarTree
FileTree tar = tarTree('someFile.tar')
在这里,我们介绍了 Gradle 对本地文件、文件集合、文件树、文件拷贝和归档文件的操作方式。更详细的请参考官方文档:https://docs.gradle.org/current/userguide/working_with_files.html
Gradle 中的依赖分别为直接依赖,项目依赖,本地jar 依赖。案例如下:
dependencies {
//①.依赖当前项目下的某个模块[子工程]
implementation project(':subject01')
//②.直接依赖本地的某个jar文件
implementation files('libs/foo.jar', 'libs/bar.jar')
//②.配置某文件夹作为依赖项
implementation fileTree(dir: 'libs', include: ['*.jar'])
//③.直接依赖
implementation 'org.apache.logging.log4j:log4j:2.17.2'
}
(1)直接依赖本地的某个jar文件




//②.直接依赖本地的某个jar文件
implementation files('libs/foo.jar', 'libs/bar.jar')
implementation fileTree('dir':'libs',includes: ['*.jar'],excludes: ['.war'])
(2)依赖当前项目下的某个模块[子工程]



确保在settings.gradle有include 'subproject01'


//①.依赖当前项目下的某个模块[子工程]
implementation project(':subproject01')
(3)直接依赖
直接依赖:依赖的类型 依赖的组名 依赖的名称 依赖的版本号

直接依赖:在项目中直接导入的依赖,就是直接依赖implementation 'org.apache.logging.log4j:log4j:2.17.2' 上面是简写法,完整版写法如下:
implementation group: 'org.apache.logging.log4j', name: 'log4j', version: '2.17.2'
group/name/version
共同定位一个远程仓库,version 最好写一个固定的版本号,以防构建出问题,implementation 类似
maven 中的依赖的scope,对比 maven 中的依赖:
<dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
<scope>compile</scope>
</dependency>
</dependencies>
当执行 build 命令时,gradle 就会去配置的依赖仓库中下载对应的 Jar,并应用到项目中。
类似于 Maven 的 scope 标签,gradle 也提供了依赖的类型,具体如下所示:
| 类型 | 介绍 |
|---|---|
| compileOnly | 由java插件提供,曾短暂的叫provided,后续版本已经改成了compileOnly,适用于编译期需要而不需要打包的情况 |
| runtimeOnly | 由 java 插件提供,只在运行期有效,编译时不需要,比如mysql 驱动包。,取代老版本中被移除的 runtime |
| implementation | 由 java 插件提供,针对源码[src/main 目录] ,在编译、运行时都有效,取代老版本中被移除的 compile |
| testCompileOnly | 由 java 插件提供,用于编译测试的依赖项,运行时不需要 |
| testRuntimeOnly | 由 java 插件提供,只在测试运行时需要,而不是在测试编译时需要,取代老版本中被移除的testRuntime |
| testImplementation | 由 java 插件提供,针对测试代码[src/test 目录] 取代老版本中被移除的testCompile |
| providedCompile | war 插件提供支持,编译、测试阶段代码需要依赖此类jar 包,而运行阶段容器已经提供了相应的支持,所以无需将这些文件打入到war 包中了;例如servlet-api.jar、jsp-api.jar |
| compile | 编译范围依赖在所有的 classpath 中可用,同时它们也会被打包。在gradle 7.0 已经移除 |
| api | java-library 插件提供支持,这些依赖项可以传递性地导出给使用者,用于编译时和运行时。取代老版本中被移除的 compile |
| compileOnlyApi | java-library 插件提供支持,在声明模块和使用者在编译时需要的依赖项,但在运行时不需要。 |
官方文档参考:
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin:
各个依赖范围的关系和说明
https://docs.gradle.org/current/userguide/upgrading_version_6.html#sec:configuration_removal :
依赖范围升级和移除
https://docs.gradle.org/current/userguide/java_library_plugin.html#java_library_plugin:
API 和implemention 区别
https://docs.gradle.org/current/userguide/java_plugin.html#java_plugin:
执行java 命令时都使用了哪些依赖范围的依赖。提示 1:java 插件提供的功能,java-library 插件都提供

如下所示:

编译时:如果 libC 的内容发生变化,由于使用的是 api 依赖,依赖会传递,所以 libC、libA、projectX 都要发生变化,都需要重新编译,速度慢,运行时:libC、libA、projectX 中的class 都要被加载。
编译时:如果libD 的内容发生变化,由于使用的是implemetation 依赖,依赖不会传递,只有libD、libB 要变化并重新编译,速度快,运行时:libC、libA、projectX 中的class 都要被加载。
拓展 3:api 和implementation 案例分析

api 的适用场景是多module 依赖,moduleA 工程依赖了 module B,同时module B 又需要依赖了 module C,modelA 工程也需要去依赖 module C,这个时候避免重复依赖module,可以使用 module B api 依赖的方式去依赖module C,modelA 工程只需要依赖 moduleB 即可。
总之,除非涉及到多模块依赖,为了避免重复依赖,咱们会使用api,其它情况我们优先选择implementation,拥有大量的api 依赖项会显著增加构建时间。
依赖冲突是指 “在编译过程中, 如果存在某个依赖的多个版本, 构建系统应该选择哪个进行构建的问题”,如下所示:

A、B、C 都是本地子项目 module,log4j 是远程依赖。
编译时: B 用 1.4.2 版本的 log4j,C 用 2.2.4 版本的 log4j,B 和 C 之间没有冲突
打包时: 只能有一个版本的代码最终打包进最终的A对应的jar |war包,对于 Gradle 来说这里就有冲突了
依赖库

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
}

刷新上述内容后我们可以看到hibernate内依赖一个org.slf4j:slf4j-api:1.4.0
修 改 build.gradle
然后在系统当中引入implementation 'org.slf4j:slf4j-api:1.4.0'

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation 'org.hibernate:hibernate-core:3.6.3.Final'
implementation 'org.slf4j:slf4j-api:1.4.0'
}
刷新系统这时系统当中有两个相同的依赖

如上所示:
默认下,Gradle 会使用最新版本的 jar 包
【考虑到新版本的 jar 包一般都是向下兼容的】,实际开发中,还是建议使用官方自带的这种解决方案。
当然除此之外,Gradle 也为我们提供了一系列的解决依赖冲突的方法: exclude 移除一个依赖,不允许依赖传递,强制使用某个版本。

dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation('org.hibernate:hibernate-core:3.6.3.Final'){
//排除某一个库(slf4j)依赖:如下三种写法都行(选择其中一种)
exclude group: 'org.slf4j'
exclude module: 'slf4j-api'
exclude group: 'org.slf4j',module: 'slf4j-api'
}
//排除之后,使用手动的引入即可。
implementation 'org.slf4j:slf4j-api:1.4.0'
}
hibernate当中的org.slf4j被排除


dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation('org.hibernate:hibernate-core:3.6.3.Final'){
//不允许依赖传递,一般不用
transitive(false)
}
//排除之后,使用手动的引入即可
implementation 'org.slf4j:slf4j-api:1.4.0'
}

在添加依赖项时,如果设置 transitive 为false,表示关闭依赖传递。
即内部的所有依赖将不会添加到编译和运行时的类路径。
!!
设置!!强制使用对应的版本
dependencies {
testImplementation 'org.junit.jupiter:junit-jupiter-api:5.8.1'
testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.8.1'
implementation('org.hibernate:hibernate-core:3.6.3.Final')
//强制使用某个版本!!【官方建议使用这种方式】
implementation('org.slf4j:slf4j-api:1.4.0!!')
//这种效果和上面那种一样,强制指定某个版本
implementation('org.slf4j:slf4j-api:1.4.0'){
version{
strictly("1.4.0")
}
}
}


//下面我们配置,当 Gradle 构建遇到依赖冲突时,就立即构建失败
configurations.all() {
Configuration configuration ->
//当遇到版本冲突时直接构建失败
configuration.resolutionStrategy.failOnVersionConflict()
}


动态版本声明-不建议使用)(版本号用+代替或者latest:integration)
//设置最新版本
implementation('org.slf4j:slf4j-api:+')



简单的说,通过应用插件我们可以:
在项目构建过程中做很多事情,把插件应用到项目中,通常可以完成:
1、可以添加任务【task】到项目中,从而帮助完成测试、编译、打包等。
2、可以添加依赖配置到项目中。
3、可以向项目中拓展新的扩展属性、方法等。
4、可以对项目进行一些约定,如应用 Java 插件后,约定src/main/java 目录是我们的源代码存在位置,编译时编译这个目录下的Java 源代码文件。

脚本插件的本质就是一个脚本文件,使用脚本插件时通过apply from:将脚本加载进来就可以了,后面的脚本文件可以是本地的也可以是网络上的脚本文件,下面定义一段脚本,我们在 build.gradle 文件中使用它,具体如下:
version.gradle文件



ext {
company= "itbluebox"
cfgs = [
compileSdkVersion : JavaVersion.VERSION_1_8
]
spring = [
version : '5.0.0'
]
}
下面将将在构建文件中使用这个脚本文件,具体如下:
build.gradle文件

//map作为参数
apply from :"version.gradle"
task taskVersion{
doLast{
println "name is :${company},JDK is ${cfgs.compileSdkVersion},code is ${spring.version}"
}
}
运行



意义:脚本文件模块化的基础,可按功能把我们的脚本进行拆分一个个公用、职责分明的文件,然后在主脚本文件引用, 比如:将很多共有的库版本号一起管理、应用构建版本一起管理等。
对象插件之内部插件[核心插件]二进制插件[对象插件]就是实现了 org.gradle.api.Plugin 接口的插件,每个 Java Gradle 插件都有一个 plugin id。

(1)plugins DSL

plugins {
id 'java'
}
(2)project.apply(apply:map具名参数方式)
key: plugin
value: 插件id,插件全类名、插件的简类名
可通过如下方式使用一个 Java 插件:
指定插件value,id的方式

apply plugin : 'java' //map

编译成功

具名参数方式或者:
//也可以使用闭包作为project.apply方法的一个参数
apply{
plugin 'java'
}
通过上述代码就将 Java 插件应用到我们的项目中了,对于 Gradle 自带的核心插件都有唯一的 plugin id,其中 java 是Java 插件的 plugin id,这个 plugin id 必须是唯一的,可使用应用包名来保证 plugin id 的唯一性。这里的 java 对应的具体类型是 org.gradle.api.plugins.JavaPlugin,所以可以使用如下方式使用 Java 插件:
使用方式1:Map具名参数,全类名

apply plugin:org.gradle.api.plugins.JavaPlugin
org.gradle.api.plugins默认导入:
使用方式2

apply plugin: JavaPlugin

apply plugin: 'java' //核心插件,无需事先引入,

apply{
plugin: 'java'
}
使用方式3:插件的id
Gradle 中提供的二进制插件【核心插件】,
可参考: https://docs.gradle.org/current/userguide/plugin_reference.html
如果是使用第三方发布的二进制插件,一般需要配置对应的仓库和类路径,

//使用传统的应用方式
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
maven { url 'http://maven.aliyun.com/nexus/content/groups/public' }
jcenter()
}
// 此处先引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
//再应用插件
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,不必写版本号
但是如果是第三方插件已经被托管在
https://plugins.gradle.org/



网站上,就可以不用在 buildscript 里配置 classpath
依赖了,直接使用新出的 plugins DSL 的方式引用,案例如下: 使 用 plugins DSL 方 式
plugins {
id 'org.springframework.boot' version '2.4.1'
}
注意:
buildscript{}要放在build.gradle 文件的最前面,而新式plugins{}没有该限制。gradle 插件官网的第三方插件有两种使用方式,一是传统的buildscript 方式,一种是 plugins DSL 方式 。参考官方文档
参考地址:https://docs.gradle.org/current/userguide/custom_plugins.html


class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.task('hello') {
doLast {
println 'Hello from the GreetingPlugin'
}
}
}
}
// Apply the plugin
apply plugin: GreetingPlugin
Output of gradle -q hello

> gradle -q hello
Hello from the GreetingPlugin


abstract class GreetingPluginExtension {
abstract Property<String> getMessage()
GreetingPluginExtension() {
message.convention('Hello from GreetingPlugin')
}
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
def extension = project.extensions.create('greeting', GreetingPluginExtension)
// Add a task that uses configuration from the extension object
project.task('hello') {
doLast {
println extension.message.get()
}
}
}
}
apply plugin: GreetingPlugin
// Configure the extension
greeting.message = 'Hi from Gradle'

Output of gradle -q hello
> gradle -q hello
Hi from Gradle


interface GreetingPluginExtension {
Property<String> getMessage()
Property<String> getGreeter()
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
def extension = project.extensions.create('greeting', GreetingPluginExtension)
project.task('hello') {
doLast {
println "${extension.message.get()} from ${extension.greeter.get()}"
}
}
}
}
apply plugin: GreetingPlugin
// Configure the extension using a DSL block
greeting {
message = 'Hi'
greeter = 'Gradle'
}

我们直接执行 hello 任务./gradle hello 即可,这种方式实现的插件我们一般不使用,
因为这种方式局限性太强,只能本Project,而其他的Project 不能使用
buildSrc 是Gradle 默认的插件目录,编译 Gradle 的时候会自动识别这个目录,将其中的代码编译为插件。
buildSrc 的 java Module,



buildSrc 从 included modules 移除,
重新构建

然后只保留 build.gradle和src/main 目录,


其他全部删掉,注意名字一定是 buildSrc,不然会找不到插件。

apply plugin: 'groovy' // 必 须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter()
mavenCentral() //必须
}
//把项目入口设置为src/main/groovy
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
}
}
src/main 下创建代码入口目录,如下:Text.groovy,注意文件后缀为groovy,文件要引入package com.itbluebox




import org.gradle.api.Plugin
import org.gradle.api.Project
class Text implements Plugin<Project>{
@Override
void apply(Project project) { project.task("itbluebox"){
doLast{
println("自定义itbluebox插件")
}
}
}
}
接下来在 main 目录下创建
resources 目录,
在 resources 目录下创建 META-INF 目录,
在 META-INF 目录下创建
gradle-plugins 目录,在gradle-plugins 目录下创建properties 文件






properties 文件可以自己命名,
但是要以.properties 结尾,
比如
com.itbluebox.plugin.properties,
其 com.itbluebox.plugin 就是定义的包名路径

最后需要在properties 文件中指明我们实现插件的全类名
implementation-class=com.itbluebox.Text
到目前为止我们的插件项目已经写完了,

在 module 引入我们写的插件
apply plugin:'com.itbluebox.plugin',然后执行插件的Task,gradle itbluebox

这种形式的写法,在我们整个工程的module 都可以使用,但也只是限制在本工程,其他工程不能使用。
第二种写插件的方式他只能在本工程中使用,
而其他的项目工程不能使用,
有时候我们需要一个插件在多个工程中使用,
这时候我们就需要把插件上传maven 中。

然后在settings.gradle 文件中使用include




apply plugin: 'groovy' // 必 须
apply plugin: 'maven-publish'
dependencies {
implementation gradleApi() //必须
implementation localGroovy() //必须
}
repositories {
google()
jcenter()
mavenCentral() //必须
}
//把项目入口设置为src/main/groovy
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
}
}
publishing {
publications {
myLibrary(MavenPublication) {
groupId = 'com.itbluebox.plugin' //指定GAV坐标信息
artifactId = 'library'
version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories {
maven { url "$rootDir/lib/release" }
//发布项目到私服中
// maven {
// name = 'myRepo' //name属性可选,表示仓库名称,url必填
// //发布地址:可以是本地仓库或者maven私服
// //url = layout.buildDirectory.dir("repo")
// //url='http://my.org/repo'
// // change URLs to point to your repos, e.g. http://my.org/repo
// //认证信息:用户名和密码
// credentials {
// username = 'joe'
// password = '****'
// }
// }
}
}


设置多个组件

aaaa(MavenPublication) {
groupId = 'com.itbluebox.plugin' //指定GAV坐标信息
artifactId = 'library'
version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}




build.gradle 文件中将插件添加到 classpath:
buildscript {
repositories {
maven { url "$rootDir/lib/release" }
}
dependencies {
classpath "com.itbluebox.plugin:library:1.1"
}
}
apply plugin: 'java'
//是在 itblueboxplugin 中定义的插件 ID
apply plugin: 'com.itbluebox.plugin'
gradle build 指令就会在控制台看到自定义插件的输出,说明自定义插件就已经生效了。最后,至于如何写一个插件,能帮助项目更加自动化或者便捷化,是值得大家未来需要长期思考、关注、努力的点。
apply plugin: '插件名'

当我们在工程中引入插件后,
插件会自动的为我们的工程添加一些额外的任务来完成相应的功能。
以Java 插件为例,当我们加入java 插件之后,就加入了如下功能:
具体大家可通过gradle tasks 查看加入某个插件前后的区别。
说明:Gradle 中的任务依赖关系是很重要的,
它们之间的依赖关系就形成了构建的基本流程。
第三点:工程目录结构

一些插件对工程目结构有约定,所以我们一般遵循它的约定结构来创建工程,这也是 Gradle 的“约定优于配置”原则。例如java 插件规定的项目源集目录结构如下所示:
如果要使用某个插件就应该按照它约定的目录结构设置,这样能大大提高我们的效率,当然各目录结构也可以自己定义。
比如前面我们提到的 依赖的类型[依赖管理]部分,不同的插件提供了不同的依赖管理。
例如:Java 插件会为工程添加一些常用的属性,我们可以直接在编译脚本中直接使用。

当然还有一些其他属性

参考官网:https://docs.gradle.org/current/userguide/plugin_reference.html,
以Java 插件为例,讲解需要关注的几点:


第一点:我们要关注插件使用
plugins {
id 'java'
}
第二点:我们要关注插件的功能
我们可通过官方文档介绍了解某个插件功能或者百度、再或者大家可以通过 gradle tasks 查看加入java 插件前后的区别。
第三点:项目布局
一般加入一个插件之后,插件也会提供相应的目录结构,例如:java 插件的目录结构

当然这个默认的目录结构也是可以改动的例如:

sourceSets {
main {
java {
srcDirs = ['src/itbluebox/java']
}
resources {
srcDirs = ['src/itbluebox/resources']
}
}
}

main 目录不高亮

也可以设置针对test目录

test {
java {
srcDirs = ['src/bbb/java']
}
resources {
srcDirs = ['src/bbb/resources']
}
}


test的目录的高亮也消失了

可以手动创建对应标准的目录结构


全选











修改编译后文件输出目录结构

output.resourcesDir = file('out/bin');
java.destinationDirectory.set(file('out/bin'))



指定源码和字节码

sourceCompatibility(JavaVersion.VERSION_1_8)
targetCompatibility(JavaVersion.VERSION_1_8)
也可设置源集的属性等信息。

第四点:依赖管理:以java 插件为例,提供了很多依赖管理项
源集依赖关系配置

测试源集依赖关系配置第五点:额外的属性和方法:
可参考官方文档: sourceCompatibility(JavaVersion.VERSION_1_8)
● build.gradle 是一个gradle 的构建脚本文件,支持java、groovy 等语言。
● 每个project 都会有一个build.gradle 文件,该文件是项目构建的入口,可配置版本、插件、依赖库等信息。
● 每个build 文件都有一个对应的 Project 实例,对build.gradle 文件配置,本质就是设置Project 实例的属性和方法。
● 由于每个 project 都会有一个build 文件,
那么Root Project 也不列外.Root Project 可以获取到所有 Child Project,
所以在Root Project 的 build 文件中我们可以对Child Project 统一配置,比如应用的插件、依赖的maven 中心仓库等。

build 文件中常见的属性和方法如下所示:
代码参考:
//指定使用什么版本的JDK语法编译源代码,跟编译环境有关,在有java插件时才能用
sourceCompatibility = 1.8
//指定生成特定于某个JDK版本的class文件:跟运行环境有关,在有java插件时才能用
targetCompatibility = 1.8
//业务编码字符集,注意这是指定源码解码的字符集[编译器]
compileJava.options.encoding "UTF-8"
//测试编码字符集,注意这是指定源码解码的字符集[编译器]
compileTestJava.options.encoding "UTF-8"
//编译JAVA文件时采用UTF-8:注意这是指定源码编码的字符集【源文件】
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
//编译JAVA文件时采用UTF-8:注意这是指定文档编码的字符集【源文件】
tasks.withType(Javadoc) {
options.encoding = "UTF-8"
}
提示 1:group+name+version 类似于 maven 的group+artifactId+version
提示 2:encoding 解决业务代码与测试代码中文乱码问题

repositories {
//gradle中会按着仓库配置的顺序,从上往下依次去对应的仓库中找所需要的jar包:
//如果找到,则停止向下搜索,如果找不到,继续在下面的仓库中查找
//指定去本地某个磁盘目录中查找:使用本地file文件协议:一般不用这种方式
maven { url 'file:///D:/repos/mavenrepos3.5.4'}
maven { url "$rootDir/lib/release" }
//指定去maven的本地仓库查找
mavenLocal()
//指定去maven的私服或者第三方镜像仓库查找
maven { name "Alibaba" ; url "https://maven.aliyun.com/repository/public" }
maven { name "Bstek" ; url "https://nexus.bsdn.org/content/groups/public/" }
//指定去maven的远程仓库查找:即 https://repo.maven.apache.org/maven2/
mavenCentral()
//去google仓库查找
google()
}
allprojects
是对所有project(包括Root Project+ child Project[当前工程和所有子工程])
的进行统一配置,

allprojects {
tasks.create('hello') {
doLast {
task ->
println "project name is $task.project.name"
}
}
}
gradle clean
gradle build
而subprojects
是对所有Child Project 的进行统一配置。测试如下:
subprojects {
hello.doLast{
task->
println "here is subprojects $task.project.name"
}
}
通常在 subprojects 和allprojects 中:
allprojects(){ //本质Project中的allprojects方法,传递一个闭包作为参数。
apply plugin: 'java'
ext {
junitVersion = '4.10'
..
}
task allTask{
...
}
repositories {
...
}
dependencies {
...
}
}
subprojects(){
…//同上面allprojects中的方法。
}
拓展 1: 如果是直接在根project 配置 repositories 和 dependencies 则只针对根工程有效。
拓展:我们也可以在对单个 Project 进行单独配置:
project('subject01') {
task subject01 {
doLast {
println 'for subject01'
}
}
}
执行gradle build 指令即可查看测试效果。
Project 和 Task 都允许用户添加额外的自定义属性,要添加额外的属性,通过应用所属对象的ext 属性即可实现。
添加之后可以通过 ext 属性对自定义属性读取和设置,如果要同时添加多个自定义属性,可以通过 ext 代码块:

//自定义一个Project的属性
ext.age = 18
//通过代码块同时自定义多个属性
ext {
phone = 19292883833
address="itbluebox"
}
task extCustomProperty {
//在task中自定义属性
ext {
desc = "奥利给"
}
doLast {
println " 年 龄 是 :${age}"
println "电话是:${phone}"
println "地址是:${address}"
println "itbluebox:${desc}"
}
}
测试:通过 gradle extCustomProperty
输出结果为:

拓展 1: ext 配置的是用户自定义属性,而gradle.properties 中一般定义 系统属性、环境变量、项目属性、JVM 相关配置信息。
例如
gradle.properties 文件案例:加快构建速度的,
gradle.properties 文件中的属性会自动在项目运行时加载。


设置此参数主要是编译下载包会占用大量的内存,可能会内存溢出

org.gradle.jvmargs=-Xms4096m -Xmx8192m
# 开启gradle缓存
org.gradle.caching=true
# 开启并行编译
org.gradle.parallel=true
# 启用新的孵化模式
org.gradle.configureondemand=true
# 开启守护进程
org.gradle.daemon=true
详细请参考:https://docs.gradle.org/current/userguide/build_environment.html#sec:gradle_configuration_properties
buildscript 里是gradle 脚本执行所需依赖,分别是对应的 maven 库和插件。案例如下:
import org.apache.commons.codec.binary.Base64
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'commons-codec',
name: 'commons-codec',
version: '1.2'
}
}
tasks.register('encode')
{
doLast {
def byte[] encodedString = new Base64().encode('hello world\n'.getBytes())
println new String(encodedString)
}
}
需要注意的是:
1. buildscript{}必须在 build.gradle 文件的最前端。
2. 对于多项目构建,项目的 buildscript ()方法声明的依赖关系可用于其所有子项目的构建脚本。
3. 构建脚本依赖可能是 Gradle 插件。
案例如下所示:
//老式apply插件的引用方式,使用apply+buildscript
buildscript {
ext {
springBootVersion = "2.3.3.RELEASE"
}
repositories {
mavenLocal()
maven {
url 'http://maven.aliyun.com/nexus/content/groups/public'
}
jcenter()
}
//此处引入插件
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java' //核心插件,无需事先引入
apply plugin: 'org.springframework.boot' //社区插件,需要事先引入,才能应用,不必写版本号
接下来,将咱们写好的模块发布发布到公司的私服以供别人使用,如下所示:


plugins {
//id 'java-library' //如果发布war包,需要war插件,java-library支持带源码、文档发布
id 'maven-publish'
}

//带源码和javadoc的发布:需要'java-library'插件支持:它是java的升级版,java插件的功能java-library都有
//javadoc.options.encoding="UTF-8"
//java {
// withJavadocJar()
// withSourcesJar()
//}
publishing {
publications {
myLibrary(MavenPublication) {
groupId = 'org.gradle.sample' //指定GAV坐标信息artifactId = 'library'
version = '1.1'
from components.java//发布jar包
//from components.web///引入war插件,发布war包
}
}
repositories {
//本地仓库位于USER_HOME/.m2/repository mavenLocal()
//发布项目到私服中
maven {
name = 'myRepo' //name属性可选,表示仓库名称,url必填
//发布地址:可以是本地仓库或者maven私服
//url = layout.buildDirectory.dir("repo")
// change URLs to point to your repos, e.g. http://my.org/repo
def releasesRepoUrl = layout.buildDirectory.dir('repos/releases')
def snapshotsRepoUrl = layout.buildDirectory.dir('repos/snapshots')
url = version.endsWith('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl
//认证信息:用户名和密码
// credentials {
// username = 'joe'
// password = '****'
// }
}
}
}
执 行 发 布 命 令 , 将 项 目 发 布 到 本 地 仓 库 或 者 远 程 仓 库 。
常 见 的 发 布 指 令 有 :
generatePomFileForPubNamePublication: 生成 pom 文件
publishPubNamePublicationToRepoNameRepository:发布项目到指定仓库,如果没有仓库名,默认为maven
publishPubNamePublicationToMavenLocal: 将 PubName 发布复制到本地 Maven 仓库中包括POM 文件和其他元数据。
publish: 发布到 repositories 中指定的仓库(为比如 Maven 私服)
publishToMavenLocal: 执行所有发布任务中的操作发布到本地 maven 仓库
【默认在用户家目录下的 .m2/repository】。


发布成功

发布war包

apply plugin: 'war'

from components.web///引入war插件,发布war包



带源码和javadoc的发布

//带源码和javadoc的发布:需要'java-library'插件支持:它是java的升级版,java插件的功能java-library都有
javadoc.options.encoding="UTF-8"
java {
withJavadocJar()
withSourcesJar()
}




生命周期中的这些钩子函数都是由 gradle 自动回调完成的,
利用这些钩子函数可以帮助我们实现一些我们想要的功能。

Gradle 在生命周期各个阶段都提供了用于回调的钩子函数:
Gradle 初始化阶段:
● 在 settings.gradle 执行完后,会回调 Gradle 对象的 settingsEvaluated 方法
● 在构建所有工程 build.gradle 对应的Project 对象后,也既初始化阶段完毕,会回调 Gradle 对象的projectsLoaded 方法
Gradle 配置阶段:
● Gradle 会循环执行每个工程的 build.gradle 脚本文件
● 在执行当前工程build.gradle 前,会回调Gradle 对象的 beforeProject 方法和当前Project 对象的 beforeEvaluate 方法, 虽然 beforeEvalute 属于 project 的生命周期, 但是此时 build script 尚未被加载, 所以 beforeEvaluate 的设置依 然要在 init script 或 setting script 中进行,不要在 build script 中使用 project.beforeEvaluate 方法。
● 在执行当前工程 build.gradle 后,会回调 Gradle 对象的afterProject 方法和当前Project 对象的 afterEvaluate 方法
● 在所有工程的 build.gradle 执行完毕后,会回调 Gradle 对象的 projectsEvaluated 方法
● 在构建 Task 依赖有向无环图后,也就是配置阶段完毕,会回调TaskExecutionGraph 对象的 whenReady 方法
Gradle 执行阶段:
● Gradle 会循环执行Task 及其依赖的 Task
● 在当前 Task 执行之前,会回调 TaskExecutionGraph 对象的 beforeTask 方法
● 在当前 Task 执行之后,会回调 TaskExecutionGraph 对象的 afterTask 方法当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法。
提示:Gradle 执行脚本文件的时候会生成对应的实例,主要有如下几种对象:
1、Gradle 对象:在项目初始化时构建,全局单例存在,只有这一个对象
2、Project 对象:每一个build.gradle文件 都会转换成一个 Project 对象,类似于maven中的pom.xml文件
3、Settings 对象:settings.gradle 会转变成一个 settings 对象,和整个项目是一对一的关系,一般只用到include方法
4.、Task对象: 从前面的有向无环图中,我们也可以看出,gradle最终是基于Task的,一个项目可以有一个或者多个Task
钩子函数代码演示:项目目录结构如下:

在root project 的settings.gradle 文件中添加:

gradle.settingsEvaluated { //1.settingsEvaluated钩子函数,在初始化阶段完成
println "settingsEvaluated"
}
gradle.projectsLoaded { //2.projectsLoaded钩子函数,在初始化阶段完成
println "projectsLoaded"
}
//声明一个变量:表示当前项目名,在每次执行某个项目的beforeEvaluate方法时先给projectName变量赋值
//这样方便在:gradle.beforeProject和afterProject两个钩子函数使用。
def projectName=""
gradle.addProjectEvaluationListener( new ProjectEvaluationListener(){
//3.执行各个project的beforeEvaluate:在配置阶段完成@Override
void beforeEvaluate(Project project)
{
projectName=project.name
println "${project.name} Project beforeEvaluate"
}
//5.执行各个project的afterEvaluate:在配置阶段完成@Override
void afterEvaluate(Project project, ProjectState projectState) {
println "${project.name} Project afterEvaluate"
}
});
gradle.beforeProject {
//4.执行各个project的beforeProject:在配置阶段完成
println "${projectName} beforeProject..."
}
gradle.afterProject {
//6.执行各个project的afterProject:在配置阶段完成
println "${projectName} afterProject..."
}
//7.所有工程的 build.gradle 执行完毕后,回调 Gradle 对象的 projectsEvaluated 方法:在配置阶段完成
def rootProjectName=rootProject.getName()
gradle.projectsEvaluated {
println "${rootProjectName} projectsEvaluated..."
}
//8.配置阶段完毕后,回调 TaskExecutionGraph 对象的 whenReady 方法:在配置阶段完成
gradle.taskGraph.whenReady {
println "${rootProjectName} taskGraph whenReady..."
}
//9.在当前Task执行之前,会回调 TaskExecutionGraph 对象的 beforeTask方法:在执行阶段完成
gradle.taskGraph.beforeTask { task ->
println "this is the task ${task.name} of the project ${task.getProject().name} beforeTask.."
}
//10.在当前Task执行之后,会回调 TaskExecutionGraph 对象的 afterTask方法:在执行阶段完成
gradle.taskGraph.afterTask { task ->
println "this is the task ${task.name} of the project ${task.getProject().name} afterTask.."
}
//11.当所有的 Task 执行完毕后,会回调 Gradle 对象的 buildFinish 方法:在执行阶段完成
gradle.buildFinished {
println "${rootProjectName} buildFinished..."
}
在root 的build.gradle 文件中添加:
task A {
println "root taskA"
doFirst(){
println "root taskA doFirst"
}
doLast(){
println "root taskA doLast"
}
}
在subject01 的 build.gradle 文件中添加:
task B {
println "SubProject01 taskB"
doFirst(){
println "SubProject01 taskB doFirst"
}
doLast(){
println "SubProject01 taskB doLast"
}
}
在subject02 的 build.gradle 文件中添加:
//task C 在上面
task C{
// 依赖task D
dependsOn 'D'
println "SubProject02 taskC"
doFirst(){
println "SubProject02 taskC doFirst"
}
doLast(){
println "SubProject02 taskC doLast"
}
}
//task D 在下面
task D {
println "SubProject02 taskD"
doFirst(){
println "SubProject02 taskD doFirst"
}
doLast(){
println "SubProject02 taskD doLast"
}
}
测试:在root 工程的根目录执行:
gradle C .就能看到 gradle 生命周期的三个阶段,
及每个阶段执行的钩子函数、
还有在执行阶段有依赖关系的任务的执行顺序问题。
拓展 1:在settings.gradle 中添加监听器,查看task 有向无环图:
gradle.taskGraph.addTaskExecutionGraphListener(new TaskExecutionGraphListener() {
@Override // 生成有向无环图
void graphPopulated(TaskExecutionGraph taskExecutionGraph) {
taskExecutionGraph.allTasks.forEach(task->{//核心逻辑:通过taskExecutionGraph获得所有的task
taskExecutionGraph.allTasks.forEach(releaseTask->{
println "itbluebox:" + releaseTask.getProject().name + ":" + releaseTask.name
})
})
}
})
测试:在root 工程根目录下执行:gradle C。查看测试结果:
拓展 2: 计算 Gradle 构建过程中各个阶段的耗时:需要注意,这里只是计算了初始化阶段的 settings 文件,并没有计算init.gradle 初始化的时间。
def projectName=rootProject.getName() //定义项目名
long beginOfSetting = System.currentTimeMillis() //初始化阶段开始时间
def beginOfConfig // 配置阶段开始时间
def configHasBegin = false //配置阶段是否开始了,只执行一次
def beginOfProjectConfig = new HashMap() //存放每个build.gradle 执行之前的时间
def beginOfTaskExecute // 执行阶段开始时间
gradle.projectsLoaded { // 初始化阶段执行完毕
println "${projectName}工程 初始化总耗时 ${System.currentTimeMillis() - beginOfSetting} ms"
}
//build.gradle 执行前
gradle.beforeProject { Project project ->
if(!configHasBegin){
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put( project, System.currentTimeMillis())
}
//build.gradle 执行后
gradle.afterProject { Project project ->
def begin = beginOfProjectConfig.get(project)
if(project.name == projectName) {
println "根工程${projectName} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
}else{
println "子工程${project.name} 配置阶段耗时:${System.currentTimeMillis() - begin} ms"
}
}
gradle.taskGraph.whenReady {
//配置阶段完毕
println "整个${projectName}项目在配置阶段总耗时:${System.currentTimeMillis() - beginOfConfig} ms"
beginOfTaskExecute = System.currentTimeMillis()
}
//执行阶段开始
gradle.taskGraph.beforeTask {Task task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println "${task.name}在执行阶段耗时:${System.currentTimeMillis() - task.ext.beginOfTask} ms"
}
}
gradle.buildFinished {//执行阶段完毕
println " 执行阶段总耗时:${System.currentTimeMillis() - beginOfTaskExecute} ms"
println " 整个构建过程耗时:${System.currentTimeMillis() - beginOfSetting} ms"
}