0. 前言
1. 项目构建历史
1. 石器时代(自己动手)
- 依赖管理
最开始的时候如果需要依赖第三方的 jar 包,需要把jar放到lib目录中,如果jar包多了不好管理,很容易出现版本冲突问题。每个项目需要使用到同一个jar包都得拷贝一份到项目中,很占用存储空间。
- 测试
每个功能都需要书写测试类,在main中写测试非常麻烦,能不写一般都是不会写。就算写了也是很简单的测试下而已。
- 打包
通过eclipse打包然后传递到服务器或者放入依赖的项目中。
- 上传
通过一些文件上传工具(FTP) 上传jar包到项目下。
以上这些操作都是比较频繁的,但是很多操作都不能省略,像这种重复而又没有技术含量的操作是很无聊的。所以就有了构建工具的出现。
2. 工业时代(构建工具)
- 依赖管理
可以做依赖管理,将jar包统一管理起来,更加的清晰和方便,而且仅仅是依赖管理,不需要拷贝jar包到项目中。
- 自动化
可以自动执行测试、打包、发布任务。
原则 **机器能做的事情,绝不自己动手去做.大大提高开发效率!!!**
2. 主流的构建工具
构建工具 | 诞生年份 | 描述 |
---|---|---|
Apache Ant | 2000年 | 软件编译、测试、部署等步骤联系在一起加以自动化的一个工具,大多用于Java环境中的软件开发。 |
Apache Maven | 2004年 | 从Ant中借用了绝大多数构建任务,其突出的是依赖管理和项目发布。 |
Gradle | 2012年 | 使用Groovy、Kotlin语言(Gradle-4.x+支持Kotlin)构建脚本,不再像Maven一样使用XML。 |
3. Gradle简介
一个开源的项目自动化构建工具,建立在Apache Ant和Apache Maven概念的基础上,并引入了基于 Groovy 的特定领域语言(DSL),而不再使用XML形式管理构建脚本。
DSL(Domain Specific Language)定义:针对某一领域,具有受限表达性的一种计算机程序设计语言。只针对一个领域做出来的简洁语言,而非为了通用而设计。
1. Groovy基本介绍
1. 什么是Groovy?
Groovy是基于Java虚拟机的一种敏捷的动态语言,它是一种成熟的OOP(面向对象)编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。
2. Groovy优缺点
与Java相比,Groovy的不同点或优势如下:
- Groovy完全兼容Java语法,可做脚本也可做类。
- 分号是可选的,一般不加分号,以换行作为结束。
- 类、方法、字段都是公共的,没有访问权限限制。
- 默认生成具名(名值对)参数构造器“key:value”。
- 字段不定义访问权限时,编译器自动给字段添加“getter/setter”方法。
- 字段可使用点来获取,无访问权限的也可使用“getter/setter”来操作。
- 方法可省略“return”关键字,自动检索最后一行的结果作为返回值。
- 空值比较不会有“NullPointerException”异常抛出。
3. Groovy高级特性
- assert断言:可以用“assert”代替之前Java的断言语句。
- 可选类型:可使用类JavaScript的弱类型,可使用“def”来表示任意类型。
- 方法调用:调用带参方法时可省略括号。
- 字符串定义:字符串定义有三种方式:单引号、双引号、三个单引号。
- 集合API:集合的定义和使用更加简单,API和Java有所不同,但兼容Java API。
- 闭包 :Groovy的一大特性,跟方法类似的代码块,可赋给一个变量也可以做为参数传递给一个方法,像普通方法一样调用。
总结 综合了ES6、Scala、Python、Java等语言特性。
2. Groovy代码实操
1. 下载安装Groovy
参考:https://www.yuque.com/polaris-docs/knowledge/windows8-groovy-2.4.12
2. idea中创建Groovy工程
3. 代码实操
1. 基本语法
Groovy与Java比较:
JavaBean的定义
- 可省略分号。
- 可省略“getter/setter”。
- 可省略“return”。
- 无权限修饰符自动生成“getter/setter”。
默认带有具名构造器“key:value”。 ```groovy / 类Java风格版 / class Student {
private String userName // 可省略分号 private String email; // 省略“getter/setter” int age; // 无权限修饰符自动生成“getter/setter”
String getUserName(){ userName; // 可省略“return” //return userName; }
}
/ Groovy风格版 / class Student {
def userName
def email
def age
}
- **创建对象和操作字段**
- “getter/setter”方式操作字段。
- 点(“.”)方式调用字段。
- 调用自动生成的“getter/setter”方法。
- 调用具名构造器“key:value”。
```groovy
/** 使用脚本方式创建对象(去掉*.groovy文件中“class”关键字即可) **/
Student stu =new Student();
// 调用“getter/setter”
stu.setUserName("groovy")
// 等同于:println(stu.getUserName())
println stu.getUserName()
// 点(“.”)方式调用字段
stu.email="groovy@apache.org"
println stu.email
// 调用自动生成的“getter/setter”方法
stu.setAge(23)
println stu.getAge()
// 调用具名构造器“key:value”
Student stu2=new Student(userName: "groovy",email: "groovy@apache.org",age: 18)
println stu2.userName
- 空值比较不抛出空指针异常
// 空值使用
stu2.email=null
println stu2.email.equals(null)
2. 高级特性
主要: 基本语法,字符串,集合,闭包(重点)。 ```groovy println “==============基本语法==============” // 变量声明 def name=”polaris” age=18 // 带参数方法的调用,可以省略括号 println(name+”:”+age) println name+”:”+age // 断言 assert age==18
println “============== 字符串 ==============” // 字符串定义有三种方式:单引号、双引号、三个单引号 str1=’polaris’ // 定义普通字符串 str2=”polaris:${str1}” // 可引用变量 str3=’’’ 第1行 第2行 。。。。 还可以有更多行~ ‘’’ println str1 println str2 println str3
println “============== 集合 ==============” // list集合,使用[]来声明集合 def list=[‘a’,’b’,’c’] // 给集合添加数据 list.add(‘d’) list << ‘e’ // groovy特有的list添加方式 println list println list.getClass() // 使用java.util.ArrayList
// Map映射使用[key:value]方式定义 def map=[‘username’:’groovy’,’age’:18] // 给map添加数据 map.put(‘email’,’groovy@apache.org’) map.gender=’boy’
println map
- **闭包**
闭包简单理解就是“{ }”括起来的代码块,跟方法类似,可带参和不带参。闭包可以赋给一个变量也可以当做参数传递给一个方法,在方法中调用闭包。
- **语法**
```groovy
/** 闭包的定义格式 **/
{
[param1,param2... ->]
/** 执行体**/
}
// [] 括起来的内容表示可有可无
// 闭包调用:
// 需要使用变量来接收再调用
def fun = {[param1,param2... ->] /*执行体*/ }
fun([param]) /*或*/ fun.call([param])
- 示例 ```groovy package org.polaris.groovy.demo
// 定义不带参合带参闭包 def c1={ println “不带参数闭包” } def c2={ x -> println “带参:${x}” }
// 定义指定类型,接受不带参闭包的方法 def m1(Closure closure){ //closure() closure.call() }
// 定义无指定类型,接受带参闭包的方法 def m2(closure){ //closure(‘groovy’) closure.call(‘groovy’) }
println “============== 调用方法传入已经定义好的闭包” m1 c1 m2 c2
println “============== 调用方法传入匿名闭包” m1 ({ println ‘不带参数闭包’ })
m2 { y -> println “带参:${y}” }
提示 调用方法时带入新定义闭包是Gradle使用最多的操作。
<a name="ufGqE"></a>
# 3. Gradle项目构建实操
<a name="jStYn"></a>
## 1. 下载安装Gradle
_**说明:Gradle自带Groovy,如:Gradle-4.6内嵌Groovy-2.4.12。**_<br />参考:[https://www.yuque.com/polaris-docs/knowledge/windows8-gradle-4.6](https://www.yuque.com/polaris-docs/knowledge/windows8-gradle-4.6)<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608626527054-ddf6773d-4d5c-4d61-ab8e-c7888a644670.png#align=left&display=inline&height=236&margin=%5Bobject%20Object%5D&name=image.png&originHeight=236&originWidth=539&size=34381&status=done&style=none&width=539)
<a name="DFCyQ"></a>
## 2. 简单项目构建
_**说明:idea自带Gradle和Groovy插件,所以可以直接使用,Eclipse则需要安装相关插件。**_
<a name="WE4Ft"></a>
### 1. JavaSE独立工程
**步骤如下:**
1. 菜单依次进入:“file” -> “new project” -> “Gradle” -> 勾选“Java”。
1. 录入工程坐标信息(同Maven)。
- **GroupId**:org.polaris.gradle
- **ArtifactId**:gradleJava
- **Version**:1.0.0
3. 勾选“Use local graedle distribution”,并选择“Gradle home”(指定到自行安装的Gradle主目录)。
![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608631587886-0a818ca0-4257-4f28-984c-210fd93ded8d.png#align=left&display=inline&height=475&margin=%5Bobject%20Object%5D&name=image.png&originHeight=700&originWidth=1100&size=43725&status=done&style=none&width=746)
4. 创建类“App”(自定义,路径:src/main/java)。
5. 打包:点击右侧边栏的Gradle面板,“Gradle” -> “gradleJava” -> “Tasks” -> “build ”-> “jar”,生成jar位置:“build/libs/gradleJave-1.0.0.jar”。
![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608632243281-173ef60e-48c3-4911-8b90-0f56765f3f95.png#align=left&display=inline&height=502&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1004&originWidth=1920&size=202786&status=done&style=none&width=960)
6. 执行jar,进入Terminal。
```bash
java -classpath build\libs\gradleJava-1.0.0.jar org.polaris.gradle.App
2. JavaEE独立工程
步骤如下:
- 菜单依次进入:“file” -> “new project” -> “Gradle” -> 勾选“Java”和“Web”。
- 录入工程坐标信息(同Maven)。
- GroupId:org.polaris.gradle
- ArtifactId:gradleWeb
- Version:1.0.0
- 勾选“Use local graedle distribution”,并选择“Gradle home”(指定到自行安装的Gradle主目录)。
- 打包:点击右侧边栏的Gradle面板,“Gradle” -> “gradleWeb” -> “Tasks” -> “build ”-> “war”,生成war位置:“build/libs/gradleWeb-1.0.0.jar”。
4. Gradle核心概念**
Gradle构建脚本中最重要的两个概念是project和Task,任何一个Gradle构建都由一个或者多个project组成,每个project包括许多的构建部分,可以是一个jar包,也可以是一个web应用,也可以是多个 jar的整合,可以部署应用和搭建环境。每个project由一个或多个Task组成,每个Task表示在构建执行过程中的一个原子操作。如编译、打包、生成 javadoc、发布到仓库等操作。Project、Task之间的相互关系如下图:
说明:“Project1”依赖“Project2”,所以需先构建“Project2”。“Project2”中有三个任务“Task F/G/H”,由于依赖关系,先执行“Task G”,再执行“Task F”,再执行“Task H”。“Project1”中的Task也是先执行被依赖的任务,先执行“Task B/E”,再执行“Task A”,再执行“Task C”。
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中属性的其他配置方式。
2. Task
每个任务在构建执行过程中会被封装成“org.gradle.api.Task”对象。主要包括任务的动作和任务依赖。任务动作定义了一个原子操作。可以定义依赖其他任务,动作顺序和执行条件。那任务有哪些相关操作呢?
- 任务主要操作动作
- dependsOn:依赖相关操作。
- doFirst:任务执行之前执行的方法。
- doLast、<<:任务执行之后执行的方法。
- 示例
```groovy
task t1 {
doFirst{
} println ‘hello t1’ doLast{println 't1 do first'
} }println 't1 do last'
task t2 (dependsOn:’t1’){ doFirst{ println ‘t2 do first’ } println ‘hello t2’ doLast{ println ‘t2 do last’ } }
执行结果如下:
HH:mm:ss: Executing task ‘t2’…
hello t1 hello t2 :t1 t1 do first t1 do last :t2 t2 do first t2 do last
BUILD SUCCESSFUL in *s
_**注意:直接定义在任务下的代码会在配置project时执行,其他时候不执行**__**,**__**就算依赖也不执行。**__**只有在doFirst或doLast中配置的操作才会在调用任务或者依赖执行时调用**__**。**__**所以以后自定义任务执行代码需要写在**__**doFirst**__**或**__**doLast**__**中,除非先在构建Project时就执行**__**。**_
- **自定义任务**
任务是Gradle构建中的两个基本概念之一,而任务的定义和使用有多种形式。
- **任务定义**
- [x] 定义任务基本语法。
- [x] 任务的常见定义方式。
```groovy
// 1. 定义任务基本语法
task tName1 {
println "直接带闭包的定义方式"
}
task tName2(){
println "带括号的定义方式"
}
// 2. 常见定义方式
task t1 {
doFirst{
println '任务t1调用前执行'
}
}
task t2 (dependsOn:'t1'){
doLast{
println '任务t2调用后执行'
}
}
// doLast的简写方式,该语法在Gradle-5.x中不再兼容
task t3 << {
println 'doLast的简写,任务t3调用后执行'
}
- 任务的依赖配置
- 定义任务时参数依赖。
task t2 (dependsOn:'t1'){
doLast{
println '任务t2调用后执行'
}
}
- 定义任务时参数依赖。
[x] 任务内部依赖。
task t2 {
dependsOn:'t1'
doFirst{
println '任务t2调用前执行'
}
}
[x] 外部添加依赖。
t2.dependsOn t1
[x] 通过闭包添加依赖。 ```groovy task taskX { doLast{
println 'taskX '
} }
/ taskX依赖任务名称以“lib”打头的任务 /
taskX.dependsOn {
tasks.findAll {
task -> task.name.startsWith(‘lib’)
}
}
task lib1 { doLast{ println ‘lib1’ } }
task lib2 { doLast{ println ‘lib2’ } }
task notALib { doLast{ println ‘notALib’ } }
- **动态任务**
```groovy
4.times { val ->
task "tk${val}" << {
println "The task is task${val}"
}
}
- 添加任务描述
可以向任务添加描述。 执行Gradle任务时会显示此描述。 这可以通过使用description关键字。
将以下代码复制并保存到build.gradle文件中。其效果:鼠标放到任务上时有描述提示。
// 方式1
task test() {
description "this is task description"
// do...
}
//方式2
task test(dependsOn: war, description: "this is task description") << {
// do...
}
- 任务自定义属性
ext.myProperty = "The property value"
5. Gradle生命周期管理
Gradle的生命周期分三个阶段:初始化阶段,配置阶段,执行阶段。那这三个阶段在做什么事情呢?1. 初始化阶段(Initialization)
通过settings.gradle判断有哪些项目需要初始化,加载所有需要初始化的项目的build.gradle文件并为每个项目创建project对象。2. 配置阶段(Configuration)
执行各项目下的build.gradle脚本,完成project的配置,并且构造Task任务依赖关系图以便在执行阶段按照依赖关系执行Task中的配置代码。
配置代码 配置阶段就需要执行的代码,如下:task configCode{
println 'config Code'
}
3. 执行阶段(Execution)
通过配置阶段的Task图,按顺序执行需要执行的任务中的动作代码,就是执行任务中写在doFirst或doLast中的代码。
动作代码 任务调用才会执行的代码,如下: ```groovy task executeCode { doFirst{ println ‘executeCode do first’ } doLast{ println ‘executeCode do last’ } }
// doLast的简写方式,该语法在Gradle-5.x中不再兼容 task executeCode << { println ‘execute Code’ }
<a name="X4rtP"></a>
# 6. Gradle依赖管理
几乎所有基于JVM的软件项目都需要依赖外部的类库来重用现有的功能代码。自动化依赖管理可以明确依赖的版本,能解决传递性依赖带来的版本冲突问题。
<a name="pttqc"></a>
## 1. 声明依赖关系
- **工件坐标(jar包标志)**
- **group**:指明jar包所在的分组。
- **name**:指明jar包的名称。
- **version**:指明jar包的版本。
```groovy
apply plugin: 'java'
repositories {
mavenCentral()
}
dependencies {
compile group: 'org.hibernate', name: 'hibernate-core', version: '5.4.21.Final'
testCompile group: 'junit', name: 'junit', version: '5.+'
}
2. 依赖阶段配置
依赖关系配置只是定义了一组依赖关系。 可以使用此功能声明从Web下载外部依赖关系。这定义了以下不同的标准配置。在build.gradle中的dependencies中配置依赖。
- 编译(compile):编译项目的生产源所需的依赖关系,测试代码编译和运行以及源码运行一定存在。
- 运行时(runtime):运行时生产类所需的依赖关系。 默认情况下,还包括编译时依赖项,只有源码运行和测试运行存在。
- 测试编译(testCompile):编译项目测试源所需的依赖项。 默认情况下,它包括编译的产生的类和编译时的依赖,测试代码的编译和运行存在。
- 测试运行时(testRuntime):运行测试所需的依赖关系。 默认情况下,它包括运行时和测试编译依赖项。
从依赖影响的代码范围可划分为:源码依赖(包括:compile , runtime)和测试依赖(包括:testCompile, testRuntime),只有测试代码的运行存在。
以上的四种配置选用的主要判断依据是:“是否仅是运行阶段需要依赖?”或“__是否仅是测试阶段需要依赖**”。**
3. 依赖关系配置
比如:“A”依赖“B”,如果“C”依赖“A”,那么“C”依赖“B”。就是因为依赖的传递性,所以才会出现版本的冲突问题。以下通过一张图来了解下Gradle的自动化依赖管理流程。
案例1:依赖传递
加入logback依赖(logback中包含logback-core和slf4j,根据依赖传递性,配置依赖logback即可使用slf4j),步骤:
- 到中央仓库查找logbok的配置。(http://mvnrepository.com/search?q=logback)
拷贝配置到build.gradle中配置依赖。
dependencies {
testCompile 'junit:junit:4.12'
testCompile 'ch.qos.logback:logback-classic:1.2.3'
}
刷新依赖配置。
- 书写测试方法。 ```groovy import org.slf4j.Logger; import org.slf4j.LoggerFactory;
public class MyTest {
private static final Logger logger=LoggerFactory.getLogger(MyTest.class);
public static void main(String[] args) {
logger.info("测试日志输出!");
}
}
<a name="ao7aP"></a>
### 案例2:版本冲突问题
管理依赖的最重要的环节就是传递性依赖过程中存在的版本冲突的问题处理。在之前的手动管理依赖过程中经常遇到版本冲突问题,版本一冲突程序就无法正常运行。而作为版本管理工具就应该拥有解决此问题的功能。<br />[点击查看【processon】](https://www.processon.com/embed/5fe314e4e0b34d299ffada79)<br />依赖引入:
```groovy
compile group: 'org.hibernate', name: 'hibernate-core', version: '3.6.3.Final'
hibernate3.6.3需要依赖以上几个jar包,而依赖的“hibernate-commons-annotations-*.jar”依赖slf4j,当前Hibernate框架也依赖了slf4j。由于传递性依赖的特点,两个不同版本的jar包会被依赖进来,这样就存在版本冲突的问题。存在版本冲突问题是需要解决的,如果没有解决程序就不能正常运行,如何来解决此问题?
- Maven解决版本冲突问题
Maven按最短路径原则和优先声明原则来处理自动处理传递性依赖版本冲突。
最短路径原则:
- D1路径为:项目-> A -> D1。
- D2路径为:项目 -> B-> E1 -> D2。
- D1路径比D2短,所以使用D1。
优先声明原则:E1和E2的路径距离相同,而E1比E2先声明,所以使用E1 ,不使用E2。
以上是Maven的自动解决方案,而Gradle的自动解决方案是怎样的?
- Gradle解决版本冲突问题
Gradle的默认自动解决版本冲突的方案是选用版本最高的。一般项目都是向下兼容,所以Gradle的自动解决方案也是比较合理的。如果有特殊需求想手动修改依赖呢?在Gradle中有哪些设置方案,如何设置?
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"
}
}
说明:以上配置指定了项目中不再依赖任何版本的slf4f-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'
}
}
修改默认配置策略,对所有jar不做冲突自动解决。在build.gradle中配置如下代码:
configurations.all{
resolutionStrategy{
// 修改gradle不自动处理版本冲突
failOnVersionConflict()
}
}
说明:以上配置完成,如果存在依赖jar包版本冲突问题,Gradle将不再自动处理,构建时会抛异常。此操作可查看有哪些 jar存在版本冲突问题。3. 仓库配置
在添加外部依赖关系时, Gradle在存储库中查找它们。 存储库只是文件的集合,按分组,名称和版本
来组织构造。 默认情况下,Gradle不定义任何存储库。 我们必须至少明确地定义一个存储库。一个Java工程通常会依赖于外部的jar包,Gradle可以使用Maven的仓库来获取或者发布相应的jar包。
- 公共仓库(中央仓库)
Gradle没有自己的中央仓库,可配置使用Maven的中央仓库:mavenCentral/jcenter。
- 私有仓库
配置从本地maven仓库中获取依赖的jar包,不远程加载jar包,使用mavenLocal。
- 自定义maven仓库
自定义仓库来源,一般指向公司的Maven私服(普遍做法)。
- 文件仓库(用的少)
注意:可配置多个仓库,查找是按顺序来查找,找到则返回,没找到继续往下查找。jcenter:当前世界上最大的Java和Android开源软件构件仓库。repositories {
// 本地仓库
mavenLocal()
// 中央仓库
// jcenter()
mavenCentral()
// 私服
maven {
url ''
}
// ivy本地仓库
ivy {
url "../local-repo"
}
// ivy远程仓库
ivy {
url "http://repo.mycompany.com/repo"
}
}
4. 发布文件
依赖关系配置也用于发布文件。 这些已发布的文件称为工件。 通常,我们使用插件来定义工件。 但是
需要告诉Gradle在哪里发布文件。可以通过将存储库附加到上传存档任务来实现此目的。 请查看以下用于发布Maven存储库的语法。 执行时, Gradle将根据项目需求生成并上传pom.xml, 在build.gradle文件中使用此代码。
使用Gradle构建发布脚本,即可快速方便地发布文件到Maven仓库。有2种插件可以选择,一种是“maven”,一种是“maven-publish”。“maven”插件内置了一个方法“uploadArchives”,这个方法会将当前项目编译打包并发布。“maven-publish”将在后续章节进行讲解。 ```groovy / 发布配置1:简单发布 / apply plugin: ‘maven’
uploadArchives { repositories { mavenDeployer { repository(url: “file://localhost/G:/gradleRepository/“) } } }
/ 发布配置2:自定义相关信息 / apply plugin: ‘maven’
uploadArchives { repositories { mavenDeployer { repository(url: “file://localhost/G:/gradleRepository/“) pom.project { groupId = “org.polaris.gradle” artifactId = “test” version = “1.0.1” packaging ‘jar’ developers { developer { id ‘polaris’ name ‘wj’ timezone ‘Asia/Shanghai’ } }
description 'Some test of jar'
}
}
}
}
<a name="5eCuw"></a>
# 7. 多项目构建
在企业中,一个比较复杂的项目往往是分成几个小项目来协同完成,这就涉及到多项目的构建,而多项目构建则需要把一个大项目进行“项目模块化”。通过模块的互相协作完成整个功能。
<a name="f5MQn"></a>
## 1. 案例描述
以一个点评网站“comment”的搭建来做模块的划分和关系的搭建。<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608723038719-57faacde-86b6-4444-a708-ca0ac65e4773.png#align=left&display=inline&height=485&margin=%5Bobject%20Object%5D&name=image.png&originHeight=485&originWidth=366&size=56212&status=done&style=none&width=366)<br />在之前使用Maven的多项目构建时,一般需要一个root项目来统一管理所有的模块。这里Gradle也一样使用一个root项目来统一管理所有的模块。关系图如下:<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608723070293-5478fdb7-9675-4bf1-acc9-a1fa0f757bf6.png#align=left&display=inline&height=424&margin=%5Bobject%20Object%5D&name=image.png&originHeight=424&originWidth=503&size=47127&status=done&style=none&width=503)<br />所有项目(包括root项目)的公用配置在allprojects中配置,所有子模块的公用配置可以在subprojects中来配置,build.gradle针对项目的配置项都可以配置在allprojects/subprojects中。
<a name="E81BB"></a>
## 2. 案例分析
1. 所有的项目都需要使用Java插件,web项目也需要依赖Java环境。
1. web子项目需打为war包。
1. 统一配置公共属性,例如:“group”、“version”。
1. 统一管理资源库。
1. 通用依赖配置,例如logback日志功能的引入。
<a name="zE5Vn"></a>
## 3. 操作步骤
<a name="CUSAu"></a>
### 1. 创建项目
创建root项目以及所有的子模块项目。
1. 创建Gradle的Java项目(ArtifactId:comment,根项目)。
1. 在comment下创建Java模块(ArtifactId:core),右键:“new”->“Module”->“Gradle”,“Java”。
1. 在comment下创建Java模块(ArtifactId:model)。
1. 在comment下创建web模块(ArtifactId:admin)。
1. 在comment下创建web模块(ArtifactId:web)。
1. 删除comment下的src,只做模块管理项目,不写业务。
项目结构和settings.gradle配置信息如下:
<a name="iEL5j"></a>
### ![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608726308230-bb8774e0-89f3-467d-b0be-fb83a0ac8994.png#align=left&display=inline&height=202&margin=%5Bobject%20Object%5D&name=image.png&originHeight=260&originWidth=961&size=104025&status=done&style=none&width=746)2. 配置项目依赖
1. “model”删除junit依赖(“model”无需测试)。
1. “core”依赖“model ”。
```groovy
dependencies {
// core依赖model子模块
// 模块的依赖使用project(":模块名") 方式.
compile project(":model")
testCompile group: 'junit', name: 'junit', version: '4.11'
}
“admin”和“web”模块依赖“core”模块。
dependencies {
// 依赖 core 模块
compile project(":core")
}
3. 配置插件
所有项目使用“java”插件,“web”项目使用“war”插件。
配置公用“java”插件引入。
在comment项目下的build.gradle中将“java”插件和版本引入的配置转到allprojects中,因为“root”项目作为管理项目也需要加入“java”插件,所以此处不能使用subprojects。
allprojects {
// 统一引入java插件和版本指定
apply plugin :"java"
sourceCompatibility = 1.8
}
- 删除所有子模块项目中的“java”插件引入和版本指定。
- 检查是否配置成功。
- 执行:“core”/“model”->Tasks->build->jar,能导出jar即OK。
- 执行:“admin”/“web”->Tasks->build->war,能导出war即OK。
4. 公共配置
统一公共属性配置和资源库。
- 统一资源库配置。
comment的build.gradle中将repositories移动到allprojects/subprojects中,删除所有子模块中的资源库配置项。
allprojects {
// 统一引入java插件和版本指定
apply plugin :"java"
sourceCompatibility = 1.8
// 统一指定所有项目的资源库
repositories {
mavenCentral()
}
}
- 统一属性配置。
将所有模块中的group和version统一配置在comment的build.gradle的allprojects/subprojects中,子模块的配置删除。
subprojects {
// 统一group和version
group 'org.polaris.gradle'
version '1.0.0'
}
也可使用ext给project拓展属性或者使用属性配置文件gradle.properties来配置统一的属性。
- 统一日志管理。
在comment下build.gradle中的allprojects/subprojects中配置dependencies。
allprojects {
//...
// 共用依赖配置
dependencies {
compile 'ch.qos.logback:logback-classic:1.2.2'
}
}
5. 依赖关系配置
6. 总结
- 在“root”项目的build.gradle中使用allprojects/subprojects来做公共的配置。
- 所有项目使用“java”,“web”项目使用“war”。
- 属性配置文件的抽取(gradle.properties) 。
-
8. Gradle部署
项目发布可以将咱们写好的模块发布给别人去使用,也可以发布到公司的公共仓库以供依赖的项目使用。这是公司中多项目协同合作的重要环节。
1. 项目发布流程
由Gradle将项目打包和创建metadata文件。
-
2. 项目发布实操
添加“maven-publish”插件。
apply plugin :'maven-publish'
配置发布任务。
publishing {
publications {
// publishProject为自定义名称,可写多个发布任务
publishProject(MavenPublication){
from components.java // 发布jar包
//from components.war // 发布war包
}
}
// 配置发布到哪里
repositories {
maven {
// 指定要上传的Maven私服仓库
url = "http://192.168.80.1:8081/nexus/content/repositories/central/"
// 认证用户和密码
credentials {
username 'admin'
password 'admin123'
}
}
}
}
源码发布。(选) ```groovy task sourceJar(type: Jar) { from sourceSets.main.allJava }
publishing{ publications{ publishJarProject(MavenPublication){ from components.java // 调用打包源码任务做发布 artifact sourceJar { classifier “sources” } } } // 配置发布到私服地址 repositories { // … } }
4. 执行发布。
![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608728541527-09e529de-0dfe-4cfa-b364-3b981b2f17f4.png#align=left&display=inline&height=272&margin=%5Bobject%20Object%5D&name=image.png&originHeight=272&originWidth=377&size=55779&status=done&style=none&width=377)
- **generatePomFileForPublishProjectPublication**:生成pom文件。
- **publish**:发布到repositories中指定的仓库(一般为Maven私服)。
- **publishPublishProjectPublicationToMavenLocal**:执行publishProject指定操作到指定仓库。
- **publishPublishProjectPublicationToRepository**:执行publishProject中的操作并发布到指定仓库(私服)。
- **publishToMavenLocal**:执行所有发布任务中的操作发布到本地Maven仓库。
_**提示:一般在公司就是将项目发布到私服供其他项目使用,直接操作publish,发布到本地使用publishToMavenLocal即可。**_<br />完整的配置如下:
```groovy
apply plugin :'maven-publish'
// 打包源码
task sourceJar(type: Jar) {
from sourceSets.main.allJava
}
publishing {
publications {
// publishProject为自定义名称,可写多个发布任务
publishProject(MavenPublication){
from components.java // 发布jar包
//from components.war // 发布war包
}
// 带源码的发布任务
publishJarProject(MavenPublication){
from components.java
// 调用打包源码任务做发布
artifact sourceJar {
classifier "sources"
}
}
}
// 配置发布到哪里
repositories {
maven {
// 指定要上传的Maven私服仓库
url = "http://192.168.80.1:8081/nexus/content/repositories/central/"
// 认证用户和密码
credentials {
username 'admin'
password 'admin123'
}
}
}
}
3. 项目加入Web容器
对于JavaWeb项目来说,web容器是必不可少的,毕竟JavaWeb项目需要运行在web容器中,接下来咱们就看下使用Gradle如何加入web容器。
Gradle使用嵌入式Tomcat部署web项目,使用“gradle-tomcat-plugin”插件。(参考:https://github.com/bmuschko/gradle-tomcat-plugin/blob/master/README.md)
引入步骤如下:
- 创建gradle的web项目。
将二进制插件添加到构建(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之后。
Tomcat版本配置。
dependencies {
// 配置依赖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版本可以按照需求选择版本,修改tomcatVersion即可
启动Tomcat,运行项目。
在IDEA右边出现4个任务按钮:
- tomcatJasper:调用jsp转化器,将jsp转为类并编译为class文件。
- tomcatRun:启动tomcat服务器部署项目。
- tomcatRunWar:启动tomcat服务器部署war。
- tomcatStop:关闭tomcat服务器。
点击“tomcatRun”即可启动tomcat。
- Tomcat常用配置修改。 ```groovy // 单一配置 //tomcat.httpPort = 80 // 配置http端口 //tomcat.contextPath = ‘/‘ // 配置上下文路径
// 统一配置 tomcat{ httpPort = 80 // 配置http端口 contextPath = ‘/‘ // 配置上下文路径 }
详细配置查看“gradle-tomcat-plugin”插件介绍,如果需要完全控制任务或不想使用预配置的任务则需使用“TomcatBasePlugin”插件。
```groovy
apply plugin: 'com.bmuschko.tomcat-base'
具体配置看官网,此处先快速入手,能够把web项目跑起来即可。
- 加入Servlet等其他编译依赖。
由于目前只能使用jsp和一些静态的资源,动态资源Servlet无法进行编译,所以还需要加入Servlet的依赖。
# 只在编译时依赖,运行环境下不需要
providedCompile 'javax.servlet:javax.servlet-api:3.1.0'
9. Gradle插件
目前很多企业都开始使用Gradle来进行项目的管理,对于Java Web项目来说,war插件的出现,让项目部署只需复制粘贴即可,那有没有办法让JavaWeb项目的部署像PC端软件一样双击某个执行文件即可?有没有办法自动检测项目的更新,自由编译与加载从而方便开发人员呢?答案是yes,咱们可以使用gretty来实现这些功能。
gretty插件支持热部署、HTTPS、转发、调试、自动化运行环境等诸多特性,支持jetty、tomcat等多种Servlet容器。
1. 插件类型
Gradle中有两种类型的插件:脚本插件和二进制插件。
- 脚本插件:是一个额外的构建脚本,它提供了一种声明性方法来操作构建,通常在构建中使用(通过“apply from: ‘*.gradle’”方式引)。
- 二进制插件:是实现插件接口并采用编程方法来操作构建的类,二进制插件可以驻留在插件JAR 中的一个构建脚本和项目层次结构或外部(通过“apply plugin: xxxx ”方式引用)。
2. 简单操作
- 加入gretty插件。 ```groovy // build.gradle
// 方式1:apply安装 apply from: ‘https://raw.github.com/gretty-gradle-plugin/gretty/master/pluginScripts/gretty.plugin‘
// 方式2:plugins安装 plugins { id ‘java’ id ‘war’ id “org.akhikhl.gretty” version “2.0.0” }
以上两种方式选一种来安装就ok,如果使用的Gradle版本比较低,可以使用以下方式来配置。
```groovy
// JDK6+,Gradle 1.10+
// build.gradle
// 方式3:buildscript声明,apply引用
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'org.akhikhl.gretty:gretty:+'
}
}
apply plugin: 'org.akhikhl.gretty'
- 启动使用gretty容器。
默认使用jetty9容器,在IDEA工具中可看到如下操作。
- Run系列:无需执行停止线程操作(1-编译项目 -> 2-启动wab-app)。
- gradle appRun/appRunDebug:不依赖war,点击“停止”按钮停止服务器。
- gradle appRunWar/appRunWarDebug:依赖war,点击“停止”按钮停止服务器。
- Start 系列:需要执行停止线程操作(1-编译项目 -> 2-使用新线程开启服务,等待HTTP请求)。
- gradle appStart/appStartDebug:不依赖war,用“gradle appStop”停止。
- gradle appRestart:不依赖war重启。
- gradle appStartWar/appStartWarDebug:依赖war,用“gradle appStop”停止。
- 自选择容器系列:强制使用jetty/tomcat容器,包含Run、Start、Stop功能和“Run系列/Start系列”相同。
参考: https://gretty-gradle-plugin.github.io/gretty-doc/Gretty-tasks.html
3. 常用功能
参考:https://gretty-gradle-plugin.github.io/gretty-doc/Gretty-configuration.html#_scaninterval
1. 容器配置
修改默认Servlet容器和其常用属性。
gretty {
// 指定默认Servlet容器,支持jetty7/8/9,jetty9.3/9.4(仅jdk8),tomcat7/8
servletContainer = 'tomcat8'
// 修改服务器端口
httpPort = 80
// 修改上下文路径
contextPath = '/'
}
2. 热部署(gretty-1.1.5+)
gretty-1.0.0+可使用“managedClassReload=true”来启动即时重新加载已更改的class文件,也可以手动修改热部署的属性。
- 常用属性
- scanInterval:监听周期(秒),设置为0:关闭热部署。默认为1:每秒扫描加载1次。
- scanDir:需要监听的文件夹。
- recompileOnSourceChange:监听源码变动,自动编译。
- reloadOnClassChange:编译的类发生改变,自动加载。
- reloadOnConfigChange:监听WEB-INF或META-INF发生改变。
- reloadOnLibChange:监听依赖发生改变。
- fastReload:默认为true,监听webapp/中的内容,文件发生改变,无需重启。
默认值
- scanInterval = 1
- scanDir
- ${projectdir}/src/main/java
- ${projectdir}/src/main/groovy
- ${projectdir}/src/main/resources
- ${projectdir}/build/classes/main
- ${projectdir}/build/resources/main
- recompileOnSourceChange = true
- reloadOnClassChange = true
- reloadOnConfigChange = true
- reloadOnLibChange = true
- fastReload = true
3. 新增资源目录
// 除src/main/webapp外,可另外指定资源目录
gretty{
// ...
extraResourceBase 'dir1',
extraResourceBases 'dir2','dir3'
// ...
}
4. HTTPS支持
生成自签名证书,只能在开发中使用。
证书生成位置:// https配置
gretty {
//httpEnabled = false // 禁用http
httpsEnabled = true // 启用https生成自签名证书
// httpsPort = 443 // 默认是8443
}
certificate:”${project.buildDir}/ssl/cert”
- key-store:”${project.buildDir}/ssl/keystore”
- key-store and key-manager passwords:”${project.buildDir}/ssl/properties”
注意:此处用的是gretty自签名证书,并非指向“Certificate Authority(CA)”。可手动配置引用已经存在的证书。
手动配置:
gretty {
sslKeyStorePath = '/some/path/keystore'
sslKeyStorePassword = 'someKeystorePassword'
sslKeyManagerPassword = 'someKeyManagerPassword'
sslTrustStorePath = '/another/path/trust_keystore'
sslTrustStorePassword = 'someTrustStorePassword'
}
5. 转发(gretty-1.1.7+)
步骤1:在WEB-INF/web.xml中加入以下内容。
<filter>
<filter-name>RedirectFilter</filter-name>
<filter-class>org.akhikhl.gretty.RedirectFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>RedirectFilter</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>REQUEST</dispatcher>
<dispatcher>FORWARD</dispatcher>
</filter-mapping>
步骤2:创建WEB-INF/filter.groovy,设置转发规则。 ```groovy // 根地址转发到index.html filter relPath: ‘/‘, { forward ‘index.html’ }
// 旧地址转发到新地址 filter relPath: ‘/old/path’, { redirect contextPath + ‘/new/path’ }
// 地址参数转为查询参数 filter relPath: ~’/path/(.*)’, { matches -> redirect new URIBuilder(requestURI).setPath(contextPath + ‘/anotherPath’).setQuery(matches.relPath[0][1]) }
// 将HTTP流量全部转发至HTTPS filter scheme: ‘http’, { redirect new URIBuilder(requestURI).setScheme(‘https’).setPort(httpsPort) }
<a name="q4Pgv"></a>
### 6. 调试(Debug)
在启动项目常用操作中有很多的xxxDebug操作,这些都是debug方式运行。不过在IDE工具中直接运行没有效果,在idea中可以使用远程调试来实现debug调试。<br />![image.png](https://cdn.nlark.com/yuque/0/2020/png/788484/1608784218655-dd6f1316-0688-4882-a2be-91dabb23248e.png#align=left&display=inline&height=370&margin=%5Bobject%20Object%5D&name=image.png&originHeight=370&originWidth=202&size=58446&status=done&style=none&width=202)
<a name="NXSJL"></a>
#### 1. Debug配置
```groovy
// 为所有的debug命令配置参数
gretty {
debugPort = 5005 // 默认
debugSuspend = true // 默认
}
// 仅针对appRunDebug
gretty {
afterEvaluate {
appRunDebug {
debugPort = 5005 // 默认
debugSuspend = true // 默认
}
}
}
2. Debug实操
- 双击某个debug任务,例:appStartDebug。
- 配置idea远程调试。
- 启动idea远程调试。
- 选择远程调试并启动
- 启动后控制台信息
- 等待项目启动即可进行调试
选择控制台的“Run”选项卡,观察项目启动。启动成功后就可以请求需要debug的资源了。
- debug调试。
7. 可执行项目
1. 生成可执行项目(gradle buildProduct)
gretty可以类SpringBoot的项目打包功能,可以把一个项目导出成拥有Servlet容器和启动程序的项目,还可以将项目整体压缩,操作如下:
gradle buildProject
生成可执行的项目,生成目录:build/output/${project.name}。项目结构如下:
- conf:包含服务器的配置文件。
- runner:包含Servlet容器的运行库。
- starter:包含Servlet容器部署java程序需要的运行库。
- webapps:一个或多个web项目。
- restart.bat/sh:Linux/Win重启应用脚本。
- run.bat/sh:Linux/Win启动服务脚本。
- start.bat/sh:Linux/Win启动应用脚本。
- stop.bat/sh:Linux/Win停止应用和服务脚本。
- 多应用,需在build.gradle中配置product。
product {
webapp project // include this project
webapp ':ProjectA'
webapp ':ProjectB'
}
2. 打包可执行项目(gradle archiveProduct)
将可执行项目打包(zip),生成路径:build/output/${project.name}-${project.version}.zip。gradle archiveProduct
4. 自定义插件
在创建自定义插件时,需要编写一个插件的实现, Gradle实例化插件并使用“Plugin.apply()”方法调用插件实例。1. 自定义插件实操
以下示例包含一个简单的“hello”插件,它将一个问候任务添加到项目中,在build.gradle文件中使用此代码。 ```groovy apply plugin: HelloPlugin
class HelloPlugin implements Plugin
使用以下代码执行上述脚本:
```bash
gradle -q hello
# 以下为执行输出
Hello from the HelloPlugin
2. 自定义插件扩展
大多数插件需要从构建脚本中的配置获得支持, Gradle项目有一个关联“ ExtensionContainer ”对象,它有助于跟踪传递给插件的所有设置和属性。我们在项目中添加一个简单的扩展对象,例如添加一个问候语扩展du象, build.gradle文件中使用此代码如下所示:
// 自定义插件扩展
class HelloPlugin implements Plugin<Project> {
void apply(Project project) {
// Add the 'greeting' extension object
project.extensions.create("greeting", HelloPluginExtension)
// Add a task that uses the configuration
project.task('hello') {
doLast{
println project.greeting.message
}
}
}
}
class HelloPluginExtension {
String message
}
apply plugin: HelloPlugin
// 方法1
/** greeting{
message="gradle extensions"
} **/
// 方法2
greeting.message="gradle extensions"
使用以下代码执行上述脚本:
gradle -q hello
# 以下为执行输出
gradle extensions
Gradle为每个扩展对象添加了一个配置闭包,因此可以将分组设置在一起,如下代码, 在build.gradle文件中使用此代码。
apply plugin: GreetingPlugin
greeting {
message = 'Hi'
greeter = 'Gradle'
}
class GreetingPlugin implements Plugin<Project> {
void apply(Project project) {
project.extensions.create("greeting", GreetingPluginExtension)
project.task('hello') {
doLast{
println "${project.greeting.message} from ${project.greeting.greeter}"
}
}
}
}
class GreetingPluginExtension {
String message
String greeter
}
使用以下代码执行上述脚本:
gradle -q hello
# 以下为执行输出
Hi from Gradle
10. Gradle常用命令
命令 | 描述 |
---|---|
gradle —help | 帮助命令 |
gradle -v | 查看版本 |
gradle projects | 列出构建文件中定义的所有项目 |
gradle tasks | 列出可执行的所有任务(即查看可以执行的命令) |
gradlle tasks —all | 显示任务间的依赖关系 |
gradle ${taskName} | 执行任务${taskName} |
gradle ${testask1} ${testask2} | 执行多个任务 |
gradle -b path/new.gradle ${taskName} | 指定build.gradle位置和文件名,并执行任务${taskName} |
gradle -m build | 试运行build |
gradle build | 构建 |
gradle build —profile | 产生build运行时间的报告,结果存储在build/report/profile目录,名称为build运行的时间 |
gradle build -x test | 跳过测试构建 |
gradle releaseTarGz -x signArchives -x test | 编译打包tgz包,跳过测试类 |
gradle build —continue | 继续执行任务而忽略前面失败的任务 |
gradle test | 执行测试 |
gradle test —test 包名.类名.方法名 | 执行指定包、类和方法的测试 |
gradle jar | 把项目打成可执行jar包 |
gradle init | 创建一个Gradle项目的骨架 |
gradle wrapper | 生成wrapper包 |
gradle javadoc | 生成javadoc |
gradle dependencies | 查看依赖 |
gradle check | 执行代码质量检测 |
gradle clean | 清空所有编译、打包生成的文件(即:清空build目录) |
gradlew -v | 版本号 |
gradlew clean | 清除build文件夹 |
gradlew build | 检查依赖并编译打包。默认build会把debug、release环境的包都打出来,如果正式发布只需要打Release的包 |
gradlew testDebug —tests=’*.MyTestClass’ | 单独测试某个类 |
gradlew assembleDebug | 编译并打Debug包 |
gradlew assembleRelease | 编译并打Release的包 |
说明:Gradle的命令日志输出有ERROR(错误信息)、QUIET(重要信息)、WARNGING(警告信息)、LIFECYLE(进程信息)、 INFO(一般信息)、DEBUG (调试信息)一共6个级别。在执行Gradle任务是可以适时地调整信息输出等级,以方便地观看执行结果。
- -q/—quit:启用重要信息级别,改级别下只会输出自己在命令行下打印的信息及错误信息。
- -i/—info:会输出除DEBUG以外的所有信息。
- -d/—dubug:会输出所有日志信息。
- -s/—stacktrace:会输出详细的错误堆栈。
参考
博客园:Gradle入门指南之gretty插件(安装、命令与核心特性)
https://www.cnblogs.com/gzdaijie/p/5267166.html
简书:Gradle高级-gretty插件
https://www.jianshu.com/p/d0cb18c4738b