├── .gitignore ├── LICENSE ├── README.md ├── SUMMARY.md ├── book.json ├── build-script ├── build-block.md ├── build-lifetime.md ├── introduce.md ├── summary.md └── tasks.md ├── cover.jpg ├── dependency-management ├── configure-respositories.md ├── declaring-dependencies.md ├── dependency-problems.md ├── introduce.md ├── learn-by-example.md ├── local-cache.md ├── quick-overview.md └── summary.md ├── first-project ├── build-java-project.md ├── gradle-wrapper.md ├── introduce-this.md ├── introduce.md ├── summary.md └── web-development.md ├── gradle ├── continuous-dilivery.md ├── gradle-features.md ├── gradle.md ├── install-gradle.md ├── start-with-gradle.md ├── summary.md ├── using-command-line.md └── why-gradle.md ├── images ├── 4-1.png ├── 4-2.png ├── 4-3.png ├── 4-4.png ├── 5-1.png ├── 5-10.png ├── 5-2.png ├── 5-3.png ├── 5-4.png ├── 5-5.png ├── 5-6.png ├── 5-7.png ├── 5-8.png ├── 5-9.png ├── build-process.png ├── dag.png ├── dag1.png ├── dag10.png ├── dag11.png ├── dag12.png ├── dag13.png ├── dag14.png ├── dag15.png ├── dag16.png ├── dag17.png ├── dag18.png ├── dag19.png ├── dag2.png ├── dag20.png ├── dag21.png ├── dag22.png ├── dag23.png ├── dag24.png ├── dag25.png ├── dag26.png ├── dag27.png ├── dag28.png ├── dag29.png ├── dag3.png ├── dag30.png ├── dag31.png ├── dag32.png ├── dag33.png ├── dag34.png ├── dag35.png ├── dag36.png ├── dag37.png ├── dag38.png ├── dag39.png ├── dag4.png ├── dag40.png ├── dag41.png ├── dag42.png ├── dag43.png ├── dag44.png ├── dag45.png ├── dag46.png ├── dag47.png ├── dag5.png ├── dag51.png ├── dag52.png ├── dag53.png ├── dag54.png ├── dag6.png ├── dag7.png ├── dag8.png └── dag9.png ├── multi-project ├── assemble.md ├── configure-subproject.md ├── customizing.md ├── individual.md ├── introduce.md ├── module-project.md └── summary.md ├── project-automation ├── benefits-of-project-automation.md ├── build-tools.md ├── build-tools.md~ ├── java-build-tools.md ├── life-without-project-automation.md ├── project-automation.md ├── summary.md └── types-of-project-automation ├── push └── test-with-gradle ├── automated-test.md ├── configuring-test-execution.md ├── introduce.md ├── test-java-application.md ├── unit-testing.md └── unit-testing.md~ /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .gradle/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Lippi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 又多了一份Gradle教程:Gradle In Action 3 | 4 | # Gradle In Action(Gradle实战)中文版 5 | 6 | * Gradle In Action中文版(Gradle中文教程), 已完成翻译。 7 | 8 | * 如果发现不通顺或者有歧义的地方, 可以在评论里指出来, 我会及时改正的 9 | 10 | * [Github托管地址](https://github.com/LippiOuYang/GradleInActionZh) 11 | 12 | * [阅读地址](http://lippiouyang.gitbooks.io/gradle-in-action-cn/content/) 13 | 14 | 15 | 16 | ### 贡献者列表 17 | 18 | 成员 | 联系方式 | Github 19 | :------|:------|:------ 20 | Lippi | ouyanglip@gmail.com | [Github](https://github.com/LippiOuYang) 21 | Kary | kary@163.com | [Github](https://github.com/kary) 22 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | * [关于本书](README.md) 3 | * [项目自动化](project-automation/project-automation.md) 4 | * [构建工具](project-automation/build-tools.md) 5 | * [Java构建工具](project-automation/java-build-tools.md) 6 | * [Gradle下一代构建工具](gradle/gradle.md) 7 | * [为什么选择Gradle](gradle/why-gradle.md) 8 | * [Gradle强大的特性](gradle/gradle-features.md) 9 | * [连续传递的特性](gradle/continuous-dilivery.md) 10 | * [安装Gradle](gradle/install-gradle.md) 11 | * [Gradle起步](gradle/start-with-gradle.md) 12 | * [使用命令行](gradle/using-command-line.md) 13 | * [开始你的第一个Gradle项目](first-project/introduce.md) 14 | * [介绍这个Gradle项目](first-project/introduce-this.md) 15 | * [构建Java项目](first-project/build-java-project.md) 16 | * [Gradle部署Web项目](first-project/web-development.md) 17 | * [Gradle包装器](first-project/gradle-wrapper.md) 18 | * [构建脚本基础](build-script/introduce.md) 19 | * [构建块](build-script/build-block.md) 20 | * [管理任务](build-script/tasks.md) 21 | * [构建生命周期](build-script/build-lifetime.md) 22 | * [依赖管理](dependency-management/introduce.md) 23 | * [快速预览](dependency-management/quick-overview.md) 24 | * [实战依赖管理](dependency-management/learn-by-example.md) 25 | * [声明依赖](dependency-management/declaring-dependencies.md) 26 | * [使用和配置仓库](dependency-management/configure-respositories.md) 27 | * [多项目构建](multi-project/introduce.md) 28 | * [项目模块化](multi-project/module-project.md) 29 | * [多项目打包](multi-project/assemble.md) 30 | * [配置子项目](multi-project/configure-subproject.md) 31 | * [拆分项目文件](multi-project/individual.md) 32 | * [自定义项目](multi-project/customizing.md) 33 | * [总结](multi-project/summary.md) 34 | * [使用Gradle进行测试](test-with-gradle/introduce.md) 35 | * [自动化测试](test-with-gradle/automated-test.md) 36 | * [测试Java应用](test-with-gradle/test-java-application.md) 37 | * [单元测试](test-with-gradle/unit-testing.md) 38 | -------------------------------------------------------------------------------- /book.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["disqus"], 3 | "pluginsConfig": { 4 | "disqus": { 5 | "shortName": "lippi" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /build-script/build-block.md: -------------------------------------------------------------------------------- 1 | #构建块 2 | 3 | 每个Gradle构建都包括三个基本的构建块:项目(projects)、任务(tasks)和属性(properties),每个构建至少包括一个项目,项目包括一个或者多个任务,项目和任务都有很多个属性来控制构建过程。 4 | 5 | Gradle运用了领域驱动的设计理念(DDD)来给自己的领域构建软件建模,因此Gradle的项目和任务都在Gradle的API中有一个直接的class来表示,接下来我们来深入了解每一个组件和它对应的API。 6 | 7 | ##项目 8 | 9 | 在Gradle术语里项目表示你想构建的一个组件(比如一个JAR文件),或者你想完成的一个目标(比如打包app),如果你以前使用过Maven,你应该听过类似的概念。与Maven pom.xml相对应的是build.gradle文件,每个Gradle脚本至少定义了一个项目。当开始构建过程后,Gradle基于你的配置实例化org.gradle.api.Project这个类以及让这个项目通过project变量来隐式的获得。下图列出了API接口和最重要的方法。 10 | 11 | ![](/images/dag24.png) 12 | 13 | 一个项目可以创建新任务、添加依赖和配置、应用插件和其他脚本,许多属性比如name和description都是可以通过getter和setter方法来访问。 14 | 15 | Project实例允许你访问你项目所有的Gradle特性,比如任务的创建和依赖了管理,记住一点当访问你项目的属性和方法时你并不需要显式的使用project变量--Gradle假定你的意思是Project实例,看看下面这个例子: 16 | 17 | //没有使用project变量来设置项目的描述 18 | setDescription("myProject") 19 | //使用Grovvy语法来访问名字和描述 20 | println "Description of project $name: " + project.description 21 | 22 | 在之前的章节,我们只处理到单个peoject的构建,Gradle支持多项目的构建,软件设计一个很重要的概念是模块化,当一个软件系统变得越复杂,你越想把它分解成一个个功能性的模块,模块之间可以相互依赖,每个模块有自己的build.gradle脚本。 23 | 24 | ##任务 25 | 26 | 我们在第二章的时候就创建了一些简单的任务,你应该了解两个概念:任务动作(actions)和任务依赖,一个动作就是任务执行的时候一个原子的工作,这可以简单到打印hello world,也可以复杂到编译源代码。很多时候一个任务需要在另一个任务之后执行,尤其是当一个任务的输入依赖于另一个任务的输出时,比如项目打包成JAR文件之前先要编译成class文件,让我们来看看Gradle API中任务的表示:org.gradle.api.Task 接口。 27 | 28 | ![](/images/dag25.png) 29 | 30 | 31 | ##属性 32 | 33 | 每个Project和Task实例都提供了setter和getter方法来访问属性,属性可以是任务的描述或者项目的版本号,在后续的章节,你会在具体例子中读取和修改这些属性值,有时候你要定义你自己的属性,比如,你想定义一个变量来引用你在构建脚本中多次使用的一个文件,Gradle允许你通过外部属性来定义自己的变量。 34 | 35 | ###外部属性 36 | 37 | 外部属性一般存储在键值对中,要添加一个属性,你需要使用ext命名空间,看一个例子: 38 | 39 | //Only initial declaration of extra property requires you to use ext namespace 40 | project.ext.myProp = 'myValue' 41 | ext { 42 | someOtherProp = 123 43 | } 44 | 45 | //Using ext namespace to access extra property is optional 46 | assert myProp == 'myValue' 47 | println project.someOtherProp 48 | ext.someOtherProp = 567 49 | 50 | 相似的,外部属性可以定义在一个属性文件中: 51 | 通过在/.gradle路径或者项目根目录下的 gradle.properties文件来定义属性可以直接注入到你的项目中,他们可以通过 project实例来访问,注意/.gradle目录下只能有一个 Gradle属性文件即使你有多个项目,在属性文件中定义的属性可以被所有的项目访问,假设你在你的gradle.properties文件中定义了下面的属性: 52 | 53 | exampleProp = myValue 54 | someOtherProp = 455 55 | 56 | 你可以在项目中访问这两个变量: 57 | 58 | assert project.exampleProp == 'myValue' 59 | 60 | task printGradleProperty << { 61 | println "Second property: $someOtherProp" 62 | } 63 | 64 | **定义属性的其他方法** 65 | 66 | 你也可以通过下面的方法来定义属性: 67 | 68 | * 通过-P命令行选项来定义项目属性 69 | * 通过-D命令行选项来定义系统属性 70 | * 环境属性遵循这个模式: `ORG_GRADLE_PROJECT_propertyName=someValue` 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /build-script/build-lifetime.md: -------------------------------------------------------------------------------- 1 | ##掌握构建生命周期 2 | 3 | 作为一个构建脚本的开发者,你不应该局限于编写任务动作或者配置逻辑,有时候你想在指定的生命周期事件发生的时候执行一段代码。生命周期事件可以在指定的生命周期之前、之中或者之后发生,在执行阶段之后发生的生命周期事件就该是构建的完成了。 4 | 5 | 假设你希望在构建失败时能够在开发阶段尽早得到反馈,给构建生命周期事件添加回调有两种方法:一是通过闭包,二是实现 Gradle API 的一个监听接口,Gradle并没有要求你监听生命周期事件,这完全决定于你,通过监听器实现的优势就是可以给你的类写单元测试,看看下面这幅图会有一点直观的印象: 6 | 7 | 8 | ![](/images/dag27.png) 9 | 10 | 在配置阶段,Gradle决定在任务在执行阶段的执行顺序,依赖关系的内部结构是通过直接的无环图(DAG)来表示的,图中的每一个任务称为一个节点,每一个节点通过边来连接,你很有可能通过dependsOn或者隐式的依赖推导来创建依赖关系。记住DAG图从来不会有环,就是说一个已经执行的任务不会再次执行,下面这幅图将要的展示了这个过程: 11 | 12 | ![](/images/dag28.png) 13 | 14 | 回想一下之前我们实现的makeReleaseVersion任务是在release任务之前执行的,我们可以编写一个生命周期回调方法来取代之前写一个任务来执行版本修改任务。构建系统准确知道在执行之前应该运行哪些任务,你可以查询任务图来查看它是否存在,下面这幅图展示了访问任务执行图的相关接口: 15 | 16 | ![](/images/dag29.png) 17 | 18 | 接下来我们来添加相应的监听方法,下面这段代码通过调用whenReady方法来注册回调接口,当任务图创建的时候这个回调会自动执行,你知道这个逻辑会在任何任务之前执行,所以你可以移除makeReleaseVersion任务。 19 | 20 | gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph -> 21 | //检查任务图是否包括release任务 22 | if(taskGraph.hasTask(release)) { 23 | 24 | if(!version.release) { 25 | 26 | version.release = true 27 | 28 | ant.propertyfile(file: versionFile) { 29 | entry(key: 'release', type: 'string', operation: '=', 30 | value: 'true') 31 | } 32 | } 33 | } 34 | } 35 | 36 | 你也可以实现一个监听器来实现同样的效果,首先在构建脚本中编写一个实现指定监听器的类,然后在构建中注册这个实现,监听任务执行图的接口是TaskExecutionGraphListener,编写的时候你只需要实现graphPopulate(TaskExecutionGraph)方法,下图表示了这个过程: 37 | 38 | ![](/images/dag30.png) 39 | 40 | 下面是编程实现: 41 | 42 | class ReleaseVersionListener implements TaskExecutionGraphListener { 43 | final static String releaseTaskPath = ':release' 44 | 45 | @Override 46 | 47 | void graphPopulated(TaskExecutionGraph taskGraph) { 48 | //查看是否包含release任务 49 | if(taskGraph.hasTask(releaseTaskPath)) { 50 | List allTasks = taskGraph.allTasks 51 | //查找release任务 52 | Task releaseTask = allTasks.find {it.path == releaseTaskPath } 53 | Project project = releaseTask.project 54 | 55 | if(!project.version.release) { 56 | 57 | project.version.release = true 58 | project.ant.propertyfile(file: project.versionFile) { 59 | entry(key: 'release', type: 'string', operation: '=', 60 | value: 'true') 61 | } 62 | } 63 | } 64 | } 65 | } 66 | def releaseVersionListener = new ReleaseVersionListener() 67 | //注册监听器 68 | gradle.taskGraph.addTaskExecutionGraphListener(releaseVersionListener) 69 | 70 | 71 | -------------------------------------------------------------------------------- /build-script/introduce.md: -------------------------------------------------------------------------------- 1 | #简介 2 | 3 | 在第三章,我们在Gradle核心插件的帮助下构建了一个Java Web项目,我们了解到这些插件都是可以自定义来适应自己的非标准化的构建需求、给你的项目添加可执行的构建逻辑来配置tasks。 4 | 5 | 在这一章,我们来学习Gradle构建的基本构建块(blocks),比如项目和任务,以及他们是如何对应到Gradle API的类中,通过这些类的方法你可以获得一些属性来控制构建过程,你也将学习到如何使用属性来控制构建行为。 6 | 7 | 你将学习到如何定义简单的任务,更复杂一点的是编写自定义的任务类,接下来我们会接触到像访问任务属性、定义显式和隐式的依赖、添加递增的构建支持以及使用Gradle自带的任务类型。我们也会了解到Gradle的构建生命周期来更好的理解构建是怎么配置和执行的。 8 | -------------------------------------------------------------------------------- /build-script/summary.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/build-script/summary.md -------------------------------------------------------------------------------- /build-script/tasks.md: -------------------------------------------------------------------------------- 1 | #管理任务 2 | 3 | 每个新创建的任务都是 org.gradle.api.DefaultTask 类型, [org.gradle.api.Task](https://github.com/gradle/gradle/blob/master/subprojects/core-api/src/main/java/org/gradle/api/Task.java) 的标准实现,DefaultTask 所有的域都是私有的,意味着他们只能通过 setter 和 getter 方法来访问,庆幸的是Groovy提供了一些语法糖来允许你通过名字来使用域。 4 | 5 | ##管理项目的版本 6 | 7 | 许多公司或者开源组织有他们自己的发布版本的措施,一般用主版本号和次版本号来表示,也会用SNAPSHOT来表示项目正在开发中,版本号是通过String类型来表示,如果你想准确获得项目的主版本号,那应该怎么办?使用正则表达式匹配点号然后过滤得到主版本号和次版本号?如果我们用一个类来表示是不是更简单? 8 | 9 | 你可以很简单的通过类的域来设置、查询和修改你的版本号的某个部分,甚至你可以把版本信息直接保存在一个文件里,比如一个文件或者数据库里,避免通过修改构建脚本来更改版本号,如下图所示: 10 | 11 | ![](/images/dag26.png) 12 | 13 | 通过编程来控制版本对于自动化项目生命周期很有必要,比如:你的代码通过了单元测试准备交付了,当前的版本是1.3-SNAPSHOT,在打包成war文件之前你想把它变成发布版本1.3然后自动部署到服务器中,这些步骤可以划分为多个任务:一个用来修改项目的版本号另一个用于打包WAR文件。 14 | 15 | ##声明任务的动作(actions) 16 | 17 | 动作就是在你的任务中放置构建逻辑的地方,Task接口提供了两个方法来声明任务的动作: 18 | doFirst和doLast,当任务执行的时候,定义在闭包里的动作逻辑就按顺序执行。 19 | 20 | 接下来我们会写一个简单的任务printVersion,任务的作用就是打印项目的版本号,在任务 21 | 的最后一个动作定义这个逻辑。 22 | 23 | version = '0.1-SNAPSHOT' 24 | 25 | task printVersion { 26 | doLast { 27 | println "Version: $version" 28 | } 29 | } 30 | 31 | 前面我们讲过左移操作符是方法doLast的快捷键,他们的作用是一样的,当你执行gradle printVersion,你应该得到下面的输出: 32 | 33 | gradle printVersion 34 | :printVersion 35 | Version: 0.1-SNAPSHOT 36 | 37 | 如果你用doFirst方法的话输出的结果是一样的: 38 | 39 | task printVersion { 40 | doFirst { 41 | println "Version: $version" 42 | } 43 | } 44 | 45 | **给已经存在的任务添加动作** 46 | 47 | 到目前为止,你只是给printVersion这个任务添加了单个动作,要么是第一个或者最后一个,对于每个任务可以有多个动作,实际上,当任务创建的时候你可以添加任意多个动作,每一个任务都有一个动作清单,他们在运行的时候是执行的,接下来我们来修改之前的例子: 48 | 49 | task printVersion { 50 | //任务的初始声明可以添加first和last动作 51 | doFirst { 52 | println "Before reading the project version" 53 | } 54 | 55 | doLast { 56 | println "Version: $version" 57 | } 58 | } 59 | 60 | //你可以在任务的动作列表的最前面添加其他任务,比如: 61 | 62 | printVersion.doFirst { println "First action" } 63 | 64 | 由此可知,我们可以添加额外的动作给已经存在的任务,当你想添加动作的那个任务不是你自己写的时候这会非常有用,你可以添加一些自定义的逻辑,比如你可以添加doFirst动作到compile-Java任务来检查项目是否包含至少一个source文件。 65 | 66 | **访问任务属性** 67 | 68 | 接下来我们来改善一下输出版本号的方法,Gradle提供一个基于SLF4J库的日志实现,除了实现了基本的日志级别(DEBUG, ERROR, INFO, TRACE, WARN))外,还添加了额外的级别,日志实例可以通过任务的方法来直接访问,接下来,你将用QUIET级别打印项目的版本号: 69 | 70 | 71 | task printVersion << { 72 | logger.quiet "Version: $version" 73 | } 74 | 75 | 访问任务的属性是不是很容易?接下来我将给你展示两个其他的属性,group和description,两个都是documentation任务的一部分,description属性简短的表示任务的目的,group表示任务的逻辑分组。 76 | 77 | task printVersion( 78 | group: 'versioning', 79 | description: 'Prints project version.') << { 80 | logger.quiet "Version: $version" 81 | } 82 | 83 | 你也可以通过setter方法来设置属性: 84 | 85 | task printVersion { 86 | group = 'versioning' 87 | description = 'Prints project version.' 88 | doLast { 89 | logger.quiet "Version: $version" 90 | } 91 | } 92 | 93 | 当你运行gradle tasks,你会看到任务显示在正确的分组里和它的描述信息: 94 | 95 | gradle tasks 96 | :tasks 97 | ... 98 | Versioning tasks 99 | ---------------- 100 | printVersion - Prints project version. 101 | ... 102 | 103 | ##定义任务依赖 104 | 105 | 106 | dependsOn方法用来声明一个任务依赖于一个或者多个任务,接下来通过一个例子来讲解运用不同的方法来应用依赖: 107 | 108 | task first << { println "first" } 109 | task second << { println "second" } 110 | 111 | //声明多个依赖 112 | task printVersion(dependsOn: [second, first]) << { 113 | logger.quiet "Version: $version" 114 | } 115 | 116 | task third << { println "third" } 117 | //通过任务名称来声明依赖 118 | third.dependsOn('printVersion') 119 | 120 | 你可以通过命令行调用third任务来执行这个任务依赖链: 121 | 122 | $ gradle -q third 123 | first 124 | second 125 | Version: 0.1-SNAPSHOT 126 | third 127 | 128 | 仔细看这个执行顺序,你有没用发现printVersion声明了对second和first任务的依赖,但是first在second任务前执行了,*Gradle里面任务的执行顺序并不是确定的*。 129 | 130 | **任务依赖执行顺序** 131 | 132 | Gradle并不保证依赖的任务能够按顺序执行,dependsOn方法只是定义这些任务应该在这个任务之前执行,但是这些依赖的任务具体怎么执行它并不关心,如果你习惯用命令式的构建工具来定义依赖(比如ant)这可能会难以理解。在Gradle里面,执行顺序是由任务的输入输出特性决定的,这样做有很多优点,比如你想修改构建逻辑的时候你不需要去了解整个任务依赖链,另一方面,因为任务不是顺序执行的,就可以并发的执行来提高性能。 133 | 134 | ##终结者任务 135 | 136 | 在实际情况中,你可能需要在一个任务执行之后进行一些清理工作,一个典型的例子就是Web容器在部署应用之后要进行集成测试,Gradle提供了一个finalizer任务来实现这个功能,你可以用finalizedBy方法来结束一个指定的任务: 137 | 138 | task first << { println "first" } 139 | task second << { println "second" } 140 | //声明first结束后执行second任务 141 | first.finalizedBy second 142 | 143 | 你会发现任务first结束后自动触发任务second: 144 | 145 | $ gradle -q first 146 | first 147 | second 148 | 149 | ##添加随意的代码 150 | 151 | 接下来我们来学习怎么在build脚本中定义一些随机的代码,在实际情况下,如果你熟悉Groovy的语法你可以编写一些类或者方法,接下来你将会创建一个表示版本的类,在Java中一个class遵循bean的约定(POJO),就是添加setter和getter方法来访问类的域,到后面发现手工写这些方法很烦人,Groovy有个对应的概念叫POGO(plain-old Groovy object),他们的setter和getter方法在生成字节码的时候自动添加,因此运行的时候可以直接访问,看下面这个例子: 152 | 153 | version = new ProjectVersion(0, 1) 154 | 155 | class ProjectVersion { 156 | Integer major 157 | Integer minor 158 | Boolean release 159 | 160 | ProjectVersion(Integer major, Integer minor) { 161 | this.major = major 162 | this.minor = minor 163 | this.release = Boolean.FALSE 164 | } 165 | 166 | ProjectVersion(Integer major, Integer minor, Boolean release) { 167 | this(major, minor) 168 | this.release = release 169 | } 170 | 171 | @Override 172 | String toString() { 173 | //只有release为false的时候才添加后缀SNAPSHOT 174 | "$major.$minor${release ? '' : '-SNAPSHOT'}" 175 | } 176 | } 177 | 178 | 当运行这个修改的脚本之后,你可以看到printVersion的输出和之前一样,但是你还是得手工修改build脚本来更改版本号,接下来你将学习如何把版本号存储在一个文件里然后配置你的脚本去读取这个配置。 179 | 180 | ##任务的配置 181 | 182 | 在你写代码之前,你要新建一个属性文件version.properties,内容如下: 183 | 184 | major = 0 185 | minor = 1 186 | release = false 187 | 188 | **添加任务配置块** 189 | 190 | 接下来我们将声明一个任务loadVersion来从属性文件中读取版本号并赋给ProjectVersion实例,第一眼看起来和其他定义的任务一样,仔细一看你会注意到你没有定义动作或者使用左移操作符,在Gradle里称之为任务配置块(task configuration)。 191 | 192 | ext.versionFile = file('version.properties') 193 | //配置任务没有左移操作符 194 | task loadVersion { 195 | project.version = readVersion() 196 | } 197 | 198 | ProjectVersion readVersion() { 199 | logger.quiet 'Reading the version file.' 200 | //如果文件不存在抛出异常 201 | if(!versionFile.exists()) { 202 | throw new GradleException("Required version file does not exist:$versionFile.canonicalPath") 203 | } 204 | 205 | Properties versionProps = new Properties() 206 | 207 | //groovy的file实现了添加方法通过新创建的流来读取 208 | 209 | versionFile.withInputStream { stream -> 210 | versionProps.load(stream) 211 | } 212 | //在Groovy中如果这是最后一个语句你可以省略return关键字 213 | new ProjectVersion(versionProps.major.toInteger(), 214 | versionProps.minor.toInteger(), versionProps.release.toBoolean()) 215 | } 216 | 217 | 接下来运行printVersion,你会看到loadVersion任务先执行了: 218 | 219 | $ gradle printVersion 220 | Reading the version file. 221 | :printVersion 222 | Version: 0.1-SNAPSHOT 223 | 224 | 你也许会很奇怪这个任务是怎么调用的,你没有声明依赖,也没有在命令行中调用它。**任务配置块总是在任务动作之前执行的**,理解这个行为的关键就是Gradle的构建生命周期,我们来看下Gradle的构建阶段: 225 | 226 | ![](/images/4-1.png) 227 | 228 | **Gradle的构建生命周期** 229 | 230 | 无论你什么时候执行一个gradle build,都会经过三个不同的阶段:初始化、配置和执行。 231 | 232 | 在初始化阶段,Gradle给你的项目创建一个Project实例,你的构建脚本只定义了单个项目,在多项目构建的上下文环境中,构建的阶段更为重要。根据你正在执行的项目,Gradle找出这个项目的依赖。 233 | 234 | 下一个阶段就是配置阶段,Gradle构建一些在构建过程中需要的一些模型数据,当你的项目或者指定的任务需要一些配置的时候这个阶段很有帮助。 235 | 236 | **记住不管你执行哪个build哪怕是gradle tasks配置代码都会执行** 237 | 238 | 在执行阶段任务按顺序执行,执行顺序是通过依赖关系决定的,标记为up-to-date的任务会跳过,比如任务B依赖于任务A,当你运行gradle B的时候执行顺序将是A->B。 239 | 240 | 241 | ##声明任务的输入和输出 242 | 243 | Gradle通过比较两次build之间输入和输出有没有变化来确定这个任务是否是最新的,如果从上一个执行之后这个任务的输入和输出没有发生改变这个任务就标记为up-to-date,跳过这个任务。 244 | 245 | ![](/images/4-2.png) 246 | 247 | 输入可以是一个目录、一个或者多个文件或者随机的属性,任务的输出可以是路径或者文件,输入和输出在DefaultTask类中用域来表示。假设你想创建一个任务把项目的版本由SNAPSHOT改为release,下面的代码定义一个新任务给release变量赋值为true,然后把改变写入到文件中。 248 | 249 | task makeReleaseVersion(group: 'versioning', description: 'Makes project a release version.') << { 250 | version.release = true 251 | //ant的propertyfile任务提供很方便的方法来修改属性文件 252 | ant.propertyfile(file: versionFile) { 253 | entry(key: 'release', type:'string',operation: '=', value: 'true') 254 | } 255 | } 256 | 257 | 运行这个任务会修改版本属性并写入到文件中。 258 | 259 | $ gradle makeReleaseVersion 260 | :makeReleaseVersion 261 | 262 | $ gradle printVersion 263 | :printVersion 264 | Version: 0.1 265 | 266 | ##编写自定义的任务 267 | 268 | 269 | makeReleaseVersion的逻辑比较简单,你可能不用考虑代码维护的问题,随着构建逻辑越来越复杂,你添加了越来越多的简单的任务,这时候你就有需要用类和方法来结构化你的代码,你可以把你编写源代码的那一套代码实践搬过来。 270 | 271 | **编写自定义任务类** 272 | 273 | 之前提到过,Gradle会给每一个任务创建一个DefaultTask类型的实例,当你要创建一个自定义的任务时,你需要创建一个继承自DefaultTask的类,看看下面这个例子: 274 | 275 | class ReleaseVersionTask extends DefaultTask { 276 | //通过注解声明任务的输入和输出 277 | @Input Boolean release 278 | @OutputFile File destFile 279 | 280 | ReleaseVersionTask() { 281 | //在构造器里设置任务的分组和描述 282 | group = 'versioning' 283 | description = 'Makes project a release version.' 284 | } 285 | //通过注解声明要执行的任务 286 | @TaskAction 287 | void start() { 288 | project.version.release = true 289 | ant.propertyfile(file: destFile) { 290 | entry(key: 'release', type: 'string', operation: '=', value: 'true') 291 | } 292 | } 293 | } 294 | 295 | **通过注解来表达输入和输出** 296 | 297 | 任务输入和输出注解给你的实现添加了语法糖,他们和调用TasksInputs和TaskOutputs方法是一样的效果,你一眼就知道任务期望什么样的输入数据以及会产生什么输出。我们使用@Input注解来声明输入属性release,用@OutputFile来定义输出文件。 298 | 299 | **使用自定义的任务** 300 | 301 | 上面我们实现了自定义的动作方法,但是我们怎么使用这个方法,你需要在build脚本中创建一个ReleaseVersionTask类型的任务,通过给属性赋值来设定输入和输出: 302 | 303 | //定义一个ReleaseVersionTask类型的任务 304 | task makeReleaseVersion(type: ReleaseVersionTask) { 305 | //设定任务属性 306 | release = version.release 307 | destFile = versionFile 308 | } 309 | 310 | 311 | **复用自定义的任务** 312 | 313 | 假设你在另一个项目中想使用前面这个自定义的任务,在另一个项目中需求又不太一样,用来表示版本的POGO有不同的域,比如下面这个: 314 | 315 | class ProjectVersion { 316 | Integer min 317 | Integer maj 318 | Boolean prodReady 319 | 320 | @Override 321 | String toString() { 322 | "$maj.$min${prodReady? '' : '-SNAPSHOT'}" 323 | } 324 | } 325 | 326 | 327 | 此外,你还想把版本文件名改为project-version.properties,需要怎么做才能复用上面那个自定义的任务呢? 328 | 329 | task makeReleaseVersion(type: ReleaseVersionTask) { 330 | release = version.prodReady 331 | //不同的版本文件 332 | destFile = file('project-version.properties') 333 | } 334 | 335 | ##Gradle自带的任务类型 336 | 337 | Gradle自带的任务类型继承自DefaultTask,Gradle提供了很多自带的任务类型,这里我只介绍两个,Zip和copy用在发布项目中。 338 | 339 | //eg.使用任务类型来备份发布版本 340 | task createDistribution(type: Zip, dependsOn: makeReleaseVersion) { 341 | //引用war任务的输出 342 | from war.outputs.files 343 | //把所有文件放进ZIP文件的src目录 344 | from(sourceSets*.allSource) { 345 | into 'src' 346 | } 347 | //添加版本文件 348 | from(rootDir) { 349 | include versionFile.name 350 | } 351 | } 352 | 353 | task backupReleaseDistribution(type: Copy) { 354 | //引用createDistribution的输出 355 | from createDistribution.outputs.files 356 | into "$buildDir/backup" 357 | } 358 | 359 | task release(dependsOn: backupReleaseDistribution) << { 360 | logger.quiet 'Releasing the project...' 361 | } 362 | 363 | **任务依赖推导** 364 | 365 | 你可能注意到上面通过dependsOn方法来显示声明两个任务之间的依赖,可是,一些任务并不是直接依赖于其他任务(比如上面createDistribution依赖于war)。Gradle怎么知道在任务之前执行哪个任务?通过使用一个任务的输出作为另一个任务的输入,依赖就推导出来了,结果依赖的任务自动执行了,我们来看一下完整的执行图: 366 | 367 | $ gradle release 368 | :makeReleaseVersion 369 | :compileJava 370 | :processResources UP-TO-DATE 371 | :classes 372 | :war 373 | :createDistribution 374 | :backupReleaseDistribution 375 | :release 376 | Releasing the project... 377 | 378 | 运行build之后你可以在build/distribution目录找到生成的ZIP文件,这是打包任务的默认输出目录,下面这个图是生成的目录树: 379 | 380 | ![](/images/4-3.png) 381 | 382 | ##在buildSrc目录创建代码 383 | 384 | 在前面我们创建了两个类,ProjectVersion和ReleaseVersionTask,这些类可以移动到你项目的buildSrc目录,buildSrc目录是一个放置源代码的可选目录,你可以很容易的管理你的代码。Gradle采用了标准的项目布局,java代码在src/main/java目录,Groovy代码应该在src/main/groovy目录,在这些目录的任何代码都会自动编译然后放置到项目的classpath目录。这里你是在处理class,你可以把他们放到指定的包里面,假如com.manning.gia,下面显示了Groovy类在项目中的目录结构: 385 | 386 | ![](/images/4-4.png) 387 | 388 | 不过要记住把这些类放在源代码目录需要额外的工作,这和在脚本文件中定义有点不一样,你需要导入Gradle的API,看看下面这个例子: 389 | 390 | package com.manning.gia 391 | import org.gradle.api.DefaultTask 392 | import org.gradle.api.tasks.Input 393 | import org.gradle.api.tasks.OutputFile 394 | import org.gradle.api.tasks.TaskAction 395 | 396 | class ReleaseVersionTask extends DefaultTask { 397 | (...) 398 | } 399 | 400 | 反过来,你的构建脚本需要从buildSrc中导入编译的classes(比如 com.manning.gia.ReleaseVersionTask),下面这个是编译任务输出: 401 | 402 | $ gradle makeReleaseVersion 403 | :buildSrc:compileJava UP-TO-DATE 404 | :buildSrc:compileGroovy 405 | :buildSrc:processResources UP-TO-DATE 406 | :buildSrc:classes 407 | :buildSrc:jar 408 | :buildSrc:assemble 409 | :buildSrc:compileTestJava UP-TO-DATE 410 | :buildSrc:compileTestGroovy UP-TO-DATE 411 | :buildSrc:processTestResources UP-TO-DATE 412 | :buildSrc:testClasses UP-TO-DATE 413 | :buildSrc:test 414 | :buildSrc:check 415 | :buildSrc:build 416 | :makeReleaseVersion UP-TO-DATE 417 | 418 | 到此为止你学习了简单任务的创建,自定义的task类,指定Gradle API提供的task类型,查看了任务动作和任务配置的区别,以及他们的使用情形,任务配置和任务动作是在不同阶段执行的。 419 | 420 | -------------------------------------------------------------------------------- /cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/cover.jpg -------------------------------------------------------------------------------- /dependency-management/configure-respositories.md: -------------------------------------------------------------------------------- 1 | ##配置远程仓库 2 | 3 | Gradle支持下面三种不同类型的仓库: 4 | 5 | ![](/images/5-8.png) 6 | 7 | 下图是配置不同仓库对应的Gradle API: 8 | 9 | ![](/images/5-9.png) 10 | 11 | 12 | 下面以Maven仓库来介绍,Maven仓库是Java项目中使用最为广泛的一个仓库,库文件一般是以JAR文件的形式存在,用XML(POM文件)来来描述库的元数据和它的传递依赖。所有的库文件都存储在仓库的指定位置,当你在构建脚本中声明了依赖时,这些属性用来找到库文件在仓库中的准确位置。group属性标识了Maven仓库中的一个子目录,下图展示了Cargo依赖属性是怎么对应到仓库中的文件的: 13 | 14 | ![](/images/5-10.png) 15 | 16 | RepositoryHandler接口提供了两个方法来定义Maven仓库,mavenCentral方法添加一个指向仓库列表的引用,mavenLocal方法引用你文件系统中的本地Maven仓库。 17 | 18 | **添加Maven仓库** 19 | 20 | 要使用Maven仓库你只需要调用mavenCentral方法,如下所示: 21 | 22 | repositories { 23 | mavenCentral() 24 | } 25 | 26 | 27 | **添加本地仓库** 28 | 29 | 本地仓库默认在 /.m2/repository目录下,只需要添加如下脚本来引用它: 30 | 31 | repositories { 32 | mavenLocal() 33 | } 34 | 35 | **添加自定义Maven仓库** 36 | 37 | 如果指定的依赖不存在与Maven仓库或者你想通过建立自己的企业仓库来确保可靠性,你可以使用自定义的仓库。仓库管理器允许你使用Maven布局来配置一个仓库,这意味着你要遵守artifact的存储模式。你也可以添加验证凭证来提供访问权限,Gradle的API提供两种方法配置自定义的仓库:maven()和mavenRepo()。下面这段代码添加了一个自定义的仓库,如果Maven仓库中不存在相应的库会从自定义仓库中查找: 38 | 39 | repositories { 40 | mavenCentral() 41 | maven { 42 | name 'Custom Maven Repository', 43 | url 'http://repository.forge.cloudbees.com/release/') 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dependency-management/declaring-dependencies.md: -------------------------------------------------------------------------------- 1 | ##声明依赖 2 | 3 | DSL配置block dependencies用来给配置添加一个或多个依赖,你的项目不仅可以添加外部依赖,下面这张表显示了Gradle支持的各种不同类型的依赖。 4 | 5 | ![](/images/5-3.png) 6 | 7 | 这一章只介绍外部模块依赖和文件依赖,我们来看看Gradle APi是怎么表示依赖的。 8 | 9 | **理解依赖的API表示** 10 | 11 | 每个Gradle项目都有一个DependencyHandler的实例,你可以通过getDependencies()方法来获取依赖处理器的引用,上表中每一种依赖类型在依赖处理器中都有一个相对应的方法。每一个依赖都是Dependency的一个实例,group, name, version, 和classifier这几个属性用来标识一个依赖,下图清晰的表示了项目(Project)、依赖处理器(DependencyHandler)和依赖三者之间的关系: 12 | 13 | ![](/images/5-4.png) 14 | 15 | ###外部模块依赖 16 | 17 | 在Gradle的术语里,外部库通常是以JAR文件的形式存在,称之为外部模块依赖,代表项目层次外的一个模块,这种类型的依赖是通过属性来唯一的标识,接下来我们来介绍每个属性的作用。 18 | 19 | **依赖属性** 20 | 21 | 当依赖管理器从仓库中查找依赖时,需要通过属性的结合来定位,最少需要提供一个name。 22 | 23 | * group: 这个属性用来标识一个组织、公司或者项目,可以用点号分隔,Hibernate的group是org.hibernate。 24 | * name: name属性唯一的描述了这个依赖,hibernate的核心库名称是hibernate-core。 25 | * version: 一个库可以有很多个版本,通常会包含一个主版本号和次版本号,比如Hibernate核心库3.6.3-Final。 26 | * classifier: 有时候需要另外一个属性来进一步的说明,比如说明运行时的环境,Hibernate核心库没有提供classifier。 27 | 28 | **依赖的写法** 29 | 30 | 你可以使用下面的语法在项目中声明依赖: 31 | 32 | dependencies { 33 | configurationName dependencyNotation1, dependencyNotation2, ... 34 | } 35 | 36 | 你先声明你要给哪个配置添加依赖,然后添加依赖列表,你可以用map的形式来注明,你也可以直接用冒号来分隔属性,比如这样的: 37 | 38 | ![](/images/5-5.png) 39 | 40 | //声明外部属性 41 | ext.cargoGroup = 'org.codehaus.cargo' 42 | ext.cargoVersion = '1.3.1' 43 | 44 | dependencies { 45 | //使用映射声明依赖 46 | compile group: cargoGroup, name: 'cargo-core-uberjar', version: cargoVersion 47 | //用快捷方式来声明,引用了前面定义的外部属性 48 | cargo "$cargoGroup:cargo-ant:$cargoVersion" 49 | } 50 | 51 | 如果你项目中依赖比较多,你把一些共同的依赖属性定义成外部属性可以简化build脚本。 52 | 53 | Gradle没有给项目选择默认的仓库,当你没有配置仓库的时候运行deployTOLocalTomcat任务的时候回出现如下的错误: 54 | 55 | $ gradle deployToLocalTomcat 56 | :deployToLocalTomcat FAILED 57 | FAILURE: Build failed with an exception. 58 | 59 | Where: Build file '/Users/benjamin/gradle-in-action/code/chapter5/cargo-configuration/build.gradle' line: 10 60 | 61 | What went wrong: 62 | Execution failed for task ':deployToLocalTomcat'. 63 | > Could not resolve all dependencies for configuration ':cargo'. 64 | > Could not find group:org.codehaus.cargo, module:cargo-core-uberjar, version:1.3.1. 65 | Required by: 66 | :cargo-configuration:unspecified 67 | > Could not find group:org.codehaus.cargo, module:cargo-ant,version:1.3.1. 68 | Required by: 69 | :cargo-configuration:unspecified 70 | 71 | 到目前为止还没讲到怎么配置不同类型的仓库,比如你想使用MavenCentral仓库,添加下面的配置代码到你的build脚本中: 72 | 73 | repositories { 74 | mavenCentral() 75 | } 76 | 77 | **检查依赖报告** 78 | 79 | 当你运行dependencies任务时,这个依赖树会打印出来,依赖树显示了你build脚本声明的顶级依赖和它们的传递依赖: 80 | 81 | ![](/images/5-6.png) 82 | ![](/images/5-7.png) 83 | 84 | 仔细观察你会发现有些传递依赖标注了*号,表示这个依赖被忽略了,这是因为其他顶级依赖中也依赖了这个传递的依赖,Gradle会自动分析下载最合适的依赖。 85 | 86 | **排除传递依赖** 87 | 88 | Gradle允许你完全控制传递依赖,你可以选择排除全部的传递依赖也可以排除指定的依赖,假设你不想使用UberJar传递的xml-api的版本而想声明一个不同版本,你可以使用exclude方法来排除它: 89 | 90 | dependencies { 91 | cargo('org.codehaus.cargo:cargo-ant:1.3.1') { 92 | exclude group: 'xml-apis', module: 'xml-apis' 93 | } 94 | cargo 'xml-apis:xml-apis:2.0.2' 95 | } 96 | 97 | exclude属性值和正常的依赖声明不太一样,你只需要声明group和(或)module,Gradle不允许你只排除指定版本的依赖。 98 | 99 | 有时候仓库中找不到项目依赖的传递依赖,这会导致构建失败,Gradle允许你使用transitive属性来排除所有的传递依赖: 100 | 101 | dependencies { 102 | cargo('org.codehaus.cargo:cargo-ant:1.3.1') { 103 | transitive = false 104 | } 105 | // 选择性的声明一些需要的库 106 | } 107 | 108 | **动态版本声明** 109 | 110 | 如果你想使用一个依赖的最新版本,你可以使用latest.integration,比如声明 Cargo Ant tasks的最新版本,你可以这样写 `org.codehaus 111 | .cargo:cargo-ant:latest-integration`,你也可以用一个+号来动态的声明: 112 | 113 | dependencies { 114 | //依赖最新的1.x版本 115 | cargo 'org.codehaus.cargo:cargo-ant:1.+' 116 | } 117 | 118 | Gradle的dependencies任务可以清晰的看到选择了哪个版本,这里选择了1.3.1版本: 119 | 120 | $ gradle –q dependencies 121 | ------------------------------------------------------------ 122 | Root project 123 | ------------------------------------------------------------ 124 | Listing 5.4 Excluding a single dependency 125 | Listing 5.5 Excluding all transitive dependencies 126 | Listing 5.6 Declaring a dependency on the latest Cargo 1.x version 127 | Exclusions can be 128 | declared in a shortcut 129 | or map notation. 130 | 120 CHAPTER 5 Dependency management 131 | cargo - Classpath for Cargo Ant tasks. 132 | \--- org.codehaus.cargo:cargo-ant:1.+ -> 1.3.1 133 | \--- ... 134 | 135 | 136 | ###文件依赖 137 | 138 | 如果你没有使用自动的依赖管理工具,你可能会把外部库作为源代码的一部分或者保存在本地文件系统中,当你想把项目迁移到Gradle的时候,你不想去重构,Gradle很简单就能配置文件依赖。下面这段代码复制从Maven中央仓库解析的依赖到libs/cargo目录。 139 | 140 | task copyDependenciesToLocalDir(type: Copy) { 141 | //Gradle提供的语法糖 142 | from configurations.cargo.asFileTree 143 | into "${System.properties['user.home']}/libs/cargo" 144 | } 145 | 146 | 运行这个任务之后你就可以在依赖中声明Cargo库了,下面这段代码展示了怎么给cargo配置添加JAR文件依赖: 147 | 148 | dependencies { 149 | cargo fileTree(dir: "${System.properties['user.home']}/libs/cargo",include: '*.jar') 150 | } 151 | 152 | -------------------------------------------------------------------------------- /dependency-management/dependency-problems.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/dependency-management/dependency-problems.md -------------------------------------------------------------------------------- /dependency-management/introduce.md: -------------------------------------------------------------------------------- 1 | ##简介 2 | 3 | 在第三章我们在构建To Do应用的时候学习到了怎么声明对Servlet ApI的依赖,Gradle的领域特定语言使得声明依赖和仓库变得很简单,你只需要在dependencies脚本中声明你所依赖的库,然后你需要告诉构建系统要从哪个仓库里下载依赖。提供了这两个信息,Gradle就能自动解析、下载依赖到你的电脑上,如果有需要会存储在本地缓存中必备以后需要。 4 | 5 | 这一章我们将介绍Gradle对依赖管理的强大支持,学习依赖分组和定位不同类型仓库的DSL元素。依赖管理看起来很容易,但是当出现依赖解析冲突时就会很棘手,复杂的依赖关系可能导致构建中依赖一个库的多个版本。Gradle通过分析依赖树得到依赖报告,你将很容易找到一个指定的依赖的来源,为什么选择这个版本来处理版本冲突。 6 | 7 | Gradle有自己的依赖管理实现,为了避免其他依赖管理软件比如Ivy和Maven的缺点,Gradle关心的是性能、可靠性和复用性。 8 | -------------------------------------------------------------------------------- /dependency-management/learn-by-example.md: -------------------------------------------------------------------------------- 1 | ##依赖管理实战 2 | 3 | 在前面我们学习了怎么使用Jetty插件来使用自带的Jetty容器来部署一个TODo应用,Jetty是一个轻量级的开发容器,启动非常快。很多企业级的应用都使用其他的Web容器来部署应用,假设你使用的是Apache Tomcat。 4 | 5 | -------------------------------------------------------------------------------- /dependency-management/local-cache.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/dependency-management/local-cache.md -------------------------------------------------------------------------------- /dependency-management/quick-overview.md: -------------------------------------------------------------------------------- 1 | ##简要概述依赖管理 2 | 3 | 4 | 几乎所有基于JVM的项目都会或多或少依赖其他库,假设你在开发一个基于web的项目,你很可能会依赖很受欢迎的开源框架比如Spring MVC来提高效率。Java的第三方库一般以JAR文件的形式存在,一般用库名加版本号来标识。随着开发的进行依赖的第三方库增多小的项目变的越来越大,组织和管理你的JAR文件就很关键。 5 | 6 | ###不算完美的依赖管理技术 7 | 8 | 由于Java语言并没提供依赖管理的工具,所以你的团队需要自己开发一套存储和检索依赖的想法。你可能会采取以下几种常见的方法: 9 | 10 | * 手动复制JAR文件到目标机器,这是最原始的很容易出错的方法。 11 | * 使用一个共享的存储介质来存储JAR文件(比如共享的网盘),你可以加载网络硬盘或者通过FTP检索二进制文件。这种方法需要开发者事先建立好与仓库的连接,手动添加新的依赖到仓库中。 12 | * 把依赖的JAR文件同源代码都添加到版本控制系统中。这种方法不需要任何额外的步骤,你的同伴在拷贝仓库的时候就能检索依赖的改变。另一方面,这些JAR文件占用了不必要的空间,当你的项目存在相互之间依赖的时候你需要频繁的check-in的检查源代码是否发生了改变。 13 | 14 | ###自动管理依赖的重要性 15 | 16 | 尽管上面的方法都能用,但是这距离理想的解决方案差远了,因为他们没有提供一个标准化的方法来命名和管理JAR文件。至少你得需要开发库的准确版本和它依赖的库(传递依赖),这个为什么这么重要? 17 | 18 | **准确知道依赖的版本** 19 | 20 | 如果在项目中你没有准确声明依赖的版本这将会是一个噩梦,如果没有文档你根本无法知道这个库支持哪些特性,是否升级一个库到新的版本就变成了一个猜谜游戏因为你不知道你的当前版本。 21 | 22 | **管理传递依赖** 23 | 24 | 在项目的早期开发阶段传递依赖就会是一个隐患,这些库是第一层的依赖需要的,比如一个比较常见的开发方案是将Spring和Hibernate结合起来这会引入超过20个其他的开发库,一个库需要很多其他库来正常工作。下图展示了Hibernate核心库的依赖图: 25 | 26 | ![](/images/5-1.png) 27 | 28 | 如果没有正确的管理依赖,你可以会遇到没想到过的编译期错误和运行期类加载问题。我们可以总结到我们需要一个更好的方式来管理依赖,一般来讲你想在项目元数据中声明你的依赖和它的版本号。作为一个项目自动化的过程,这个版本的库会自动从中央仓库下载、安装到你的项目中,我们来看几个现有的开源解决方案。 29 | 30 | **使用自动化的依赖管理** 31 | 32 | 在Java领域里支持声明的自动依赖管理的有两个项目:Apache Ivy(Ant项目用的比较多的依赖管理器)和Maven(在构建框架中包含一个依赖管理器),我不再详细介绍这两个的细节而是解释自动依赖管理的概念和机制。 33 | 34 | Ivy和Maven是通过XML描述文件来表达依赖配置,配置包含两部分:依赖的标识加版本号和中央仓库的位置(可以是一个HTTP链接),依赖管理器根据这个信息自动定位到需要下载的仓库然后下载到你的机器中。库可以定义传递依赖,依赖管理器足够聪明分析这个信息然后解析下载传递依赖。如果出现了依赖冲突比如上面的Hibernate core的例子,依赖管理器会试着解决。库一旦被下载就会存储在本地的缓存中,构建系统先检查本地缓存中是否存在需要的库然后再从远程仓库中下载。下图显示了依赖管理的关键元素: 35 | 36 | ![](/images/5-2.png) 37 | 38 | 39 | Gradle通过DSL来描述依赖配置,实现了上面描述的架构。 40 | 41 | ###自动依赖管理面临的挑战 42 | 43 | 虽然依赖管理器简化了手工的操作,但有时也会遇到问题。你会发现你的依赖图中会依赖同个库的不同版本,使用日志框架经常会遇到这个问题,依赖管理器基于一个特定的解决方案只选择其中一个版本来避免版本冲突。如果你想知道某个库引入了什么版本的传递依赖,Gradle提供了一个非常有用的依赖报告来回答这个问题。下一节我会通过一个例子来讲解。 -------------------------------------------------------------------------------- /dependency-management/summary.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/dependency-management/summary.md -------------------------------------------------------------------------------- /first-project/build-java-project.md: -------------------------------------------------------------------------------- 1 | #构建Java项目# 2 | 3 | 上一节我们简要介绍了如何编写一个单机的To Do应用,接下来要打包部署成可执行的应用,我们需要编译源代码,生成的class文件需要打包到JAR文件中。JDK提供了javac 和jar工具帮助你实现这些任务,但是你也不想每次源代码发生变化时你都手动去执行这些任务吧。 4 | 5 | Gradle插件能够自动化完成这些任务,插件引入了一些领域特有的观念,其中一个Gradle插件就是java插件,Java插件不仅仅只有编译和打包的功能,它给你的项目安排了一个标准布局,并确保你所有的任务都是按序执行,现在该应用java插件来构建你的build脚本了。 6 | 7 | ##使用java插件## 8 | 9 | 每个Gradle项目都会创建一个build.gradle文件,如果你想使用java插件只需要添加下面这行代码: 10 | 11 | apply plugin: 'java' 12 | 13 | 这一行代码足以构建你的项目,但是Gradle怎么知道你的源代码放在哪个位置呢?java插件的一个约定就是源代码的位置,默认情况下插件搜索src/main/java路径下的文件,你的包名com.manning.gia.todo会转换成源代码根目录下的子目录,创建build脚本之后你的项目结构应该是这样的: 14 | 15 | ![](/images/dag12.png) 16 | 17 | ##构建项目## 18 | 19 | 现在你可以构建你的项目了,java插件添加了一个build任务到你项目中,build任务编译你的代码、运行测试然后打包成jar文件,所有都是按序执行的。运行gradle build之后你的输出应该是类似这样的: 20 | 21 | $ gradle build 22 | :compileJava 23 | :processResources UP-TO-DATE 24 | :classes 25 | :jar 26 | :assemble 27 | :compileTestJava UP-TO-DATE 28 | :processTestResources UP-TO-DATE 29 | :testClasses UP-TO-DATE 30 | :test 31 | :check 32 | :build 33 | 34 | 输出的每一行都表示一个可执行的任务,你可能注意到有一些任务标记为 `UP-TO-DATE`,这意味着这些任务被跳过了,gradle能够自动检查哪些部分没有发生改变,就把这部分标记下来,省的重复执行。在大型的企业项目中可以节省不少时间。执行完gradle build之后项目结构应该是类似这样的: 35 | 36 | ![](/images/dag13.png) 37 | ![](/images/dag14.png) 38 | 39 | 在项目的根目录你可以找到一个build目录,这里包含了所有的输出,包含class文件,测试报告,打包的jar文件,以及一些用来归档的临时文件。如果你之前使用过maven,它的标准输出是target,这两个结构应该很类似。jar文件目录build/libs下可以直接运行,jar文件的名称直接由项目名称得来的,这里是todo-app。 40 | 41 | ##运行项目## 42 | 43 | 你只需要使用JDK的java命令就可以执行这个应用了: 44 | 45 | $ java -cp build/classes/main com.manning.gia.todo.ToDoApp 46 | --- To Do Application --- 47 | Please make a choice: 48 | (a)ll items 49 | (f)ind a specific item 50 | (i)nsert a new item 51 | (u)pdate an existing item 52 | (d)elete an existing item 53 | (e)xit 54 | > 55 | 56 | 接下来我们会学习如何自定义项目结构。 57 | 58 | ##自定义你的项目## 59 | 60 | Java插件是一个非常固执的框架,对于项目很多的方面它都假定有默认值,比如项目布局,如果你看待世界的方法是不一样的,Gradle给你提供了一个自定义约定的选项。想知道哪些东西是可以配置的?可以参考这个手册:[http://www.gradle.org/docs/current/dsl/](http://www.gradle.org/docs/current/dsl/),之前提到过,运行命令行gradle properties可以列出可配置的标准和插件属性以及他们的默认值。 61 | 62 | **修改你的项目和插件属性** 63 | 64 | 接下来你将学习如何指定项目的版本号、Java源代码的兼容级别,前面你用的java命令来运行应用程序,你需要通过命令行选项-cp build/classes/main指定class文件的位置给Java运行时。但是要从JAR文件中启动应用,你需要在manifest文件MANIFEST.MF中包含首部Main-Class。看下面的脚本你就明白怎么操作了: 65 | 66 | //Identifies project’sversion through a number scheme 67 | version = 0.1 68 | 69 | //Sets Java version compilation compatibility to 1.6 70 | sourceCompatibility = 1.6 71 | 72 | //Adds Main-Class header to JAR file’s manifest 73 | 74 | jar { 75 | manifest { 76 | attributes 'Main-Class': 'com.manning.gia.todo.ToDoApp' 77 | } 78 | } 79 | 80 | 打包成JAR之后,你会发现JAR文件的名称变成了todo-app-0.1.jar,这个jar包含了main-class首部,你就可以通过命令java -jar build/libs/todo-app-0.1.jar运行了。 81 | 82 | 接下来学习如何改变项目的默认布局: 83 | 84 | //Replaces conventional source code directory with list of different directories 85 | 86 | sourceSets { 87 | main { 88 | java { 89 | srcDirs = ['src'] 90 | } 91 | } 92 | //Replaces conventional test source code directory with list of different directories 93 | 94 | test { 95 | java { 96 | srcDirs = ['test'] 97 | } 98 | } 99 | } 100 | 101 | //Changes project output property to directory out 102 | 103 | buildDir = 'out' 104 | 105 | **配置和使用外部依赖** 106 | 107 | 在Java世界里,依赖是分布的以JAR文件的形式存在,许多库都从仓库里获得,比如一个文件系统或中央服务器。Gradle需要你指定至少一个仓库作为依赖下载的地方,比如mavenCentral: 108 | //Shortcut notation for configuring Maven Central 2 repository accessible under http://repo1.maven.org/maven2 109 | 110 | repositories { 111 | mavenCentral() 112 | } 113 | 114 | **定义依赖** 115 | 116 | 接下来就是定义依赖,依赖通过group标识,name和version来确定,比如下面这个: 117 | 118 | dependencies { 119 | compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.1' 120 | } 121 | 122 | 123 | Gradle是通过配置来给依赖分组,Java插件引入的一个配置是compile,你可以很容易区分这个配置定义的依赖是用来编译源代码的。 124 | 125 | **解析依赖** 126 | 127 | Gradle能够自动检测并下载项目定义的依赖: 128 | 129 | $ gradle build 130 | :compileJava 131 | Download http://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.pom 132 | 133 | Download http://repo1.maven.org/maven2/org/apache/commons/commons-parent/22/commons-parent-22.pom 134 | 135 | Download http://repo1.maven.org/maven2/org/apache/apache/9/apache-9.pom 136 | Download http://repo1.maven.org/maven2/org/apache/commons/commons-lang3/3.1/commons-lang3-3.1.jar 137 | 138 | :processResources UP-TO-DATE 139 | ... 140 | :build 141 | 142 | 143 | -------------------------------------------------------------------------------- /first-project/gradle-wrapper.md: -------------------------------------------------------------------------------- 1 | #Gradle包装器# 2 | 3 | 你把你的Web应用给你的同伴Mike看,他看完之后觉得很有意思想加入你给项目添加一些高级特性。你把代码添加到版本控制系统当中(VCS),因此它可以下载代码,由于Mike从来没有用过Gradle构建工具,所以他问你用的哪个版本的Gradle以及怎么安装Gradle,他也不知道怎么去配置Gradle,从以往的经验来看,Mike清醒的知道不同版本的构建工具或者运行环境对对构建的影响有多大。对于在一个机器上可以运行,另一个机器无法运行的情况他看的太多了,经常是由于运行时环境不兼容的原因。 4 | 5 | 对于这个问题Gradle提供了一个非常方便和实用的方法:Gradle包装器,包装器是Gradle的一个核心特性,它允许你的机器不需要安装运行时就能运行Gradle脚本,而且她还能确保build脚本运行在指定版本的Gradle。它会从中央仓库中自动下载Gradle运行时,解压到你的文件系统,然后用来build。终极目标就是创建可靠的、可复用的、与操作系统、系统配置或Gradle版本无关的构建。 6 | 7 | 8 | ##配置Gradle包装器## 9 | 10 | 在设置你的包装器之前,你需要做两件事情:创建一个包装任务,执行这个任务生成包装文件。为了能让你的项目下载压缩的Gradle运行时,定义一个Wrapper类型的任务 11 | 在里面指定你想使用的Gradle版本: 12 | 13 | task wrapper(type: Wrapper) { 14 | gradleVersion = '1.7' 15 | } 16 | 17 | 然后执行这个任务: 18 | 19 | $ gradle wrapper 20 | :wrapper 21 | 22 | 整个过程如下图: 23 | 24 | ![](/images/dag21.png) 25 | 26 | 执行完之后,你就能看到下面这个wrapper文件和你的构建脚本: 27 | 28 | ![](/images/dag22.png) 29 | 30 | 31 | 记住你只需要运行gradle wrapper一次,以后你就能用wrapper来执行你的任务,下载下来的wrapper文件会被添加到版本控制系统中。如果你的系统中已经安装了Gradle运行时,你就不需要再添加一个gradle wrapper任务,你可以直接运行gradle wrapper任务,这个任务会使用你的Gradle当前版本来生成包装文件。 32 | 33 | ##使用包装器## 34 | 35 | 上面生成了两个执行脚本,一个是运行在*nix系统上的gradlew,另一个是运行在Windows系统上的gradlew.bat,你只需要根据你的系统环境来执行对应的那一个脚本,比如上面提到的Mike执行了gradlew.bat jettyRun任务,下面是输出: 36 | 37 | > gradlew.bat jettyRun 38 | 39 | Downloading http://services.gradle.org/distributions/gradle-1.7-bin.zip 40 | ... 41 | //Unzips compressed wrapper file to predefined local directory 42 | Unzipping C:\Documents and Settings\Mike\.gradle\wrapper\dists\gradle-1.7- bin\35oej0jnbfh6of4dd05531edaj\gradle-1.7-bin.zip to C:\Documents andSettings\Mike\.gradle\wrapper\dists\gradle-1.7-bin\35oej0jnbfh6of4dd05531edaj 43 | 44 | Set executable permissions for: C:\Documents and Settings\Mike\.gradle\wrapper\dists\gradle-1.7- bin\35oej0jnbfh6of4dd05531edaj\gradle-1.7\bin\gradlew.bat 45 | 46 | :compileJava 47 | :processResources UP-TO-DATE 48 | :classes 49 | 50 | > Building > :jettyRun > Running at http://localhost:9090/todo 51 | 52 | 53 | 整个过程如下: 54 | 55 | ![](/images/dag23.png) 56 | 57 | ##自定义包装器## 58 | 59 | 一些公司的安全措施非常严格,特别是当你给政府工作的时候,你能够访问外网的能力是被限制的,在这种情况下你怎么让你的项目使用Gradle包装器?所以你需要修改默认配置: 60 | 61 | task wrapper(type: Wrapper) { 62 | //Requested Gradle version 63 | gradleVersion = '1.2' 64 | //Target URL to retrieve Gradle wrapper distribution 65 | distributionUrl = 'http://myenterprise.com/gradle/dists' 66 | //Path where wrapper will be unzipped relative to Gradle home directory 67 | distributionPath = 'gradle-dists' 68 | } 69 | 70 | 非常直接明显对不对?你还可以了解更多的特性,如果你想了解更多关于Gradle包装器DSL的信息,可以查看这个网址:[http://gradle.org/docs/current/dsl/org.gradle.api.tasks.wrapper.Wrapper.html](http://gradle.org/docs/current/dsl/org.gradle.api.tasks.wrapper.Wrapper.html) 71 | 72 | -------------------------------------------------------------------------------- /first-project/introduce-this.md: -------------------------------------------------------------------------------- 1 | #第一个Gradle项目# 2 | 3 | 这一章将通过一个例子来介绍Gradle的强大特性,你将从中学到怎么用Gradle的标准插件来引导、配置和运行你的应用,这章结束的时候你应该对Gradle的工作机制有个清晰的认识。 4 | 5 | #The To Do Application# 6 | 7 | 我们经常需要同时管理多个项目,有时候会发现多个项目很难维护达到了难以控制的地步,为了摆脱这个困惑,我们可以维护一个to-do列表,显然你可以把你所有要完成的任务写在一张纸上,当时如果能够随时随地查询你要完成的任务岂不更方便? 8 | 9 | ##任务管理的情形## 10 | 11 | 现在你知道了你的最终目的,每一个任务管理系统都是由一系列的任务组成的,任务通常有一个标题,任务可以添加到任务列表中,可以标记任务的完成状态以及删除任务,如下图所示: 12 | 13 | ![](/images/dag9.png) 14 | ![](/images/dag10.png) 15 | 16 | ##实现用户交互功能## 17 | 18 | 我们发现这个TO DO 应用包含典型的创建、读取、更新、删除操作(CRUD),要持久化数据,你需要用一个模型来给任务建模,我们创建一个叫ToDoItem的Java类,一个POJO对象,为了简化这个应用,我们这里不采用数据库来存储,而是把数据存储在内存中,这很容易实现。实现存储接口的类是InMemoryToDoRespository,缺点就是你的应用程序关闭之后你就无法持久化数据了,后面我们会继续完善这个应用。 19 | 20 | 每一个标准的Java应用都有一个Main Class,应用程序的入口。这里的main class是ToDoApp,我们将会展现一栏的命令给用户选择,每一个命令被映射成一个枚举类型CommandLineInput,ComandLineInputHandler类用来处理用户输入执行相应的任务。 21 | 下图显示了整个流程: 22 | 23 | ![](/images/dag11.png) 24 | 25 | ##搭建应用的每一个模块## 26 | 27 | *表示Todo模型的类ToDoItem* 28 | 29 | package com.manning.gia.todo.model; 30 | 31 | public class ToDoItem implements Comparable { 32 | private Long id; 33 | private String name; 34 | private boolean completed; 35 | (...) 36 | } 37 | 38 | 39 | *模型持久化接口ToDoRepository* 40 | 41 | package com.manning.gia.todo.repository; 42 | 43 | import com.manning.gia.todo.model.ToDoItem; 44 | import java.util.Collection; 45 | 46 | public interface ToDoRepository { 47 | List findAll(); 48 | ToDoItem findById(Long id); 49 | Long insert(ToDoItem toDoItem); 50 | void update(ToDoItem toDoItem); 51 | void delete(ToDoItem toDoItem); 52 | } 53 | 54 | 接下来创建一个可扩展的、线程安全的实现: 55 | 56 | package com.manning.gia.todo.repository; 57 | 58 | public class InMemoryToDoRepository implements ToDoRepository { 59 | private AtomicLong currentId = new AtomicLong(); 60 | private ConcurrentMap toDos = new ConcurrentHashMap(); 61 | 62 | @Override 63 | public List findAll() { 64 | List toDoItems = new ArrayList(toDos.values()); 65 | Collections.sort(toDoItems); 66 | return toDoItems; 67 | 68 | } 69 | 70 | @Override 71 | public ToDoItem findById(Long id) { 72 | return toDos.get(id); 73 | } 74 | 75 | @Override 76 | public Long insert(ToDoItem toDoItem) { 77 | Long id = currentId.incrementAndGet(); 78 | toDoItem.setId(id); 79 | toDos.putIfAbsent(id, toDoItem); 80 | return id; 81 | } 82 | 83 | 84 | @Override 85 | public void update(ToDoItem toDoItem) { 86 | toDos.replace(toDoItem.getId(), toDoItem); 87 | } 88 | 89 | @Override 90 | public void delete(ToDoItem toDoItem) { 91 | toDos.remove(toDoItem.getId()); 92 | } 93 | 94 | } 95 | 96 | 97 | *应用程序的入口* 98 | 99 | package com.manning.gia.todo; 100 | import com.manning.gia.todo.utils.CommandLineInput; 101 | import com.manning.gia.todo.utils.CommandLineInputHandler; 102 | 103 | public class ToDoApp { 104 | public static final char DEFAULT_INPUT = '\u0000'; 105 | public static void main(String args[]) { 106 | CommandLineInputHandler commandLineInputHandler = new 107 | CommandLineInputHandler(); 108 | char command = DEFAULT_INPUT; 109 | 110 | while(CommandLineInput.EXIT.getShortCmd() != command) { 111 | commandLineInputHandler.printOptions(); 112 | String input = commandLineInputHandler.readInput(); 113 | char[] inputChars = input.length() == 1 ? input.toCharArray() 114 | char[] { DEFAULT_INPUT }; 115 | command = inputChars[0]; 116 | CommandLineInput commandLineInput = CommandLineInput.getCommandLineInputForInput(command); 117 | commandLineInputHandler.processInput(commandLineInput); 118 | } 119 | 120 | } 121 | } 122 | 123 | 到目前为止我们讨论了应用的组件和用户交互。接下来就要用Gradle实现项目的自动化构建,编译源代码、打包JAR文件、运行应用。 124 | 125 | -------------------------------------------------------------------------------- /first-project/introduce.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/first-project/introduce.md -------------------------------------------------------------------------------- /first-project/summary.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/first-project/summary.md -------------------------------------------------------------------------------- /first-project/web-development.md: -------------------------------------------------------------------------------- 1 | #用Gradle开发Web项目# 2 | 3 | Java服务端的Web组件(JavaEE)提供动态扩展能力允许你在web容器或者应用服务器中运行你的程序,就像Servlet这个名字的意思,接收客户端的请求返回响应,在MVC架构中充当控制器的角色,Servlet的响应通过视图组件--JSP来渲染,下图展示了一个典型的MVC架构的Java应用。 4 | 5 | ![](/images/dag15.png) 6 | 7 | WAR(web application archive)用来捆绑Web组件、编译生成的class文件以及其他资源文件如部署描述符、HTML、JavaScript和CSS文件,这些文件组合在一起就形成了一个Web应用,要运行Java Web应用,WAR文件需要部署在一个服务器环境---Web容器。 8 | 9 | Gradle提供拆箱插件用来打包WAR文件以及部署Web应用到本地的Servlet容器,接下来我们就来学习怎么把Java应用编程Web应用。 10 | 11 | **添加Web组件** 12 | 13 | 接下来我们将创建一个Servlet,ToDoServlet,用来接收HTTP请求,执行CRUD操作,并将请求传递给JSP。你需要写一个todo-list.jsp文件,这个页面知道怎么去渲染todo items,提供一些UI组件比如按钮和指向CURD操作的链接,下图是用户检索和渲染todo items的流程: 14 | 15 | ![](/images/dag16.png) 16 | 17 | **Web 控制器** 18 | 19 | 下面这个就是web 控制器ToDoServlet,用来处理所有的URL请求: 20 | 21 | package com.manning.gia.todo.web; 22 | 23 | public class ToDoServlet extends HttpServlet { 24 | private ToDoRepository toDoRepository = new InMemoryToDoRepository(); 25 | 26 | @Override 27 | protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { 28 | String servletPath = request.getServletPath(); 29 | String view = processRequest(servletPath, request); 30 | RequestDispatcher dispatcher = request.getRequestDispatcher(view); 31 | dispatcher.forward(request, response); 32 | 33 | } 34 | 35 | private String processRequest(String servletPath, HttpServletRequest request) { 36 | 37 | if(servletPath.equals("/all")) { 38 | List toDoItems = toDoRepository.findAll(); 39 | request.setAttribute("toDoItems", toDoItems); 40 | return "/jsp/todo-list.jsp"; 41 | } 42 | else if(servletPath.equals("/delete")) { 43 | (...) 44 | } 45 | (...) 46 | return "/all"; 47 | } 48 | 49 | } 50 | 51 | 52 | 对于每一个接收的请求,获取Servlet路径,基于CRUD操作在processRequest方法中处理请求,然后通过请求分派器请求传递给todo-list.jsp。 53 | 54 | **使用War和Jetty插件** 55 | 56 | Gradle支持构建和运行Web应用,接下来我将介绍两个web应用开发的插件War和Jetty,War插件继承了Java插件用来给web应用开发添加一些约定、支持打包War文件。Jetty是一个很受欢迎的轻量级的开源Web容器,Gradle的Jetty插件继承War插件,提供部署应用程序到嵌入的容器的任务。 57 | 58 | 既然War插件继承了Java插件,所以你在应用了War插件后无需再添加Java插件,即使你添加了也没有负面影响,应用插件是一个非幂等的操作,因此对于一个指定的插件只运行一次。添加下面这句到你的build.gradle脚本中: 59 | 60 | apply plugin: 'war' 61 | 62 | 除了Java插件提供的约定外,你的项目现在多了Web应用的源代码目录,将打包成war文件而不是jar文件,Web应用默认的源代码目录是src/main/webapp,如果所有的源代码都在正确的位置,你的项目布局应该是类似这样的: 63 | 64 | ![](/images/dag17.png) 65 | ![](/images/dag18.png) 66 | 67 | 你用来实现Web应用的帮助类不是java标准的一部分,比如javax.servlet.HttpServlet,在运行build之前,你应该确保你声明了这些外部依赖,War插件引入了两个新的依赖配置,用于Servlet依赖的配置是providedCompile,这个用于那些编译器需要但是由运行时环境提供的依赖,你现在的运行时环境是Jetty,因此用provided标记的依赖不会打包到WAR文件里面,运行时依赖比如JSTL这些在编译器不需要,但是运行时需要,他们将成为WAR文件的一部分。 68 | 69 | dependencies { 70 | providedCompile 'javax.servlet:servlet-api:2.5' 71 | runtime 'javax.servlet:jstl:1.1.2' 72 | } 73 | 74 | 75 | build Web项目和Java项目一样,运行gradle build后打包的WAR文件在目录build/libs下,输出如下: 76 | 77 | $ gradle build 78 | :compileJava 79 | :processResources UP-TO-DATE 80 | :classes 81 | :war 82 | :assemble 83 | :compileTestJava UP-TO-DATE 84 | :processTestResources UP-TO-DATE 85 | :testClasses UP-TO-DATE 86 | :test 87 | :check 88 | :build 89 | 90 | War插件确保打包的WAR文件符合JAVA EE规范,war任务拷贝web应用源目录src/main/webapp到WAR文件的根目录,编译的class文件保存在WEB-INF/classes,运行时依赖的库放在WEB-INF/libs,下面显示了WAR文件todo-webapp-0.1.war的目录结构: 91 | 92 | ![](/images/dag19.png) 93 | 94 | ##自定义WAR插件## 95 | 96 | 假设你把所有的静态文件放在static目录,所有的web组件放在webfiles,目录结构如下: 97 | 98 | ![](/images/dag20.png) 99 | 100 | 101 | //Changes web application source directory 102 | 103 | webAppDirName = 'webfiles' 104 | 105 | //Adds directories css and jsp to root of WAR file archive 106 | war { 107 | from 'static' 108 | } 109 | 110 | **在嵌入的Web容器中运行** 111 | 112 | 嵌入式的Servlet容器完全不知道你的应用程序信息,直到你提供准确的classpath和源代码目录,你可以手动编程设置,当然也可以选择 Jetty 插件自动帮你完成了所有的工作,只需要添加下面一条命令: 113 | 114 | apply plugin: 'jetty' 115 | 116 | 运行Web应用需要用到的任务是jettyRun,启动Jetty容器并且无需创建WAR文件,这个命令的输出应该类似这样的: 117 | 118 | $ gradle jettyRun 119 | :compileJava 120 | :processResources UP-TO-DATE 121 | :classes 122 | > Building > :jettyRun > Running at http://localhost:8080/todo-webapp-jetty 123 | 124 | 最后一行Jetty插件给你提供了一个URL用来监听HTTP请求,打开浏览器输入这个链接就能看到你编写的Web应用了。 125 | 126 | Jetty插件默认监听的端口是8080,上下文路径是todo-webapp-jetty,你也可以自己配置成想要的: 127 | 128 | jettyRun { 129 | httpPort = 9090 130 | contextPath = 'todo' 131 | } 132 | 133 | 这样你就把监听端口改成了9090,上下文目录改成了todo。 134 | 135 | 136 | 137 | 138 | 139 | 140 | -------------------------------------------------------------------------------- /gradle/continuous-dilivery.md: -------------------------------------------------------------------------------- 1 | #连续传递的特性# 2 | 3 | 编译源代码只是整个过程的一个方面,更重要的是,你要把你的软件发布到生产环境中来产生商业价值,所以,你要运行测试,构建分布、分析代码质量、甚至为不同目标环境提供不同版本,然后部署。整个过程进行自动化操作是很有必要的。 4 | 整个过程可以参考下图: 5 | 6 | ![](/images/dag8.png) 7 | 8 | 整个过程可以分成以下几个步骤: 9 | * 编译源代码 10 | * 运行单元测试和集成测试 11 | * 执行静态代码分析、生成分析报告 12 | * 创建发布版本 13 | * 部署到目标环境 14 | * 部署传递过程 15 | * 执行冒烟测试和自动功能测试 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /gradle/gradle-features.md: -------------------------------------------------------------------------------- 1 | Gradle提供了一些默认的Tasks给Java项目,比如,编译源代码、运行测试、打包JAR.每一个Java项目都有一个标准的路径布局,这个布局定义了去哪里找项目的源代码、资源文件和测试代码,你也可以在配置中修改这些默认位置。 2 | 3 | Gradle的约定类似于Maven的约定优于配置的实现,Maven的约定就是一个项目只包含一个Java源代码路径,只产生一个JAR文件,对于企业级开发来讲这样是显然不够的,Gradle允许你打破传统的观念,Gradle的构建生命周期如下图: 4 | 5 | ![](/images/dag6.png) 6 | ![](/images/dag5.png) 7 | 8 | #和其他构建工具集成# 9 | 10 | Gradle完全兼容Ant、Maven,你可以很容易的从Ant或Maven迁移到Gradle,Gradle并不强迫你完全把你的Build逻辑迁移过来,它允许你复用已有的Ant构建逻辑。Gradle完全兼容Maven和Ivy仓库,你可以从中检索依赖也可以发布你的文件到仓库中,Gradle提供转换器能把Maven的构建逻辑转换成Gradle的构建脚本。 11 | 12 | ##从Ant,Maven迁移到Gradle## 13 | 14 | 现有的Ant脚本可以无缝的导入到Gradle项目中,Ant的Target在运行时直接映射成Gradle的任务,Gradle有一个AntBuilder可以把你的Ant脚本混成Gradle的DSL(领域特定语言),这些脚本看起来像是Ant的XML,但是去掉了尖括号,对于Ant用户来说非常方便,不需要担心过渡到Gradle的学习周期。 15 | 16 | Gradle能够解析现有的Maven POM,从而得到传递性依赖的信息,并且引入到当前项目中,在此基础上,它也支持排除传递性依赖或者干脆关闭传递性依赖,这一点是Maven所不具备的特性。 17 | Gradle项目使用Maven项目生成的资源已经不是个问题了,接着需要反过来考虑,Maven用户是否能够使用 Gradle生成的资源呢?或者更简单点问,Gradle项目生成的构件是否可以发布到Maven仓库中供人使用呢?这一点非常重要,因为如果做不到这一点,你可能就会丢失大量的用户。幸运的是Gradle再次给出了令人满意的答案。使用Gradle的Maven Plugin,用户就可以轻松地将项目构件上传到Maven仓库中: 18 | 19 | apply plugin: 'maven' 20 | ... 21 | uploadArchives { 22 | repositories.mavenDeployer { 23 | repository(url: "http://localhost:8088/nexus/content/repositories/snapshots/") { 24 | authentication(userName: "admin", password: "admin123") 25 | pom.groupId = "com.juvenxu" 26 | pom.artifactId = "account-captcha" 27 | } 28 | } 29 | } 30 | 31 | 在上传的过程中,Gradle能够基于build.gradle生成对应的Maven POM文件,用户可以自行配置POM信息,比如这里的groupId和artifactId,而诸如依赖配置这样的内容,Gradle是会自动帮你进行转换的。由于Maven项目之间依赖交互的直接途径就是仓库,而Gradle既能够使用Maven仓库,也能以Maven的格式将自己的内容发布到仓库中,因此从技术角度来说,即使在一个基于Maven的大环境中,局部使用Gradle也几乎不会是一个问题。 32 | ![](/images/dag7.png) 33 | 34 | 35 | -------------------------------------------------------------------------------- /gradle/gradle.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/gradle/gradle.md -------------------------------------------------------------------------------- /gradle/install-gradle.md: -------------------------------------------------------------------------------- 1 | ##安装Gradle 2 | 3 | Gradle的安装非常方便,下载ZIP包,解压到本地目录,设置 GRADLE_HOME 环境变量并将 GRADLE_HOME/bin 加到 PATH 环境变量中,安装就完成了。用户可以运行gradle -v命令验证安装,这些初始的步骤和Maven没什么两样。我这里安装的Gradle版本是1.10,详细信息见下: 4 | 5 | ᐅ gradle -v 6 | 7 | ------------------------------------------------------------ 8 | Gradle 1.10 9 | ------------------------------------------------------------ 10 | Build time: 2013-12-17 09:28:15 UTC 11 | Build number: none 12 | Revision: 36ced393628875ff15575fa03d16c1349ffe8bb6 13 | Groovy: 1.8.6 14 | Ant: Apache Ant(TM) version 1.9.2 compiled on July 8 2013 15 | Ivy: 2.2.0 16 | JVM: 1.7.0_45 (Oracle Corporation 24.45-b08) 17 | OS: Mac OS X 10.9.2 x86_64 18 | 19 | -------------------------------------------------------------------------------- /gradle/start-with-gradle.md: -------------------------------------------------------------------------------- 1 |

