新一代构建工具gradle

一.项目自动化介绍

1.石器时代:自动化构建工具产生之前

  依赖管理:将所有使用到的jar包放到lib目录下面,多的上百个,很容易产生版本冲突

  测试:能不写就不写,写了也是一个一个写一下main方法

  打包:Eclipse的导出jar包、war包

  上传:通过FTP上传到服务器解压运行

2.构建工具的作用

  a.依赖管理,包括各种jar包、各种版本

  b.能自动执行测试、打包、发布

  c.机器能干的活,绝不自已动手

3.主流构建工具

  Ant:编译、测试、打包

  Maven:依赖管理、发布

  Gradle:Groovy

4.Gradle是什么

  一个开源的项目自动化构建工具,建立在Apache Ant和Apache Maven概念的基础上,并引入了基于Groovy的特定领域语言(DSL),而不再使用XML形式管理构建脚本。

 

二.准备使用Gradle

1.Gradle安装

  确保已经安装JDK,java -version

  从Gradle官网下载Gradle,https://gradle.org/

  配置环境变量:GRADLE_HOME

  添加到path,%GRADLE_HOME%\bin;

  验证是否安装成功,gradle -v

2.gradle下载下来解压后

  bin目录下就两个可执行文件,一个是Windows下的可执行文件,一个是Linux下的可执行文件

  init.d目录下有一个readme.txt文件,所有初始化脚本都在这个目录下,也就是说每次执行之前都会把这个目录下的脚本执行一遍

  lib目录下就是gradle本身所依赖的jar包,因为它是基于JVM的,所有它所依赖的都是jar包形式

  media目录下都是一个图标文件,没有什么特别的

3.groovy基础知识-理论介绍

  Groovy是用Java虚拟机的一种敏捷的动态语言,它是一种成熟的面向对象编程语言,既可以用于面向对象编程,又可以用作纯粹的脚本语言。使用该种语言不必编写过多的代码,同时又具有闭包和动态语言中的其他特性。

  与Java比较:

  a.Groovy完全兼容Java的语法

  b.分号是可选的

  c.类、方法默认是public的

  d.编译器给属性自动添加getter/setter方法

  e.属性可以直接用点号获取

  f.最后一个表达式的值会被作为返回值

  g.==等同于equals(),不会有NullPointerExceptions

  高效的Groovy特性

  a.assert语句

  b.可选类型定义

  c.可选的括号

  d.字符串

  e.集合API

  f.闭包

4.groovy基础知识-与java比较

  操作实例

  这里的JDK也选择我本地安装的JDK

  然后我们打开groovy的窗口。

  然后用Java的方式写一个Bean,如下所示:

  然后用groovy语法改造这个Bean如下,依然正确。

  简单运用运行结果如下:

5.groovy基础知识-高效特性

  在groovy里面,assert在任何地方都是可以执行的。

   如下定义一个list,然后通过<<方式追加一个元素,assert判断是类型和大小

6.groovy基础知识-重点

  

三.第一个Gradle项目

1.介绍

  ToDo应用程序,只实现添加待办事项

  Java应用程序版

  Web版

2.TODO-应用程序版本

  自动生成的build.gradle内容如下:

  目录结构

  编写简单示例代码:

  App.java

package com.bijian.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);
        }
    }
}

  TodoItem.java

package com.bijian.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;
    }

    public String toString() {
        return name + (hasDone ? " hasDone":"need to do") + "!";
    }
}

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

  build:也可以实现打成jar包的同样功能,因为build就是执行build.gradle这个构建脚本,这里使用的是java插件,所以构建完成后也是jar包的形式

  classes:将源代码编译成class类

  clean:清除之前的构建

  点击这个jar执行后,即会把当前源文件编译完成并打成jar包。执行时如出现如下错误:

  通过如下方式进行设置:

File ->Settings ->Editor ->File Encodings 这种方式修改的文件编码方式只对当前 project 起作用,每次新建了一个工程后还需要重新设置编码方式。
File ->Other Settings ->Default Settings ->Editor ->File Encodings ,这儿设置的是默认的文件编码方式,所有新建的工程使用的都是默认的文件编码方式。

   设置完成后,重新双击jar执行正常,在build->libs下新生成jar文件。

  执行这个jar包(因为这个jar包中有一个main方法),命令:java -classpath build/libs/todo-1.0-SNAPSHOT.jar com.bijian.gradle.todo.App

3.TODO-WEB版

  新增webapp目录及其下文件夹和文件,并在resources下新增log.properties(这里只是看最终打包后把这个文件放到war包的哪个地方)

  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_4_0.xsd"
         version="3.1"
         metadata-complete="true">

    <display-name>Welcome to Tomcat</display-name>
    <description>
        Welcome to Tomcat
    </description>
