一.自动化构建工具的作用
1.没有自动化构建工具的时代
- 依赖管理:将所有依赖的jar包放到lib目录下,一不留神就会出现版本冲突
- 测试:能不写就不写,写了也是一个一个运行main方法简单测试
- 打包:通过eclipse的导出war包
- 上传:通过ftp上传到服务器上
2.自动化构建工具的作用
- 依赖管理
- 测试、打包、发布
- 机器能干的活,绝不自己动手
3.主流构建工具
- Ant:编译、测试、打包
- Maven:在Ant基础上添加了依赖管理和发布
- Gradle:在Maven的基础上添加了使用Groovy进行管理构建脚本,而不再使用xml
二.关于Gradle
Gradle是什么
- 一个开源的项目自动化构建工具,建立在Apache Ant和Apache Maven概念的基础上,并引入了基于Groovy的特定领域语言(DSL),而不再使用XML形式管理构建脚本
三.准备使用Gradle
1.Gradle安装
-
Gradle是基于JVM的,故本地必须要有JDK
-
从Gradle官网下载Gradle,https://gradle.org
-
配置环境变量GRADLE_HOME
-
添加到PATH
export GRADLE_HOME=/Users/wangzhe/Documents/class/gradle-4.10.2 export PATH=$PATH:$GRADLE_HOME/bin
-
验证是否安装成功
gradle -v
2.Groovy是什么
- Groovy是用于Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用于纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。
- Groovy完全兼容Java的语法,即在Groovy中可以编写Java代码
- 分号是可选的
- 类和方法默认是public
- 编译器给属性自动添加getter/setter方法
- 属性可以直接用点号获取
- 在方法中最后一个表达式的值会被作为返回值(即可以不写return)
- 在Groovy中,==等同于equals(),不会有NullPointerExceptions
- 高效的Groovy特性
- 自带assert语句,可以在任何地方执行断言操作
- 类型是可选的,即弱类型,def 变量名即可
- 括号是可选的,调用方法时如果有参数,其参数是可以不写的
- Groovy字符串有三种表达性
'xxxx'
"xxxx"
'''xxxx'''
- 集合API,Groovy中List和Map有更简单的写法
- 具有闭包的特性
3.groovy基础知识-与java比较
//定义工程版本对象——点击运行按钮进行运行,结果会输出到console控制台
public class ProjectVersion{
private int major;//大版本
private int minor;//小版本
ProjectVersion(int major, int minor) {
this.major = major
this.minor = minor
}
int getMajor() {
major
}
void setMajor(int major) {
this.major = major
}
//
// int getMinor() {
// return minor
// }
//
// void setMinor(int minor) {
// this.minor = minor
// }
}
ProjectVersion v1 = new ProjectVersion(1,2);
println(v1.minor)//2 自动创建getter和setter
println(v1.major)//1 将最后一个表达式看做返回值
ProjectVersion v2 = null
println(v2 == v1)//false 使用==判断时相当于equals()方法,不会抛出空指针异常
4.groovy基础知识-高效特性
//Groovy 高效特性
//1. 可选的类型定义
def version = 1
//2. assert
//assert version==2//失败的断言,在任何地方都可以执行
//3. 括号是可选的
println(version)//1
println version //1
//4. 字符串
def s1 = 'Jack'//仅仅是一个字符串
def s2 = "Gradle version is ${version}"//可以使用${}插入变量
def s3 = '''Jack is so smart.'''//可以换行,输出的结果也是换行的
println s1//Jack
println s2//Gradle version is 1
println s3//Jack is so smart
//5. 集合API
// list
def buildTools = ['ant','maven']
buildTools << 'gradle' //表示向buildTools中追加元素
assert buildTools.getClass() == ArrayList//不会出现异常,表示是ArrayList类型
assert buildTools.size() == 3//不会出现异常,表示当前集合中有三个元素
//map——对应java中的LinkedHashMap
def buildYears = ['ant':2000,'maven':2004]
buildYears.gradle = 2009//表示向buildYears的map中追加元素
println buildYears.ant//2000
println buildYears['gradle']//2009
println buildYears.getClass()//class java.util.LinkedHashMap
//6.闭包——通常当做方法参数来使用
//c1的参数为v,方法体打印v;c2不包含参数
def c1 = {
v ->
println v
}
def c2 = {
print 'hello'
}
def method1(Closure closure){
closure('param')
}
def method2(Closure closure){
closure()
}
method1(c1)//param
method2(c2)//hello
5.groovy基础知识-重点
//Gradle的构建脚本
//每个Gradle的构建脚本都有个Project实例,且每个Gradle构建脚本中所有代码默认的作用域都是Project
//plugin:'java'表示plugin这个参数的值为java;apply是一个方法
apply plugin:'java'
//version表示Project上有一个属性叫version值等于0.1
version = '0.1'
//repositories是一个方法,mavenCentral()作为参数调用repositories方法
repositories{
mavenCentral()
}
//dependencies是一个方法,compile 'commons-codec:commons-codec:1.6'闭包作为参数调用dependencies方法
dependencies{
compile 'commons-codec:commons-codec:1.6'
}
四.第一个Gradle项目
1.创建Gradle项目(TODO-应用程序版本)
- 创建Gradle项目
File -> new -> Project -> Gradle(Java 1.8) -> 输入GroupId和ArtifactId -> use local gradle distribution并选择对应的本地gradle路径 -> 填入项目名 -> Finish
-
Gradle项目目录[固定的目录结构]
-
.gradle
和.idea
是编译器自动生成的文件,可忽略 -
src
表示代码目录main源代码目录;test测试代码目录
-
build.gradle
文件//表示使用java的插件 plugins { id 'java' } group 'com.gradletest' version '1.0-SNAPSHOT' //源文件适应的版本 sourceCompatibility = 1.8 //仓库 repositories { mavenCentral() } //依赖管理 dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' }
-
setting.gradle
文件扫描二维码关注公众号,回复: 3301974 查看本文章
-
-
在main/java下创建
com.gradletest.todo.TodoItem.java
用于定义Todo类package com.gradletest.todo; public class TodoItem { //待办事项的名称 private String name; //已完成 private boolean hasDone; public TodoItem(String name) { this.name = name; hasDone = false; } public String getName() { return name; } public void setName(String name) { this.name = name; } public boolean isHasDone() { return hasDone; } public void setHasDone(boolean hasDone) { this.hasDone = hasDone; } @Override public String toString() { return "TodoItem{" + "name='" + name + '\'' + ", hasDone=" + hasDone + '}'; } }
-
在main/java下创建
com.gradletest.todo.App.java
用于执行对应的todo操作package com.gradletest.todo; import java.util.Scanner; public class App { public static void main(String [] args){ int i = 0; Scanner scanner = new Scanner(System.in); while(++i>0){ System.out.println(i+".please input todo item name:"); TodoItem item = new TodoItem(scanner.nextLine()); System.out.println(item); } } }
-
使用自动化构建工具Gradle来构建jar包
-
点击右侧
Gradle->项目名->Tasks->build
- jar 可以将当前文件打成jar包,打包在build/libs/todo-1.0-SNAPSHOT.jar
- build 也可以,因为相当于执行build.gradle构建脚本,因为脚本中是以java的形式,所以构建完之后也是jar包
- classes 将源代码编译成class类
- clean 清除之前的构建
-
点击jar会创建build包并可以在libs路径下看到指定打包好的jar包
-
在命令行执行jar包的对应类
java -classpath build/libs/todo-1.0-SNAPSHOT.jar com.gradletest.todo.App
,结果就是直接运行App.java的结果
-
2.创建Gradle项目(TODO-WEB版本)
-
添加webapp包及包下的WEB-INF与其中的web.xml
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1" metadata-complete="true"> <!--metadata-complete为true时,该web不会加载注解配置的web组件(如Servlet、Filter、Listener等)--> <!--配置Web应用的首页列表--> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>index.htm</welcome-file> </welcome-file-list> </web-app>
-
在webapp下编写一个简单的index.html用于测试
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> 这里是首页 </body> </html>
-
在build.gradle中添加
apply plugin:'war'
的war插件plugins { id 'java' } group 'com.gradletest' version '1.0-SNAPSHOT' apply plugin:'war'//添加war插件 //源文件的版本 sourceCompatibility = 1.8 repositories { mavenCentral() } dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' }
-
可以发现在右侧Gradle的build中会多一个war选项,点击即会将项目打成war包,war包放到Tomcat下即可运行
-
war包放到webapp中后会自动解压缩
- 第一级目录为:
META-INF
、WEB-INF
、index.html
,第一个目录为打包成war包时自动生成的,后两个文件为webapp下的内容 WEB-INF
中存在classes文件和web.xml文件- classes中存在项目编写代码的包的字节码文件和resources文件夹下的文件
- 第一级目录为:
五.高级应用
1.构建脚本概要
-
构建块
- Gradle构建的两个基本概念是项目(project)和任务(task),每个构建至少包含一个项目,项目中包含一个或多个任务。在多项目构建中,一个项目可以依赖于其他项目;类似的,任务可以形成一个依赖关系图来确保他们的执行顺序。
-
项目(project)
一个项目代表一个正在构建的组件(比如一个jar文件),当构建启动后,Gradle会基于build.gradle实例化一个org.gradle.api.Project类,并且能够通过project变量使其隐式可用。
【即在build.gradle中所有的变量方法的调用都是在Project类中定义】
//例如: group 'com.gradletest' //等同于project.group = 'com.gradletest'
- 主要属性
- group组
- name名称(一个组中不能有重名的)
- version(版本号)
- 主要方法
- apply应用插件
- dependencies声明项目依赖于哪些jar包或其他项目
- repositories表示去哪个仓库找依赖的jar包
- task声明项目里有什么任务
- 先找到仓库,根据group、name和version唯一确定一个组件
- 属性的其他配置方式
- ext定义的属性可以直接引用
- gradle.properties用键值对的方式声明属性
- 主要属性
-
任务(task)
任务对应
org.gradle.api.Task
,主要包括任务动作和任务依赖。任务动作定义了一个最小的工作单元。可以定义依赖其他任务、动作序列和执行条件。- 主要方法
- dependsOn声明任务依赖
- doFirst在任务列表的最前面添加动作
- doLast(简写方式:
<<
)在任务列表的最末尾添加动作
- 主要方法
-
Tasks中的war是war插件的任务,其余的是java插件带的任务
-
当我们执行jar的task任务时,只执行了jar任务,但是前面执行了,说明jar任务依赖其余三个任务:compileJava、processResources、classes。下图中的UP-TO-DATE表示从上次执行jar后信息没有改变,所以跳过了相关任务的执行。
-
在一个项目里面任务不是必须的,大多数情况下我们都使用插件提供的功能,如果插件完成不了的任务我们会自定义任务
-
settings.gradle
文件用来管理多项目构建
2.自定义任务
-
自定义创建包任务
-
定义创建包的任务,并声明文件名称,在任务类中添加创建文件夹闭包的动作
//定义闭包执行根据文件夹名称创建文件夹操作 def createDir= { path -> File dir = new File(path); if(!dir.exists()){ dir.mkdirs(); } } //创建一个新建文件夹的任务 task makeJavaDir() { def paths = ["selfdefine/java","selfdefine/test"] //在动作列表的最前面插入一个动作 doFirst{ paths.forEach(createDir) } }
-
自定义任务在右侧
Gradle->Tasks->other
下可以查看,并点击运行 -
则会在项目根路径下添加对应的文件夹
-
-
自定义创建web包任务(使用任务的依赖)
-
定义创建web包任务,利用dependsOn设定任务依赖makeJavaDir任务,并添加创建文件夹闭包的动作
task makeWebDir(){ dependsOn 'makeJavaDir' def paths = ["src/main/webapp","src/test/webapp"] doLast{ paths.forEach(createDir) } }
-
同样在
Gradle->Tasks->other
查看并执行任务 -
则会首先执行makeJavaDir的任务,再执行当前任务中定义的动作
-
3.构建生命周期
-
第一个阶段:初始化阶段
- 初始化阶段主要是初始化项目,决定哪些项目要参与到项目构建当中
- Gradle会根据脚本创建一个Project类对象并在构建脚本中隐式可用
- 再多项目构建中,在初始化阶段会初始化所有将要参与构建中的项目
-
第二个阶段:配置阶段
-
用来生成Task的依赖顺序和执行顺序(根据配置代码,简单可以理解为除了动作代码之外全是动作代码)
-
初始化任务
-
举例:
task loadVersion{ project.version = '1.0' }
-
-
第三个阶段:执行阶段
-
执行task的动作代码
-
举例:
task loadVersion << { print 'success' }
-
-
钩子方法(平常使用时,不常用到)
4.依赖管理
-
概述:几乎所有的基于JVM的软件项目都需要依赖外部类库来重用现有的功能。自动化的依赖管理可以明确依赖的版本,可以解决因传递性依赖带来的版本冲突。
-
工件坐标:工件简单的可以理解为一个jar包,坐标为三个属性group、name、version。通过三个属性确定唯一的jar包。
-
常用仓库:仓库就是存放jar包的地方。
- 常用的仓库有mavenLocal、mavenCentral、jcenter。mavenCentral和jcenter为公网上的公共仓库,我们可以向上面上传jar包或依赖里面的jar包。mavenLocal是在本地通过maven使用过的jar包都会存在本地仓库中。
- 自定义maven仓库(常用):maven私服,公司搭建内部仓库管理公司内部的jar包
- 文件仓库(不常用):本地机器上面的文件路径也可以作为仓库,不建议使用,因为我们使用构建工具的目的是到各个机器上都可以使用,如果使用文件仓库则会违反这个原则。
-
依赖的传递性
-
B依赖A,如果C依赖B,那么C依赖A
-
正是由于依赖的传递性,会出现版本冲突
-
自动化依赖管理
-
依赖管理的阶段
- 编译阶段compile、运行阶段runtime
- 测试编译阶段testCompile、测试运行阶段testRuntime
- 运行时阶段都是扩展于编译阶段的,即在编译阶段依赖的jar包,在运行时都会依赖;在运行时依赖的jar包,在编译时不一定会依赖
- 如果是源代码依赖的,测试代码都会依赖;测试代码依赖的,源代码不一定会依赖
-
-
实例使用依赖
-
将原dependencies中的依赖junit方式改成compile,则可以通过
Gradle->Source Sets->main->Dependencies
查看到对应依赖的jar包(即从测试编译阶段变成了编译阶段的依赖) -
Gradle
中的项目名右击,选择auto-import
,则当修改对应依赖阶段即相关操作时自动刷新 -
什么时候使用编译依赖,什么时候使用运行依赖?
大部分都是使用编译依赖,因为我们编译时需要使用,运行时也需要使用。
-
添加依赖
-
在https://search.maven.org/搜索对应需要的依赖,本例添加的是日志记录依赖
-
从网站上获得对应的依赖工件坐标,并引入编译阶段依赖
compile 'ch.qos.logback:logback-classic:1.3.0-alpha4'
-
依赖的写法
'ch.qos.logback:logback-classic:1.3.0-alpha4'
等同于group:'ch.qos.logback',name:'logback-classic',version:'1.3.0-alpha4'
-
由于logback依赖又依赖别的jar包,故可以通过Dependencies中看出其依赖的其他jar
-
使用添加的依赖
package com.gradletest.todo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Scanner; public class App { static Logger log = LoggerFactory.getLogger(App.class); public static void main(String [] args){ int i = 0; Scanner scanner = new Scanner(System.in); while(++i>0){ log.info("{}.please input todo item name:",i);//以logback的日志格式打印 TodoItem item = new TodoItem(scanner.nextLine()); System.out.println(item); } } }
则会以日志的形式进行输出
18:38:19.727 [main] INFO com.gradletest.todo.App - 1.please input todo item name:
-
-
-
添加仓库
-
仓库会按顺序查找jar包,第一个查找到了就不往下查找,没查找到就继续往下查找
-
使用maven私服——通常有私服都会将私服放在第一位,先找私服
maven{ url ''//私服的地址 }
-
可以配合多个仓库使用
repositories { //按顺序查找jar包,第一个查找到了就不往下查找,没查找到就继续往下查找 //使用maven私服——通常有私服都会将私服放在第一位,先找私服 maven{ url ''//私服的地址 } //可以配多个仓库 mavenLocal() //表示从mavenCentral仓库获取jar mavenCentral() }
-
5.解决版本冲突
-
解决版本冲突的具体步骤
- 查看依赖报告,确定哪些版本进行冲突
- 排除传递性依赖
- 强制指定一个版本
-
Gradle会默认使用冲突版本中最高版本的jar包
-
修改Gradle默认解决策略(当版本冲突时,设置为构建失败)
configurations.all{ resolutionStrategy{ failOnVersionConflict() } }
-
自行解决冲突
-
排除传递性依赖,如下:
compile('org.hibernate:hibernate-core:3.6.3.Final'){ exclude group:'org.slf4j',module:'slf4j-api'//排除具体jar包的依赖,module即是坐标中的name属性 //transitive=false 排除所有的传递依赖(少见) }
-
强制指定一个版本,如下:
configurations.all{ resolutionStrategy{ force 'org.slf4j:slf4j-api:1.7.24'//强制指定版本 } }
-
-
可以使用
Gradle->项目->help->dependencies
查看所有jar包的依赖
6.多项目构建
-
项目模块化:在企业项目中,包层次和类关系比较复杂,把代码拆分成模块通常是最佳实践,这需要你清晰的划分功能的边界,比如把业务逻辑和数据持久化拆分开来。项目符合高内聚低耦合时,模块就变得很容易,这是一条非常好的软件开发实践。
-
TODO模块依赖关系
- Web(页面)依赖Model(源数据)和Repository(存储数据)
- Repository依赖Model
- 则根据依赖的传递性,Web只需要依赖Repository则完成对Repository和Model的依赖
-
配置要求
- 所有项目应用Java插件
- web子项目打包成WAR
- 所有项目添加logback日志功能
- 统一配置公共属性,如version、group等
-
构建项目
-
创建子模块
项目右击->new Module->填写模块名->Finish
- 创建model模块:用于存储源数据
- 创建repository模块:用于存储真实数据
- 创建web模块:用于编写页面
-
创建子模块的同时会自动将子模块信息include到settings.gradle中。多项目构建时,settings.gradle用来管理当前项目有哪些子项目,root为根项目。并根据右侧Gradle可以查看对应项目的相关信息。
rootProject.name = 'todo'//根项目名 include 'model' include 'repository' include 'web'
-
由于repository中需要操作model内容,故repository模块需要依赖model模块
//对子项目的依赖——特殊的写法project(":模块名") compile project(":model");
-
由于web需要依赖repository和model,但是repository依赖model,故web只需要依赖repository即可传递依赖获得model
compile project(":repository")
-
注意:子模块依赖方式采用
compile project(':子模块名')
-
-
完成配置要求
-
所有项目应用java插件
-
将所有子项目的应用java插件的语句删除,统一在根项目的gradle中配置
//删除内容 plugins { id 'java' } sourceCompatibility = 1.8
-
在根项目的build.gradle中添加
allprojects{ apply plugin:'java' sourceCompatibility = 1.8 }
-
-
web子项目打包成WAR
-
将根项目的war插件删除,添加到web子项目的build.gradle中
apply plugin:'war'//添加war插件
-
查看右侧Gradle,只有web子项目有war的任务
-
-
所有子项目添加logback日志功能
-
在根项目的build.gradle中添加
subprojects{}
并引入对应依赖【allprojects和subprojects中的配置跟build.gradle一样】allprojects{ apply plugin:'java' sourceCompatibility = 1.8 } subprojects { repositories { //表示从mavenCentral仓库获取jar mavenCentral() } dependencies { //在https://search.maven.org/搜索对应需要的依赖,本例添加的是日志记录依赖 compile 'ch.qos.logback:logback-classic:1.3.0-alpha4' //表示在测试阶段依赖的jar包 compile group: 'junit', name: 'junit', version: '4.12' } } group 'com.gradletest' //等同于project.group = 'com.gradletest' version '1.0-SNAPSHOT' repositories { //表示从mavenCentral仓库获取jar mavenCentral() }
-
在子项目中删除引入的junit依赖,因为已经通过根项目配过了
//删除的语句 compile group: 'junit', name: 'junit', version: '4.12'
-
-
统一配置公共属性,如version、group等
-
在根项目下创建gradle.properties【注意不能写错】,并配置如下
group=com.gradletest version='1.0-SNAPSHOT'
-
删除所有子项目中的group和version配置
-
-
在根项目执行clean则会clean根项目及所有子项目的构建文件。如果相对指定子项目进行clean,则在指定子项目中执行clean。
-
在model模块执行jar,只会执行model模块中的内容。在web模块执行jar,会先编译model,之后编译repository,最后编译web模块内容。因为web依赖repository,repository依赖model。
-
总结:如果想构建所有的项目,则在根项目下执行对应任务;如果想构建单项目,则在单项目下执行对应的任务。
-
7.Gradle测试
-
自动化测试
- 一些开源的测试框架比如JUnit,TestNG能够帮助你编写可复用的结构化的测试,为了运行这些测试,你要先编译它们,就像编译源代码一样。测试代码的作用仅仅用于测试的情况,不应该被发布到生产环境中,需要把源代码和测试代码分开来。
-
测试项目布局
-
测试配置
dependencies{ testCompile 'junit:junit:4.11' }
-
测试类被Gradle发现
- 任何继承自junit.framework.TestCase或groovy.util.GroovyTestCase的类
- 任何被@RunWith注解的类
- 任何至少包含一个被@Test注解的类
8.发布
-
发布到本地和远程仓库
-
项目发布的流程
-
添加插件
apply plugin:'maven-publish'
-
配置发布信息(要发布什么和仓库地址)
publishing{ publications{ //自定义发布方法名 myPublish(MavenPublication){ from components.java } } //配置远程仓库的信息 repositories { maven{ name "myPro"//仓库名称 url ""//仓库地址——通常用于私服(点击publishing中的publish发布到私服地址) } } }
-
执行发布任务
Gradle->publishing->publishToMavenLocal
将项目发布到本地仓库中。若想发到公司私服,则可以配置repositories的url信息并点击publish选项。 -
Mac用户可以在
~/.m2/repository/groupId路径
查看对应发布的项目
-