Gradle

2 |

Gradle目前的版本是2.4,根据其Wiki上的Roadmap,Gradle有着让很多成熟项目都汗颜的文档,其包括了安装指南、基本教程、以及一份近300页的全面用户指南。这对于用户来说是非常友好的,同时也说明了Gradle的开发者对这个项目非常有信心,要知道编写并维护文档可不是件轻松的工作,对于Gradle这样未来仍可能发生很大变动的项目来说尤为如此。

3 |

类似于Maven的pom.xml文件,每个Gradle项目都需要有一个对应的build.gradle文件,该文件定义一些任务(task)来完成构建工作,当然,每个任务是可配置的,任务之间也可以依赖,用户亦能配置缺省任务,就像这样:

4 |
  5 | defaultTasks 'taskB'
  6 | 
  7 | task taskA << {
  8 |     println "i'm task A"
  9 | }
 10 | 
 11 | task taskB << {
 12 |     println "i'm task B, and I depend on " + taskA.name
 13 | }
 14 | 
 15 | taskB.dependsOn taskA
 16 | 
17 |

运行命令$ gradle -q之后(参数q让Gradle不要打印错误之外的日志),就能看到如下的预期输出:

18 |
 19 | i'm task A
 20 | i'm task B, and I depend on taskA
 21 | 