</web-app>

  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中增加:apply plugin: 'war',将会在右边多出一个war,同样双击运行成功后将在build/libs下产生war包。

  将打出来的war包拷贝到本地安装的tomcat的webapps目录下,然后双击bin目录下的startup.bat启动tomcat服务器,启动成功后,在浏览器中输入http://localhost:8080/todo-1.0-SNAPSHOT/index.html运行效果如下:

  这里我们再到tomcat的webapps目录下,可以看到todo-1.0-SNAPSHOT.war被自动解压。

  打开todo-1.0-SNAPSHOT目录

 

 

四.高级应用

1.构建脚本介绍

  构建块:Gradle构建中的两个基本概念是项目(project)和任务(task),每个构建至少包含一个项目,项目中包含一个或多个任务。在多项目构建中,一个项目可以依赖于其他项目;类似的,任务可以形成一个依赖关系图来确保他们的执行顺序。

  项目(Project):一个项目代表一个正在构建的组件(比如一个jar文件),当构建启动后,Gradle会基于build.gradle实例化一个org.gradle.api.Project类,并且能够通过project变量使其隐式可用。常用的属性:group、name、version,这三个属性就是一个组件的坐标。重要的方法是apply、dependencies、repositories、task。

  apply:应用一个插件,它是在Project上的一个方法。

  dependencies:用来声明依赖于哪些jar包或其它项目。

  repositories:是一个仓库,它就是说去哪个仓库去找依赖的jar包,也就是说先找到仓库,在仓库里再根据group、version唯一确定一个组件。

  task:用来声明项目里有什么任务的。

  属性的其他配置方式:ext(可以直接在这上面定义属性,可以直接引用)、gradle.properties(在gradle.properties里面可用键值对的方式来声明属性)

  任务(task):任务对应org.gradle.api.Task。主要包括任务动作和任务依赖。任务动作定义了一个最小的工作单元。可以定义依赖于其他任务、动作序列和执行条件。有dependsOn、doFirst、doLast<<重要方法。dependsOn是声明任务依赖的。task也是一个动作列表,doFirst在任务列表的最前面添加一个动作,doLast在动作的末尾添加一个动作,一个任务里面可以执行多次doFirst、doLast,也就是说动作列表里面可以包含多个动作。

  隐式可用,如group 'com.bijian.gradle'其实就是project.group='com.bijian.gradle',按住Ctrl鼠标点击,可以打开Project类,可以看到,它们其实就是调用Project的setGroup方法。 

 

  同理,version调用的是setVersion方法。名称是在settings.gradle里的,有一个rootProject.name='todo'。settings.gradle是用来管理多项目构建的。

  apply是PluginAware接口的一个方法,由于Project继承了这个接口,所以它也有apply方法。

  repositories、dependencies也是调用Project里的repositories、dependencies方法,

 

  我们可以自已定义任务,但是我们使用插件以后,默认是带了很多任务的,如下所示:

2.自定义任务

  自定义一个任务来帮我们自动创建目录结构的功能。

def createDir = {
    path ->
        File dir = new File(path);
        if(!dir.exists()) {
            dir.mkdirs();
        }
}
task makeJavaDir() {
    def paths = ['src/main/java','src/main/resources','src/test/java','src/test/resources']
    doFirst {
        paths.forEach(createDir)
    }
}

task makeWebDir() {
    dependsOn 'makeJavaDir'
    def paths = ['src/main/webapp','src/test/webapp']
    doLast {
        paths.forEach(createDir)
    }
}

  makeJavaDir和makeWebDir都是创建目录,其中makeWebDir依赖makeJavaDir。另外,doLast和doFirst在这个应用中没有区别,用哪个都行。

 

  自定义任务在other下,双击运行可以看到效果。以后我们每次新建工程之后,就可以把这个代码放到build.gradle里面,如果觉得这样很麻烦的话,可以把这个功能做成插件。

3.构建生命周期

  初始化:gradle会根据构建脚本创建一个项目,也就是一个Project类,并且在这个构建脚本中隐式可用。在多项目构建中,这个阶段也是很重要的,它会初始化所有将要参与到构建中的项目。

  配置:用来生成task的依赖顺序以及执行顺序,根据配置代码来生成的,配置代码就是除了动作代码外的就是配置代码。

  执行:执行动作代码,执行完后一个构建就完成了。

  上面自定义的任务里面,doFirst和doLast中的代码就是动作代码,是执行阶段会执行的代码,其它属于配置阶段的代码,dependsOn只能在配置阶段执行,因为配置阶段就要决定任务的依赖关系和执行顺序。

  gradle是非常灵活的,还为我们提供了勾子方法。

  一句话,初始化阶段就是初始化项目,有哪些项目需要参与到构建当中。配置阶段就是生成task的依赖关系和执行图。执行阶段就是执行task的动作。 

