zoukankan      html  css  js  c++  java
  • 初识构建工具-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%in;

    • 验证是否安装成功, 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

  • 相关阅读:
    CentOS6 配置阿里云 NTP 服务
    使用docker-compose运行nginx容器挂载时遇到的文件/目录问题
    Springboot配置文件参数使用docker-compose实现动态配置
    Dockerfile文件全面详解
    docker 生成mysql镜像启动时自动执行sql
    CentOS无法识别NTFS格式U盘完美解决方案
    网络模型与网络策略
    k8s更换网络插件:从flannel更换成calico
    数据采集实战(四)-- 线性代数习题答案下载
    leedcode 146. LRU 缓存机制(哈希+双向链表)
  • 原文地址:https://www.cnblogs.com/zhaozihan/p/6902132.html
Copyright © 2011-2022 走看看