22 |

这不是和Ant如出一辙么?的确是这样,这种“任务”的概念与用法与Ant及其相似。Ant任务是Gradle世界的第一公民,Gradle对Ant做了很好的集成。除此之外,由于Gradle使用的Grovvy脚本较XML更为灵活,因此,即使我自己不是Ant用户,我也仍然觉得Ant用户会喜欢上Gradle。

23 |

依赖管理和集成Maven仓库

24 |

我们知道依赖管理、仓库、约定优于配置等概念是Maven的核心内容,抛开其实现是否最优不谈,概念本身没什么问题,并且已经被广泛学习和接受。那Gradle实现了这些优秀概念了么?答案是肯定的。

25 |

先看依赖管理,我有一个简单的项目依赖于一些第三方类库包括SpringFramework、JUnit、Kaptcha等等。原来的Maven POM配置大概是这样的(篇幅关系,省略了部分父POM配置):

26 |
 27 |     <properties>
 28 |         <kaptcha.version>2.3</kaptcha.version>
 29 |     </properties>
 30 | 
 31 |     <dependencies>
 32 |         <dependency>
 33 |             <groupId>com.google.code.kaptcha</groupId>
 34 |             <artifactId>kaptcha</artifactId>
 35 |             <version>${kaptcha.version}</version>
 36 |             <classifier>jdk15</classifier>
 37 |         </dependency>
 38 |         <dependency>
 39 |             <groupId>org.springframework</groupId>
 40 |             <artifactId>spring-core</artifactId>
 41 |         </dependency>
 42 |         <dependency>
 43 |             <groupId>org.springframework</groupId>
 44 |             <artifactId>spring-beans</artifactId>
 45 |         </dependency>
 46 |         <dependency>
 47 |             <groupId>org.springframework</groupId>
 48 |             <artifactId>spring-context</artifactId>
 49 |         </dependency>
 50 |         <dependency>
 51 |             <groupId>junit</groupId>
 52 |             <artifactId>junit</artifactId>
 53 |         </dependency>
 54 |     </dependencies>
