构建工具的作用
依赖管理
测试,打包,发布
主流的构建工具
Ant:提供编译,测试,打包
Maven:在Ant的基础上提供了依赖管理和发布的功能
Gradle:在Maven的基础上使用Groovy管理构建脚本,不再使用XML来管理
Gradle
一个开源的项目自动化构建工具,建立在Apache Ant 和Apache Maven概念的基础上,并引入了基于Groovy的特定邻域语言(DSL),而不在使用XML形式管理构建脚本。
Gradle的下载与安装
下载地址:https://gradle.org/releases
配置环境变量,GRADLE_HOME
添加到path,%GRADLE_HOME%\bin;
验证是否安装成功, gradle -v
Groovy
Groovy是用于Java虚拟机的一种敏捷的动态语言,他是一种成熟的面向对象的编程语言,既可以用于面向对象编程,也可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言的其他特性。
与Java相比较,Groovy完全兼容Java语法,分号是可选的,类和方法默认都是public,编译器给属性自动添加getter/setter方法,属性可以直接用点号获取,不再需要通过方法来获取,同时最后一个表达式的值会被作为返回值,==等同于equals(),不会有NullPointerException。
Groovy的高效特性中,自带了assert语句,它是弱类型的编程语言,直接通过def来定义类型;同时它的括号是可选的,字符串有多种写法。
功能演示
IDE:idea
打开Groovy的控制台,Tools -> Groovy Console
可选的定义类型
def version = 1
def不是一个明确的类型,它类似于javascript里面的var,定义一个变量,变量的类型是自动推断出来的,在这里version就是int类型。
assert
assert version == 2
这是一个失败的断言,因为version是等于1,执行后返回false
括号可选
println version
执行结果任然是1
字符串
def s1 = 'zzh'//单纯的字符串
def s2 = "gradle version is ${version}"//可以插入变量
def s3 = '''zzh
is
handsome'''
println s1
println s2
println s3
s1表示单纯字符串,s2可以插入变量,s3则是可以换行
集合api -> List
buildTools的默认类型是ArrayList,向里面追加一个元素buildTools << 'gradle'。
def buildTools = ['ant','maven']
buildTools << 'gradle'
println buildTools.getClass()
assert buildTools.getClass() == ArrayList
assert buildTools.size() == 3
集合api -> Map
buildYears的默认类型为LinkedHashMap,在Map上添加一个元素直接用点号就行,元素的访问有两种类型,可以用点号也可以用字符串
def buildYears = ['ant':2000,'maven':2004]
buildYears.gradle = 2009
println buildYears.ant
println buildYears['gradle']
println buildYears.getClass()
闭包
简单来说就是一个代码块,跟方法一样可以有参数也可以没有,还可以被赋值给一个变量,也可以当作一个参数传递给一个方法。
//包含参数的闭包,参数v省略了类型,“->”后面是方法体
def c1 = {
v ->
println v
}
//不包含参数的闭包
def c2 = {
println 'hello'
}
//使用闭包作为参数,Closure是groovy自带的,不要导入java里面的包
def method1(Closure closure){
closure('param') //带参数的闭包
}
def method2(Closure closure){
closure() //不带参数的闭包
}
def method3(Closure closure) {
closure()
}
method1(c1)
method2(c2)
method3 {
println '省略方法参数括号直接传入闭包'
}
快速实现添加待办事项应用程序
先进行实例演示,之后再讲解具体步骤功能。
Java应用程序版
Gradle管理jar包,通过在控制台输入代办事项的名称在控制台显示代办事项。
创建todo应用程序
先手动创建src相关目录:
代办事项的bean:TodoItem.java
package com.zzh.gradle.todo;
public class TodoItem {
//代办事项的名称
private String name;
//已完成
private boolean hasDone;
public TodoItem(String name) {
this.name = name;
}
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 name + (hasDone ? " hasDone" : " need to do") + "!";
}
}
从控制台读取代办事项的名称并打印:App.java
package com.zzh.gradle.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 projects
build:根据项目中的build.gradle构建脚本,脚本中的语句apply plugin: 'java'即使用java构建插件,所以构建完后也是jar包的形式。
clean:清楚之前的构建。
点击jar后,及进行编译java,处理资源文件,生成字节码,打包成jar包
执行jar包
因为这个jar包里面有包含main方法的类,所以可以直接执行,而没有main方法的jar包一般会作为依赖被其他的工程引用。
在终端执行
Web版
Gradle管理Web应用程序的功能
添加webapp目录:
其中log.properties只是用于演示打包之后这个配置文件会在压缩包什么位置。
index.html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>todo</title>
<script type="text/javascript">
function addTodo() {
var name = document.getElementById("name").value;
var list = document.getElementById("list");
list.innerHTML = list.innerHTML + "<p>" + name + "</p>";
}
</script>
</head>
<body>
<h1>TODO-web版</h1>
<label>请输入代办事项名称:</label><input id="name" type="text"/>
<button onclick="addTodo()">添加</button>
<h3>代办事项列表:</h3>
<div id="list"></div>
</body>
</html>
build.gradle添加war插件
加上apply plugin: 'war' 之后刷新,看到build下面多出了war构建功能,点击之后,找到war包:
将war包放在本机Tomcat的webapp文件夹下,启动tomcat:
war包自动解压缩后的文件夹中,可以看到配置文件和字节码是同级目录,路径是正确的。
基本原理
构建脚本介绍
构建块:
Gradle中的两个基本概念是项目(Project)和任务(task),每个构建至少包含一个项目,项目中包含一个或多个任务。在多项目构建中,一个项目可以依赖于其他项目;类似的,任务可以形成一个依赖关系图来确保他们的执行顺序。
项目1依赖于项目2,任务A依赖于任务B和C,所以B和C要在A之前执行完。
项目:
一个项目代表一个正在构建的组件(比如一个jar文件),当构建启动后,Gradle会基于build.gradle实例化一个org.gradle.api.Project类,并且能够通过project变量使其隐式可用。
项目的主要属性:
- group:组
- name:名称,等同于maven里的ArtifactId
- version:版本号
项目的主要方法:
apply:应用一个插件
dependencies:声明项目依赖于哪些jar或者是其他项目
repositories:指定仓库,根据group,name,version唯一确定一个组件
task: 声明项目里的任务
任务(task)
任务对应org.gradle.api.Task,主要包括任务动作和任务依赖。任务动作定义了一个最小的工作单元。可以定义依赖于其他任务,动作序列和执行条件。
任务的主要方法:
dependsOn:声明任务依赖
doFirst:在任务列表的最前面添加一个动作。
doLast:在任务列表的末尾添加一个动作。
自定义任务
在前面创建目录结构的时候都是手动创建,现在用自定义任务来完成自动创建目录结构的功能。
创建有参闭包用于创建目录
传进的参数是字符串路径:
def createDir = {
path ->
File dir = new File(path)
if (!dir.exists()) {
dir.mkdirs()
}
}
在build.gradle中声明任务并给任务添加动作
定义一个数组,里面存放目录路径,同时给任务添加一个动作doFirst
task makeJavaDir() {
def paths = ['src/main/java', 'src/main/resources', 'src/test/java', 'src/test/resources']
doFirst{
paths.forEach(createDir)
}
}
执行自定义任务
执行makeJavaDir后目录被创建:
定义任务创建Web的目录
因为java工程有的目录Web工程都有,只是Web工程多了webapp目录,可以使用web工程依赖java工程,这样web只用创建特有的目录即可.添加doLast动作:
task makeWebDir(){
dependsOn 'makeJavaDir'
def paths = ['src/main/webapp','src/test/webapp']
doLast {
paths.forEach(createDir)
}
}
先将test文件夹删除,测试是否会先执行makeJavaDir:
先执行makeJavaDir,在执行makeWebDir,目录也被创建好了:
构建生命周期
第一阶段:初始化
Gradle会根据构建脚本创造一个Project类,并在构建脚本中隐式可用。
第二阶段:配置
生成task的依赖顺序和执行顺序,并初始化任务。比如:
task loadVersion{
project.version='1.0'
}
第三阶段:执行
执行动作代码。例如doLast:
task loadVersion <<{
print 'success'
}
依赖管理
几乎所有的基于JVM的软件项目都需要依赖外部类库来重用现有的功能。自动化的依赖管理可以明确依赖的版本,可以解决因传递性依赖带来的版本冲突。
工件坐标
group,name,version三个属性可以唯一确定一个jar包。
仓库
仓库用来存放jar包
依赖阶段关系
编译时依赖的jar包在运行时都会依赖,在运行时依赖的在编译阶段不会依赖。源代码依赖的测试代码都会依赖,反之则不一定。
比如在build.gradle里的
dependencies {
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
testCompile就是测试编译阶段依赖的jar包,compile则是编译阶段依赖的jar包logback。
在App.java里面添加日志:
修改仓库配置
repositories可以配置多个仓库,除了默认的*仓库mavenCertral,还可以配置mavenLocal()本地仓库,当有多个仓库的时候是按仓库的顺序去查找jar包。
还可以配置私服仓库,地址就写在url里面。
repositories {
mavenLocal()
mavenCentral()
maven{
url ''
}
}
解决版本冲突
查看依赖报告
排除传递性依赖
强制一个版本
Gradle会自动依赖最高版本的jar包,这是gradle的默认处理方式。
比如现在添加hibernate-core的依赖:
dependencies {
compile group: 'org.hibernate',name: 'hibernate-core', version: '3.6.3.Final'
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
hibernate-core对slf4j-api有依赖,依赖的版本是1.6.11,而之前的logback-classic对slf4-api也有依赖,版本是1.7.22,现在加入之后被强制依赖最高的版本1.7.22:
执行 Tasks -> help -> dependencies 任务看到详细的依赖转变:
- 修改默认解决策略:
当出现版本冲突的时候,不使用最新的包,直接让它构建失败,这样就能知道哪些出现了版本冲突。
configurations.all{
resolutionStrategy{
failOnVersionConflict()
}
}
加入之后报错:
- 解决冲突:强制指定
configurations.all{
resolutionStrategy{
force 'org.slf4j:slf4j-api:1.7.22'
}
}
多项目构建
项目模块化
在企业项目中,包层次和类关系比较复杂,把代码拆分成模块通常是最佳实践,这需要清晰的划分功能边界,比如把业务逻辑和数据持久化划分开来。项目符合高内聚低耦合时,模块化就变得很容易,这是一条非常好的软件开发实践。
TODO模块依赖关系
配置子项目
创建模块repository,model,web;通过右键todo项目 -> New -> Module;
将原来的src拖进web子模块中。
打开settings.gradle生成目录结构:可以看到根项目是todo,剩下三个项目都是子模块要include进来的。
现在让repository模块依赖model模块,web模块依赖repository模块,这样web模块也能依赖model模块了。
- repository的build.gradle:
dependencies {
compile project(":model")
testCompile group: 'junit', name: 'junit', version: '4.12'
}
- web的build.gradle:
dependencies {
compile project(":repository")
compile group: 'org.hibernate',name: 'hibernate-core', version: '3.6.3.Final'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
查看依赖关系可以看到web模块依赖于repository模块,repository模块有依赖于modle模块:
多项目构建实战
先把原来的自定义任务删除
配置要求
所有项目应用Java插件
web子项目打包成WAR
所有项目添加logback日志功能
统一配置公共属性
所有项目应用Java插件
现在存在一个问题,项目和子项目中都有apply plugin: 'java',可以进行统一处理,将子项目中的apply plugin: 'java'和sourceCompatibility = 1.8都删除,在根项目的build.gradle下通过allprojects进行配置:
group 'com.zzh.gradle'
version '1.0-SNAPSHOT'
apply plugin: 'war'
allprojects{
apply plugin: 'java'
sourceCompatibility = 1.8
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
web子项目打包成WAR
将根项目下的apply plugin: 'war' 删除,在web子项目的build.gradle中加上就行。
所有项目添加logback日志功能
使用subprojects来演示配置logback的功能,用allprojects也能达到同样效果。将根项目中的repositories和dependencies放入subprojects中,这样子项目中依赖就不用配置junit
root下的build.gradle:
group 'com.zzh.gradle'
version '1.0-SNAPSHOT'
allprojects{
apply plugin: 'java'
sourceCompatibility = 1.8
}
subprojects{
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
repositories {
mavenLocal()
mavenCentral()
}
多项目构建小结:
当多项目构建的时候,每个子项目的build.gradle只是对其他子项目的依赖或者其他的个性化配置,共同的都会放在根项目下进行配置。
Gradle测试
自动化测试
一些开源的测试框架比如JUnit,TestNG可以编写可复用的结构化的测试,为了运行这些测试,需要先编译。测试代码的作用仅仅用于测试的情况,不应该被发布到生产环境中,需要把源代码和测试代码分开来。
TodoRepository.java
package com.zzh.gradle.todo.repository;
import com.zzh.gradle.todo.model.TodoItem;
import java.util.HashMap;
import java.util.Map;
public class TodoRepository {
private static Map<String, TodoItem> items = new HashMap<>();
public void save(TodoItem item) {
System.out.println("" + item);
items.put(item.getName(), item);
}
public TodoItem query(String name) {
return items.get(name);
}
}
测试类
package com.zzh.gradle.todo.repository;
import com.zzh.gradle.todo.model.TodoItem;
import org.junit.Assert;
import org.junit.Test;
import static org.junit.Assert.*;
public class TodoRepositoryTest {
private TodoRepository repository = new TodoRepository();
@Test
public void save() throws Exception {
TodoItem item = new TodoItem("zzh");
repository.save(item);
Assert.assertNotNull(repository.query(item.getName()));
}
}
执行repository子模块的build任务,查看结果:
发布
根项目的build.gradle中使用插件“maven-publish”
group 'com.zzh.gradle'
version '1.0-SNAPSHOT'
allprojects{
apply plugin: 'java'
sourceCompatibility = 1.8
apply plugin: 'maven-publish'
publishing{
publications{
myPublish(MavenPublication){
from components.java
}
}
}
repositories{
maven{
name "myRepo"
url ""
}
}
}
subprojects{
repositories {
mavenCentral()
}
dependencies {
compile group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.1'
testCompile group: 'junit', name: 'junit', version: '4.12'
}
}
myPublish是自己定义的方法名字,component.java表示把java产生的输出发布到仓库里面。repositories里的仓库名字可以自己定义,url地址一般来说就是私服地址。应用插件之后gradle的tasks增加了publishing:
执行所有的MavenLocal任务:publishToMavenLocal
打开本地仓库(根据自己配置的地址):
发布的步骤
使用maven-publish插件
配置要发布的内容和仓库地址
执行发布任务