4.依赖管理

  几乎所有的基于JVM的软件项目都需要依赖外部类库来重用现有的功能。自动化的依赖管理可以明确依赖的版本,可以解决因传递性依赖带来的版本冲突。

  工作坐标:group、name、version。通过这三个属性可以唯一确定一个jar包。

  仓库就是用来存放jar包的地方,常用仓库

  a.mavenLocal:通过Maven使用过的jar包都会在本地仓库存有一份,mavenCentral/jcenter:公网上的公共仓库

  b.在实际工作中最常用的是自定义maven仓库,也就是私服。

  c.文件仓库:就是本地机器上的文件路径,也可以做为仓库。这个非常不建议大家去使用,因为我们使用构建工具就是为了让构建一致性,到处构建,结果应该都是一样的,如果跟具体的机器有关的话,就违反了我们使用构建工具的初忠,当然,这个实在有特殊需求的话可以使用一下。

  依赖的传递性

  B依赖A,如果C依赖B,那么C依赖A

  依赖阶段配置

  源代码两个阶段:compile、runtime

  测试代码也有两个阶段:testCompile、testRuntime

  运行时阶段是扩展于编译阶段的,如果在编译阶段依赖的jar包在运行时都会依赖,如果在运行时依赖的编译阶段不会依赖。如果是源代码依赖的,测试代码都会依赖,测试代码依赖的,源代码不一定依赖。

  ‘将testCompile改testRuntime,将变成运行阶段依赖。如果修改后一定要刷新的话,可以点击工程右键,勾选上Auto-import。

  mavenCentral的网址是:https://search.maven.org/

  如果我们要给工程添加一个日志功能,要添加日志的依赖,如使用logback。

  添加到我们的工程中后,工程会自动下载依赖jar包。

  添加运行后发现我们成功的添加了logback的依赖。我们没有自已去写logback的实现,没有写代码,就是重用了别人的轮子。即使用别人的依赖,实现自已的需求。

  大多数情况下都是使用compile时依赖,因为我们编译时需要使用,运行时也需要使用。有一些情况,如jdbc的实现类,作为一个驱动,编译源代码因为使用的是接口,可以不用实现,因为JDK里在包含了jdbc的接口,驱动里面包含具体实现类,我们编译时只需有接口就可以了,而运行的时候就需要具体的实现类,所以这个时候就可以使用运行时依赖。当然,如果使用编译时依赖也是可以的。实在区分不了的话,都可以使用编译阶段的依赖。

  依赖的写法

  ch.qos.logback:logback-classic:1.2.1,这种是冒号分隔的简短的写法,第一个冒号之前的是groupId,第二个冒号之前的是name,最后是version。group、name、version就是坐标,在仓库中就可以通过这个坐标唯一确定一个jar包。

  我们也可以配置多个仓库,如果有多个仓库,就是按仓库的顺序查找jar包的,如果前面的仓库查找到jar包了,就不会到后面的仓库中去查找。

mavenLoacl()就是本地的maven仓库。

  传递性依赖

5.解决版本冲突

  解决版本冲突的具体步骤:

  a.查看依赖报告

  b.排除传递性依赖

  c.强制一个版本。

  gradle的默认处理版本冲突的方法是:会自动帮我们解决版本冲突,会自动依赖最高版本的jar包。

  修改默认解决策略

configurations.all{
    resolutionStrategy{
         failOnVersionConflict()
    }
}

  这样,当出现版本冲突的时候,不使用最新的包,直接让他构建失败,这样我们就知道哪些出现了版本冲突,要不然我们很难发现哪些出现了版本冲突。

  解决冲突:

  a.排除传递性依赖

compile('org.hibernate:hibernate-core:3.6.3.Final') {
    exclude group:"org.slf4j",module:"slf4j-api"
    //transitive=false
}

  exclude group:"org.slf4j",module:"slf4j-api":排除具体jar包的依赖,这里的module就是我们坐标中的name属性,版本是不需要指定的,这是一种把低版本的依赖排除掉。

  transitive=false:排除所有的传递性依赖

  b.强制指定一个版本

  我们可以指定为最新的版本,或者比当前所有的依赖都要高的版本