55 |

然后我将其转换成Gradle脚本,结果是惊人的:

56 |
 57 | dependencies {
 58 |     compile('org.springframework:spring-core:2.5.6')
 59 |     compile('org.springframework:spring-beans:2.5.6')
 60 |     compile('org.springframework:spring-context:2.5.6')
 61 |     compile('com.google.code.kaptcha:kaptcha:2.3:jdk15')
 62 |     testCompile('junit:junit:4.7')
 63 | }
64 |

注意配置从原来的28行缩减至7行!这还不算我省略的一些父POM配置。依赖的groupId、artifactId、 version,scope甚至是classfier,一点都不少。较之于Maven或者Ant的XML配置脚本,Gradle使用的Grovvy脚本杀伤力太大了,爱美之心,人皆有之,相比于七旬老妇松松垮垮的皱纹,大家肯定都喜欢少女紧致的脸蛋,XML就是那老妇的皱纹。

65 |

关于Gradle的依赖管理起初我有一点担心,就是它是否有传递性依赖的机制呢?经过文档阅读和实际试验后,这个疑虑打消了,Gradle能够解析现有的Maven POM或者Ivy的XML配置,从而得到传递性依赖的信息,并且引入到当前项目中,这实在是一个聪明的做法。在此基础上,它也支持排除传递性依赖或者干脆关闭传递性依赖,其中第二点是Maven所不具备的特性。

