初识构建工具-gradle

构建工具的作用

  • 依赖管理

  • 测试,打包,发布

主流的构建工具

  • 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

jar:把当前的源文件编译完后打包成jar包。

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插件

  • 配置要发布的内容和仓库地址

  • 执行发布任务


总体流程

GitHub地址:

learning_gradle

posted @ 2017-05-28 17:02  六月的余晖  阅读(5499)  评论(0编辑  收藏  举报