configurations.all{
    resolutionStrategy{
        force 'org.slf4j:slf4j-api:1.7.24'
    }
}

  操作实例:

  此时,我增加hibernate依赖

  此时,我们点击Tasks->help->dependencies

  这样我们可以清晰地看到详细的依赖,这里构建成功了,说明gradle默认帮我们处理了版本冲突。

  我们修改策略,如果发现版本冲突,就构建失败。

  这时,我们可以使用排除传递性依赖方法解决如下:

  此时可以构建成功。

  也可以使用另外一种方法,就是强制指定一个版本。

  当然,也可以在仓库中查到最新的版本,强制指定到最新的版本。 

6.多项目构建介绍

  项目模块化:在企业项目中,包层次和类关系比较复杂,把代码拆分成模块通常是最佳实践,这需要你清晰的划分功能的边界,比如把业务逻辑和数据持久化拆分开来。项目符合高内聚低耦合时,模块化就变得很容易,这是一条非常好的软件开发实践。

  配置要求:

  所有项目应用Java插件

  web子项目打包成WAR

  所有项目添加logback日志功能

  统一配置公共属性

7.多项目构建实战

  a.子模块创建

  点“完成”,web模块就建好了。

  然后将web模块下的src目录全部删除,将外面工程的src拖入。

  成功后,我们看到settings.gradle内容如下:

  用同样的方法创建model、repository两个模块。

  需说明的是repository模块依赖model模块,需增加如下模块的依赖关系。

  此时,settings.gradle内容也同步发生了变化。

  在web模块中新增对repository模块的依赖,然后能看到传递性依赖model模块。

  b.依赖关系的处理

  model中的build.gradle中的内容可以不用配置了,根模块配置就可以了

 

  repository模块的build.gradle内容如下:

repositories {
    mavenCentral()
}

dependencies {
    compile project(":model")
}

  web模块的build.gradle内容如下:

apply plugin: 'war'

repositories {
    mavenCentral()
}

dependencies {
    compile project(":repository")
}

  根路径下的build.gradle内容如下:

allprojects{
    apply plugin: 'java'
    sourceCompatibility = 1.8
}

subprojects {
    repositories {
        mavenCentral()
    }
    dependencies {
        compile 'ch.qos.logback:logback-classic:1.2.1'
        testRuntime group: 'junit', name: 'junit', version: '4.12'
    }
}

repositories {
    mavenCentral()
}

  并在根路径下新建gradle.properties,内容如下:这里是统一配置group和version

group=com.bijian.gradle
version=1.0-SNAPSHOT

  说明:

  右边的构建根项目下的命令会依次执行各个子项目对应的命令,如果想单独执行某个子项目的命令,单独执行某个子项目下的命令就可以了。

  如执行根模块下的clean。

  如执行web模块的war命令,会对先执行依赖模块的任务,如下所示:

  小结:settings.gradle用来管理项目和子项目的,每个子项目下的build.gradle只用来配置当前子项目个性化的需求,所有相同的配置都要在根项目的build.gradle进行配置,当然,也可以在子配置中进行配置,但这样会出现重复配置,我们后期不太好维护。

8.自动化测试

  一些开源的测试框架比如JUnit、TestNG能够帮助你编写可复用的结构化的测试,为了运行这些测试,你要先编译它们,就像编译源代码一样。测试代码的作用仅仅用于测试的情况,不应该被发布到生产环境中,需要把源代码和测试代码分开来。

  测试配置:

dependencies{
     testCompile 'junit:junit:4.11'
}

  如果test执行失败,后面的步骤就不会执行了,构建就会失败,并且给提示具体的错误信息。

  我们在repository模块下新增测试代码。

   双击运行repository下的build命令,如下所示:

  此时,打开repository->build->reports->tests->test下的index.html,查看测试运行结果。

  修改测试断言,测试失败,可以看到如下的具体信息。

9.发布

  一般最终发布到我们公司的私服上去,供其它项目使用或其他同事使用,我们最常用的仓库就是maven仓库,因为gradle没有自已的仓库,ivy现在开发很少使用了。

  发现用maven-publish这个插件,在这个插件下在配置两个属性就可以了,如下所示:

  配置后可以在右边看到多出publishing目录及下面的任务。

  执行完publishMyPublishPublicationToMavenLocal命令后,将会在本地Maven仓库中产生相应的jar包。

   简单来说,我们用三步就可以发布到仓库里面去,第一步使用maven publish这个插件,第二步配置我们要发布什么、仓库地址,第三步执行一下发布任务就可以了。

 

五.课程总结

 

学习地址:https://www.imooc.com/learn/833

posted on 2019-04-14 13:50  bijian1013  阅读(310)  评论(0)    收藏  举报

导航