66 |

自动化依赖管理的基石是仓库,Maven中央仓库已经成为了Java开发者不可或缺的资源,Gradle既然有依赖管理,那必然也得用到仓库,这当然也包括了Maven中央仓库,就像这样:

67 |
 68 | repositories {
 69 |     mavenLocal()
 70 |     mavenCentral()
 71 |     mavenRepo urls: "http://repository.sonatype.org/content/groups/forge/"
 72 | }
73 |

这段代码几乎不用解释,就是在Gradle中配置使用Maven本地仓库、中央仓库、以及自定义地址仓库。在我实际构建项目的时候,能看到终端打印的下载信息,下载后的文件被存储在 USER_HOME/.gradle/cache/ 目录下供项目使用,这种实现的方法与Maven又是及其类似了,可以说Gradle不仅最大限度的继承Maven的很多理念,仓库资源也是直接拿来用。

74 |

Gradle项目使用Maven项目生成的资源已经不是个问题了,接着需要反过来考虑,Maven用户是否能够使用 Gradle生成的资源呢?或者更简单点问,Gradle项目生成的构件是否可以发布到Maven仓库中供人使用呢?这一点非常重要,因为如果做不到这一点,你可能就会丢失大量的用户。幸运的是Gradle再次给出了令人满意的答案。使用Gradle的Maven Plugin,用户就可以轻松地将项目构件上传到Maven仓库中:

75 |
 76 | apply plugin: 'maven'
 77 | ...
 78 | uploadArchives {
 79 |     repositories.mavenDeployer {
 80 |         repository(url: "http://localhost:8088/nexus/content/repositories/snapshots/") {
 81 |             authentication(userName: "admin", password: "admin123")
 82 |             pom.groupId = "com.juvenxu"
 83 |             pom.artifactId = "account-captcha"
 84 |         }
 85 |     }
 86 | }
87 |

在上传的过程中,Gradle能够基于build.gradle生成对应的Maven POM文件,用户可以自行配置POM信息,比如这里的groupId和artifactId,而诸如依赖配置这样的内容,Gradle是会自动帮你进行转换的。由于Maven项目之间依赖交互的直接途径就是仓库,而Gradle既能够使用Maven仓库,也能以Maven的格式将自己的内容发布到仓库中,因此从技术角度来说,即使在一个基于Maven的大环境中,局部使用Gradle也几乎不会是一个问题。

88 |

约定优于配置

89 |

如同Ant一般,Gradle给了用户足够的自由去定义自己的任务,不过同时Gradle也提供了类似Maven的约定优于配置方式,这是通过Gradle的Java Plugin实现的,从文档上看,Gradle是推荐这种方式的。Java Plugin定义了与Maven完全一致的项目布局:

90 |
    91 |
  • 92 |

    src/main/java

    93 |
  • 94 |
  • 95 |

    src/main/resources

    96 |
  • 97 |
  • 98 |

    src/test/java

    99 |
  • 100 |
  • 101 |

    src/test/resources

    102 |
  • 103 |
104 |

区别在于,使用Groovy自定义项目布局更加的方便:

105 |
106 | sourceSets {
107 |     main {
108 |         java {
109 |             srcDir 'src/java'
110 |         }
111 |         resources {
112 |             srcDir 'src/resources'
113 |         }
114 |     }
115 | }
116 |

Gradle Java Plugin也定义了构建生命周期,包括编译主代码、处理资源、编译测试代码、执行测试、上传归档等等任务:

117 |

118 |

Figure 1. Gradle的构建生命周期

119 |

相对于Maven完全线性的生命周期,Gradle的构建生命周期略微复杂,不过也更为灵活,例如jar这个任务是用来打包的,它不像Maven那样依赖于执行测试的test任务,类似的,从图中可以看到,一个最终的build任务也没有依赖于uploadArchives任务。这个生命周期并没有将用户限制得很死,举个例子,我希望每次build都发布 SNAPSHOT版本到Maven仓库中,而且我只想使用最简单的$ gradle clean build命令,那只需要添加一行任务依赖配置即可:

120 |
121 | build.dependsOn 'uploadArchives'
122 |

由于Gradle完全是基于灵活的任务模型,因此很多事情包括覆盖现有任务,跳过任务都非常易于实现。而这些事情,在Maven的世界中,实现起来就比较的麻烦,或者说Maven压根就不希望用户这么做。

123 |

小结

124 |

一番体验下来,Gradle给我最大的感觉是两点。其一是简洁,基于Groovy的紧凑脚本实在让人爱不释手,在表述意图方面也没有什么不清晰的地方。其二是灵活,各种在Maven中难以下手的事情,在Gradle就是小菜一碟,比如修改现有的构建生命周期,几行配置就完成了,同样的事情,在Maven中你必须编写一个插件,那对于一个刚入门的用户来说,没个一两天几乎是不可能完成的任务。

125 |

不过即使如此,Gradle在未来能否取代Maven,在我看来也还是个未知数。它的一大障碍就是Grovvy,几乎所有 Java开发者都熟悉XML,可又有几个人了解Groovy呢?学习成本这道坎是很难跨越的,很多人抵制Maven就是因为学起来不容易,你现在让因为一个构建工具学习一门新语言(即使这门语言和Java非常接近),那得到冷淡的回复几乎是必然的事情。Gradle的另外一个问题就是它太灵活了,虽然它支持约定优于配置,不过从本文你也看到了,破坏约定是多么容易的事情。人都喜欢自由,爱自定义,觉得自己的需求是多么的特别,可事实上,从Maven的流行来看,几乎95%以上的情况你不需要自行扩展,如果你这么做了,只会让构建变得难以理解。从这个角度来看,自由是把双刃剑,Gradle给了你足够的自由,约定优于配置只是它的一个选项而已,这初看起来很诱人,却也可能使其重蹈Ant的覆辙。Maven在Ant的基础上引入了依赖管理、仓库以及约定优于配置等概念,是一个很大的进步,不过在我现在看来,Gradle并没有引入新的概念,给我感觉它是一个结合Ant和Maven理念的优秀实现。

126 |

如果你了解Groovy,也理解Maven的约定优于配置,那试试Gradle倒也不错,尤其是它几乎能和现有的Maven系统无缝集成,而且你也能享受到简洁带来的极大乐趣。其实说到简洁,也许在不久的将来Maven用户也能直接享受到,Polyglot Maven在这方面已经做了不少工作。

127 | 128 | -------------------------------------------------------------------------------- /gradle/summary.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/gradle/summary.md -------------------------------------------------------------------------------- /gradle/using-command-line.md: -------------------------------------------------------------------------------- 1 | #使用命令行操作# 2 | 3 | 我们可以用Gradle命令来执行特定的任务,运行一个任务需要你知道该任务的名称,如果Gradle能够告诉你有哪些任务可以执行那岂不是很棒?Gradle提供了一个辅助的任务tasks来检查你的构建脚本,然后显示所有的任务,包含一个描述性的消息。 4 | 5 | $ gradle -q tasks 6 | 输出如下: 7 | 8 | All tasks runnable from root project 9 | 10 | Build Setup tasks 11 | 12 | setupBuild - Initializes a new Gradle build. [incubating] 13 | wrapper - Generates Gradle wrapper files. [incubating] 14 | 15 | 16 | Help tasks 17 | ---------- 18 | dependencies - Displays the dependencies of root project'grouptherapy'. 19 | dependencyInsight - Displays the insight into a specific dependency in root 20 | ➥ project 'grouptherapy'. 21 | help - Displays a help message 22 | projects - Displays the sub-projects of root project 'grouptherapy'. 23 | properties - Displays the properties of root project 'grouptherapy'. 24 | tasks - Displays the tasks runnable from root project 'grouptherapy' (some of 25 | ➥ the displayed tasks may belong to subprojects). 26 | 27 | Other tasks 28 | ----------- 29 | groupTherapy 30 | 31 | To see all tasks and more detail, run with --all. 32 | 33 | Gradle提供任务组的概念,简而言之就是将一些任务归为一组,你可以执行这个组里面所有的任务,没有分组的任务在Other tasks,任务分组后面会讲到。 34 | 35 | #任务执行# 36 | 37 | 要执行一个任务,只需要输入gradle + 任务名,Gradle确保这个任务和它所依赖的任务都会执行,要执行多个任务只需要在后面添加多个任务名。 38 | 39 | ##任务名称缩写## 40 | 41 | Gradle提高效率的一个办法就是能够在命令行输入任务名的驼峰简写,当你的任务名称非常长的时候这很有用,当然你要确保你的简写只匹配到一个任务,比如下面的情况: 42 | 43 | task groupTherapy << { 44 | ... 45 | } 46 | task generateTests << { 47 | ... 48 | } 49 | 这时候你使用gradle gT的时候Gradle就会报错,因为有多个任务匹配到gT: 50 | 51 | $ gradle gT 52 | FAILURE: Could not determine which tasks to execute. 53 | * What went wrong: 54 | Task 'gT' is ambiguous in root project 'grouptherapy'. Candidates are: 55 | ➥ 'generateTests', 'groupTherapy'. 56 | * Try: 57 | Run gradle tasks to get a list of available tasks. 58 | 59 | BUILD FAILED 60 | 61 | ##运行的时候排除一个任务## 62 | 63 | 比如运行的时候你要排除yayGradle0,你可以使用-x命令来完成 64 | 65 | $ gradle groupTherapy -x yayGradle0 66 | :yayGradle1 67 | Gradle rocks 68 | :yayGradle2 69 | Gradle rocks 70 | :groupTherapy 71 | 运行的时候Gradle排除了yayGradle0任务和它依赖的任务startSession。 72 | 73 | #命令行选项# 74 | 75 | * -i:Gradle默认不会输出很多信息,你可以使用-i选项改变日志级别为INFO 76 | * -s:如果运行时错误发生打印堆栈信息 77 | * -q:只打印错误信息 78 | * -?-h,--help:打印所有的命令行选项 79 | * -b,--build-file:Gradle默认执行build.gradle脚本,如果想执行其他脚本可以使用这个命令,比如gradle -b test.gradle 80 | * --offline:在离线模式运行build,Gradle只检查本地缓存中的依赖 81 | * -D, --system-prop:Gradle作为JVM进程运行,你可以提供一个系统属性比如:-Dmyprop=myValue 82 | * -P,--project-prop:项目属性可以作为你构建脚本的一个变量,你可以传递一个属性值给build脚本,比如:-Pmyprop=myValue 83 | 84 | ----------------- 85 | * tasks:显示项目中所有可运行的任务 86 | * properties:打印你项目中所有的属性值 87 | 88 | 89 | -------------------------------------------------------------------------------- /gradle/why-gradle.md: -------------------------------------------------------------------------------- 1 | #为什么选择Gradle# 2 | 3 | 如果你曾经使用过构建工具,你可能会对遇到的问题感到很沮丧,构建工具不是应该自动帮你完成项目的构建吗?你不得不向性能、扩展性等妥协。 4 | 5 | 比如你在构建一个项目的发布版本时,你要把一个文件拷贝到指定的位置,你在项目的元数据那里添加了版本的描述,如果版本号匹配一个特定的数字时,就把文件从A拷贝到B处。如果你依赖XML来构建,你要实现这个任务就像噩梦一样,你只能通过非标准的机制来添加一些脚本到构建中,结果就是把XML和脚本混在一起,随着时间的推移,你会添加越来越多的自定义的代码,结果就是项目越来越复杂很难维护。为什么不考虑用表达式的语言来定义你的构建逻辑呢? 6 | 7 | 另外一个例子,Maven跟随约定优于配置的规范,引入了标准化的项目布局和构建生命周期,给很多项目确保一个统一的结构这是个不错的方法。然而你手上的项目刚好和传统的约定不一样。Maven的一个严格的约定就是每个项目都要生成一个artifact,比如jar文件,但是你怎么从同一个源代码结构中创建两个不同的JAR文件,因此你不得不分开创建两个项目。 8 | 9 | #Java构建工具的发展# 10 | 11 | 最早出现的是Ant,Ant里的每一个任务(target)都可以互相依赖,Ant的最大缺点就是依赖的外部库也要添加到版本控制系统中,因为Ant没有一个机制来把这些jar文件放在一个中央库里面,结果就是不断的拷贝和粘贴代码。 12 | 13 | 随后Maven在2004年出现了,Maven引入了标准的项目和路径结构,还有依赖管理,不幸的是自定义的逻辑很难实现,唯一的方法就是引入插件。 14 | 15 | 随后Ant通过Apache Ivy引入依赖管理来跟上Maven的脚步,Ant和Ivy集成实现了声明式的依赖,比如项目的编译和打包过程 16 | 17 | Gradle的出现满足了很多现在构建工具的需求,Gradle提供了一个DSL(领域特定语言),一个约定优于配置的方法,还有更强大的依赖管理,Gradle使得我们可以抛弃XML的繁琐配置,引入动态语言Groovy来定义你的构建逻辑。 18 | 19 | ------------------------- 20 | 21 | #为什么要选择Gradle 22 | 23 | 假如你是一个开发者,项目自动构建是你每天工作的一部分,难道你就不想让你的构建代码和你写的源代码一样可以扩展、测试和维护?Gradle的构建脚本是声明式的、可读的,可以清晰的表达意图。使用Groovy代替XML来写代码大大减少了构建代码的大小。更重要的是,Gradle集成了其他构建工具,比如Ant和Maven,使得原来的项目很容易迁徙到Gradle。 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /images/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/4-1.png -------------------------------------------------------------------------------- /images/4-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/4-2.png -------------------------------------------------------------------------------- /images/4-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/4-3.png -------------------------------------------------------------------------------- /images/4-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/4-4.png -------------------------------------------------------------------------------- /images/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-1.png -------------------------------------------------------------------------------- /images/5-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-10.png -------------------------------------------------------------------------------- /images/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-2.png -------------------------------------------------------------------------------- /images/5-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-3.png -------------------------------------------------------------------------------- /images/5-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-4.png -------------------------------------------------------------------------------- /images/5-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-5.png -------------------------------------------------------------------------------- /images/5-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-6.png -------------------------------------------------------------------------------- /images/5-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-7.png -------------------------------------------------------------------------------- /images/5-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-8.png -------------------------------------------------------------------------------- /images/5-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/5-9.png -------------------------------------------------------------------------------- /images/build-process.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/build-process.png -------------------------------------------------------------------------------- /images/dag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag.png -------------------------------------------------------------------------------- /images/dag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag1.png -------------------------------------------------------------------------------- /images/dag10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag10.png -------------------------------------------------------------------------------- /images/dag11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag11.png -------------------------------------------------------------------------------- /images/dag12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag12.png -------------------------------------------------------------------------------- /images/dag13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag13.png -------------------------------------------------------------------------------- /images/dag14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag14.png -------------------------------------------------------------------------------- /images/dag15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag15.png -------------------------------------------------------------------------------- /images/dag16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag16.png -------------------------------------------------------------------------------- /images/dag17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag17.png -------------------------------------------------------------------------------- /images/dag18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag18.png -------------------------------------------------------------------------------- /images/dag19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag19.png -------------------------------------------------------------------------------- /images/dag2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag2.png -------------------------------------------------------------------------------- /images/dag20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag20.png -------------------------------------------------------------------------------- /images/dag21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag21.png -------------------------------------------------------------------------------- /images/dag22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag22.png -------------------------------------------------------------------------------- /images/dag23.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag23.png -------------------------------------------------------------------------------- /images/dag24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag24.png -------------------------------------------------------------------------------- /images/dag25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag25.png -------------------------------------------------------------------------------- /images/dag26.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag26.png -------------------------------------------------------------------------------- /images/dag27.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag27.png -------------------------------------------------------------------------------- /images/dag28.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag28.png -------------------------------------------------------------------------------- /images/dag29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag29.png -------------------------------------------------------------------------------- /images/dag3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag3.png -------------------------------------------------------------------------------- /images/dag30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag30.png -------------------------------------------------------------------------------- /images/dag31.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag31.png -------------------------------------------------------------------------------- /images/dag32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag32.png -------------------------------------------------------------------------------- /images/dag33.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag33.png -------------------------------------------------------------------------------- /images/dag34.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag34.png -------------------------------------------------------------------------------- /images/dag35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag35.png -------------------------------------------------------------------------------- /images/dag36.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag36.png -------------------------------------------------------------------------------- /images/dag37.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag37.png -------------------------------------------------------------------------------- /images/dag38.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag38.png -------------------------------------------------------------------------------- /images/dag39.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag39.png -------------------------------------------------------------------------------- /images/dag4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag4.png -------------------------------------------------------------------------------- /images/dag40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag40.png -------------------------------------------------------------------------------- /images/dag41.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag41.png -------------------------------------------------------------------------------- /images/dag42.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag42.png -------------------------------------------------------------------------------- /images/dag43.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag43.png -------------------------------------------------------------------------------- /images/dag44.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag44.png -------------------------------------------------------------------------------- /images/dag45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag45.png -------------------------------------------------------------------------------- /images/dag46.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag46.png -------------------------------------------------------------------------------- /images/dag47.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag47.png -------------------------------------------------------------------------------- /images/dag5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag5.png -------------------------------------------------------------------------------- /images/dag51.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag51.png -------------------------------------------------------------------------------- /images/dag52.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag52.png -------------------------------------------------------------------------------- /images/dag53.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag53.png -------------------------------------------------------------------------------- /images/dag54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag54.png -------------------------------------------------------------------------------- /images/dag6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag6.png -------------------------------------------------------------------------------- /images/dag7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag7.png -------------------------------------------------------------------------------- /images/dag8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag8.png -------------------------------------------------------------------------------- /images/dag9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/images/dag9.png -------------------------------------------------------------------------------- /multi-project/assemble.md: -------------------------------------------------------------------------------- 1 | ##多项目打包 2 | 3 | 上一节你给你的项目定义了一个层次化的目录结构,整个项目包含一个根目录和每个模块一个子目录,这一节你将学习怎么用Gradle来构建这样一个项目结构。 4 | 5 | 首先在你的根目录新建一个build.gradle文件,创建一个空的build脚本然后运行gradle projects: 6 | 7 | $ gradle projects 8 | :projects 9 | ------------------------------------------------------------ 10 | Root project 11 | ------------------------------------------------------------ 12 | Root project 'todo' 13 | No sub-projects 14 | 15 | 接下来学习怎么通过settings.gradle来定义项目的子项目。 16 | 17 | ![](/images/dag38.png) 18 | 19 | ###介绍设置文件 20 | 21 | 设置文件用来表示项目的层次结构,默认的设置文件名称是settings.gradle,对于你想添加的每一个子项目,调用include方法来添加。 22 | 23 | //参数是项目路径,不是文件路径 24 | include 'model' 25 | include 'repository', 'web' 26 | 27 | 提供的项目路径是相对于根目录,记住冒号:是用来分隔目录层次,比如你想表示model/todo/items这个目录,在gradle里面是model:todo:items这样表示。接下来执行gradle projects任务: 28 | 29 | $ gradle projects 30 | :projects 31 | ------------------------------------------------------------ 32 | Root project 33 | ------------------------------------------------------------ 34 | Root project 'todo' 35 | +--- Project ':model' 36 | +--- Project ':repository' 37 | +--- Project ':web' 38 | 39 | 通过添加当个设置文件,你就创建了一个多项目的构建包含一个根项目和三个子项目,不需要额外的配置。 40 | 41 | ###理解settings 的API表示 42 | 43 | 在Gradle开始执行构建之前,它创建一个Settings类型的实例,Settings接口直接用来表示settings文件,主要目的是通过代码来动态添加项目参与到多项目构建中,下图是你可以访问的Gradle API。 44 | 45 | ![](/images/dag39.png) 46 | 47 | 之前我们介绍过Gradle有三个生命周期,实例化阶段->配置阶段->执行阶段,Settings处于实例化阶段,Gradle自动找出一个子项目是否处在一个多项目构建中。 48 | 49 | ![](/images/dag40.png) 50 | 51 | ###设置文件解析 52 | 53 | Gradle允许你从根目录或者任何子目录中运行构建任务,只要它包含一个build脚本,Gradle怎么知道一个子项目是不是一个多项目构建的一部分呢?他需要查找settings文件,Gradle通过两步来查找设置文件。 54 | 55 | 1. Gradle查找和当前目录具有相同嵌套级别的master目录下的设置文件 56 | 2. 如果第一步没有找到设置文件,Gradle从当前目录开始逐步查找父目录 57 | 58 | 如果找到了settings文件,项目包含在另一个项目中,这个项目就当成是多项目构建的一部分。整个过程如下所示: 59 | 60 | ![](/images/dag41.png) 61 | ![](/images/dag42.png) 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /multi-project/configure-subproject.md: -------------------------------------------------------------------------------- 1 | ##配置子项目 2 | 3 | 到目前为止你已经把ToDo项目根据功能拆分成多个模块,接下来可以用之前的方法来定义构建逻辑,下面有几点需要主要: 4 | 5 | * 根目录和子目录使用相同的group和version属性值 6 | * 所有的子目录都是Java项目需要Java插件来正常工作,所以你只需要在子项目中应用Java插件 7 | * web子项目是唯一一个依赖外部库的项目,它需要打包成WAR而不是JAR 8 | * 子项目之间可以定义模块依赖 9 | 10 | 接下来你将学习如何定义特定的和共有的构建逻辑,怎么样去避免重复的配置。有些子项目可能依赖其他项目的源代码,比如repository项目依赖model项目,通过声明项目依赖可以避免拷贝源代码。 11 | 12 | ###理解项目的API表示 13 | 14 | 之前我介绍过项目Project可能会用到的一些API,接下来还有一些API用在多项目构建中。project方法用于声明指定项目的构建代码,需要提供项目的路径,比如:model。有时候你想给所有的项目或者只有子项目定义一些逻辑,你可以使用allprojects和subprojects方法,比如你想给所有的子项目添加Java插件,你需要使用subprojects方法。 15 | 16 | ![](/images/dag43.png) 17 | 18 | ###定义项目特有的行为 19 | 20 | 指定项目的行为通过project方法来定义,为了给三个子项目model、repository、web定义构建逻辑,你需要给它们分别创建一个配置块。下面build.gradle文件: 21 | 22 | ext.projectIds = ['group': 'com.manning.gia', 'version': '0.1'] 23 | 24 | group = projectIds.group 25 | version = projectIds.version 26 | 27 | project(':model') { 28 | group = projectIds.group 29 | version = projectIds.version 30 | apply plugin: 'java' 31 | } 32 | 33 | project(':repository') { 34 | group = projectIds.group 35 | version = projectIds.version 36 | apply plugin: 'java' 37 | } 38 | 39 | project(':web') { 40 | group = projectIds.group 41 | version = projectIds.version 42 | apply plugin: 'java' 43 | apply plugin: 'war' 44 | apply plugin: 'jetty' 45 | 46 | repositories { 47 | mavenCentral() 48 | } 49 | 50 | dependencies { 51 | providedCompile 'javax.servlet:servlet-api:2.5' 52 | runtime 'javax.servlet:jstl:1.1.2' 53 | } 54 | 55 | } 56 | 57 | 从整个项目的根目录那里,你可以执行子项目的任务,需要记住项目路径是通过冒号分隔,比如你想执行model子项目的build任务,在命令行中执行gradle :modle:build就可以,如下所示: 58 | 59 | $ gradle :model:build 60 | :model:compileJava 61 | :model:processResources UP-TO-DATE 62 | :model:classes 63 | :model:jar 64 | :model:assemble 65 | :model:compileTestJava UP-TO-DATE 66 | :model:processTestResources UP-TO-DATE 67 | :model:testClasses UP-TO-DATE 68 | :model:test 69 | :model:check 70 | :model:build 71 | 72 | 73 | ###声明项目依赖 74 | 75 | 声明项目依赖和声明外部依赖非常类似,只需要在dependencies配置块中声明,如下所示: 76 | 77 | project(':model') { 78 | ... 79 | } 80 | 81 | project(':repository') { 82 | ... 83 | 84 | dependencies { 85 | //声明编译期依赖项目model 86 | compile project(':model') 87 | 88 | } 89 | 90 | } 91 | 92 | 93 | project(':web') { 94 | ... 95 | 96 | dependencies { 97 | //声明编译期依赖项目repository 98 | compile project(':repository') 99 | providedCompile 'javax.servlet:servlet-api:2.5' 100 | runtime 'javax.servlet:jstl:1.1.2' 101 | } 102 | 103 | } 104 | 105 | 这样就定义了我们的项目依赖,注意当一个项目依赖于另一个项目时,另一个项目的项目依赖和外部依赖同样会被添加进来,在构建周期的初始化阶段,Gradle决定项目的执行顺序。 106 | 107 | 从根目录的执行顺序是这样的: 108 | 109 | ![](/images/dag44.png) 110 | 111 | 112 | 有时候你并不需要重新构建那些并未改变的项目,Gradle提供了部分构建partial builds的选项,通过命令行选项-a或者--no-rebuild。比如你只改变了repository项目不想重新构建model子项目,你可以这样做:gralde :repository:build -a,如下所示: 113 | 114 | $ gradle :repository:build -a 115 | :repository:compileJava 116 | :repository:processResources UP-TO-DATE 117 | :repository:classes 118 | :repository:jar 119 | :repository:assemble 120 | :repository:compileTestJava UP-TO-DATE 121 | :repository:processTestResources UP-TO-DATE 122 | :repository:testClasses UP-TO-DATE 123 | :repository:test 124 | :repository:check 125 | :repository:build 126 | 127 | ###定义共同的行为 128 | 129 | 上面你在所有的子项目中添加了Java插件,给所有的项目添加了一个外部属性ProjectIds,当你的子项目变得比较多的时候这样子做可能是个问题,接下来你可以通过allprojects和subprojects方法来改善你的构建代码。 130 | 131 | ![](/images/dag45.png) 132 | 133 | 这是什么意思?这意味着你可以用allprojects方法给所有的项目添加group和version属性,由于根项目不需要Java插件,你可以使用subprojects给所有子项目添加Java插件,如下所示: 134 | 135 | allprojects { 136 | group = 'com.manning.gia' 137 | version = '0.1' 138 | } 139 | 140 | subprojects { 141 | apply plugin: 'java' 142 | } 143 | 144 | 145 | project(':repository') { 146 | dependencies { 147 | compile project(':model') 148 | } 149 | } 150 | 151 | project(':web') { 152 | apply plugin: 'war' 153 | apply plugin: 'jetty' 154 | 155 | repositories { 156 | mavenCentral() 157 | } 158 | 159 | dependencies { 160 | compile project(':repository') 161 | providedCompile 'javax.servlet:servlet-api:2.5' 162 | runtime 'javax.servlet:jstl:1.1.2' 163 | } 164 | } 165 | 166 | 167 | 168 | 169 | 170 | 171 | -------------------------------------------------------------------------------- /multi-project/customizing.md: -------------------------------------------------------------------------------- 1 | ##自定义脚本 2 | 3 | Gradle构建脚本的标准名称是build.gradle,在一个多项目构建的环境中,你想自定义你的构建脚本名称来显得高大上一点,因为多个项目有相同的构建脚本名称可能会混淆,接下来介绍如何使用自定义的脚本名称。 4 | 5 | 还是之前那个例子,假设所有的子项目路径都是以todo-开头,比如web子项目就是在todo-web目录下,构建脚本名称应该清晰的表示它的作用,如下图所示: 6 | 7 | ![](/images/dag47.png) 8 | 9 | 要使这个结构起作用关键点就是settings文件,它提供了除了包含哪个子目录的其他功能,实际上设置文件是一个构建脚本,它会在构建生命周期的评估阶段执行,通过Gradle提供的API来添加自定义的逻辑,如下所示: 10 | 11 | //通过目录来添加子项目 12 | include 'todo-model', 'todo-repository', 'todo-web' 13 | 14 | //设置根项目的名字 15 | rootProject.name = 'todo' 16 | 17 | //迭代访问所有根目录下的子项目,设置自定义的构建脚本名称 18 | rootProject.children.each { 19 | it.buildFileName = it.name + '.gradle' - 'todo-' 20 | } 21 | 22 | 23 | -------------------------------------------------------------------------------- /multi-project/individual.md: -------------------------------------------------------------------------------- 1 | ##拆分项目文件 2 | 3 | 到目前为止我们自定义了一个build.gradle和settings.gradle文件,随着你添加越来越多的子项目和任务到build.gradle中,代码的维护性将会下降。通过给每个子项目建立一个单独的build.gradle文件可以解决这个问题。 4 | 5 | 接下来我们在每个子项目的目录下创建一个build.gradle文件,目录如下: 6 | 7 | ![](/images/dag46.png) 8 | 9 | 现在你可以把构建逻辑从原先的build脚本中拆分开来放到合适的位置。 10 | 11 | ###定义根项目的构建代码 12 | 13 | 移除了那些与特定子项目相关的代码后,根项目的内容变得非常简单,只需要保留allprojects和subprojects配置块,如下所示: 14 | 15 | allprojects { 16 | group = 'com.manning.gia' 17 | version = '0.1' 18 | } 19 | 20 | subprojects { 21 | apply plugin: 'java' 22 | } 23 | 24 | ###定义子项目的构建代码 25 | 26 | 接下来你只需要把与特定项目相关的构建代码移到相应的build.gradle文件里就可以了,如下所示: 27 | 28 | repository子项目的构建代码: 29 | 30 | dependencies { 31 | compile project(':model') 32 | } 33 | 34 | web子项目的构建代码: 35 | 36 | apply plugin: 'war' 37 | apply plugin: 'jetty' 38 | 39 | repositories { 40 | mavenCentral() 41 | } 42 | 43 | dependencies { 44 | compile project(':repository') 45 | providedCompile 'javax.servlet:servlet-api:2.5' 46 | runtime 'javax.servlet:jstl:1.1.2' 47 | } 48 | 49 | 运行这个多项目构建和之前单独的一个build脚本的结果完全一样,当时你该上了构建代码的可读性和维护性。 50 | 51 | 52 | -------------------------------------------------------------------------------- /multi-project/introduce.md: -------------------------------------------------------------------------------- 1 | ##简介 2 | 3 | 每一个活跃的项目会随着时间慢慢增长的,一开始可能只是个很小的项目到后面可能包含很多包和类。为了提高可维护性和解藕的目的,你可能想把项目根据逻辑和功能来划分成一个个模块。模块通常按照等级来组织,相互之间可以定义依赖。 4 | 5 | Gradle给项目模块化提供了强大的支持,在Gradle中每个模块都是一个项目,我们称之为多项目构建,这一章介绍Gradle的多项目构建。 6 | -------------------------------------------------------------------------------- /multi-project/module-project.md: -------------------------------------------------------------------------------- 1 | ##项目模块化 2 | 3 | 在企业项目中,包层次和类关系比较负责,把代码拆分成模块是一个比较困难的任务,因为这需要你清晰的划分功能的边界,比如把业务逻辑和数据持久化拆分开来。 4 | 5 | ###解耦和聚合 6 | 7 | 但你的项目符合高内聚低耦合时,模块化就变得很容易,这是一条非常好的软件开发实践。一个很好的模块化的例子就是Spring框架,spring框架提供了很多服务,比如MVC web框架、事务管理器、JDBC数据库连接等,下图展示了Spring3.x的模块间的关系: 8 | 9 | ![](../images/dag32.png) 10 | 11 | 看起来这个架构非常的庞大吓人,定义了非常多的组件相互间的依赖关系比较复杂,在实际使用过程中你并不需要导入整个框架到你的项目中,你可以选择你需要使用的服务。幸运的是模块之间的依赖都是通过元数据指定的,Gradle的依赖管理很容易解析它的传递依赖。 12 | 13 | ###区分模块 14 | 15 | 下面我们还是继续之前那个ToDo的例子,我们来把它拆分为多个模块。 16 | 17 | ![](/images/dag33.png) 18 | ![](/images/dag34.png) 19 | 20 | 你已经根据类的功能把它们拆分成一个个包,基本上分为下面几个功能: 21 | 22 | * 模型: 用来表示数据 23 | * 仓库: 检索和存储数据 24 | * Web: 用来处理HTTP请求、渲染页面的Web组件 25 | 26 | 虽然这是一个非常小的项目,这些模块之间也有依赖关系: 27 | 28 | ![](/images/dag35.png) 29 | 30 | ###重构成模块 31 | 32 | 现在很容易把存在的项目结构重构成几个模块,对于每个模块,使用合适的名称创建一个子目录,把相关的代码移动到里面。默认的源代码路径src/main/java还是毫发无损,Web模块需要默认的web应用源代码目录src/main/webapp,下面显示了模块化的项目布局: 33 | 34 | ![](/images/dag36.png) 35 | ![](/images/dag37.png) 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /multi-project/summary.md: -------------------------------------------------------------------------------- 1 | ##总结 2 | 3 | 通过把系统中的项目模块化可以提高代码的复用性、维护性,符合高内聚低耦合的软件开发准则。 4 | -------------------------------------------------------------------------------- /project-automation/benefits-of-project-automation.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/project-automation/benefits-of-project-automation.md -------------------------------------------------------------------------------- /project-automation/build-tools.md: -------------------------------------------------------------------------------- 1 | #构建工具 2 | 3 | 你需要的就是一套工具,能把你自动化构建的需求表示成可执行的顺序的任务(tasks),比如编译源代码,拷贝生成的class文件,组装交付。每一个任务都是一个工作单元,任务的顺序很重要,我们把任务和相互之间的依赖建模成一种有向无环图,比如下面这个: 4 | ![](/images/dag.png) 5 | 6 | **有向无环图** 7 | 8 | 包含两个部分: 9 | 10 | * 节点(node):一个工作单元,在这里就是一个任务,比如编译源代码 11 | * 边(edge): 一个有方向的边,表示相邻节点之间的依赖关系,如果一个任务定义了依赖,这个依赖的任务要在这个任务之前执行。 12 | 13 | ![](/images/dag1.png) 14 | 15 | **构建工具的组成** 16 | 17 | 1. Build File. 包含构建需要的配置,定义了项目的依赖关系,比如第三方库的,以及以任务的形式存在的指令,定义了任务之间的先后顺序。 18 | 19 | 2. Build inpus and outputs: 任务把输入经过一系列步骤后产生输出。 20 | 21 | ![](/images/dag2.png) 22 | 23 | 3. 依赖管理。 24 | 25 | ![](/images/dag3.png) 26 | 27 | 28 | -------------------------------------------------------------------------------- /project-automation/build-tools.md~: -------------------------------------------------------------------------------- 1 | #构建工具 2 | 3 | 你需要的就是一套工具,能把你自动化构建的需求表示成可执行的顺序的任务(tasks),比如编译源代码,拷贝生成的class文件,组装交付。每一个任务都是一个工作单元,任务的顺序很重要,我们把任务和相互之间的依赖建模成一种有向无环图,比如下面这个: 4 | ![](/images/dag.png) 5 | 6 | ![](/images/dag8.png) 7 | 8 | **有向无环图** 9 | 10 | 包含两个部分: 11 | 12 | * 节点(node):一个工作单元,在这里就是一个任务,比如编译源代码 13 | * 边(edge): 一个有方向的边,表示相邻节点之间的依赖关系,如果一个任务定义了依赖,这个依赖的任务要在这个任务之前执行。 14 | 15 | ![](/images/dag1.png) 16 | 17 | **构建工具的组成** 18 | 19 | 1. Build File. 包含构建需要的配置,定义了项目的依赖关系,比如第三方库的,以及以任务的形式存在的指令,定义了任务之间的先后顺序。 20 | 21 | 2. Build inpus and outputs: 任务把输入经过一系列步骤后产生输出。 22 | 23 | ![](/images/dag2.png) 24 | 25 | 3. 依赖管理。 26 | 27 | ![](/images/dag3.png) 28 | 29 | 30 | -------------------------------------------------------------------------------- /project-automation/java-build-tools.md: -------------------------------------------------------------------------------- 1 | #java构建工具 2 | 3 | **Ant** 4 | 5 | Ant 是 Apache 组织下的一个跨平台的项目构建工具,它是一个基于任务和依赖的构建系统,是过程式的。开发者需要显示的指定每一个任务,每个任务包含一组由 XML 编码的指令,必须在指令中明确告诉 Ant 源码在哪里,结果字节码存储在哪里,如何将这些字节码打包成 JAR 文件。Ant 没有生命周期,你必须定义任务和任务之间的依赖,还需要手工定义任务的执行序列和逻辑关系。这就无形中造成了大量的代码重复。 6 | 7 | ![](/images/dag4.png) 8 | 9 | **maven** 10 | 11 | Maven 是 Apache 组织下的一个跨平台的项目管理工具,它主要用来帮助实现项目的构建、测试、打包和部署。Maven 提供了标准的软件生命周期模型和构建模型,通过配置就能对项目进行全面的管理。它的跨平台性保证了在不同的操作系统上可以使用相同的命令来完成相应的任务。Maven 将构建的过程抽象成一个个的生命周期过程,在不同的阶段使用不同的已实现插件来完成相应的实际工作,这种设计方法极大的避免了设计和脚本编码的重复,极大的实现了复用。 12 | 13 | Maven 不仅是一个项目构建工具还是一个项目管理工具。它有约定的目录结构(表 1)和生命周期,项目构建的各阶段各任务都由插件实现,开发者只需遵照约定的目录结构创建项目,再配置文件中生命项目的基本元素,Maven 就会按照顺序完成整个构建过程。Maven 的这些特性在一定程度上大大减少了代码的重复。 14 | -------------------------------------------------------------------------------- /project-automation/life-without-project-automation.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/project-automation/life-without-project-automation.md -------------------------------------------------------------------------------- /project-automation/project-automation.md: -------------------------------------------------------------------------------- 1 | 2 | #项目自动化简介# 3 | 4 | **想象一下没有自动化构建工具的场景** 5 | 6 | 大部分的软件开发者都会面临下面的情形: 7 | 8 | * 让IDE完成所有的工作. 用IDE来编码,导航到源代码、实现新特性、编译代码、重构代码、运行单元测试,一旦代码写完了,就按下编译按钮。一旦IDE提示没有编译错误测试通过,然后就把代码放入版本控制系统中以便与其他人分享。IDE是非常强大的工具,但是每个人都要安装一套标准的版本来执行上面介绍的任务,当你需要使用一个只有新版IDE才有的特性时,你就不得不更新到新版的IDE。 9 | 10 | * 我的电脑上运行正常. 由于时间比较紧,Joe检查版本控制的代码发现编译不了,似乎是源代码中缺少了某个类,因此他联系了Tom,Tom非常困惑怎么代码在Joe的电脑上没办法编译成功,和Joe讨论完之后,他意识到自己忘记提交一个类到版本控制当中,所以无法编译成功,接下来整个团队都阻塞在这一步,直到Tom提交缺失的那个类上去。 11 | 12 | * 代码集成简直就是个灾难. Acem有两个开发小组,一个集中于开发基于web的用户接口,另一小组集中开发服务器后台程序,当两个小组的人集中在一起测试整个程序时,发现程序的某些功能没有按照预期那样运行,一些链接无法解析或者直接返回错误的结果。 13 | 14 | * 测试过程慢的像蜗牛. QA小组非常急切的接收第一版的app,可想而知,他们对低质量的程序是没什么耐心的,每次程序修改之后,都要进行相同的测试过程。小组停下来检查每次提交的改变,最新的版本是通过IDE构建的,代码传递到测试服务器,但是整个团队都在等待测试结果。 15 | 16 | 这时候你就需要一个自动化的构建工具。 17 | 18 | **项目自动化的优势** 19 | 20 | 1. 避免手工介入 21 | 2. 创建可重复的构建过程 22 | 3. 使得构建非常便捷 23 | 24 | **构建过程** 25 | 26 | 大多数情况,用户在命令行执行一个脚本,脚本定义了任务执行的顺序,比如:编译源代码、从A路径复制文件到B路径、装配交付,这种自动化构建过程一天可能执行数次。 27 | 28 | ![](/images/build-process.png) 29 | -------------------------------------------------------------------------------- /project-automation/summary.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/project-automation/summary.md -------------------------------------------------------------------------------- /project-automation/types-of-project-automation: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/project-automation/types-of-project-automation -------------------------------------------------------------------------------- /push: -------------------------------------------------------------------------------- 1 | git add --all 2 | git commit -am "new post" 3 | git push origin master 4 | -------------------------------------------------------------------------------- /test-with-gradle/automated-test.md: -------------------------------------------------------------------------------- 1 | ##自动化测试 2 | 3 | 如果你想构建可靠的高质量的软件,自动化测试将是你工具箱里面非常关键的一个部分,它帮助你减少手工测试的代价,提高你的开发小组重构已有代码的能力。 4 | 5 | ###自动化测试的类型 6 | 7 |  并非所有的自动化测试都是相似的,他们通常在作用域、实现方式和执行时间上有所差异,我把他们分成三种类型的测试:单元测试、集成测试和功能测试。 8 | 9 | * 单元测试用于测试你代码的最小单元,在基于java的项目中这个单元就是一个方法(method),在单元测试中你会避免与其他类或者外部的系统打交道。单元测试很容易编写,执行起来非常快速,能够在开发阶段给你代码的正确性提供反馈。 10 | * 集成测试用于测试某一个组件或者子系统。你想确保不同类之间的交互能够按照预期一样,一个典型的情况就是逻辑层需要和数据库打交道。因此相关的子系统、资源文件和服务层必须在测试执行阶段是可访问的。集成测试通常比单元测试运行更慢,更难维护,出现错误时也比较难诊断。 11 | * 功能测试用于测试一个应用的功能,包括和外部系统的交互。功能测试是最难实现也是运行最慢的,因为他们需要模仿用户交互的过程,在web开发的情况,功能测试应该包括用户点击链接、输入数据或者在浏览窗口提交表单这些情形,因为用户接口可能随着时间改变,功能测试的维护将会很困难。 12 | 13 | ###自动化测试金字塔 14 | 15 | 你可能想知道到底哪一种测试最适合你的项目,在现实环境中你可能会混合使用这几种测试方法来确保你的代码在不同架构层面都是正确的。你需要写多少测试取决于编写和维护测试的时间消耗。测试越简单就越容易执行,一般来讲你的项目应该包含很多单元测试,少量的集成测试以及更少的功能测试。 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test-with-gradle/configuring-test-execution.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EZLippi/GradleInActionZh/972c36e032eddee518cd604872540a99d942a572/test-with-gradle/configuring-test-execution.md -------------------------------------------------------------------------------- /test-with-gradle/introduce.md: -------------------------------------------------------------------------------- 1 | ##简介 2 | 3 | 在之前的章节我们实现了一个简单但是功能齐全的web项目、学习了如何使用Gradle来构建和运行这个项目。测试代码是软件开发周期中非常重要的一环,能够确保软件的行为能符合预期。这一章我将讲述如何使用Gradle来组织、配置和执行测试代码,学习如何写单元测试、集成测试和功能测试并把他们集成到项目构建中。 4 | 5 | Gradle集成了很多Java和Groovy测试框架,在本章的最后你会用JUnit、TestNG和Spock来编写和执行测试,学习控制测试日志的输出、监听测试生命周期事件,以及如何提高测试性能。 6 | -------------------------------------------------------------------------------- /test-with-gradle/test-java-application.md: -------------------------------------------------------------------------------- 1 | #测试Java应用 2 | 3 | 一些开源的测试框架比如JUnit,TestNG能够帮助你编写可复用的结构化的测试,为了运行这些测试,你要先编译它们,就像编译源代码一样。测试代码的作用仅仅用于测试的情况,你可不想把你的测试代码发布到生产环境中,把源代码和测试代码混在一起可不是个好主意。通常你会把源代码和测试代码分开来,比如Gradle的标准项目布局src/main/java和src/test/java。 4 | 5 | ##项目布局 6 | 7 | 在前面我们讲到默认的项目布局,源代码是src/main/java,资源文件是在src/main/resources,测试源代码路径也是这样,你把测试代码放在src/test/java,资源文件放在src/test/resources,编译之后测试的class文件在build/classes/test下。 8 | 9 | 所有的测试框架都会生成至少一个文件用来说明测试执行的结果,最普遍的格式就是XML格式,你可以在build/test-results路径下找到这些文件,XML文件的可读性比较差,许多测试框架都允许把测试结果转换成报告,比如JUnit可以生成HTML格式的报告,Gradle把测试报告放在build/reports/test。下图清晰的显示了项目的布局: 10 | 11 | ![](/images/dag51.png) 12 | 13 | 上面讲了这么多测试框架,Gradle怎么知道你想使用哪一个呢?你需要声明对外部库的依赖。 14 | 15 | ###测试配置 16 | 17 | Java插件引入了两个配置来声明测试代码的编译期和运行期依赖:testCompile和testRuntime,我们来看一下怎么声明一个对JUnit框架的编译期依赖: 18 | 19 | dependencies { 20 | testCompile 'junit:junit:4.11' 21 | } 22 | 23 | 另外一个配置testRuntime用来声明那些编译期用不着但是在运行期需要的依赖,记住用于测试的依赖不会影响你源代码的classpath,换句话说他们不会用在编译或打包过程。然而,对于处理依赖来讲测试配置继承了源代码相关配置,比如testCompile继承了compile配置的依赖,testRuntime继承了runtime和testCompile和他们的父类,他们父类的依赖会自动传递到testCompile或testRuntime中。如下图所示: 24 | 25 | 26 | ![](/images/dag52.png) 27 | 28 | ###测试任务 29 | 30 | 在之前的任务我们可能注意到任务图一直有四个任务是up-to-date的然后被跳过了,这是因为你没有编写任何测试代码Gradle就不需要编译或执行。下图显示了这四个任务在任务图中的位置: 31 | 32 | 33 | ![](/images/dag53.png) 34 | 35 | 从图中可以看到测试编译和测试执行阶段是在源代码被编译和打包之后的,如果你想避免执行测试阶段你可以在命令行执行gradle jar或者让你的任务依赖jar任务。 36 | 37 | ###自动测试检查 38 | 39 | 对于build/classes/test目录下的所有编译的测试类,Gradle怎么知道要执行哪一个呢?答案就是所有匹配下面几条描述的都会被检查: 40 | 41 | * 任何继承自junit.framework.TestCase 或groovy.util.GroovyTestCase的类 42 | * 任何被@RunWith注解的子类 43 | * 任何至少包含一个被@Test注解的类 44 | 45 | 如果没有找到符合条件的,测试就不会执行,接下来我们会使用不同框架来编写单元测试。 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /test-with-gradle/unit-testing.md: -------------------------------------------------------------------------------- 1 | ##单元测试 2 | 3 | 作为一个Java开发者,你有很多个测试框架可选,这一节我将介绍传统的JUnit和TestNG,如果你没有接触过这些框架,你可以先看看他们的在线文档。 4 | 5 | 6 | ###使用JUnit 7 | 8 | 你将给你之前的ToDo应用的存储类InMemoryToDoRepository.java编写单元测试,为了突出不同框架的相同和不同之处,所有的单元测试都会验证同一个类的功能。接下来你给子项目repository编写测试,放置测试代码的正确位置是在测试的标准布局里,在src/test/java目录下创建一个名叫InMemoryToDoRepositoryTest.java的类,你可以学习测试驱动开发的相关理论,在代码中添加适当的断言语句,下面这段代码用来测试插入功能的正确性。 9 | 10 | import com.manning.gia.todo.model.ToDoItem; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import java.util.List; 14 | import static org.junit.Assert.*; 15 | 16 | public class InMemoryToDoRepositoryTest { 17 | private ToDoRepository inMemoryToDoRepository; 18 | //用这个注解标识的都会在类的所有测试方法之前执行 19 | @Before 20 | public void setUp() { 21 | inMemoryToDoRepository = new InMemoryToDoRepository(); 22 | } 23 | //用这个注解的都会作为测试用例 24 | @Test 25 | public void insertToDoItem() { 26 | ToDoItem newToDoItem = new ToDoItem();   27 | newToDoItem.setName("Write unit tests"); 28 | Long newId = inMemoryToDoRepository.insert(newToDoItem); 29 | //错误的断言会导致测试失败  30 | assertNull(newId); 31 | ToDoItem persistedToDoItem = inMemoryToDoRepository.findById(newId); 32 | assertNotNull(persistedToDoItem); 33 | assertEquals(newToDoItem, persistedToDoItem); 34 | } 35 | } 36 | 37 | 接下来你需要在依赖配置中添加JUnit的依赖: 38 | 39 | project(':repository'){ 40 | repositories { 41 | mavenCentral() 42 | } 43 | 44 | dependencies { 45 | compile project(':model') 46 | testCompile 'junit:junit:4.11' 47 | } 48 | } 49 | 50 | 之前我们讲过test任务会先编译源代码,生成Jar文件,然后编译测试代码最后执行测试,下面的命令行输出显示了有一个断言出错的情况: 51 | 52 | $ gradle :repository:test 53 | :model:compileJava 54 | :model:processResources UP-TO-DATE 55 | :model:classes 56 | :model:jar 57 | :repository:compileJava 58 | :repository:processResources UP-TO-DATE 59 | :repository:classes 60 | :repository:compileTestJava 61 | :repository:processTestResources UP-TO-DATE 62 | :repository:testClasses 63 | :repository:test 64 | 65 | com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 66 | > testInsertToDoItem FAILED//出错方法的名字 67 | java.lang.AssertionError at InMemoryToDoRepositoryTest.java:24 68 | //测试结果概括 69 | 1 test completed, 1 failed 70 | :repository:test FAILED 71 | 72 | FAILURE: Build failed with an exception. 73 | 74 | * What went wrong: 75 | Execution failed for task ':repository:test'. 76 | > There were failing tests. See the report at: 77 | ➥ file:///Users/ben/dev/gradle-in-action/code/chapter07/junit-test- 78 | ➥ failing/repository/build/reports/tests/index.html 79 | 80 | 从输出可以看出一个断言失败了,这正是你想看到的,显示的信息并没有告诉你为什么测试失败了,指示告诉你第24行的断言失败了,如果你有很多个测试,你需要打开测试报告才能找到出错的原因,你可以在任务使用-i选项打印日志输出: 81 | 82 | $ gradle :repository:test –i 83 | ... 84 | com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 85 | > testInsertToDoItem FAILED 86 | java.lang.AssertionError: expected null, but was:<1> 87 | at org.junit.Assert.fail(Assert.java:88) 88 | at org.junit.Assert.failNotNull(Assert.java:664) 89 | at org.junit.Assert.assertNull(Assert.java:646) 90 | at org.junit.Assert.assertNull(Assert.java:656) 91 | at com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 92 | ➥ .testInsertToDoItem(InMemoryToDoRepositoryTest.java:24) 93 | ... 94 | 95 | 在堆栈树我们可以找到出错的原因是newId的值我们假定是null的实际上为1,所以断言出错了,修改之后再运行可以看到所有测试都通过了: 96 | 97 | $ gradle :repository:test 98 | :model:compileJava 99 | :model:processResources UP-TO-DATE 100 | :model:classes 101 | :model:jar 102 | :repository:compileJava 103 | :repository:processResources UP-TO-DATE 104 | :repository:classes 105 | :repository:compileTestJava 106 | :repository:processTestResources UP-TO-DATE 107 | :repository:testClasses 108 | :repository:test 109 | 110 | Gradle可以生成更加视觉化的测试报告,你可以在build/reports/test目录下找到HTML文件,打开HTML文件你应该可以看到类似这样的东西: 111 | 112 | ![](/images/dag54.png) 113 | 114 | ###使用其他测试框架 115 | 116 | 你可能在你的项目中想使用其他的测试框架比如TestNG和Spock 117 | 118 | **使用testNG** 119 | 120 | 比如你想用testNG来编写相同的测试类,相似的,你用testNG指定的注解来标识相应的方法,要想你的构建执行testNG测试,你需要做两件测试: 121 | 122 | 1. 声明对testNG库的依赖 123 | 2. 调用Test#useTestNG()方法来声明测试过程使用testNG框架 124 | 125 | 如下图所示来配置脚本文件: 126 | 127 | project(':repository'){ 128 | repositories { 129 | mavenCentral() 130 | } 131 |   132 | dependencies { 133 | compile project(':model') 134 | testCompile 'org.testng:testng:6.8' 135 | } 136 | //设置使用testNG来测试 137 | test.useTestNG() 138 |   139 | } 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /test-with-gradle/unit-testing.md~: -------------------------------------------------------------------------------- 1 | ##单元测试 2 | 3 | 作为一个Java开发者,你有很多个测试框架可选,这一节我将介绍传统的JUnit和TestNG,如果你没有接触过这些框架,你可以先看看他们的在线文档。 4 | 5 | 6 | ###使用JUnit 7 | 8 | 你将给你之前的ToDo应用的存储类InMemoryToDoRepository.java编写单元测试,为了突触不同框架的相同和不同之处,所有的单元测试都会验证同一个类的功能。接下来你给子项目repository编写测试,放置测试代码的正确位置是在测试的标准布局里,在src/test/java目录下创建一个名叫InMemoryToDoRepositoryTest.java的类,你可以学习测试驱动开发的相关理论,在代码中添加适当的断言语句,下面这段代码用来测试插入功能的正确性。 9 | 10 | import com.manning.gia.todo.model.ToDoItem; 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import java.util.List; 14 | import static org.junit.Assert.*; 15 | 16 | public class InMemoryToDoRepositoryTest { 17 | private ToDoRepository inMemoryToDoRepository; 18 | //用这个注解标识的都会在类的所有测试方法之前执行 19 | @Before 20 | public void setUp() { 21 | inMemoryToDoRepository = new InMemoryToDoRepository(); 22 | } 23 | //用这个注解的都会作为测试用例 24 | @Test 25 | public void insertToDoItem() { 26 | ToDoItem newToDoItem = new ToDoItem();   27 | newToDoItem.setName("Write unit tests"); 28 | Long newId = inMemoryToDoRepository.insert(newToDoItem); //错误的断言会导致测试失败  29 | assertNull(newId); 30 | ToDoItem persistedToDoItem = inMemoryToDoRepository.findById(newId); 31 | assertNotNull(persistedToDoItem); 32 | assertEquals(newToDoItem, persistedToDoItem); 33 | } 34 | } 35 | 36 | 接下来你需要在依赖配置中添加JUnit的依赖: 37 | 38 | project(':repository')repositories { 39 | mavenCentral() 40 | } 41 | { 42 | } 43 | dependencies { 44 | compile project(':model') 45 | testCompile 'junit:junit:4.11' 46 | } 47 | 48 | 之前我们讲过test任务会先编译源代码,生成Jar文件,然后编译测试代码最后执行测试,下面的命令行输出显示了有一个断言出错的情况: 49 | 50 | $ gradle :repository:test 51 | :model:compileJava 52 | :model:processResources UP-TO-DATE 53 | :model:classes 54 | :model:jar 55 | :repository:compileJava 56 | :repository:processResources UP-TO-DATE 57 | :repository:classes 58 | :repository:compileTestJava 59 | :repository:processTestResources UP-TO-DATE 60 | :repository:testClasses 61 | :repository:test 62 | 63 | com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 64 | > testInsertToDoItem FAILED//出错方法的名字 65 | java.lang.AssertionError at InMemoryToDoRepositoryTest.java:24 66 | //测试结果概括 67 | 1 test completed, 1 failed 68 | :repository:test FAILED 69 | 70 | FAILURE: Build failed with an exception. 71 | 72 | * What went wrong: 73 | Execution failed for task ':repository:test'. 74 | > There were failing tests. See the report at: 75 | ➥ file:///Users/ben/dev/gradle-in-action/code/chapter07/junit-test- 76 | ➥ failing/repository/build/reports/tests/index.html 77 | 78 | 从输出可以看出一个断言失败了,这正是你想看到的,显示的信息并没有告诉你为什么测试失败了,指示告诉你第24行的断言失败了,如果你有很多个测试,你需要打开测试报告才能找到出错的原因,你可以在任务使用-i选项打印日志输出: 79 | 80 | $ gradle :repository:test –i 81 | ... 82 | com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 83 | > testInsertToDoItem FAILED 84 | java.lang.AssertionError: expected null, but was:<1> 85 | at org.junit.Assert.fail(Assert.java:88) 86 | at org.junit.Assert.failNotNull(Assert.java:664) 87 | at org.junit.Assert.assertNull(Assert.java:646) 88 | at org.junit.Assert.assertNull(Assert.java:656) 89 | at com.manning.gia.todo.repository.InMemoryToDoRepositoryTest 90 | ➥ .testInsertToDoItem(InMemoryToDoRepositoryTest.java:24) 91 | ... 92 | 93 | 在堆栈树我们可以找到出错的原因是newId的值我们假定是null的实际上为1,所以断言出错了,修改之后再运行可以看到所有测试都通过了: 94 | 95 | $ gradle :repository:test 96 | :model:compileJava 97 | :model:processResources UP-TO-DATE 98 | :model:classes 99 | :model:jar 100 | :repository:compileJava 101 | :repository:processResources UP-TO-DATE 102 | :repository:classes 103 | :repository:compileTestJava 104 | :repository:processTestResources UP-TO-DATE 105 | :repository:testClasses 106 | :repository:test 107 | 108 | Gradle可以生成更加视觉化的测试报告,你可以在build/reports/test目录下找到HTML文件,打开HTML文件你应该可以看到类似这样的东西: 109 | 110 | ![](/images/dag54.png) 111 | 112 | ###使用其他测试框架 113 | 114 | 你可能在你的项目中想使用其他的测试框架比如TestNG和Spock 115 | 116 | **使用testNG** 117 | 118 | 比如你想用testNG来编写相同的测试类,相似的,你用testNG指定的注解来标识相应的方法,要想你的构建执行testNG测试,你需要做两件测试: 119 | 120 | 1. 声明对testNG库的依赖 121 | 2. 调用Test#useTestNG()方法来声明测试过程使用testNG框架 122 | 123 | 如下图所示来配置脚本文件: 124 | 125 | project(':repository'){ 126 | repositories { 127 | mavenCentral() 128 | } 129 |   130 | dependencies { 131 | compile project(':model') 132 | testCompile 'org.testng:testng:6.8' 133 | } 134 | //设置使用testNG来测试 135 | test.useTestNG() 136 |   137 | } 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | --------------------------------------------------------------------------------