<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; Groovy</title>
	<atom:link href="https://blog.gmem.cc/tag/groovy/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 03 Apr 2026 04:13:36 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>基于nGrinder进行负载测试</title>
		<link>https://blog.gmem.cc/load-testing-based-on-ngrinder</link>
		<comments>https://blog.gmem.cc/load-testing-based-on-ngrinder#comments</comments>
		<pubDate>Tue, 05 Mar 2019 08:34:34 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Test]]></category>
		<category><![CDATA[Groovy]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=25673</guid>
		<description><![CDATA[<p>简介 nGrinder是一个基于Grinder的压力测试平台。在此平台上你可以创建测试脚本、执行测试、监控目标服务器，同步的生成测试结果。 架构 基础组件 nGrinder由两种关键组件： 控制器：提供性能测试的Web接口，支持协调测试进程、收集并展示测试相关的统计信息，允许用户创建或修改测试脚本 代理，可以运行在两种模式下： Agent：运行线程、进程，将负载施加到目标服务器上 Monitor：监控目标服务器的性能，例如CPU/内存 代理启动后会尝试连接到控制器，然后被添加到AgentControllerServer —— 类似于Agent的池中。当用户发起一个性能测试后，nGrinder会创建一个协调Agents的SingleConsole（和Grinder中的Console作区分，起名叫SingleConsole），所需数量的Agent从AgentControllerServer交付给SingleConsole管理，SingleConsole向Agents发送测试脚本、测试资源，并控制测试流，直到测试完毕。测试完毕后，Agents回到AgentControllerServer的Agent池中，供后续测试重用。SingleConsole则回到ConsoleManager中。 nGrinder和Grinder最大的不同是，前者在控制器中持有多个Console实例、Agents。Console之间是相互独立的，并且可以并行的运行。Agent可以被预先分配给Console，也可以根据需要随时被请求、分配。nGrinder力求对Agent机器的最大化利用。其它测试平台，常常为测试任务预先分配Agents。用户为了争抢资源，常常会提前申请Agent而不充分利用。nGrinder仅在测试运行时才真正动态分配Agent。 架构图 集群架构 从3.1版本开始nGrinder开始支持控制器集群。性能测试可以被集群中的某个控制器调度，可以在多组Agent（称为Region）上执行。 &#160; 上图的架构中，包含两个控制器组成的集群： 所有控制器共享数据库、网络文件系统。所有控制器的[crayon-69d26a9b705fa640674127-i/]必须指向同一NFS路径 每个控制器可以有自己独特的属性、日志输出，这些信息存放在[crayon-69d26a9b70602056849572-i/] 所有控制器同步EhCache缓存中的共享数据 每个nGrinder控制器实例都可以独立的服务用户，需要注意的是nGrinder没有提供分布式会话，因此切换控制器后你需要重新登陆。 <a class="read-more" href="https://blog.gmem.cc/load-testing-based-on-ngrinder">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/load-testing-based-on-ngrinder">基于nGrinder进行负载测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">简介</span></div>
<p>nGrinder是一个基于Grinder的压力测试平台。在此平台上你可以创建测试脚本、执行测试、监控目标服务器，同步的生成测试结果。</p>
<div class="blog_h2"><span class="graybg">架构</span></div>
<div class="blog_h3"><span class="graybg">基础组件</span></div>
<p>nGrinder由两种关键组件：</p>
<ol>
<li>控制器：提供性能测试的Web接口，支持协调测试进程、收集并展示测试相关的统计信息，允许用户创建或修改测试脚本</li>
<li>代理，可以运行在两种模式下：
<ol>
<li>Agent：运行线程、进程，将负载施加到目标服务器上</li>
<li>Monitor：监控目标服务器的性能，例如CPU/内存</li>
</ol>
</li>
</ol>
<p><span style="background-color: #c0c0c0;">代理</span>启动后会尝试连接到控制器，然后被<span style="background-color: #c0c0c0;">添加到AgentControllerServer</span> —— 类似于Agent的池中。当用户发起一个性能测试后，nGrinder会创建一个<span style="background-color: #c0c0c0;">协调Agents的SingleConsole</span>（和Grinder中的Console作区分，起名叫SingleConsole），所需数量的<span style="background-color: #c0c0c0;">Agent从AgentControllerServer交付给SingleConsole</span>管理，SingleConsole<span style="background-color: #c0c0c0;">向Agents发送测试脚本、测试资源，并控制测试流，直到测试完毕</span>。测试完毕后，<span style="background-color: #c0c0c0;">Agents回到AgentControllerServer的Agent池中</span>，供后续测试重用。<span style="background-color: #c0c0c0;">SingleConsole则回到ConsoleManager</span>中。</p>
<p>nGrinder和Grinder最大的不同是，前者在控制器中<span style="background-color: #c0c0c0;">持有多个Console实例、Agents</span>。Console之间是相互独立的，并且可以并行的运行。Agent可以被预先分配给Console，也可以根据需要随时被请求、分配。nGrinder力求对Agent机器的最大化利用。其它测试平台，常常为测试任务预先分配Agents。用户为了争抢资源，常常会提前申请Agent而不充分利用。nGrinder<span style="background-color: #c0c0c0;">仅在测试运行时才真正动态分配Agent</span>。</p>
<div class="blog_h3"><span class="graybg">架构图</span></div>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2019/03/ngrinder-architecture-0.png"><img class="aligncenter size-full wp-image-25701" src="https://blog.gmem.cc/wp-content/uploads/2019/03/ngrinder-architecture-0.png" alt="ngrinder-architecture-0" width="623" height="591" /></a></p>
<div class="blog_h2"><span class="graybg">集群架构</span></div>
<p>从3.1版本开始nGrinder开始支持控制器集群。性能测试可以被集群中的某个控制器调度，可以在多组Agent（称为Region）上执行。</p>
<p><a href="https://blog.gmem.cc/wp-content/uploads/2019/03/ngrinder-architecture-clustering.png"><img class="aligncenter size-full wp-image-25687" src="https://blog.gmem.cc/wp-content/uploads/2019/03/ngrinder-architecture-clustering.png" alt="ngrinder-architecture-clustering" width="654" height="353" /></a></p>
<p>&nbsp;</p>
<p>上图的架构中，包含两个控制器组成的集群：</p>
<ol>
<li>所有控制器共享数据库、网络文件系统。所有控制器的<span style="color: #24292e;"><pre class="crayon-plain-tag">${NGRINDER_HOME}</pre>必须指向同一NFS路径</span></li>
<li>每个控制器可以有自己独特的属性、日志输出，这些信息存放在<pre class="crayon-plain-tag">${NGRINDER_EX_HOME}</pre></li>
<li>所有控制器同步EhCache缓存中的共享数据</li>
</ol>
<p>每个nGrinder控制器实例都可以独立的服务用户，需要注意的是nGrinder没有提供分布式会话，因此切换控制器后你需要重新登陆。</p>
<div class="blog_h1"><span class="graybg">用户接口</span></div>
<div class="blog_h2"><span class="graybg">快速开始</span></div>
<p>在Quick Start中输入一个URL，可以自动生成一个测试脚本。测试脚本就是一段以Groovy语言编写的JUnit测试。</p>
<div class="blog_h2"><span class="graybg">测试配置</span></div>
<p>在Home页面点击Performance Test链接，可以进入已经定义的性能测试的列表。</p>
<p>点击列表项，可以进入单个性能测试的详情页面。其第一个选项卡是Test Configuration：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 200px; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Agent</td>
<td>使用的代理数量</td>
</tr>
<tr>
<td>Vuser per agent</td>
<td>
<p>每个代理的“虚拟用户”的数量，可以展开设置线程、进程数量</p>
<p>总线程数 = 线程*进程 = 虚拟用户数 </p>
<p>和其它测试工具有所不同，nGrinder不去模拟真实的用户行为（比如说模拟10000个用户，在那每隔几秒发一个请求），而是专注于如何达到服务器的能力限制</p>
<p>如果你的确需要模拟真实用户行为，例如需要10000个虚拟用户， 你可以：</p>
<ol>
<li>准备两台性能较好的代理机器，例如2核心/6GB</li>
<li>编写Groovy脚本（比Jyhton更高效）</li>
<li>在system.conf配置 agent.max.vuser = 5000，解除默认3000的最大限制</li>
<li>调用<pre class="crayon-plain-tag">grinder.sleep(how_much_sleep_in_milliseconds)</pre>模拟用户思考导致的操作延迟</li>
<li>配置10进程 * 500线程</li>
</ol>
<p>如果代理机器的内存较低，例如4GB，则10个或更多进程会导致swap进而影响性能</p>
</td>
</tr>
<tr>
<td>Region</td>
<td>使用哪个区域中的Agent/Console</td>
</tr>
<tr>
<td>Target Host</td>
<td>
<p>被压测的主机，应当和脚本中所访问的主机一致。nGrinder控制器会在测试开始后，对Target host进行监控，你可以在图表中看到Target host的性能指标</p>
<p>你可以使用如下的形式来覆盖默认DNS解析：</p>
<pre class="crayon-plain-tag">www.gmem.cc:39.107.94.255 </pre>
</td>
</tr>
<tr>
<td>Duration</td>
<td>测试执行多久</td>
</tr>
<tr>
<td>Run Count</td>
<td>测试运行多少次</td>
</tr>
<tr>
<td>Enable Ramp-Up</td>
<td>在每个间隔之后增加进程数量</td>
</tr>
<tr>
<td>  Initial Count</td>
<td>启动测试时工作进程的数量</td>
</tr>
<tr>
<td>  Incremental Step</td>
<td>每个间隔增加进程的数量</td>
</tr>
<tr>
<td>  Initial Sleep Time</td>
<td>最初休眠的时间</td>
</tr>
<tr>
<td>  Interval</td>
<td>每个间隔的区间</td>
</tr>
<tr>
<td>Parameter</td>
<td>能够向测试传递单个名为param的参数，填写此参数的值</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">脚本管理</span></div>
<p>在Home页面点击Script链接，可以进入已经定义的脚本的列表。
<p>nGrinder内置了一个SVN服务器，用于管理用户编写的脚本文件以及资源，每个用户由独立的SVN存储库。你可以通过SVN客户端签出并管理脚本和资源，也可以通过nGrinder的UI来添加、删除、上传脚本或资源文件或目录。</p>
<div class="blog_h1"><span class="graybg">Groovy测试脚本</span></div>
<p>从3.2版本开始，nGrinder在Jython的基础上支持Groovy作为脚本编写语言。 </p>
<p>Groovy脚本是基于Junit的，因此对熟悉Java单元测试的人很友好。你可以通过SVN版本库管理压测脚本，甚至使用Maven来管理压测脚本的项目、在IDE中执行运行、调试压测脚本。</p>
<div class="blog_h2"><span class="graybg">脚本结构</span></div>
<div class="blog_h3"><span class="graybg">脚本模板</span></div>
<p>下面是一个基本的模板：</p>
<pre class="crayon-plain-tag">import static net.grinder.script.Grinder.grinder
import static org.junit.Assert.*
import static org.hamcrest.Matchers.*
import net.grinder.plugin.http.HTTPRequest
import net.grinder.script.GTest
import net.grinder.script.Grinder
import net.grinder.scriptengine.groovy.junit.GrinderRunner
import net.grinder.scriptengine.groovy.junit.annotation.BeforeThread
import net.grinder.scriptengine.groovy.junit.annotation.BeforeProcess

import org.junit.BeforeClass
import org.junit.Test
import org.junit.runner.RunWith

import HTTPClient.HTTPResponse

// 测试用例必须附加如下注解
@RunWith(GrinderRunner)
class Test {

    public static GTest test;
    public static HTTPRequest request;

    // 每个进程被创建之前，执行的方法
    // 类似的还有@AfterProcess
    @BeforeProcess
    public static void beforeClass() {
        // 创建一个GTest实例
        test = new GTest(1, "${name}");
        // 录制对此HTTPRequest的任何调用，每次对它的调用都会增加TPS
        request = new HTTPRequest();
        test.record(request);
        grinder.logger.info("before process.");

        // 如果需要录制多个HTTPRequest，需要使用多个名字不同的GTest实例
    }

    // 每个线程被创建之前，执行的方法
    // 类似的还有@AfterThread 
    @BeforeThread
    public void beforeThread() {
        grinder.statistics.delayReports=true;
        grinder.logger.info("before thread.");
    }

    // 在停止测试之前，带有这个注解的方法会被持续不断的运行
    @Test
    public void test(){
        HTTPResponse result = request.GET("${url}");
        if (result.statusCode == 301 || result.statusCode == 302) {
            grinder.logger.warn("Warning. The response may not be correct. The response code was {}.", result.statusCode);
        } else {
            // 可以进行断言，断言失败则绑定到当前线程的最后一次测试被标记为失败
            assertThat(result.statusCode, is(200));
        }
    }
}</pre>
<div class="blog_h3"><span class="graybg">特殊注解</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>@BeforeProcess</td>
<td>
<p>Groovy脚本引擎创建新进程后执行的逻辑，例如：</p>
<ol>
<li>加载所有线程共享的静态资源</li>
<li>定义GTest并instrument测试目标</li>
</ol>
</td>
</tr>
<tr>
<td>@AfterProcess</td>
<td>
<p>Groovy脚本引擎在进程被终结后执行的逻辑，例如：</p>
<ol>
<li>关闭资源文件</li>
</ol>
</td>
</tr>
<tr>
<td>@BeforeThread</td>
<td>
<p>在进程的每个测试线程执行之前执行的逻辑，例如：</p>
<ol>
<li>登陆到目标系统</li>
<li>准备线程绑定变量</li>
</ol>
</td>
</tr>
<tr>
<td>@AfterThread</td>
<td>
<p>在进程的每个测试线程执行之后执行的逻辑，例如：</p>
<ol>
<li>登出系统</li>
</ol>
</td>
</tr>
<tr>
<td>@Before</td>
<td rowspan="2">每次测试之前、之后执行的逻辑</td>
</tr>
<tr>
<td>@After</td>
</tr>
<tr>
<td>@Test</td>
<td>
<p>需要执行的测试本身，会反复调用</p>
<p>你可以为多个方法添加此注解，但是需要注意，和JUnit不同，nGrinder会让所有这些<span style="background-color: #c0c0c0;">方法共享实例变量</span></p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">代码片断</span></div>
<div class="blog_h3"><span class="graybg">向脚本传递参数</span></div>
<p>在Test Configuration中指定的单个参数，可以这样获取：</p>
<pre class="crayon-plain-tag">// 名字固定为param
System.getProperty("param", "defaultValue")</pre>
<p>实际上，上述param参数是在命令行通过<pre class="crayon-plain-tag">-Dparam=value</pre>传递给测试进程的。 </p>
<p>引入Maven依赖ngrinder-groovy，可以调用net.grinder.util.GrinderUtils定义的静态方法，获取参数并进行类型转换。</p>
<div class="blog_h3"><span class="graybg">上下文信息</span></div>
<pre class="crayon-plain-tag">processnumber=grinder.processNumber                                 //获取当前的进程号，应该会相应返回0,1,2,3,4
totalprocess=grinder.getProperties().getInt("grinder.processes", 1) //获取测试的进程总数，返回5
threadnumber=grinder.threadNumber                                   //获取当前的线程号，应该会相应返回0,1
totalthread=grinder.getProperties().getInt("grinder.threads", 1)    //获取测试的线程总数,应该返回2
run=grinder.runNumber;                                              //获取当前的测试号，应该相应返回1,2,3
totalrun=grinder.getProperties().getInt("grinder.runs", 1);         //返回测试的总数，如3</pre>
<div class="blog_h3"><span class="graybg">HTTP插件参数</span></div>
<pre class="crayon-plain-tag">HTTPPluginControl.getConnectionDefaults().timeout = 6000 </pre>
<div class="blog_h3"><span class="graybg">POST请求</span></div>
<pre class="crayon-plain-tag">request = new HTTPRequest();
request.POST("http://www.google.com", [new NVPair("key1","value1"), new NVPair("key2":"value2")] as NVPair[])</pre>
<div class="blog_h3"><span class="graybg">响应断言</span></div>
<pre class="crayon-plain-tag">def result = request.GET("http://www.google.co.kr")
assertThat(result.statusCode, is(200)) // 状态码
assertThat(result.text, containsString("google")) // 响应体</pre>
<div class="blog_h3"><span class="graybg">录制测试方法</span></div>
<p>而不是录制HTTPRequest调用：</p>
<pre class="crayon-plain-tag">@RunWith(GrinderRunner)
class TestRunner {
    static GTest test1
    static GTest test2
    @BeforeProcess
    public static void beforeProcess() {
        test1 = new GTest(1,"test1_statistics")
        test2 = new GTest(2,"test2_statistics")
    }
 
    @BeforeThread
    public void beforeThread() {
        test1.record(this, "doTransaction1"); // 录制当前对象的doTransaction1调用到测试test1
        test2.record(this, "doTransaction2"); // 录制当前对象的doTransaction2调用到测试test2
        grinder.statistics.delayReports=true;
    }
 
    @Test
    public void doTransaction1(){
    }
 
    @Test
    public void doTransaction2(){
    }
}</pre>
<p>被录制方法的执行耗时会影响测试报告。</p>
<div class="blog_h3"><span class="graybg">设置权重</span></div>
<p>你可以设置每个测试方法，占用总计的测试次数的比例：</p>
<pre class="crayon-plain-tag">@RunRate(50)  // 此方法占所有测试次数（runs）的50%
@Test
public void test1() {
}

@RunRate(20)
@Test
public void test2() {
}</pre>
<p>如果总和不到100，则会有一定的测试次数（runs）什么都不干。</p>
<div class="blog_h3"><span class="graybg">调整日志级别</span></div>
<pre class="crayon-plain-tag">LoggerFactory.getLogger("worker").setLevel(Level.ERROR)</pre>
<div class="blog_h3"><span class="graybg">解析JSON</span></div>
<pre class="crayon-plain-tag">import groovy.json.JsonSlurper

def message = """
{
  "glossary": {
     "title": "example glossary",
      "GlossDiv": {
           "title": "S"
         }
      }
  }
}"""

def jsonMsg = new JsonSlurper().parseText(message)
grinder.logger.info(jsonMsg.glossary.title)</pre>
<div class="blog_h3"><span class="graybg">解析XML </span></div>
<pre class="crayon-plain-tag">def hello = new XmlParser().parseText(message);</pre>
<div class="blog_h3"><span class="graybg">流式处理大响应</span></div>
<pre class="crayon-plain-tag">byte[] buffer = new byte[1000];

HTTPResponse result = request.GET("http://www.google.com");

// 只读取1000字节就结束
def stream = result.getInputStream();
stream.read(buffer);
stream.close();</pre>
<div class="blog_h3"><span class="graybg">按测试线程决定行为</span></div>
<pre class="crayon-plain-tag">def threadId;
@BeforeThread
public void beforeThread() {
  threadId = GrinderUtils.threadUniqId; 
}

@Test
public doTest() {
   // 根据threadId进行条件分支
}</pre>
<div class="blog_h2"><span class="graybg">使用外部库</span></div>
<p>有两种方式：</p>
<ol>
<li>在测试脚本所在目录下，建立lib目录，其中存放jar</li>
<li>使用Maven依赖管理机制 </li>
</ol>
<div class="blog_h2"><span class="graybg">使用资源文件</span></div>
<ol>
<li>在测试脚本所在目录下，建立resources目录，其中存放资源文件，然后参考下面的脚本加载资源：<br />
<pre class="crayon-plain-tag">@BeforeProcess
public static void beforeProcess() {
    text = new File("./resources/resource1.txt").text;
} </pre>
</li>
<li>使用Maven，从类路径下加载：<br />
<pre class="crayon-plain-tag">ReflectionUtils.getCallingClass(0).getResourceAsStream("/resource1.txt");</pre>
</li>
</ol>
<div class="blog_h1"><span class="graybg">REST API</span></div>
<p>nGrinder 3.3提供了超过40个REST API，使用这些REST API你可以构建自己的nGrinder前端，或者让其它工具和nGrinder进行集成。REST API的功能包括：</p>
<ol>
<li>用户管理</li>
<li>代理管理</li>
<li>脚本管理</li>
<li>性能测试管理</li>
<li>系统管理</li>
</ol>
<p>REST API使用HTTP基本身份验证。</p>
<div class="blog_h2"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag"># 克隆现有性能测试，然后启动，226为测试ID
curl -u admin:admin http://ngrinder-host/perftest/api/226/clone_and_start
# 返回JSON形式、新创建的测试
# 你可以传递agentCount、scriptRevision参数来指定代理数量、脚本的修订版

# 查看现有的性能测试
curl -u admin:admin http://ngrinder-host/perftest/api/226

# 查看性能测试列表
curl -u admin:admin http://ngrinder-host/perftest/api</pre>
<div class="blog_h2"><span class="graybg">接口形式</span></div>
<p>对于nGrinder UI的URL（其URL Path称为topic），后缀以/api通常就是其对应的REST API端点：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 400px; text-align: center;">URL</td>
<td style="width: 75px; text-align: center;">动词</td>
<td style="text-align: center;">描述</td>
</tr>
</thead>
<tbody>
<tr>
<td>http://ngrinder-host/{topic}/api/</td>
<td>GET</td>
<td>获取元素列表</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api?ids={id,id}</td>
<td>GET</td>
<td>获取多个元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{kind}?ids={id,id}</td>
<td>GET</td>
<td>获得多个元素的某个特定信息</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{id}</td>
<td>GET</td>
<td>获得一个元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{id}/{kind}</td>
<td>GET</td>
<td>获取一个元素的某个特定信息</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/</td>
<td>POST</td>
<td>新建元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{id}</td>
<td>PUT</td>
<td>修改一个元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api?ids={id,id}</td>
<td>PUT</td>
<td>修改多个元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api?action={action}&amp;ids={id,id}</td>
<td>PUT</td>
<td>指定若干元素上执行动作</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{id}?action={action}</td>
<td>PUT</td>
<td>在指定的元素上执行动作</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api/{id}</td>
<td>DELETE</td>
<td>删除一个元素</td>
</tr>
<tr>
<td>http://ngrinder-host/{topic}/api?ids={id,id}</td>
<td>DELETE</td>
<td>删除多个元素</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">接口列表</span></div>
<div class="blog_h3"><span class="graybg">PerfTest</span></div>
<p>参考：<a href="https://github.com/naver/ngrinder/wiki/REST-API-PerfTest">https://github.com/naver/ngrinder/wiki/REST-API-PerfTest</a></p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/load-testing-based-on-ngrinder">基于nGrinder进行负载测试</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/load-testing-based-on-ngrinder/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gradle学习笔记</title>
		<link>https://blog.gmem.cc/gradle-study-note</link>
		<comments>https://blog.gmem.cc/gradle-study-note#comments</comments>
		<pubDate>Fri, 16 Jan 2015 08:45:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Gradle]]></category>
		<category><![CDATA[Groovy]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10070</guid>
		<description><![CDATA[<p>基础知识 Gradle简介 Gradle是近年来流行起来的自动化构建工具，它具有以下特性： 灵活、通用的构建功能，类似Ant 基于约定的构建框架，类似Maven 强大的多工程构建支持 支持基于Apache Ivy的依赖管理，现已使用Gradle自己的依赖处理引擎（dependency resolution engine） 完整的支持既有的Maven或者Ivy构件仓库，且不需要pom.xml或者ivy.xml就可以进行依赖管理 整合型构建工具： 支持Ant任务，Gradle可以直接引入Ant工程，并在运行时将Ant targets转换为Gradle tasks 可以自动的把Maven的pom.xml转换为Gradle脚本 基于Groovy的构建脚本。构建脚本可以很容易的维护和重用 用于描述构建的丰富的领域模型 Gradle Wrapper：运行在没有安装Gradle的机器上运行Gradle构建 Gradle的核心是一个基于Groovy的、扩展的DSL（Domain Specific <a class="read-more" href="https://blog.gmem.cc/gradle-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/gradle-study-note">Gradle学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">Gradle简介</span></div>
<p>Gradle是近年来流行起来的自动化构建工具，它具有以下特性：</p>
<ol>
<li>灵活、通用的构建功能，类似Ant</li>
<li>基于约定的构建框架，类似Maven</li>
<li>强大的多工程构建支持</li>
<li>支持基于Apache Ivy的依赖管理，现已使用Gradle自己的依赖处理引擎（dependency resolution engine）</li>
<li>完整的支持既有的Maven或者Ivy构件仓库，且不需要pom.xml或者ivy.xml就可以进行依赖管理</li>
<li>整合型构建工具：
<ol>
<li>支持Ant任务，Gradle可以直接引入Ant工程，并在运行时将Ant targets转换为Gradle tasks</li>
<li>可以自动的把Maven的pom.xml转换为Gradle脚本</li>
</ol>
</li>
<li>基于Groovy的构建脚本。构建脚本可以很容易的维护和重用</li>
<li>用于描述构建的丰富的领域模型</li>
<li>Gradle Wrapper：运行在没有安装Gradle的机器上运行Gradle构建</li>
</ol>
<p>Gradle的核心是一个基于Groovy的、扩展的DSL（Domain Specific Language），而不是向Maven、Ant那样，通过XML去描述构建步骤，具有非常好的灵活性。</p>
<div class="blog_h2"><span class="graybg">Gradle重要概念</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;"> 名词</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>工程（Project）</td>
<td>每个构建都是由<span style="background-color: #c0c0c0;">一个或多个</span>projects 构成，project是一个抽象概念，既可以和通常的IDE中的工程对应，也可以仅仅代表某项需要完成的工作</td>
</tr>
<tr>
<td>任务（Task）</td>
<td>
<p>每个project可以包含一个或者多个task，task代表细化的构建步骤</p>
<p>各Task之间可以形成依赖关系，被依赖的Task总是先执行。Gradle能够依据Task形成的无回路有向图（Directed Acyclic Graph），依次执行Task，保证每个任务至多被执行一次</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">新特性</span></div>
<div class="blog_h3"><span class="graybg">3.x</span></div>
<ol>
<li>Gradle Daemon性能增强，让构建速度最快有75%的提升，默认启用</li>
<li>更好的IDE支持</li>
<li>改进并行任务执行</li>
<li>初步的Java 9支持</li>
<li>新的plugins DSL，用于启用插件，代替原先的<pre class="crayon-plain-tag">apply plugin</pre>：<br />
<pre class="crayon-plain-tag">plugins {
    id 'my.special.plugin' version '1.0' apply false
} </pre>
</li>
<li>改进增量构建</li>
<li>更快的依赖解析</li>
<li>改进eclipse-wtp插件</li>
<li>插件库升级</li>
</ol>
<div class="blog_h3"><span class="graybg">4.x</span></div>
<ol>
<li>支持依赖的并行下载</li>
<li>改进构建缓存</li>
<li>添加Google的Maven仓库的快捷方式：<br />
<pre class="crayon-plain-tag">repositories {
    google()
}</pre>
</li>
<li>支持使用Gradle属性来配置日志级别，属性名org.gradle.logging.level，支持的值：quiet, warn, lifecycle, info, debug，默认lifecycle</li>
<li>
<p>对删除文件的Task进行更好的建模。一个Task可以使用 @Destroys 注解，明确声明自己会删除文件：</p>
<pre class="crayon-plain-tag">class RemoveTempDirs extends DefaultTask {
    @Destroys
    FileCollection tempDirs

    @TaskAction
    void removeDirs() {
        project.delete(tempDirs)
    }
} </pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">5.x</span></div>
<ol>
<li>更快的构建速度</li>
<li>更细粒度的传递性依赖管理</li>
<li>内存使用更高效</li>
<li>新的Gradle调用选项</li>
<li>新的Task/Plugin API</li>
</ol>
<div class="blog_h1"><span class="graybg">gradlew</span></div>
<p>即Gradle Wrapper，是一段脚本，此脚本<span style="background-color: #c0c0c0;">调用你声明的版本的Gradle</span>，如果目标版本不存在，则从网络上下载。
<p>使用gradlew可以保证构建过程的一致性，但是网络差的话你就得静静的等待下载完成。</p>
<div class="blog_h2"><span class="graybg">生成gradlew</span></div>
<p>要生成gradlew相关的文件，需要本机已经安装任意版本的Gradle。执行命令<pre class="crayon-plain-tag">gradle wrapper</pre>即可。</p>
<p>生成的gradlew文件包括：</p>
<ol>
<li>gradle/wrapper/gradle-wrapper.properties 配置文件</li>
<li>gradle/wrapper/gradle-wrapper.jar 包含用于下载Gradle发行版的代码</li>
<li>gradlew, gradlew.bat Shell脚本和Windows批处理脚本</li>
</ol>
<pre class="crayon-plain-tag">#Tue Jun 25 10:21:26 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# 从何处下载
# 后缀 -all 表示包含源码，-bin 表示不包含源码
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip</pre>
<p>如果下载缓慢，可以手工拷贝，存放到<pre class="crayon-plain-tag">~/.gradle/wrapper/dists</pre>对应的子目录下。</p>
<div class="blog_h2"><span class="graybg">升级gradlew</span></div>
<p>执行下面的命令升级到指定版本：</p>
<pre class="crayon-plain-tag">./gradlew wrapper --gradle-version 5.4.1</pre>
<div class="blog_h2"><span class="graybg">使用gradlew</span></div>
<p>gradlew的命令行调用格式和gradle完全一致。 </p>
<div class="blog_h1"><span class="graybg">安装与配置</span></div>
<ol>
<li>安装Gradle需要JDK/JRE，版本在6.0+。Gradle内置了Groovy库，因此后者不需要</li>
<li>到<a href="http://gradle.org/gradle-download/">官网</a>下载Gradle，并解压到某个目录，该目录设置为环境变量<pre class="crayon-plain-tag">GRADLE_HOME</pre> </li>
<li>将<pre class="crayon-plain-tag">$GRADLE_HOME/bin</pre> 添加到环境变量PATH中</li>
<li>打开终端，运行<pre class="crayon-plain-tag">gradle -v</pre> 验证安装是否成功</li>
</ol>
<div class="blog_h1"><span class="graybg">与Eclipse的集成</span></div>
<div class="blog_h2"><span class="graybg">插件安装</span></div>
<p>可以从Nodeclipse提供的Update Site：http://www.nodeclipse.org/updates/gradle-ide-pack/ 安装Gradle插件。</p>
<p>此站点中杂项内容非常多，可以根据需要，选择性的安装：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">插件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Gradle IDE</td>
<td>Pivotal提供 的Gradle与Eclipse的集成</td>
</tr>
<tr>
<td>Minimalist Gradle Editor</td>
<td>.gradle文件的编辑器，提供语法高亮等简单功能</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Eclipse配置</span></div>
<div class="blog_h3"><span class="graybg">Pivotal Gradle IDE</span></div>
<p>打开Window ⇨ Preferences ⇨ Gradle：</p>
<ol>
<li>Gradle Distribution 点选 Folder，填写<pre class="crayon-plain-tag">%GRADLE_HOME%</pre> 对应的目录</li>
<li>Gradle User Home 点选 Directory，填写<pre class="crayon-plain-tag">%USERPROFILE%\.gradle</pre> </li>
</ol>
<p>打开Window ⇨ Preferences ⇨ Gradle ⇨ Arguments：</p>
<ol>
<li>Java Home 点选Workspace JRE，选择一个已安装的JRE</li>
<li>默认JVM参数可以根据需要调整</li>
</ol>
<div class="blog_h3"><span class="graybg">Gradle for Eclipse</span></div>
<p>打开Window ⇨ Preferences ⇨ Gradle EnIDE：</p>
<ol>
<li>Gradle Home to use：填写<pre class="crayon-plain-tag">%GRADLE_HOME%</pre> 对应的目录</li>
</ol>
<div class="blog_h3"><span class="graybg">Start Explorer</span></div>
<p>打开Window ⇨ Preferences ⇨ Desktop Environment：</p>
<ol>
<li>点选 Custom Desktop Environment</li>
<li>Start Shell设置为Console2，例如<pre class="crayon-plain-tag">D:\Programs\Console2\Console.exe -d ${resource_path}</pre> </li>
</ol>
<div class="blog_h2"><span class="graybg">创建Gradle工程</span></div>
<p>打开File ⇨ New ⇨ Other，选择Gradle/Gradle Project，即可创建Gradle工程，Sample project选择Java Quickstart，点击完成。</p>
<p>初次创建Gradle IDE会去下载一些东西，完毕后，形成类似下面的工程结构：</p>
<p><img class="aligncenter size-full wp-image-10089" src="https://blog.gmem.cc/wp-content/uploads/2015/01/gradle-project.jpg" alt="gradle-project" width="335" height="213" /></p>
<p>可以看到，默认配置下Gradle沿用了Maven的工程目录习惯。除了把pom.xml换成build.gradle，Maven工程和Gradle工程的结构完全相同。</p>
<p>Eclipse工程结构和元数据完全根据build.gradle导出，这个和Maven也是类似的。工程根目录右键 ⇨ Gradle ⇨ Refresh All，可以根据当前的build.gradle更新Eclipse工程结构。</p>
<p>要构建Gradle工程，可以工程根目录右键 ⇨ Run As ⇨ Gradle Build。</p>
<div class="blog_h1"><span class="graybg">Gradle命令行</span></div>
<p>命令格式：</p>
<pre class="crayon-plain-tag">//可以同时指定多个Task
gradle [option...] [task...]</pre>
<p>用法示例：</p>
<pre class="crayon-plain-tag"># 依次调用 compile 和 test 任务
# 它们所依赖的任务会根据依赖图的顺序被依次调用，每个任务只会调用一次
# 只需要声明任务之间的依赖，不需要人工控制任务执行顺序，是相对于Maven的优势
gradle compile test

# 使用-x 排除某个任务
gradle dist -x test

# 默认情况下，一旦有一个任务失败，Gradle就会终止执行，下面的选项禁止此行为
gradle run --continue

# 简化任务名：不需要输入完整，只需要足够避免歧义的前缀
gradle ru
# 简化任务名：驼峰式首字母，下面的命令调用gradle compTest
gradle cT

# Gradle默认依据当前目录下的build.gradle，使用-b可以选择构建其它脚本
gradle -q -b subdir/myproject.gradle hello

# 使用-p，可以选择构建的目录
gradle -q -p subdir hello

# 获取子工程列表
gradle -q projects

# 列出工程中所有任务
gradle tasks
gradle -q tasks --all   #同时显示任务依赖关系

# 显示任务的详细信息
gradle help --task taskName

# 显示工程中所有依赖库的列表
gradle dependencies  #输出依据依赖配置（Dependencies Configuration）分组
# 显示api子工程的所有依赖列表
gradle -q dependencies api:dependencie
# 显示api子工程的、testCompile配置的所有依赖列表
gradle -q api:dependencies --configuration testCompile
# 显示某个特定依赖库的情况，显示groovy依赖库的情况
gradle -q api:dependencyInsight --dependency groovy --configuration compile

# 显示工程属性列表
gradle properties
gradle -q api:propertie #子工程属性列表

# 记录构建日志
gradle run --profile   # 日志存放到build/reports/profile 

# 以特定的工程属性来构建
gradle hello -PskipHello

# 设置JVM属性
gradle hello -Djava.io.tempdir=/tmp

# 丢弃已经编译的脚本缓存
gradle  --recompile--scripts 

# 显示图形界面
gradle --gui </pre>
<div class="blog_h1"><span class="graybg"><a id="gradle-project-properties"></a>Gradle工程属性</span></div>
<p>除了通过gradle命令行参数<pre class="crayon-plain-tag">-P</pre> 直接为Gradle工程添加属性之外，还可以使用<pre class="crayon-plain-tag">gradle.properties</pre> 文件。如果有多个子工程，则每个子工程的目录都可以提供此文件：</p>
<pre class="crayon-plain-tag">gradlePropertiesProp=gradlePropertiesValue
# 对于根工程，可以设置JVM系统属性，这些属性以systemProp.开头
systemProp.system=systemValue</pre>
<div class="blog_h1"><span class="graybg">编写构建脚本</span></div>
<p><pre class="crayon-plain-tag">build.gradle</pre> 本质上是Groovy脚本，任何合法的Groovy语句都可以添加到其中。</p>
<div class="blog_h3"><span class="graybg">Hello World</span></div>
<p>在当前目录下创建一个UTF-8编码的build.gradle文件，或者通过Eclipse创建一个Gradle工程。打开build.gradle文件，输入下面的内容：</p>
<pre class="crayon-plain-tag">//可以声明工程属性
description = 'Hello project'

//声明一个名为hello的task
//新任务默认是org.gradle.api.DefaultTask的实例
task hello {
    //闭包指定了hello的action
    doLast {
        println 'Hello world!'  //任何Groovy脚本都支持
    }
}

//上述任务也可以用下面的快捷声明方式
task hello &lt;&lt; {
    println 'Hello world!'
}</pre>
<p>然后，打开终端，pushd到当前目录，运行命令：<pre class="crayon-plain-tag">gradle -q hello</pre>  ，可以看到输出：<pre class="crayon-plain-tag">Hello world!</pre> 。该命令就是调用hello这个Task，而这个Task仅仅是打印一条语句到控制台。</p>
<div class="blog_h3"><span class="graybg">默认任务</span></div>
<p>使用下面的脚本可以指定默认任务，这样，如果<pre class="crayon-plain-tag">gradle</pre> 命令不指定task参数，则自动运行这些任务：</p>
<pre class="crayon-plain-tag">defaultTasks 'hello', 'run'</pre>
<div class="blog_h3"><span class="graybg">Project API</span></div>
<p>对于构建中的每个工程，Gradle都创建了一个<pre class="crayon-plain-tag">org.gradle.api.Project</pre> 类型的对象，工程脚本<pre class="crayon-plain-tag">build.gradle</pre> 的顶级作用域就是该对象（this指向当前工程）：</p>
<pre class="crayon-plain-tag">apply plugin: 'java'

println this.name     //当前对象——当前工程——的name属性
println name          //自动委托给当前对象
println project.name  //this.project == this，预定义属性
//上面三个语句打印一样的结果</pre>
<p>除了上面的name以外，你还可以访问很多Project的属性，例如： </p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">属性</td>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>project</td>
<td>Project</td>
<td>当前Project 实例对象</td>
</tr>
<tr>
<td>path</td>
<td>String</td>
<td>工程的绝对路径</td>
</tr>
<tr>
<td>description</td>
<td>String</td>
<td>工程的描述</td>
</tr>
<tr>
<td>projectDir</td>
<td>File</td>
<td>包含了构建脚本的目录</td>
</tr>
<tr>
<td>build</td>
<td>File</td>
<td>projectDir/build</td>
</tr>
<tr>
<td>group</td>
<td>Object</td>
<td>组标识</td>
</tr>
<tr>
<td>name</td>
<td>String</td>
<td>工程名称</td>
</tr>
<tr>
<td>version</td>
<td>Object</td>
<td>版本标识</td>
</tr>
<tr>
<td>ant</td>
<td>org.gradle.api.AntBuilder</td>
<td>Ant实例对象</td>
</tr>
</tbody>
</table>
<p>Project包含很多常用的方法，可以在构建脚本中使用：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>buildscript</td>
<td>配置驱动<span style="background-color: #c0c0c0;">构建过程本身的依赖</span>，常常在此脚本块中声明依赖和仓库</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Script API</span></div>
<p>每个构建脚本都自动实现<pre class="crayon-plain-tag">org.gradle.api.Script</pre> 接口，因此<a href="https://docs.gradle.org/current/dsl/org.gradle.api.Script.html">该接口</a>提供的属性、方法你可以自由的使用。</p>
<div class="blog_h3"><span class="graybg">变量声明</span></div>
<p>在构建脚本中，你可以声明两种变量：</p>
<ol>
<li>局部变量 ( local ) ：只在局部作用域可见，使用关键字<pre class="crayon-plain-tag">def</pre> 声明，例如：<br />
<pre class="crayon-plain-tag">def dest = "dest"
task copy(type: Copy) {
    form "source"
    into dest
}</pre>
</li>
<li>扩展属性 ( extra )：Gradle领域模型中所有增强对象（包括但不限于：projects, tasks, source sets）都可以添加扩展属性，扩展属性在任何地方可见。使用ext脚本块可以同时声明多个属性。下面是一个示例：<br />
<pre class="crayon-plain-tag">ext {
    //为Project添加属性
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}
//也可以这样写
ext.springVersion = "3.1.0.RELEASE"
springVersion = "3.1.2.RELEASE"  //声明以后再修改，不需要ext关键字

//这样写会报错，自定义的外部属性，必须首先使用ext声明
hibernateVersion = "3.6.10.Final"

//设置所有源码集的purpose属性
sourceSets.all { ext.purpose = null }

sourceSets {
    //分别设置每个源码集的属性
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

task printProperties &lt;&lt; {
    println springVersion
    println emailNotification
    sourceSets.matching { it.purpose == "production" }.each { println it.name }
}</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">Groovy语言特性与DSL</span></div>
<p>Gradle大量使用了Groovy提供的灵活语法特性和DSL支持。整个构建脚本都是合法的Groovy代码，虽然有时看起来更像是XML那样的配置文件：</p>
<pre class="crayon-plain-tag">//下面的脚本，更像是配置文件
dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile project(':shared')
}</pre>
<p>实际上dependencies是Project对象的一个方法，它的签名如下：</p>
<pre class="crayon-plain-tag">public interface Project{
    void dependencies(Closure configureClosure);
}</pre>
<p>可以看到，该方法的入参是一个闭包，我们可以把上面的脚本改写的更像C-Style的代码：</p>
<pre class="crayon-plain-tag">dependencies({
    //这里是闭包体，其包含了两个方法调用：
    compile( [ group: 'commons-collections', name: 'commons-collections', version: '3.2' ] )
    testCompile( project(':shared') )
    //那么，闭包里面调用的方法，是哪个对象的呢？是this也就是闭包本身吗？不是……
    //根据Groovy的规则，闭包里面的变量，其Scope由闭包的delegate属性指定，因此上面的调用实际上是：
    delegate.testCompile( project(':shared') )
    //delegate已经由Gradle自动设置为dependencies
})</pre>
<p>我们还可以把上述脚本改写为等价的Java语句： </p>
<pre class="crayon-plain-tag">Closure&lt;Object&gt; c = new Closure&lt;Object&gt;(){

    public Object call( Object... args ){
    
        assert delegate == project.dependencies
        
        Map&lt;String,String&gt; map = new HashMap&lt;String,String&gt;();
        map.put( 'group',   'commons-collections' );
        map.put( 'name',    'commons-collections' );
        map.put( 'version', '3.2' );
        delegate.compile( dep );
        
        delegate.testCompile( project(':shared') )
        
    }
}
c.delegate = project.dependencies
project.dependencies( c );</pre>
<div class="blog_h3"><span class="graybg">脚本包含</span></div>
<p>可以把其它脚本的内容包含到当前构建脚本中：</p>
<pre class="crayon-plain-tag">println "configuring $project"
task hello &lt;&lt; {
    println' 'hello form other srcipt'
}</pre><br />
<pre class="crayon-plain-tag">apply from: 'other.gradle'  //包含其它脚本</pre>
<p> 其它脚本中定义的任务，可以直接使用：<pre class="crayon-plain-tag">gradle -q hello</pre> </p>
<div class="blog_h1"><span class="graybg">依赖管理</span></div>
<p>依赖管理是任何一种构建工具的关键特性，Gradle提供了易于理解的、和Maven/Ivy兼容的依赖管理机制。Gradle的依赖管理具有以下特点：</p>
<ol>
<li>传递性依赖管理</li>
<li>支持非受管的（non-managed）依赖：依赖可以仅仅是位于版本控制工具或者共享磁盘上的文件</li>
<li>支持定制化的依赖定义：可以在构建脚本中描述依赖的层次</li>
<li>完全可定制的依赖解析机制（Dependency Resolution）</li>
<li>与Maven和Ivy完全兼容</li>
<li>与既有的依赖管理工具，例如Maven和Ivy集成</li>
</ol>
<p>依赖管理分为两部分的内容：</p>
<ol>
<li>依赖项（dependencies）：当前工程在构建或者运行时需要的东西</li>
<li>发布项（publications）：当前工程的构建的产出，这些产出可能需要上传到仓库</li>
</ol>
<p>对于依赖项，Gradle采用了类似Maven的方式进行声明，而不像Ant那样指定绝对路径。依赖项的所在位置（远程仓库或者本地目录）另外声明。对于发布项，行为依赖于配置，可能是发布到本地目录，或者远程仓库。</p>
<div class="blog_h3"><span class="graybg">依赖配置</span></div>
<p>Gradle把依赖按照<span style="background-color: #c0c0c0;">依赖配置（dependency configuration）分组</span>。每个分组具有一个名称和若干其它属性，分组可以继承其它分组。某些插件会自动引入一些依赖配置，例如Java插件添加了：</p>
<table class="fixed-word-wrap full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">依赖配置</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>compile</td>
<td>用来编译项目源代码的依赖</td>
</tr>
<tr>
<td>runtime</td>
<td>在运行时被生成的类使用的依赖，默认也作为编译时依赖</td>
</tr>
<tr>
<td>testCompile</td>
<td>编译测试代码的依赖</td>
</tr>
<tr>
<td>testRuntime</td>
<td>运行测试所需要的依赖</td>
</tr>
</tbody>
</table>
<p>除了插件自动引入的依赖配置以外，你还可以在构建脚本中自行添加。自定义依赖配置在很多情况下有用，例如添加一个既不在编译，也不在测试时需要的“运行时”依赖（典型的例子是JDBC驱动程序）到归档文件中。</p>
<p>下面示例了如何声明依赖配置：</p>
<pre class="crayon-plain-tag">configurations {
    compile {
        description = 'compile classpath'
        transitive = true //是否启用传递性依赖
    }
    runtime {
        extendsFrom compile
    }
}</pre>
<div class="blog_h3"><span class="graybg">依赖项声明</span></div>
<p>每一个依赖项声明的格式为：</p>
<pre class="crayon-plain-tag">dependencies{
    // 依赖配置名称 依赖项规格说明
}</pre>
<p>依赖项规格是什么形式取决于依赖的类型：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 18%; text-align: center;">依赖类型</td>
<td style="text-align: center;">依赖项规格说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>外部模块依赖</p>
</td>
<td>
<p>即对当前构建以外的模块（Module）的依赖，模块可以存放在Maven、Ivy远程仓库或者本地仓库中，甚至位于本地目录中。一个外部依赖使用group、name、version三个属性限定。下面是一些例子：</p>
<pre class="crayon-plain-tag">dependencies {
    //可以使用group name version字段来指定
    runtime group: 'hibernate', name: 'hibernate-core', version: '3.6.7'
    //也可以使用分号分隔的group:name:version指定
    runtime 'hibernate:hibernate-core:3.6.7','springframework:spring-aop:2.5'
    //可以控制传递性依赖
    runtime('hibernate:hibernate:3.0.5') {
        transitive = true
    }
    //仅构件标记（Artifact only notation），不去下载模块描述符（例如pom.xml），仅仅下载构件本身
    runtime "org.groovy:groovy:2.2.0@jar"
    //使用分类器字段
    otherConf group: 'test', name: 'service', version: '1.0', classifier: 'jdk14'
}</pre>
</td>
</tr>
<tr>
<td>
<p>工程依赖
</td>
<td>在多工程构建时，声明各子工程之间的依赖，例如：<br />
<pre class="crayon-plain-tag">project(':shared')</pre>
</td>
</tr>
<tr>
<td>
<p>文件依赖</p>
</td>
<td>即对位于本地文件系统上一系列文件的依赖，例如：<br />
<pre class="crayon-plain-tag">dependencies {
    runtime files('libs/a.jar', 'libs/b.jar')
    runtime fileTree(dir: 'libs', include: '*.jar')
}</pre>
</td>
</tr>
<tr>
<td>
<p>客户端模块依赖</p>
</td>
<td>类似于外部模块依赖，目标构件位于某个仓库中，但是模块的元数据文件则由当前构建指定。如果需要覆盖外部模块的元数据，可以使用这种依赖。示例：<br />
<pre class="crayon-plain-tag">runtime module(group: 'org.apache.ant', name: 'ant', version: '1.9.4') {
    dependencies "org.apache.ant:ant-launcher:1.9.4@jar",
                     "org.apache.ant:ant-junit:1.9.4"
}</pre>
</td>
</tr>
<tr>
<td>
<p>Gradle API依赖</p>
</td>
<td>针对当前Gradle版本的API的依赖，开发Gradle插件和Task类型时使用：<br />
<pre class="crayon-plain-tag">dependencies {
    compile gradleApi()
}</pre>
</td>
</tr>
<tr>
<td>
<p>本地Groovy依赖</p>
</td>
<td>针对当前Gradle使用的Groovy版本的依赖，开发Gradle插件和Task类型时使用：<br />
<pre class="crayon-plain-tag">dependencies {
    compile localGroovy()
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">排除传递性依赖</span></div>
<pre class="crayon-plain-tag">configurations {
    //为指定的依赖配置指定传递性依赖的排除规则
    compile.exclude module: 'commons'
    //为所有的依赖配置指定传递性依赖的排除规则
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

dependencies {
    compile("org.gradle.test.excludes:api:1.0") {
        exclude module: 'shared' //为单个依赖项指定传递性依赖的排除规则
    }
}</pre>
<div class="blog_h3"><span class="graybg">仓库</span></div>
<p>Gradle必须知道从哪里下载外部依赖，这是由仓库配置来指定的：</p>
<pre class="crayon-plain-tag">repositories {
    //Maven本地仓库，寻找本地仓库的逻辑与Maven相同
    mavenLocal()
    //Maven中心仓库
    mavenCentral()
    //JCenter仓库
    jcenter()
    //其它Maven远程仓库
    maven {
        //可以指定身份验证信息
        credentials {
            username 'user'
            password 'password'
        }
        url "http://repo.mycompany.com/maven2"
        //如果上面的URL找不到构件，则在下面找
        artifactUrls "http://repo.mycompany.com/jars"
    }
    //Ivy远程仓库
    ivy {
        url "http://repo.mycompany.com/repo"
    }
    //Ivy本地仓库
    ivy {
        url "../local-repo"
    }
    //扁平布局的文件系统仓库
    flatDir {
        dirs 'lib'
    }
    flatDir {
        dirs 'lib1', 'lib2'
    }
}</pre>
<div class="blog_h3"><span class="graybg">发布构件</span></div>
<p>除了可以发布到本地目录外，Gradle还支持发布构建的产出到Ivy、Maven仓库：</p>
<pre class="crayon-plain-tag">uploadArchives {
    repositories {
        //发布到扁平结构的目录
        flatDir {
            dirs 'repos'
        }
        //发布到Ivy仓库
		ivy {
		    credentials {
		        username "username"
		        password "pw"
		    }
		    url "http://repo.mycompany.com"
		}
		//发布到Maven仓库
		mavenDeployer {
            repository(url: "file://localhost/tmp/myRepo/")
        }
    }
}</pre>
<div class="blog_h1"><span class="graybg">任务详解</span></div>
<div class="blog_h3"><span class="graybg">多种定义Task的方式</span></div>
<p> task关键字本质上是Project对象的一组方法：</p>
<pre class="crayon-plain-tag">Task task(String name) throws InvalidUserDataException
Task task(String name, Closure configureClosure)
Task task(Map&lt;String,?&gt; args, String name) throws InvalidUserDataException
Task task(Map&lt;String,?&gt; args, String name, Closure configureClosure)</pre>
<p> 下面列举了不同风格的Task定义：</p>
<pre class="crayon-plain-tag">// 操作符“&lt;&lt;”被Task重载过，它接受一个闭包，该闭包可以对当前Task进行任意操作
// 直接使用不带引号的hello0，会被解释为this.hello0，Gradle可能进行了Groovy元编程，让不存在的变量指定作为当前project的属性看待

// 不带括号
task hello0 &lt;&lt; {
    description 'Hello task'   // 可以给Task添加描述
    println "hello"
}
// 带括号
task(hello1) &lt;&lt; {
    println "hello1"
}
// 任务名带引号
task('hello2') &lt;&lt;
{
    println "hello2"
}
//指定任务类型
task('copy', type: Copy) {
    //闭包的delegate是task对象，注意Gradle的这种风格，与上面章节dependencies中的闭包delegate指向是一个风格
    from(file('srcDir'))
    into(buildDir)
}
//这样也可以
tasks.create(name: 'hello') &lt;&lt; {
    println "hello"
}</pre>
<div class="blog_h3"><span class="graybg">获得Task的引用</span></div>
<p>首先，任何Task都作为Project的属性看待：</p>
<pre class="crayon-plain-tag">task hello
//作为当前工程的属性来获得Task的引用
println hello.name
println project.hello.name</pre>
<p>也可以通过<pre class="crayon-plain-tag">org.gradle.api.tasks.TaskContainer</pre> 来访问Task：</p>
<pre class="crayon-plain-tag">task hello
//作为TaskContainer的属性访问
println tasks.hello.name
println tasks['hello'].name

project(':projectA') {
    task hello
}

//也可以调用getByPath()方法
//路径根部的:可以省略
println tasks.getByPath('hello').path  //:hello
println tasks.getByPath(':hello').path //:hello
println tasks.getByPath('projectA:hello').path  //:projectA:hello
println tasks.getByPath(':projectA:hello').path //:projectA:hello</pre>
<div class="blog_h3"><span class="graybg">定制Task的类型和属性</span></div>
<p> 下面实例了指定Task的类型为<pre class="crayon-plain-tag">org.gradle.api.tasks.Copy</pre> 并且设置其属性的几种方式：</p>
<pre class="crayon-plain-tag">//Copy任务用来拷贝文件到目标目录中，支持过滤和重命名

//指定任务类型
task myCopy(type: Copy)

//同时设置属性
Copy myCopy = task(myCopy, type: Copy)
myCopy.from 'resources'
myCopy.into 'target'
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')

//闭包方式，再次注意闭包的delegate指向是当前task，delegate在Minimalist Gradle Editor中常常显示为关键字
task copy(type: Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}</pre>
<div class="blog_h3"><span class="graybg">为Task添加依赖 </span></div>
<pre class="crayon-plain-tag">//声明任务intro依赖于hello
task intro(dependsOn: 'hello') &lt;&lt; { //如果被依赖任务在后面定义，则这里引用的任务名必须加引号
    println "I'm Gradle"
}
//被依赖的任务可以在后面定义
task hello &lt;&lt; {
    println 'Hello world!'
}
//访问Task的方法也可以添加依赖
intro.dependsOn hello
//也可以通过闭包添加依赖
intro.dependsOn {
    tasks.findAll { task -&gt; task.name.startsWith('lib') }
}

project('projectA') {
    //要依赖其它项目中的任务，必须指定任务的路径
    task taskX(dependsOn: ':projectB:taskY') &lt;&lt; {
        println 'taskX'
    }
}
project('projectB') {
    task taskY &lt;&lt; {
        println 'taskY'
    }
}</pre>
<div class="blog_h3"><span class="graybg">替换一个已经存在的任务</span></div>
<pre class="crayon-plain-tag">task copy(type: Copy)
task copy(overwrite: true) &lt;&lt; { //必须设置overwrite为true
    println('I am the new one.')
}</pre>
<div class="blog_h3"><span class="graybg">跳过任务执行</span></div>
<p>Gradle提供几种方式，来绕过某个任务的执行： </p>
<pre class="crayon-plain-tag">task hello &lt;&lt; {
    println 'hello world'
//只有当工程没有属性“skipHello”的时候，才会执行hello
hello.onlyIf { !project.hasProperty('skipHello') }
//运行gradle hello -q -PskipHello 不会打印任何信息

//也可以使用StopExecutionException异常跳过任务
hello.doFirst{ //添加如下逻辑到Task最前面
     if (true) { throw new StopExecutionException() }
}

//还可以设置enabled属性，为false则不会执行
hello.enabled = false</pre>
<div class="blog_h3"><span class="graybg">跳过up-to-date的任务</span></div>
<p>Gradle判断任务是否已经是最新状态的依据是：如果任务的输入没有变化，那么当前任务不需要执行（也就是up-to-date）。更进一步说，Gradle的判断规则如下：</p>
<ol>
<li>Task执行前，获得输入文件的散列值</li>
<li>Task执行完毕后，存储输出文件的散列值</li>
<li>Task执行前，如果输入、输出的散列值和上一次执行相比，没有变化，那么任务是up-to-date </li>
</ol>
<p>可以看到，如果任务没有输出，那么它永远不会被看做是up-to-date的。如何声明任务的输入输出呢？</p>
<pre class="crayon-plain-tag">task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    
    //下面两行定义了当前任务的输入和输出
    inputs.file srcFile
    outputs.dir destDir
    
    doLast {
       //Task的逻辑
    }
}</pre>
<p>上述脚本设置了Task的inputs、outputs属性，如果没必要执行，那么Gradle会自动跳过此任务。</p>
<p>对于复杂的场景，可以调用<pre class="crayon-plain-tag">TaskOutputs.upToDateWhen()</pre> 来判断任务是否up-to-date。</p>
<div class="blog_h3"><span class="graybg">动态创建Task</span></div>
<pre class="crayon-plain-tag">//动态创建4个任务task1 ... task3
4.times { counter -&gt;
    task "task$counter" &lt;&lt; {
        println "I'm task number $counter"
    }
}

//基于规则的动态任务
//对于任何pingXxx，都作为任务看待
tasks.addRule("Pattern: ping&lt;ID&gt;") { String taskName -&gt;
    if (taskName.startsWith("ping")) {
        task(taskName) &lt;&lt; {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}
//执行命令 gradle -q pingServer1
//输出 Pinging: Server1

//这些动态任务甚至还可以用做任务依赖
task groupPing {
    dependsOn pingServer1, pingServer2
}</pre>
<div class="blog_h3"><span class="graybg">添加finally任务</span></div>
<p>类似于Java的<pre class="crayon-plain-tag">finally</pre> 代码块，这种任务用于在目标任务结束后执行，不论目标任务是否执行成功：</p>
<pre class="crayon-plain-tag">task taskX &lt;&lt; {
    throw new RuntimeException()
}
task taskY &lt;&lt; {
    println 'taskY'
}

taskX.finalizedBy taskY
//运行 gradle -q taskX
//输出 taskY</pre>
<div class="blog_h1"><span class="graybg"> 文件操作</span></div>
<p>由于大多数构建都需要操作文件，因此Gradle定义了一些额外的文件操作API。</p>
<pre class="crayon-plain-tag">/************************ 文件定位 ***************************/
// 使用一个相对路径
File configFile = project.file('src/config.xml')
// 使用一个绝对路径
configFile = file('file:/some/path.xml')
configFile = file('F:/config.xml')



/************************ 文件集合 ***************************/
FileCollection collection = files(
    'src/file1.txt',
    new File('src/file2.txt'),
    ['src/file3.txt', 'src/file4.txt'] //会被自动展开
)
//迭代集合
collection.each {File file -&gt;
    println file.name
}
//类型转换
List list = collection as List
String path = collection.asPath
File file = collection.singleFile
//添加、去除文件元素
def union = collection + files('src/file3.txt')
def different = collection - files('src/file3.txt')


/************************ 文件树 ***************************/
//从相对目录创建
FileTree tree = fileTree(dir: 'src/main')
tree.include '**/*.java'
tree.exclude '**/Abstract*'
//从归档文件创建
FileTree zip = zipTree('someFile.zip')
FileTree tar = tarTree('someFile.tar')
//遍历
tree.each {File file -&gt;
    println file
}
//过滤
FileTree filtered = tree.matching {
    include 'org/gradle/api/**'
}
//合并
FileTree sum = tree + fileTree(dir: 'src/test')


/************************ 复制文件 ***************************/
//各种文件来源
task copyTask(type: Copy) {
    //复制目录下所有文件
    from 'src/main/webapp'
    //复制单个单独文件
    from 'src/staging/index.html'
    //从一个任务的输出复制
    from copyTask
    //从一个任务的outputs属性复制
    from copyTaskWithPatterns.outputs
    //复制归档文件中的内容
    from zipTree('src/main/assets.zip')
    //最终指定目标目录
    into { getDestDir() }
}
//文件过滤
task copyTaskWithPatterns(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    include '**/*.html'
    include '**/*.jsp'
    exclude { details -&gt; details.file.name.endsWith('.html') }
}
//复制的过程中重命名
task rename(type: Copy) {
    //闭包指定重命名规则
    rename { String fileName -&gt; fileName.replace('-staging-', '') } 
    //使用正则表达式指定映射规则
    rename(/(.+)-staging-(.+)/, '$1$2')
}
//处理文件内容
task filter(type: Copy) {
    //替换文件中的标记，标记必须是@tokenName@的形式
    expand(copyright: '2009', version: '2.3.1')
    expand(project.properties)
    //使用闭包处理每一行
    filter { String line -&gt; "[$line]" }
    //使用闭包删除行
    filter { String line -&gt;  line.startsWith('-') ? null : line  }
}


/************************ 同步两个目录 ***************************/
task libs(type: Sync) {
    from configurations.runtime
    into "$buildDir/libs"
}


/************************ 创建归档文件 ***************************/
task zip(type: Zip) {
    from 'src/dist'
    into('libs') {
        from configurations.runtime //指定依赖配置类型
    }
    baseName = 'customName' //指定归档文件名
}</pre>
<div class="blog_h1"><span class="graybg">日志记录</span></div>
<div class="blog_h3"><span class="graybg">日志记录级别</span></div>
<p>Gradle支持以下日志记录级别：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;"> 日志级别</td>
<td style="text-align: center;">命令选项</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>ERROR</td>
<td> </td>
<td>打印错误消息</td>
</tr>
<tr>
<td>QUIET</td>
<td>-q  --quiet</td>
<td>打印重要的、你应当知道的消息 </td>
</tr>
<tr>
<td>WARNING </td>
<td> </td>
<td>打印警告信息</td>
</tr>
<tr>
<td>LIFECYCLE</td>
<td>默认</td>
<td>这是Gradle的默认级别，打印处理进度信息</td>
</tr>
<tr>
<td>INFO </td>
<td>-i  --info</td>
<td>打印一般性的消息 </td>
</tr>
<tr>
<td>DEBUG</td>
<td>-d --debug</td>
<td>打印调试信息</td>
</tr>
</tbody>
</table>
<p>在打印日志的同时，还可以指定命令行参数：</p>
<ol>
<li>-s  --stacktrace：打印调用栈</li>
<li>-S  --full-stacktrace：打印完整调用栈</li>
</ol>
<div class="blog_h3"><span class="graybg">打印自己的日志</span></div>
<p>可以向标准输出打印日志：</p>
<pre class="crayon-plain-tag">println 'A message which is logged at QUIET level'</pre>
<p>或者通过<pre class="crayon-plain-tag">logger</pre> 属性打印日志： </p>
<pre class="crayon-plain-tag">logger.quiet('An info log message which is always logged.')
logger.error('An error log message.')
logger.warn('A warning log message.')
logger.lifecycle('A lifecycle info log message.')
logger.info('An info log message.')
logger.debug('A debug log message.')
logger.trace('A trace log message.')</pre>
<p>还可以使用外部日志工具：</p>
<pre class="crayon-plain-tag">import org.slf4j.Logger
import org.slf4j.LoggerFactory

Logger slf4jLogger = LoggerFactory.getLogger('some-logger')
slf4jLogger.info('An info log message logged using SLF4j')</pre>
<div class="blog_h1"><span class="graybg"> Gradle的插件机制</span></div>
<p>就像Maven一样，Gradle核心本身也不能做多少有意义的事情，实际的构建工作都是由插件完成的。Gradle允许插件：</p>
<ol>
<li>扩展Gradle的DSL模型，例如可以配置新的DSL元素</li>
<li>应用“约定由于配置”原则，为工程提供缺省配置</li>
<li>引入新的默认任务集</li>
</ol>
<p>Gradle插件分为两类：</p>
<ol>
<li>脚本插件：本质上是额外的构建脚本，可以进一步的配置构建逻辑</li>
<li>二进制插件：是接口<pre class="crayon-plain-tag">org.gradle.api.Plugin</pre> 的实现，以编程的的方式操控构建</li>
</ol>
<div class="blog_h3"><span class="graybg">应用插件到工程</span></div>
<p>使用方法<pre class="crayon-plain-tag">Project.apply()</pre> 可以应用插件到工程，应用同一插件多次是安全的：</p>
<pre class="crayon-plain-tag">/** 应用一个脚本插件 **/
// 实际上就是include一段脚本
apply from: 'other.gradle'

/** 应用二进制插件 **/
apply plugin: 'java'  //基于短名
apply plugin: JavaPlugin //基于插件类，org.gradle.api.plugins包内的插件不需要全名
//通过构建脚本块可以引入插件依赖，并使用插件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:0.4.1"
    }
}
apply plugin: "com.jfrog.bintray"</pre>
<div class="blog_h3"><span class="graybg">Gradle现有插件列表</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">分类</td>
<td style="width: 25%; text-align: center;">插件标识</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5">语言插件</td>
<td>java</td>
<td>为工程添加Java编译、测试功能，是需要其它插件的基础</td>
</tr>
<tr>
<td>groovy</td>
<td>支持Groovy代码的编译</td>
</tr>
<tr>
<td>scala</td>
<td>支持Scala代码的编译</td>
</tr>
<tr>
<td>antlr</td>
<td>增加对ANTLR的生成解析器的支持</td>
</tr>
<tr>
<td colspan="2">此外还有尚在孵化中的：assembler、c、cpp、objective-c、windows-resources</td>
</tr>
<tr>
<td rowspan="6">集成插件</td>
<td>application</td>
<td>支持把Java工程通过命令行运行</td>
</tr>
<tr>
<td>ear</td>
<td>增加对构建J2EE应用程序的支持</td>
</tr>
<tr>
<td>war</td>
<td>
<p>支持构建War包</p>
</td>
</tr>
<tr>
<td>maven</td>
<td>
<p>支持发布构件到Maven仓库</p>
</td>
</tr>
<tr>
<td>sogi</td>
<td>支持构建OSGI工程</td>
</tr>
<tr>
<td>jetty</td>
<td>支持在嵌入式的Jetty容器中部署War包或者工程构建结果（解包）</td>
</tr>
<tr>
<td rowspan="7">开发插件</td>
<td>checkstyle</td>
<td>使用Checksytle对工程的Java代码质量进行检查</td>
</tr>
<tr>
<td>findbugs</td>
<td>使用Findbugs对工程的Java代码质量进行检查</td>
</tr>
<tr>
<td>codenarc</td>
<td>使用Codenarc对工程的Groovy代码质量进行检查</td>
</tr>
<tr>
<td>eclipse</td>
<td>生成Eclipse IDE的元数据，以便导入到Eclipse工作区</td>
</tr>
<tr>
<td>eclipse-wtp</td>
<td>与eclipse类似，支持生成Eclipse WTP工程结构</td>
</tr>
<tr>
<td>idea</td>
<td>生成Intellij IDEA IDE的元数据，以便可以导入</td>
</tr>
<tr>
<td>signing</td>
<td>对生成的归档文件进行签名</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Gradle构建生命周期</span></div>
<div class="blog_h3"><span class="graybg">构建的三个阶段 </span></div>
<p>Gradle构建分为三个独立的阶段：</p>
<ol>
<li>Initialization：Gradle支持多工程构建。在初始化阶段，Gradle会确定有哪些工程参与构建，并为它们创建Project实例</li>
<li>Configuration：在此阶段，所有的Project对象被合理配置，每个Project的构建脚本（build script，即build.gradle）被执行</li>
<li>Execution：Gradle依据命令行参数、当前所在目录决定需要执行的Task的子集。Task的全集在Configuration阶段已经生成</li>
</ol>
<p>下面示意构建脚本的不同部分在哪个阶段执行：</p>
<pre class="crayon-plain-tag">println '该代码在配置阶段执行'
task configured {
	println '该代码在配置阶段执行'
}
task test &lt;&lt; {
	println '该代码在执行阶段执行'
}
task testBoth {
	doFirst {
	  println '该代码在执行阶段执行，最先执行'
	}
	doLast {
	  println '该代码在执行阶段执行，最后执行'
	}
	println '该代码在配置阶段执行'
}</pre>
<div class="blog_h3"><span class="graybg">初始化阶段</span></div>
<p>在初始化阶段，Gradle必须确定进行的单工程还是多工程构建（参见下一章）。多工程的设置总是存放在settings.gradle，Gradle自<span style="background-color: #c0c0c0;">动的寻找该文件的</span>顺序如下：</p>
<ol>
<li>在当前目录下寻找</li>
<li>在同级的、名为master的目录中寻找</li>
<li>到父目录中寻找</li>
</ol>
<p>如果找到settings.gradle，Gradle寻找被构建工程是否存在于settings.gradle所声明的工程树中，如果是，则执行多工程构建。否则，执行单工程构建。找不到settings.gradle则一定执行单工程构建。</p>
<p>注意，上述自动寻找settings.gradle的逻辑，仅在多工程满足下面条件之一时发生：</p>
<ol>
<li>工程物理位置与工程树结构对应</li>
<li>或者，使用扁平目录结构，并且用master目录表示主工程</li>
</ol>
<p>否则，你必须在settings.gradle所在目录执行Gradle命令以触发多工程构建。</p>
<div class="blog_h3"><span class="graybg">单工程构建的配置与执行阶段</span></div>
<p>在初始化结束之后，如果时单工程构建，其过程很简单。Gradle会寻找命令行传入的任务名，并<span style="background-color: #c0c0c0;">把每个任务作为单独的构建执行</span>。</p>
<div class="blog_h3"><span class="graybg">生命周期事件</span></div>
<p>在生命周期各关键点<span style="background-color: #c0c0c0;">前后</span>，Gradle会发布多种事件（通知），你可以在构建脚本中对这些事件进行处理：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Project evaluation</td>
<td>在工程被evaluate前后，可以得到一个通知：<br />
<pre class="crayon-plain-tag">allprojects {
	afterEvaluate { project -&gt;
		println '$it evaluated'
	}
}</pre>
</td>
</tr>
<tr>
<td>Task creation</td>
<td>在Task被加入到工程前后，可以得到通知：<br />
<pre class="crayon-plain-tag">tasks.whenTaskAdded { task -&gt;
    task.ext.srcDir = 'src/main/java'
}</pre>
</td>
</tr>
<tr>
<td>Task execution</td>
<td>在任务执行前后，可以得到通知：<br />
<pre class="crayon-plain-tag">gradle.taskGraph.beforeTask { Task task -&gt;
	println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state -&gt;
	if (state.failure) {
		println "FAILED"
	}
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">多工程（multi-project）构建</span></div>
<p>所谓多工程构建，是指在执行单次Gradle命令时，同时完成多个工程（一个root工程，外加若干个子工程，每个子工程也可以具有自己的子工程）的构建。必须通过<pre class="crayon-plain-tag">settings.gradle</pre> 文件来指定参与到构建中的工程，该脚本中的this指向<pre class="crayon-plain-tag">org.gradle.api.initialization.Settings</pre> 。Gradle依据该脚本构建工程的树状结构，此树具有<span style="background-color: #c0c0c0;">唯一的根目录</span>，每个子工程都具有一个“路径”用于表示其在树中的位置，通常情况下<span style="background-color: #c0c0c0;">路径与子工程在文件系统中的位置一致</span>。默认的，Gradle认为<span style="background-color: #c0c0c0;">settings脚本所在目录就是根工程</span>，该行为也是可配置的。</p>
<div class="blog_h3"><span class="graybg">简单的例子</span></div>
<p>假设有一个包含四个子工程的工程，结构如下：</p>
<pre class="crayon-plain-tag">multiproject/
  api/                      #生成一个JAR包，提供WebService客户端的功能
  services/webservice/      #WebService服务端
  shared/                   #其它三个工程依赖该工程
  services/shared/</pre>
<p>要启用多工程的支持，必须在工程集的根目录添加<pre class="crayon-plain-tag">settings.gradle</pre> 文件，指定哪些工程需要被构建：</p>
<pre class="crayon-plain-tag">include "shared", "api", "services:webservice", "services:shared"</pre>
<p> 根目录通常作为工程的容器，其中可以通过<pre class="crayon-plain-tag">subprojects</pre> 来设置所有子工程共享的配置信息：</p>
<pre class="crayon-plain-tag">//所有子目录下的工程都获得下列配置信息：
subprojects {
    apply plugin: 'java'
    apply plugin: 'eclipse-wtp'

    repositories {
       mavenCentral()
    }

    dependencies {
        testCompile 'junit:junit:4.12'
    }

    version = '1.0'

    jar {
        manifest.attributes provider: 'gradle'
    }
}
//注意，由于是在subprojects段设置，因此根工程不会获得这些配置</pre>
<p>可以声明子工程之间的依赖：</p>
<pre class="crayon-plain-tag">dependencies {
    compile project(':shared')
}</pre>
<p>上面的设置可以确保shared总是在api前面构建，因为后者依赖于前者。 </p>
<div class="blog_h2"><span class="graybg">跨工程的配置</span></div>
<div class="blog_h3"><span class="graybg" style="font-weight: bold;">按需配置</span></div>
<p>默认的，Gradle使用<span style="background-color: #c0c0c0;">全量配置</span>方式：在任何Task执行之前，所有工程都被配置完毕。如果工程数量达到上百个，这会导致配置阶段耗费的时间过于漫长。对不参与本次构建的工程进行配置是不必要的，因此，Gradle 1.4引入了按需配置（Configuration on demand）模式，按需配置<span style="background-color: #c0c0c0;">仅仅去配置那些与被请求执行任务相关的工程</span>。在未来，按需配置可能变为默认或者唯一的配置模式。</p>
<p>在按需配置模式下，工程按照下面的规则配置：</p>
<ol>
<li>根工程总是被配置，这保证典型的公共配置脚本被支持（allprojects和subprojects脚本块）</li>
<li>如果Gradle命令没有指定目标，则当前目录对应的工程被配置。这保证工程默认任务（Default tasks）可以正常工作</li>
<li>依赖工程被配置，如果projectA在编译时依赖于projectB，那么构建projectA会导致projectB也被配置</li>
<li>依赖任务所在工程被配置，例如<pre class="crayon-plain-tag">taskA.dependsOn(":projectB:taskB1")</pre> 导致构建taskA时projectB被配置</li>
<li>通过命令行指定Task的路径，那么路径中牵涉到的工程都会被配置，例如<pre class="crayon-plain-tag">gradle projectA:projectB:someTask</pre> 导致projectB被配置</li>
</ol>
<p>在配置文件<pre class="crayon-plain-tag">gradle.properties</pre> 中指定<pre class="crayon-plain-tag">org.gradle.configureondemand</pre> ，可以启用按需配置模式。</p>
<div class="blog_h3"><span class="graybg">定义公共行为</span></div>
<p>以一个简单的多工程为例，根工程root包含一个子工程sub0：</p>
<pre class="crayon-plain-tag">root/
  build.gradle
  settings.gradle
  sub0/</pre>
<p>settings.gradle的内容如下：<pre class="crayon-plain-tag">include 'sub0'</pre>  。sub0下面的构建脚本文件不是必须的，其构建脚本可以<span style="background-color: #c0c0c0;">直接放在根工程的build.gradle</span>里面：</p>
<pre class="crayon-plain-tag">Closure cl = { task -&gt; println "I'm $task.project.name" }
task hello &lt;&lt; cl
project(':sub0') {
    task hello &lt;&lt; cl
}
# 在根工程目录运行gradle -q hello输出如下：
# I'm root
# I'm sub0
# 注意，所有子工程同名Task会自动执行</pre>
<p>其实，Gradle允许在任何一个工程的构建脚本中访问所有工程，只要调用<pre class="crayon-plain-tag">project(projectName)</pre> 方法即可。</p>
<p>下面的脚本块可以为所有工程添加公共行为：</p>
<pre class="crayon-plain-tag">allprojects {
    task hello &lt;&lt; { task -&gt; println "I'm $task.project.name" }
}</pre>
<p>下面的脚本则为所有子工程添加公共行为：</p>
<pre class="crayon-plain-tag">subprojects {
    hello &lt;&lt; { println "I'm $it.project.name" }
}</pre>
<p>还可以依据各种条件进行过滤，针对符合条件的工程添加行为：</p>
<pre class="crayon-plain-tag">//根据子工程的名称进行过滤
configure(subprojects.findAll {it.name != 'sub1'}) {
    hello &lt;&lt; { println '$it.project.name' }
}</pre>
<div class="blog_h2"><span class="graybg">多工程构建的Task执行规则 </span></div>
<p>当<span style="background-color: #c0c0c0;">在根工程</span>目录执行<pre class="crayon-plain-tag">gradle taskA</pre>  时，根工程的taskA以及所有子工程的taskA任务<span style="background-color: #c0c0c0;">都会被执行</span>。</p>
<p>Gradle支持指定Task的绝对路径：</p>
<pre class="crayon-plain-tag">gradle -q :hello   //根工程的hello任务
:sub0:hello        //sub0工程的hello任务
hello              //当前工程的hello任务</pre>
<p>如果路径以分号开始，则表示是绝对路径（相当于根）。根工程是唯一不需要以名称指代的工程。 </p>
<div class="blog_h3"><span class="graybg">执行依赖</span></div>
<p>要确定Task的执行顺序，可以使用<pre class="crayon-plain-tag">dependsOn</pre> ，例如：</p>
<pre class="crayon-plain-tag">//跨工程的任务依赖
task consume(dependsOn: ':producer:produce') &lt;&lt; {
    println("Consuming message: ${rootProject.producerMessage}")
}</pre>
<p>被依赖的Task总是先被执行。</p>
<div class="blog_h3 blog_h2"><span class="graybg">库（Lib）工程依赖</span></div>
<p>库依赖（lib  dependency）是一种特殊的执行依赖。以Java构建为例，工程构建时，要求作为库的Jar已经生成好（此Jar可能传递性的依赖于其它Jar）并且添加到当前工程的Classpath中。如果这些库作为多工程构建的子工程存在，这就意味着工程之间存在依赖，这种依赖使用<pre class="crayon-plain-tag">dependencies</pre> 块声明：</p>
<pre class="crayon-plain-tag">subprojects {
    // 子工程公共属性
    apply plugin: 'java'
    group = 'cc.gmem.study.gradle'
    version = '1.0'
}

project(':api') {
    configurations {
        spi  //声明一个依赖配置（dependency configurations）
    }
    dependencies {
        compile project(':shared') //声明库依赖：编译时依赖于shared工程
    }
    task spiJar(type: Jar) {
        baseName = 'api-spi'
        dependsOn classes
        from sourceSets.main.output
        include('org/gradle/sample/api/**')
    }
    //声明产出构件
    artifacts {
        spi spiJar
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared') //声明库依赖
        compile project(path: ':api', configuration: 'spi') //声明库依赖，并指定配置
        testCompile "junit:junit:4.12", project(':api')
    }
}</pre>
<p>如果要禁止依赖工程的构建，可以使用gradle命令的<pre class="crayon-plain-tag">-a</pre> 选项。</p>
<div class="blog_h1"><span class="graybg">构建Java应用</span></div>
<div class="blog_h2"><span class="graybg">添加Java插件</span></div>
<p>Gradle自带了很多插件，这些插件可以完成不同领域的构建任务。Gradle从一开始就很好的支持Java的构建，支持编译、打包、单元测试都构建动作。 </p>
<p>类似与Maven，Gradle的Java插件也是遵从“约定优于配置”的理念，因而Java插件生成了类似Maven的工程结构。</p>
<p>要添加Java插件的支持，在build.gradle中需要声明：<pre class="crayon-plain-tag">apply plugin: 'java'</pre> 。添加该插件后：</p>
<ol>
<li>自动设置src/main/java为Java源码目录，src的其它子目录布局和Maven一致</li>
<li>自动添加若干可用的Task，运行<pre class="crayon-plain-tag">gradle tasks</pre> 可以看到这些Task的详细说明</li>
</ol>
<p>下表列出Java插件提供的常用任务：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 15%; text-align: center;">分类</td>
<td style="width: 22%; text-align: center;">任务</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5"> 生命周期任务</td>
<td>build</td>
<td>对工程进行完整构建</td>
</tr>
<tr>
<td>buildNeeded</td>
<td>对工程进行完整构建，同时构建<span style="background-color: #c0c0c0;">该工程依赖的</span>所有其它工程</td>
</tr>
<tr>
<td>buildDependents</td>
<td>对工程进行完整构建，同时构建所有<span style="background-color: #c0c0c0;">依赖于该工程</span>的其它工程</td>
</tr>
<tr>
<td>assemble</td>
<td>打印工程中的所有归档文件</td>
</tr>
<tr>
<td>check</td>
<td>执行所有验证任务。其他的插件会加入更多的检查步骤。其它插件会扩展这个任务的行为，例如Checkstyle插件会引发静态代码检查</td>
</tr>
<tr>
<td rowspan="3">源码集任务  </td>
<td>compileSourceSetJava</td>
<td>使用java编译指定源码集中的Java代码</td>
</tr>
<tr>
<td>processSourceSetResources</td>
<td>拷贝指定源码集中的资源文件到资源目录</td>
</tr>
<tr>
<td>sourceSetClasses</td>
<td>Assembles给定源码集中的类和资源文件</td>
</tr>
<tr>
<td rowspan="8">其它</td>
<td>clean</td>
<td>清空构建结果</td>
</tr>
<tr>
<td>compileJava</td>
<td>编译产品（不是测试）Java代码</td>
</tr>
<tr>
<td>processResources</td>
<td>处理产品（不是测试）资源文件</td>
</tr>
<tr>
<td>classes</td>
<td>Assembles产品类和资源文件</td>
</tr>
<tr>
<td>jar</td>
<td>打Jar包</td>
</tr>
<tr>
<td>javadoc</td>
<td>生成Javadoc</td>
</tr>
<tr>
<td>test</td>
<td>通过TestNG或者JUnit运行测试</td>
</tr>
<tr>
<td>uploadArchives</td>
<td>上传构件</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">工程布局</span></div>
<p>与Maven类似，默认有src/main|test/java|resourcs目录，其它源码集存放在：<pre class="crayon-plain-tag">src/sourceSetName/java</pre> 和<pre class="crayon-plain-tag">src/sourceSetName/resources</pre> 中。要改变此默认布局，可以：</p>
<pre class="crayon-plain-tag">sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
        resources {
            srcDir 'src/resources'
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">添加外部依赖</span></div>
<p>Gradle维护一个和Maven Repository类似的依赖库，默认位于<pre class="crayon-plain-tag">%USERPROFILE%/.gradle/caches/</pre> 。我们可以指定外部仓库的位置，以便从中下载依赖库：</p>
<pre class="crayon-plain-tag">repositories {
    mavenCentral()
}</pre>
<p>使用下面的方式声明当前工程的依赖库：</p>
<pre class="crayon-plain-tag">dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}</pre>
<div class="blog_h2"><span class="graybg">设置工程属性</span></div>
<p>Java插件默认给工程添加了很多属性，通过命令<pre class="crayon-plain-tag">gradle properties</pre> 可以查看这些属性的值。你可以在build.gradle中修改属性：</p>
<pre class="crayon-plain-tag">sourceCompatibility = 1.5
version = '1.0'</pre>
<p>也可以在jar的manifest中添加属性：</p>
<pre class="crayon-plain-tag">jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}</pre>
<p>或者在测试阶段添加一个系统属性：</p>
<pre class="crayon-plain-tag">test {
    systemProperties 'property': 'value'
}</pre>
<div class="blog_h2"><span class="graybg">发布构建结果</span></div>
<p>可以声明把构建结果发布到仓库中，下面示例了如何发布到本地目录：</p>
<pre class="crayon-plain-tag">uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}</pre>
<div class="blog_h2"><span class="graybg">转换为Eclipse工程</span></div>
<p>添加插件：<pre class="crayon-plain-tag">apply plugin: 'eclipse'</pre> ，然后运行命令<pre class="crayon-plain-tag">gradle eclipse</pre> ，即可自动依据build.gradle生成对应的Eclipse工程元数据，可以导入到Eclipse工作区中。</p>
<div class="blog_h2"><span class="graybg">支持注解处理器</span></div>
<p>Annotation processing是Java 1.5引入的功能，允许在编译期进行代码检查、代码生成等操作。</p>
<p>要让Gradle能自动调用注解处理器，可以使用net.ltgt.apt插件：</p>
<pre class="crayon-plain-tag">buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "net.ltgt.gradle:gradle-apt-plugin:0.21"
  }
}

apply plugin: "net.ltgt.apt"</pre>
<p>然后，执行gradle build会调用注解处理器。</p>
<p>如果使用Intellij IDEA作为开发工具，可以将插件更换为：</p>
<pre class="crayon-plain-tag">apply plugin: "net.ltgt.apt-idea"</pre>
<p>这样，如果注解处理器生成了源代码，这些源代码的目录自动变为source root。</p>
<div class="blog_h2"><span class="graybg">可执行自包含JAR</span></div>
<pre class="crayon-plain-tag">task fatJar( type: Jar ) {
    manifest {
        attributes 'Implementation-Title': project.name,
                   'Implementation-Version': version,
                   'Main-Class': 'cc.gmem.yun.alcm.repository.source.GrpcServer'
    }
    baseName = project.name + '-all'
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree( it ).matching {
                it.exclude(
                    'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/DEPENDENCIES', 'META-INF/INDEX.LIST',
                    'META-INF/LICENSE*', 'META-INF/NOTICE*', 'META-INF/README*'
                )
            }
        }
    }
    with jar
} </pre>
<div class="blog_h1"><span class="graybg">Maven支持</span></div>
<div class="blog_h2"><span class="graybg">本地仓库</span></div>
<p>如果你希望把构件安装到Maven本地仓库，则需要此插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'maven'</pre>
<p>安装此插件后，可以使用如下任务：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">任务</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>install</td>
<td>
<p>安装构建的产出为Maven构件，并保存到本地仓库</p>
<p>可以使用系统属性指定本地仓库的位置：</p>
<pre class="crayon-plain-tag">-Dmaven.repo.local=the/path/of/the/folder</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">远程仓库</span></div>
<p>需要使用到插件：
<pre class="crayon-plain-tag">apply plugin: 'maven-publish'</pre><br />
<pre class="crayon-plain-tag">publishing {
    publications {
        maven( MavenPublication ) {
            // 源码在哪
            from components.java
            // 发布JavaDocs和源码
            artifact javadocJar
            artifact sourcesJar

            // 编辑POM内容
            pom {
                name = project.group + ":" + project.name
                url = 'https://github.com/grpc/grpc-java'
                afterEvaluate {
                    description = project.description
                }

                scm {
                    connection = 'scm:git:https://github.com/grpc/grpc-java.git'
                    developerConnection = 'scm:git:git@github.com:grpc/grpc-java.git'
                    url = 'https://github.com/grpc/grpc-java'
                }

                licenses {
                    license {
                        name = 'Apache 2.0'
                        url = 'https://opensource.org/licenses/Apache-2.0'
                    }
                }

                developers {
                    developer {
                        id = "grpc.io"
                        name = "gRPC Contributors"
                        email = "grpc-io@googlegroups.com"
                        url = "https://grpc.io/"
                        organization = "gRPC Authors"
                        organizationUrl = "https://www.google.com"
                    }
                }

                withXml {
                    if ( !( project.name in
                        [
                            "grpc-stub",
                            "grpc-protobuf",
                            "grpc-protobuf-lite",
                        ] ) ) {
                        asNode().dependencies.'*'.findAll() { dep -&gt;
                            dep.artifactId.text() in [ 'grpc-api', 'grpc-core' ]
                        }.each() { core -&gt;
                            core.version*.value = "[" + core.version.text() + "]"
                        }
                    }
                }
            }
        }
    }
    // 远程仓库配置
    repositories {
        maven {
                credentials {
                    if ( rootProject.hasProperty( 'pacloudUsername' ) &amp;&amp; rootProject.hasProperty( 'pacloudPassword' ) ) {
                        username = rootProject.pacloudUsername
                        password = rootProject.pacloudPassword
                    }
                }
                def releaseUrl = 'https://nexus.pacloud.io/repository/maven-releases/'
                def snapshotUrl = 'https://nexus.pacloud.io/repository/maven-snapshots/'
                url = version.endsWith( 'SNAPSHOT' ) ? snapshotUrl : releaseUrl
            }
        }
    }
}</pre>
<p>执行下面的命令，完成打包和发布：</p>
<pre class="crayon-plain-tag">gradle sourceJar
gradle publishToMavenLocal</pre>
<div class="blog_h1"><span class="graybg">构建Groovy应用</span></div>
<p>Gradle提供了Groovy插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'eclipse'
apply plugin: 'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.3.7'
    testCompile 'junit:junit:4.11'
}</pre>
<p>该插件引入了以下任务： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>compileGroovy</td>
<td>compileJava</td>
<td>classes</td>
<td>编译产品Groovy代码</td>
</tr>
<tr>
<td>compileTestGroovy</td>
<td>compileTestJava</td>
<td>testClasses</td>
<td>编译测试Groovy代码</td>
</tr>
<tr>
<td>compileSourceSetGroovy</td>
<td>compileSourceSetJava</td>
<td>sourceSetClasses</td>
<td>编译指定的Groovy源码集</td>
</tr>
<tr>
<td>groovydoc</td>
<td> </td>
<td> </td>
<td>生成Groovy文档</td>
</tr>
</tbody>
</table>
<p>Groovy工程的目录布局与Java工程类似，新引入src/main/groovy、src/test/groovy目录存放Groovy源码。</p>
<p>如果要改变默认工程目录布局，可以：</p>
<pre class="crayon-plain-tag">sourceSets {
    main {
        groovy {
            srcDirs = ['src/groovy']
        }
    }

    test {
        groovy {
            srcDirs = ['test/groovy']
        }
    }
}</pre>
<div class="blog_h1"><span class="graybg">构建Web应用</span></div>
<p>Gradle提供了两个和Web应用相关的插件：</p>
<ol>
<li>War插件：在Java插件的基础上进行扩展，支持构建war包 </li>
<li>Jetty插件：允许发布War到Jetty容器</li>
</ol>
<div class="blog_h2"><span class="graybg">使用War插件</span></div>
<p>要添加War插件，可以：</p>
<pre class="crayon-plain-tag">apply plugin: 'war'
//注意，该插件会自动引入Java插件</pre>
<p>运行<pre class="crayon-plain-tag">gradle build</pre>  可以编译、测试并打war包。 </p>
<p>War插件引入以下任务：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>war</td>
<td>compile</td>
<td>assemble</td>
<td>打包Java的Web归档（war）</td>
</tr>
</tbody>
</table>
<p>Web资源，例如JavaScript、CSS、HTML、JSP等，默认存放到src/main/webapp目录中。</p>
<p>War插件引入了两个新的依赖配置：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">依赖配置</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>providedCompile</td>
<td rowspan="2">与compile、runtime类似，但是它们不会被打包到war中</td>
</tr>
<tr>
<td>providedRuntime</td>
</tr>
</tbody>
</table>
<p>下面的脚本示例了如何定制War插件的属性：</p>
<pre class="crayon-plain-tag">configurations {
   moreLibs
}

repositories {
   flatDir { dirs "lib" } //把lib目录作为依赖仓库
   mavenCentral()
}
dependencies {
    providedCompile "javax.servlet:servlet-api:2.5"
}

war {
    from 'src/rootContent' // 添加文件集到war中 
    webInf { from 'src/additionalWebInf' } // 添加文件夹到WEB-INF目录下
    classpath fileTree('additionalLibs') // 添加文件夹
    classpath configurations.moreLibs // 添加一个配置到WEB-INF/lib目录下
    webXml = file('src/someWeb.xml') // 拷贝文件为WEB-INF/web.xml
}</pre>
<div class="blog_h2"><span class="graybg">使用Jetty插件</span></div>
<p>Jetty插件继承自War插件，可以通过下面的脚本添加该插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'jetty'</pre>
<p>运行<pre class="crayon-plain-tag">gradle jettyRunWar</pre> 可以打包并部署到Jetty中运行。 </p>
<p>Jetty插件引入以下任务：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>jettyRun</td>
<td>compile</td>
<td> </td>
<td>运行Jetty实例，并把解开的构建目录部署上去</td>
</tr>
<tr>
<td>jettyRunWar</td>
<td>war</td>
<td> </td>
<td>运行Jetty实例，并把打包后的war部署上去</td>
</tr>
<tr>
<td>jettyStop</td>
<td> </td>
<td> </td>
<td>停止Jetty实例</td>
</tr>
</tbody>
</table>
<p>下面的脚本示例了如何定制Jetty插件的属性：</p>
<pre class="crayon-plain-tag">jetty {
    contextPath '上下文路径，默认war的名称'
    httpPort '监听端口，默认8080'
    stopPort '管理端口'
    stopKey  '要求Jetty停止时，传递给他的key'
}</pre>
<div class="blog_h1"><span class="graybg">构建Android应用</span></div>
<p>Gradle是当前Android官方IDE—— Android Studio内置的构建工具，你可以脱离AS，直接通过命令行调用Gradle，完成Android工程的构建。</p>
<p>通过编写构建脚本， 你可以配置Android构建的如下几个方面：</p>
<ol>
<li>构建变体（Build variants）：可以生成多个不同配置的APK，例如Debug/Release配置，又例如针对不同的体系结构（x86、arm）</li>
<li>Android库依赖</li>
<li>Manifest条目：可以在构建变体配置中<span style="background-color: #c0c0c0;">覆盖</span>AndroidManifest.xml中某些元素的取值。如果需要创建多个具有不同应用名称、最小SDK版本要求、目标SDK版本的APK，可以设置Manifest条目。如果有多个Manifest文件，会根据buildType、productFlavor、/main Manifest、library Manifest的优先顺序自动合并</li>
<li>签名：可以指定数字签名参数，在构建过程中对APK进行签名</li>
<li>ProGuard：允许配置ProGuard，对APK进行混淆、裁剪和优化</li>
</ol>
<div class="blog_h2"><span class="graybg">工程与模块</span></div>
<p>Android Studio<span style="background-color: #c0c0c0;">区分工程（Project）、模块（两个概念）</span>：</p>
<ol>
<li>工程：表示一个顶级的Android开发结构（相当于Gradle的根工程），一个工程中可以包含多个模块（相当于Gradle子工程）</li>
<li>模块：表示一个可以独立构建、测试、调试的组件</li>
</ol>
<p>AS支持以下类型的模块：</p>
<ol>
<li>Android Application Module：包含手机、电视 或手表的程序代码和资源，依赖于Library Module。构建系统为此类模块生成APK归档</li>
<li>Android Library Module：可重用的Android程序代码和资源。构建系统为此类模块生成AAR（Android ARchive）归档</li>
<li>App Engine Module：包含用于与Google App Engine集成的代码和资源</li>
<li>Java Library Module：包含可重用的Java代码。构建系统为此类模块生成JAR归档</li>
</ol>
<p>AS为工程和每个模块都生成Gradle构建脚本（build.gradle），这些脚本完全遵守Gradle的规范。</p>
<p>Android Studio工程包含了一个Gradle Wrapper，此Wrapper包含一个Gradle jar、一个属性文件、Windows/Linux的Shell脚本。使用Gradle Wrapper可以确保总是运行 <pre class="crayon-plain-tag">local.properties</pre> 中声明的Gradle版本。你可以修改此文件，以使用不同的Gradle版本。</p>
<div class="blog_h2"><span class="graybg">Hello World</span></div>
<div class="blog_h3"><span class="graybg">工程构建文件</span></div>
<p>可以指定Android工程的依赖，以及所有模块的公共依赖：</p>
<pre class="crayon-plain-tag">//构建本身的依赖配置
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        //使用特定版本的Gradle
        classpath 'com.android.tools.build:gradle:1.0.1'
    }
}
//针对Android工程和所有模块的依赖配置
allprojects {
   repositories {
       jcenter()
   }
}</pre>
<div class="blog_h3"><span class="graybg">模块构建文件 </span></div>
<p>每个模块都可以有自己的构建脚本，要对模块启用Android构建的支持，必须应用android插件：</p>
<pre class="crayon-plain-tag">//注意，不得同时应用java插件，会导致构建错误
apply plugin: 'android'
//或者：
apply plugin: 'com.android.application'</pre>
<p>可以在构建脚本中覆盖src/main/AndroidManifest.xml中的设置，或者对打包选项进行定制：</p>
<pre class="crayon-plain-tag">android {
    //Android 基本设置
    compileSdkVersion 16
    buildToolsVersion "23.0.2"
    
    //覆盖AndroidManifest.xml中的manifest properties设置
    defaultConfig {
        applicationId "cc.gmem.androidstudy"
        minSdkVersion 15
        targetSdkVersion 16
        versionCode 1
        versionName "1.0"
    }
    //构建类型设置
    //默认情况下，Android插件同时创建两个构建类型：Debug、Release。两者的主要区别是：
    //Debug使用自动创建的密钥签名，而Release版本在构建时不签名
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            //设置ApplicationId，这样设备上可以同时安装Debug版本和Release版本的APK
            applicationIdSuffix ".debug"
        }
        //从debug衍生一个构建类型
        jnidebug {
            initWith(buildTypes.debug)
            packageNameSuffix ".jnidebug"
            jniDebuggable true
        }
    }
}
//声明一些依赖项
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:16.+'
}</pre>
<p>对于每个新创建的构建类型，Android插件自动创建Task：<pre class="crayon-plain-tag">assemble&lt;BuildTypeName&gt;</pre>  </p>
<div class="blog_h2"><span class="graybg">工程结构</span></div>
<p>Android插件默认使用如下的工程结构：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">目录</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>src/main/java</td>
<td rowspan="2">
<p>主源码集</p>
<p>Android的Manifest文件位于：src/main/AndroidManifest.xml</p>
<p>其它子目录包括res、assets、aidl、rs、jni、jniLibs等</p>
</td>
</tr>
<tr>
<td>src/main/resources</td>
</tr>
<tr>
<td>src/androidTest/java</td>
<td rowspan="2">Android测试源码集</td>
</tr>
<tr>
<td>src/androidTest/resources</td>
</tr>
</tbody>
</table>
<p>如果默认工程结构不能满足需要，可以使用如下脚本定制：</p>
<pre class="crayon-plain-tag">android {
    sourceSets {
        //此闭包修改源码集的默认设置
        main {
            manifest.srcFile 'AndroidManifest.xml' //指定Manif文件名称
            //指定子目录位置
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
        androidTest.setRoot('tests') //把整个androidTest源码集移动到tests目录
    }
}</pre>
<div class="blog_h2"><span class="graybg">构建任务</span></div>
<p>Android插件自动引入以下Task：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">任务</td>
<td style="text-align: center;">任务描述</td>
<td style="width: 30%; text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>assemble</td>
<td>收集工程的输出文件并打包。对于Android工程，至少输出两个APK包：Debug/Release，它们分别由任务assembleDebug、assembleRelease完成。assemble依赖于这两个任务</td>
<td rowspan="3">
<p>和Java插件一样，Android引入这三个一般性Task。这些Task本身不做实质性的工作，它们只是作为anchor，把工作委托给其它Task执行</p>
<p>不同插件使用风格一致的一般任务命名约定，可以简化构建</p>
</td>
</tr>
<tr>
<td>check</td>
<td>运行所有检查。Android插件支持基于lint的静态检查</td>
</tr>
<tr>
<td>build</td>
<td>同时执行assemble和check</td>
</tr>
<tr>
<td>clean</td>
<td colspan="2">清除工程输出 </td>
</tr>
<tr>
<td>connectedCheck</td>
<td colspan="2">检查是否有连接上的设备或者模拟器  </td>
</tr>
<tr>
<td>deviceCheck</td>
<td colspan="2">检查设备是否支持API </td>
</tr>
<tr>
<td>installDebug</td>
<td colspan="2">安装Debug版本的APK到设备</td>
</tr>
<tr>
<td>installRelease</td>
<td colspan="2">安装Release版本的APK到设备</td>
</tr>
<tr>
<td>uninstallAll</td>
<td colspan="2">从设备上卸载所有版本的APK，依赖于：uninstallDebug、uninstallRelease、uninstallDebugAndroidTest</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置APK签名</span></div>
<p>默认的，Debug构建的keystore存放在<pre class="crayon-plain-tag">$HOME/.android/debug.keystore</pre> ，如果不存在会自动创建。下面的脚本块示例了如何创建新的签名配置（signingConfigs ）并且将其应用到某个构建类型：</p>
<pre class="crayon-plain-tag">android {
    signingConfigs {
        debug {
            storeFile file("debug.keystore")
        }
        //创建新的签名配置
        myConfig {
            storeFile file("other.keystore")
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
    }

    buildTypes {
        foo {
            //应用签名配置
            signingConfig signingConfigs.myConfig
        }
    }
}</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/gradle-study-note">Gradle学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/gradle-study-note/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>基于Eclipse和Maven的Groovy开发</title>
		<link>https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev</link>
		<comments>https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev#comments</comments>
		<pubDate>Thu, 12 Jun 2014 13:28:27 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Groovy]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=9863</guid>
		<description><![CDATA[<p>Eclipse和Maven插件 插件  说明  Groovy Eclipse Update Site：http://dist.springsource.org/release/GRECLIPSE/e4.3/ 安装全部组件 该插件自带了一组Groovy编译器，其优势是不需要生成Groovy类的存根 m2e Configurator for Groovy Eclipse 此插件包含在上面的Update Site中，与m2eclipse集成，可以方便在Eclipse中，以Maven为中心管理Groovy工程。 该插件与AspectJ、Scala不兼容，因此你不能在同一工程中使用这些语言 groovy-eclipse-compiler 一个Maven插件，允许使用Groovy Eclipse编译器来编译Maven工程中的Groovy代码，并将工程配置为Groovy Project。该插件需要上述的m2e Configurator才能正常工作。 gmavenplus-plugin <a class="read-more" href="https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev">基于Eclipse和Maven的Groovy开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Eclipse和Maven插件</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 30%; text-align: center;">插件 </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Groovy Eclipse</td>
<td>
<p>Update Site：http://dist.springsource.org/release/GRECLIPSE/e4.3/ 安装全部组件</p>
<p>该插件自带了一组Groovy编译器，其优势是不需要生成Groovy类的存根</p>
</td>
</tr>
<tr>
<td>m2e Configurator for Groovy Eclipse</td>
<td>
<p>此插件包含在上面的Update Site中，与m2eclipse集成，可以方便在Eclipse中，以Maven为中心管理Groovy工程。</p>
<p>该插件与AspectJ、Scala不兼容，因此你不能在同一工程中使用这些语言</p>
</td>
</tr>
<tr>
<td>groovy-eclipse-compiler</td>
<td>
<p>一个Maven插件，允许使用Groovy Eclipse编译器来编译Maven工程中的Groovy代码，并将工程配置为Groovy Project。该插件需要上述的m2e Configurator才能正常工作。</p>
</td>
</tr>
<tr>
<td>gmavenplus-plugin</td>
<td>
<p>即GMavenPlus，一个Maven插件，是GMaven的代替者，包括了比groovy-eclipse-compiler更丰富的功能：</p>
<ol>
<li>支持GroovyDoc</li>
<li>支持Groovy Mojos</li>
<li>可以在POM中定义需要执行的Groovy脚本</li>
<li>支持InvokeDynamic特性</li>
<li>支持Groovy Android </li>
<li>支持Configuration scripts </li>
<li>支持交互式的Shell/Console</li>
</ol>
<p>该插件目前缺乏对应的m2e connector，无法在Eclipse中正常使用</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Groovy Eclipse配置</span></div>
<p>定位到Window ⇨ Preferences -Groovy进行设置：</p>
<ol>
<li>Groovy Content Assist options... ：可以让代码风格更加Groovy化，根据需要设置</li>
<li>Compiler子菜单：可以切换Groovy编译器版本，必须和Groovy运行时依赖的版本保持兼容</li>
<li>Formatter子菜单：设置代码格式化规则，可以设置的内容很少：
<ol>
<li>Position of the opening braces：起始括号是否换行</li>
<li>Remove unnecessary semicolons：移除不必要的分号</li>
</ol>
</li>
</ol>
<div class="blog_h1"><span class="graybg">groovy-eclipse-compiler示例</span></div>
<pre class="crayon-plain-tag">&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
        &lt;artifactId&gt;groovy-all&lt;/artifactId&gt;
        &lt;version&gt;2.3.7&lt;/version&gt;
    &lt;/dependency&gt;
    &lt;!-- 如果要支持Project Lombok --&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.projectlombok&lt;/groupId&gt;
        &lt;artifactId&gt;lombok&lt;/artifactId&gt;
       &lt;version&gt;1.14.8&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;
&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
            &lt;version&gt;3.1&lt;/version&gt;
            &lt;configuration&gt;
                &lt;!-- maven-compiler-plugin 实际不做任何编译工作，它总是把工作委派给真正的编译器，例如javac/groovyc--&gt;
                &lt;compilerId&gt;groovy-eclipse-compiler&lt;/compilerId&gt;
                &lt;!-- 如果要支持Project Lombok，添加： --&gt;
                &lt;verbose&gt;true&lt;/verbose&gt;
                &lt;compilerArguments&gt;
                    &lt;javaAgentClass&gt;lombok.core.Agent&lt;/javaAgentClass&gt;
                &lt;/compilerArguments&gt;
                &lt;fork&gt;true&lt;/fork&gt;
            &lt;/configuration&gt;
            &lt;dependencies&gt;
                &lt;dependency&gt;
                    &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
                    &lt;artifactId&gt;groovy-eclipse-compiler&lt;/artifactId&gt;
                    &lt;version&gt;2.9.1-01&lt;/version&gt;
                &lt;/dependency&gt;
                &lt;dependency&gt;
                    &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
                    &lt;artifactId&gt;groovy-eclipse-batch&lt;/artifactId&gt;
                    &lt;version&gt;2.3.7-01&lt;/version&gt;
                &lt;/dependency&gt;
            &lt;/dependencies&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;!-- 为Groovy源文件分配单独的存放目录 --&gt;
            &lt;groupId&gt;org.codehaus.mojo&lt;/groupId&gt;
            &lt;artifactId&gt;build-helper-maven-plugin&lt;/artifactId&gt;
            &lt;version&gt;1.5&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;add-source&lt;/id&gt;
                    &lt;phase&gt;generate-sources&lt;/phase&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;add-source&lt;/goal&gt;
                    &lt;/goals&gt;
                    &lt;configuration&gt;
                        &lt;sources&gt;
                            &lt;source&gt;src/main/groovy&lt;/source&gt;
                        &lt;/sources&gt;
                    &lt;/configuration&gt;
                &lt;/execution&gt;
                &lt;execution&gt;
                    &lt;id&gt;add-test-source&lt;/id&gt;
                    &lt;phase&gt;generate-test-sources&lt;/phase&gt;
                    &lt;goals&gt;
                        &lt;goal&gt;add-test-source&lt;/goal&gt;
                    &lt;/goals&gt;
                    &lt;configuration&gt;
                        &lt;sources&gt;
                            &lt;source&gt;src/test/groovy&lt;/source&gt;
                        &lt;/sources&gt;
                    &lt;/configuration&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;</pre>
<p>将上述内容添加到一个Maven工程的POM中以后，Update Project，该工程自动变为 Groovy Project，工程结构如下：</p>
<p><img class="aligncenter size-full wp-image-9884" src="https://blog.gmem.cc/wp-content/uploads/2014/06/groovy-project.jpg" alt="groovy-project" width="293" height="219" /></p>
<div class="blog_h1"><span class="graybg">gmavenplus-plugin详解</span></div>
<div class="blog_h2"><span class="graybg">常用Goals</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">Goal </td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>gplus:addSources</td>
<td>把Groovy源码加到工程main源码清单</td>
</tr>
<tr>
<td>gplus:addTestSources</td>
<td>把Groovy测试源码加到工程test源码清单</td>
</tr>
<tr>
<td>gplus:compile</td>
<td>编译main源代码，如果要支持InvokeDynamic，必须使用1.5.0+或者 2.0.0-beta-3+的Groovy</td>
</tr>
<tr>
<td>gplus:console</td>
<td>开启一个关联到当前工程的Console窗口</td>
</tr>
<tr>
<td>gplus:shell</td>
<td>开启一个关联到当前工程的Shell</td>
</tr>
<tr>
<td>gplus:execute</td>
<td>执行关联到当前工程的Groovy脚本（包括POM或者外部的）</td>
</tr>
<tr>
<td>gplus:generateStubs</td>
<td>
<p>为main Groovy源码生成存根，并添加到Maven源码目录，供Java编译器发现</p>
<p>之所以需要存根，是因为Java编译器无法识别Groovy代码，如果<span style="background-color: #c0c0c0;">存在Java对Groovy的调用</span>，需要生成被调用类的空壳（不含方法实现），以便Java编译的通过</p>
</td>
</tr>
<tr>
<td>gplus:groovydoc</td>
<td>为main 代码生成GroovyDoc</td>
</tr>
<tr>
<td>gplus:removeStubs</td>
<td rowspan="2">从工程中移除Groovy存根（Stub）</td>
</tr>
<tr>
<td>gplus:removeTestStubs</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">用法示例</span></div>
<div class="blog_h3"><span class="graybg">执行Groovy脚本</span></div>
<p>可以把executie目录绑定到任何阶段，如果不绑定，可以运行命令<pre class="crayon-plain-tag">mvn gplus:execute</pre> 手工执行。</p>
<p>使用下面的POM配置可以包含Groovy脚本到pom.xml中：</p>
<pre class="crayon-plain-tag">&lt;plugin&gt;
    &lt;groupId&gt;org.codehaus.gmavenplus&lt;/groupId&gt;
    &lt;artifactId&gt;gmavenplus-plugin&lt;/artifactId&gt;
    &lt;version&gt;1.5&lt;/version&gt;
    &lt;executions&gt;
        &lt;execution&gt;
            &lt;goals&gt;
                &lt;goal&gt;execute&lt;/goal&gt;
            &lt;/goals&gt;
        &lt;/execution&gt;
    &lt;/executions&gt;
    &lt;configuration&gt;
        &lt;scripts&gt;
            &lt;script&gt;
                &lt;![CDATA[
                    //Script content
                ]]&gt;
            &lt;/script&gt;
            &lt;!-- 亦可外链脚本文件 --&gt;
            &lt;script&gt;file:///${project.basedir}/src/main/resources/scr.groovy&lt;/script&gt;
        &lt;/scripts&gt;
    &lt;/configuration&gt;
    &lt;dependencies&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
            &lt;artifactId&gt;groovy-all&lt;/artifactId&gt;
            &lt;version&gt;2.3.7&lt;/version&gt;
            &lt;scope&gt;runtime&lt;/scope&gt;
        &lt;/dependency&gt;
    &lt;/dependencies&gt;
&lt;/plugin&gt;</pre>
<div class="blog_h3"><span class="graybg">编译Groovy源码</span></div>
<p>目标compile附加到Maven声明周期的相应阶段，只需要执行默认的<pre class="crayon-plain-tag">mvn compile</pre> ，就可以编译Groovy源码。</p>
<p>下面是一个典型的POM配置：</p>
<pre class="crayon-plain-tag">&lt;build&gt;
    &lt;plugins&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.codehaus.gmavenplus&lt;/groupId&gt;
            &lt;artifactId&gt;gmavenplus-plugin&lt;/artifactId&gt;
            &lt;version&gt;1.5&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;goals&gt;
                        &lt;!-- 添加Groovy源码到Maven的main源码清单，打包source jar时有用 --&gt;
                        &lt;goal&gt;addSources&lt;/goal&gt;
                        &lt;!-- 添加Groovy源码到Maven的test源码清单，打包source jar时有用 --&gt;
                        &lt;goal&gt;addTestSources&lt;/goal&gt;
                        &lt;!-- 生成main Groovy代码的Java Stub，这些存根随即被加到main源码清单，以便Java编译器可以使用之 --&gt;
                        &lt;!-- 如果不进行Java/Groovy混合编程，可以忽略这一目标 --&gt;
                        &lt;goal&gt;generateStubs&lt;/goal&gt;
                        &lt;!-- 在编译Groovy main源码前，Java main代码会和Stub一起被编译 --&gt;
                        &lt;!-- 编译Groovy main源码 --&gt;
                        &lt;goal&gt;compile&lt;/goal&gt;
                        &lt;!-- 移除main存根，因为存根只在编译Java代码时用到，如果不移除，导致souce jar中包含存根代码 --&gt;
                        &lt;goal&gt;removeStubs&lt;/goal&gt;
                        &lt;!-- 生成test源码的存根 --&gt;
                        &lt;goal&gt;testGenerateStubs&lt;/goal&gt;
                        &lt;!-- 编译test源码，与main源码类似 --&gt;
                        &lt;goal&gt;testCompile&lt;/goal&gt;
                        &lt;!-- 移除test存根 --&gt;
                        &lt;goal&gt;removeTestStubs&lt;/goal&gt;
                    &lt;/goals&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-compiler-plugin&lt;/artifactId&gt;
        &lt;/plugin&gt;
        &lt;plugin&gt;
            &lt;groupId&gt;org.apache.maven.plugins&lt;/groupId&gt;
            &lt;artifactId&gt;maven-source-plugin&lt;/artifactId&gt;
            &lt;version&gt;2.4&lt;/version&gt;
            &lt;executions&gt;
                &lt;execution&gt;
                    &lt;id&gt;attach-sources&lt;/id&gt;
                    &lt;goals&gt;
                        &lt;!-- 使用该目标，防止Groovy源码和Java Stub都被包含到source jar --&gt;
                        &lt;goal&gt;jar-no-fork&lt;/goal&gt;
                        &lt;goal&gt;test-jar-no-fork&lt;/goal&gt;
                    &lt;/goals&gt;
                &lt;/execution&gt;
            &lt;/executions&gt;
            &lt;configuration&gt;
                &lt;!-- 添加下面的配置以支持JDK 1.7+的InvokeDynamic特性，但是目标JVM必须为JDK 1.7+--&gt;
                &lt;invokeDynamic&gt;true&lt;/invokeDynamic&gt;
                &lt;!-- 可以使用Groovy配置脚本（configuration script）--&gt;
                &lt;configScript&gt;config.groovy&lt;/configScript&gt;
            &lt;/configuration&gt;
        &lt;/plugin&gt;
    &lt;/plugins&gt;
&lt;/build&gt;
&lt;dependencies&gt;
    &lt;dependency&gt;
        &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
        &lt;artifactId&gt;groovy-all&lt;/artifactId&gt;
        &lt;version&gt;2.3.7&lt;/version&gt;
    &lt;/dependency&gt;
&lt;/dependencies&gt;</pre>
<p>如果是纯粹的Groovy工程，不包含Java代码，可以这样配置Goal：</p>
<pre class="crayon-plain-tag">&lt;goals&gt;
    &lt;goal&gt;addSources&lt;/goal&gt;
    &lt;goal&gt;addTestSources&lt;/goal&gt;
    &lt;goal&gt;compile&lt;/goal&gt;
    &lt;goal&gt;testCompile&lt;/goal&gt;
&lt;/goals&gt;</pre>
<p>要为Android平台编译Groovy，只需要指定适用于该平台的Groovy的JAR即可：</p>
<pre class="crayon-plain-tag">&lt;dependency&gt;
      &lt;groupId&gt;org.codehaus.groovy&lt;/groupId&gt;
      &lt;artifactId&gt;groovy&lt;/artifactId&gt;
      &lt;version&gt;2.4.4&lt;/version&gt;
      &lt;classifier&gt;grooid&lt;/classifier&gt;
&lt;/dependency&gt;</pre>
<div class="blog_h3"><span class="graybg">指定额外的源码目录</span></div>
<p>如果你的Groovy源码不使用标准的.groovy扩展名，或者不存放在src/[main|test]/groovy目录下，必须通知GMavenPlus：</p>
<pre class="crayon-plain-tag">&lt;configuration&gt;
    &lt;sources&gt;
        &lt;source&gt;
            &lt;directory&gt;${project.basedir}/src/main/groovy&lt;/directory&gt;
            &lt;includes&gt;
                &lt;include&gt;**/*.groovy&lt;/include&gt;
            &lt;/includes&gt;
        &lt;/source&gt;
        &lt;source&gt;
            &lt;directory&gt;${project.basedir}/src/additional&lt;/directory&gt;
            &lt;includes&gt;
                &lt;include&gt;groovy/**/*.groovy&lt;/include&gt;
                &lt;include&gt;gvy/**/*.gvy&lt;/include&gt;
            &lt;/includes&gt;
        &lt;/source&gt;
    &lt;/sources&gt;
    &lt;testSources&gt;
        &lt;testSource&gt;
            &lt;directory&gt;${project.basedir}/src/test/groovy&lt;/directory&gt;
            &lt;includes&gt;
                &lt;include&gt;**/*.groovy&lt;/include&gt;
            &lt;/includes&gt;
        &lt;/testSource&gt;
    &lt;/testSources&gt;
&lt;/configuration&gt;</pre>
<div class="blog_h1"><span class="graybg">常见问题 </span></div>
<div class="blog_h3"><span class="graybg">无法调试Groovy脚本</span></div>
<p>如果可以调试正规化的Groovy类（与Java类一样，遵循存储路径与类全限定名称的对应关系），但是不能调试脚本，可能是因为当前脚本不在Classpath下面。</p>
<p>解决办法：编写脚本时，要进行包声明，例如：<pre class="crayon-plain-tag">package cc.gmem.study.groovy</pre> </p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev">基于Eclipse和Maven的Groovy开发</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/eclipse-and-mvn-based-groovy-dev/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Groovy学习笔记</title>
		<link>https://blog.gmem.cc/groovy-study-note</link>
		<comments>https://blog.gmem.cc/groovy-study-note#comments</comments>
		<pubDate>Fri, 14 Feb 2014 03:04:28 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Java]]></category>
		<category><![CDATA[Groovy]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=9903</guid>
		<description><![CDATA[<p>基础知识 Groovy简介 Groovy诞生于2004年， 它是可选类型的（optionally typed）动态语言，是最流行的JVM语言之一，比起其它语言，Groovy能够与Java无缝集成、相互调用，这是它的最大优势。 Groovy可以作为脚本语言使用（但是执行时，即便是脚本也会编译为Java类），也可以编译为Java类。在运行时，你只需要把[crayon-69d26a9b75268907289673-i/] 放在Classpath中，无需其它任何依赖或者执行引擎。 使用Groovy，你可以： 让Java代码更加简洁 自动化重复的任务 使用DSLs（domain-specific languages）进行业务建模 执行即席的脚本任务 Groovy有以下高级特性： 支持闭包（Closures） 支持动态方法（dynamic methods）  为Java平台引入元对象协议（MOP，Meta Object Protocol） 安装Groovy 可以到官网下载Groovy运行时，并将安装目录设置为环境变量[crayon-69d26a9b75270536842567-i/] ，同时把[crayon-69d26a9b75274066405913-i/] 添加到环境变量PATH中。 Groovy命令行工具 <a class="read-more" href="https://blog.gmem.cc/groovy-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/groovy-study-note">Groovy学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">基础知识</span></div>
<div class="blog_h2"><span class="graybg">Groovy简介</span></div>
<p>Groovy诞生于2004年， 它是<span style="background-color: #c0c0c0;">可选类型的（optionally typed）动态语言</span>，是最流行的JVM语言之一，比起其它语言，Groovy能够与Java无缝集成、相互调用，这是它的最大优势。</p>
<p>Groovy可以作为脚本语言使用（但是执行时，<span style="background-color: #c0c0c0;">即便是脚本也会编译为Java类</span>），也可以编译为Java类。在运行时，你只需要把<pre class="crayon-plain-tag">groovy-all-*.jar</pre> 放在Classpath中，无需其它任何依赖或者执行引擎。</p>
<p>使用Groovy，你可以：</p>
<ol>
<li>让Java代码更加简洁</li>
<li>自动化重复的任务</li>
<li>使用DSLs（domain-specific languages）进行业务建模</li>
<li>执行即席的脚本任务</li>
</ol>
<p>Groovy有以下高级特性：</p>
<ol>
<li>支持闭包（Closures）</li>
<li>支持动态方法（dynamic methods）</li>
<li> 为Java平台引入元对象协议（MOP，Meta Object Protocol）</li>
</ol>
<div class="blog_h2"><span class="graybg">安装Groovy</span></div>
<p>可以到<a href="http://www.groovy-lang.org/download.html">官网</a>下载Groovy运行时，并将安装目录设置为环境变量<pre class="crayon-plain-tag">GROOVY_HOME</pre> ，同时把<pre class="crayon-plain-tag">$GROOVY_HOME/bin</pre> 添加到环境变量PATH中。</p>
<div class="blog_h2"><span class="graybg">Groovy命令行工具</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">工具</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>groovysh</td>
<td>启动交互式Shell</td>
</tr>
<tr>
<td>groovyConsole</td>
<td>运行一个GUI来交互执行Groovy代码 </td>
</tr>
<tr>
<td>groovy</td>
<td>开始解释Groovy脚本。作为参数的脚本只需要在Classpath中存在即可</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Groovy在JVM中的运行方式</span></div>
<ol>
<li>预编译模式（Precompiled mode）：使用Groovy编译器——groovyc，把*.groovy编译为*.class文件。并把*.class放置到Classpath下，通过Java的类加载器加载这些类并运行</li>
<li>直接模式（Direct mode）：通过groovy的类加载器，在运行时直接加载*.groovy文件，并动态的生成JVM对象，这种情况下，没有生成.class文件，但是在JVM中是有对应的java.lang.Class实例的</li>
</ol>
<p>Groovy在源代码级别增强了Java，但是两者的字节码格式是完全相同的。</p>
<div class="blog_h2"><span class="graybg">GDK</span></div>
<p>GDK即Groovy Development Kit，是Groovy对JDK的补充，引入了一些新的类，并为已经现有的一些Java类增强了功能。下面列出GDK的一些特性：</p>
<ol>
<li>为所有类型引入<pre class="crayon-plain-tag">size()</pre> 方法</li>
<li>简化的数据库访问</li>
<li>简化的XML处理</li>
</ol>
<div class="blog_h2"><span class="graybg">动态性</span></div>
<p>动态性是指在运行时修改程序结构的能力。</p>
<p>Groovy生成的字节码，会在运行时转换为普通的Java类。除非使用特殊的机制影响JVM，那么这些已<span style="background-color: #c0c0c0;">加载的Java类的结构是无法动态改变的</span>。</p>
<p>Groovy为什么能具有动态语言的特征，是因为其生成的字节码，不是直接调用方法，而是像这样：</p>
<pre class="crayon-plain-tag">getMetaClass().invokeMethod(this, "method", EMPTY_PARAMS_ARRAY)</pre>
<p>也就是说，方法调用以反射的方式<span style="background-color: #c0c0c0;">转给对象的MetaClass进行处理</span>， 这就类似于Cglib等字节码增强工具一样，可以在运行时<span style="background-color: #c0c0c0;">拦截、重定向、增加/删除方法</span>。</p>
<div class="blog_h1"><span class="graybg">Groovy语言</span></div>
<div class="blog_h2"><span class="graybg">与Java在语法上的异同</span></div>
<p>Groovy和Java在语法上的共同点包括：</p>
<ol>
<li>相同的包处理机制（包括包的声明和import语句）</li>
<li>类和方法的定义（嵌套类除外）</li>
<li>控制结构语句（经典的C风格for循环除外）</li>
<li>操作符、表达式和赋值</li>
<li>异常处理</li>
<li>变量声明</li>
<li>对象实例化，引用和取消引用对象</li>
<li>方法调用</li>
</ol>
<p>Groovy和Java在语法上的差异点包括：</p>
<ol>
<li>非静态内部类的实例化语法：<br />
<pre class="crayon-plain-tag">new OuterClass.NonStaticInnerClass( outClassInstance,args )</pre>
</li>
</ol>
<p>Groovy还引入了新的语法特性：</p>
<ol>
<li>通过新的表达式和操作符访问Java对象</li>
<li>更多的对象声明方式</li>
<li>引入新的控制结构</li>
<li>引入新的数据类型、操作符和表达式</li>
<li><span style="background-color: #c0c0c0;">顶级表达式</span>中的方法调用的<span style="background-color: #c0c0c0;">括号</span>可以省略，前提是<span style="background-color: #c0c0c0;">被调用方法有至少一个参数</span>（这是为了防止与属性访问歧义，该限制可以<a href="#omit-par-0arg-mthd">通过元编程突破</a>）。链式方法调用与该规则配合使用，可以形成自然语言风格，在DSL中常用</li>
<li>作为最后一个入参的闭包，可以放在方法调用括号的外面，这一规则可以和上一条联用</li>
<li>方法重载发生在运行期而非编译期</li>
</ol>
<div class="blog_h2"><span class="graybg">自动导入的包和类</span></div>
<pre class="crayon-plain-tag">groovy.lang.* 
groovy.util.* 
java.lang.* 
java.util.* 
java.net.* 
java.io.* 
java.math.BigInteger 
java.math.BigDecimal</pre>
<p>上述包和类自动导入，而Java仅仅导入java.lang包。</p>
<div class="blog_h2"><span class="graybg">基础代码示例</span></div>
<pre class="crayon-plain-tag">package cc.gmem.study.groovy
//声明类
class Person
{
    //声明成员变量
    private int id
    //声明一个属性
    String name
    /**
     * GroovyDoc注释
     * @param name 名称
     */
    Person(String name)
    {
        this.name = name
    }
    /* 程序入口点：main方法 */
    static main(args)
    {
        //方法调用的括号，在不引起歧义的前提下可以省略
        System.out.println  'Groovy'  //字符串可以用单引号界定

        // 多个参数
        httpRequest "https://blog.gmem.cc", "application/json"
        // 命令参数
        httpRequest url: "https://blog.gmem.cc",
                    acceptType:"application/json"

        1.equals( 1 )  //万物皆对象

        //断言
        assert 1 == 1  // ==调用equals()方法进行判断，而不是比较对象地址
        xx = 1  // 如果局部变量xx不存在，等价于this.xx = 1
        //def用于声明“动态类型”
        def x = 1
        //打印变量的值
        println x

        def person = new Person('Alex')
        person.setName( 'Alex Wong' ) //getter/setter直接可以用，不需要编写方法
        person.name = 'Alex Wong' //与上面效果一样，会访问setter而不是直接访问属性

        def sls = '单引号字符串等价于java.lang.String'
        char a = 'x'  //字符类型必须静态声明，或者  'x' as char
        //groovy.lang.GString：双引号字符串中的占位符可以动态解释，在必要时，Groovy可以在GString和String之间进行转换
        println "Hello $person.name" //占位符以$开始，支持属性导航，可以使用${}形式，{}中可以是任何表达式
        def mls = '''多行
字符串'''

        //语言级别的正则式支持
        // =~表示查找操作符
        assert person.name =~ /\sWong/  //斜线界定的字符串，表示不转义反斜杠（\），在正则式中特别有用
        assert 'Alex Wang' == person.name.replaceAll( /Wong/, 'Wang' ) //替换

        //java.math.BigInteger、java.math.BigDecimal直接量
        def bigInt = 1000g
        def bigDec = 1000.01g

        //列表直接量
        def roman = ['', 'I', 'II', 'III', 'IV', 'V', 'VI', 'VII']
        assert roman[4] == 'IV' //数字索引语法访问元素
        roman[8] = 'VIII' //自动扩展列表

        //映射（字典直接量）
        def http = [
            100 : 'CONTINUE',
            200 : 'OK',
        ]
        assert http[200] == 'OK'
        http[400] = 'BAD REQUEST' //动态插入键值对

        //语言级别的范围（range）支持
        def range = 1..10
        assert range.contains(5)
        assert range.contains(15) == false
        assert range.size() == 10
        assert range.from == 1
        assert range.to == 10
        assert range.reverse() == 10..1

        //闭包支持，闭包是花括号限定的代码块，其入参列表以 -&gt;结束
        def c = {el -&gt;  println el }
        [1, 2, 3].each (c)
    }
}</pre>
<div class="blog_h2"><span class="graybg">操作符重载</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 100px; text-align: center;">操作符 </td>
<td style="width: 180px; text-align: center;">对应方法 </td>
<td style="text-align: center;">适用于</td>
</tr>
</thead>
<tbody>
<tr>
<td>a + b</td>
<td>a.plus(b)</td>
<td>Number, String,StringBuffer, Collection,Map, Date, Duration</td>
</tr>
<tr>
<td>a – b </td>
<td>a.minus(b) </td>
<td>
<p>Number, String, List, Set, Date, Duration </p>
<pre class="crayon-plain-tag">"Alex Wong" - 'Alex ' == 'Wong'</pre>
</td>
</tr>
<tr>
<td>a * b </td>
<td>a.multiply(b) </td>
<td>Number, String, Collection </td>
</tr>
<tr>
<td>a / b</td>
<td>a.div(b) </td>
<td>Number </td>
</tr>
<tr>
<td>a % b </td>
<td>a.mod(b) </td>
<td>Integral number </td>
</tr>
<tr>
<td>a++ (++a)</td>
<td>a.next() </td>
<td>Iterator, Number, String, Date, Range </td>
</tr>
<tr>
<td>a-- (--a) </td>
<td>a.previous() </td>
<td>Iterator, Number, String, Date, Range </td>
</tr>
<tr>
<td>-a </td>
<td>a.unaryMinus() </td>
<td>Number, ArrayList </td>
</tr>
<tr>
<td>+a </td>
<td>a.unaryPlus() </td>
<td>Number, ArrayList </td>
</tr>
<tr>
<td>a ** b </td>
<td>a.power(b) </td>
<td>Number </td>
</tr>
<tr>
<td>a | b </td>
<td>a.or(b) </td>
<td>Number, Boolean, BitSet, Process </td>
</tr>
<tr>
<td>a &amp; b </td>
<td>a.and(b) </td>
<td>Number, Boolean, BitSet </td>
</tr>
<tr>
<td>a ^ b </td>
<td>a.xor(b) </td>
<td>Number, Boolean, BitSet </td>
</tr>
<tr>
<td>~a </td>
<td>a.bitwiseNegate() </td>
<td>Number, String（返回正则式） </td>
</tr>
<tr>
<td>a[b] </td>
<td>a.getAt(b) </td>
<td>Object, List, Map, CharSequence, Matcher </td>
</tr>
<tr>
<td>a[b] = c</td>
<td>a.putAt(b, c)</td>
<td>Object, List, Map, StringBuffer</td>
</tr>
<tr>
<td>a &lt;&lt; b</td>
<td>a.leftShift(b)</td>
<td>integral number, StringBuffer, Writer, File, Socket, List</td>
</tr>
<tr>
<td>a &gt;&gt; b</td>
<td>a.rightShift(b)</td>
<td>Number</td>
</tr>
<tr>
<td>a &gt;&gt;&gt; b</td>
<td>a.rightShiftUnsigned(b)</td>
<td>Number</td>
</tr>
<tr>
<td>a == b</td>
<td>a.equals(b)</td>
<td>Object</td>
</tr>
<tr>
<td>a != b</td>
<td>!(a == b)</td>
<td>Object</td>
</tr>
<tr>
<td>a &lt;=&gt; b</td>
<td>a.compareTo(b)</td>
<td rowspan="5">java.lang.Comparable</td>
</tr>
<tr>
<td>a &gt; b</td>
<td>a.compareTo(b) &gt; 0</td>
</tr>
<tr>
<td>a &gt;= b</td>
<td>a.compareTo(b) &gt;= 0</td>
</tr>
<tr>
<td>a &lt; b</td>
<td>a.compareTo(b) &lt; 0</td>
</tr>
<tr>
<td>a &lt;= b</td>
<td>a.compareTo(b) &lt;= 0</td>
</tr>
<tr>
<td>a as type</td>
<td>a.asType (typeClass)</td>
<td>Any type</td>
</tr>
</tbody>
</table>
<p>自己实现操作符重载时，可以重载上述方法，以便针对具体类型实现二元操作符，例如： 
<pre class="crayon-plain-tag">class Currency
{
    int rate
    int amount
    Currency(int rate,int amount)
    {
        this.rate = rate
        this.amount = amount
    }

    Currency plus(Currency c)
    {
        //注意这里体现了Groovy的类型转换原则：返回一个更一般的类型
        def add = (amount + c.amount*c.rate / rate) as int
        return new Currency(rate, add)
    }
    Currency plus(Integer c)
    {
        return new Currency(rate, amount + c)
    }
    @Override
    public String toString()
    {
        return amount
    }
}

def usd = new Currency(6, 60 )
def cny = new Currency(1, 60 )
println usd + cny
println usd + 60</pre>
<div class="blog_h2"><span class="graybg">内置数据类型</span></div>
<p>Groovy在语言层面支持一套数据类型，包括<span style="background-color: #c0c0c0;">字符串、正则式、数字、范围、列表、映射</span>等。此外，不像Java，Groovy不存在所谓基本类型。 </p>
<div class="blog_h3"><span class="graybg">万物皆对象</span></div>
<p>在groovy中，万事万物皆对象，这与Java不同，后者包含若干基本类型（int/double/char/byte），基本类型是<span style="background-color: #c0c0c0;">专有类型</span>，其它类型是<span style="background-color: #c0c0c0;">引用类型</span>。 这些专有类型：</p>
<ol>
<li>其变量存放了数据本身，而不是数据的地址</li>
<li>不能应用面向对象的编程风格——方法调用、属性导航。不能从java.lang.Object继承方法</li>
<li>能应用多种运算符，而对应的引用类型则基本不能应用这些运算符</li>
</ol>
<div class="blog_h3">数字类型与自动拆装箱</div>
<p>Groovy扩展了Java数字类型的直接量表示：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">Java类型</td>
<td style="text-align: center;">直接量语法</td>
</tr>
</thead>
<tbody>
<tr>
<td>java.math.BigInteger</td>
<td><pre class="crayon-plain-tag">612g,1106G</pre>  </td>
</tr>
</tbody>
<tbody>
<tr>
<td>java.math.BigDecimal</td>
<td><pre class="crayon-plain-tag">3.14, 6.02E23, 94605.56G</pre></td>
</tr>
</tbody>
</table>
<p>Groovy调用Java方法时，数字类型的拆装箱完全自动进行。</p>
<div class="blog_h3">可选（动态）类型</div>
<p>Groovy的变量声明可以没有类型信息，以<pre class="crayon-plain-tag">def</pre> 开头声明的对象，称为动态类型，动态类型<span style="background-color: #c0c0c0;">指向的对象的类型可以动态变化</span>： </p>
<pre class="crayon-plain-tag">def dynaVar = 10G
//改变对象类型
dynaVar = "$dynaVar"

//如果声明为静态类型，则不能改变
BigDecimal bd = 10G
bd = "" // 报错：Cannot cast object '' with class 'java.lang.String' to class 'java.math.BigDecimal'</pre>
<div class="blog_h3">字符串</div>
<p>Groovy引入了<pre class="crayon-plain-tag">groovy.lang.GString </pre> 类型，用来支持占位符替换。Groovy支持多种字符串表示形式：</p>
<ol>
<li>单引号包围的字符串：不支持占位符替换，相当于Java的字符串。如果要表示<pre class="crayon-plain-tag">char</pre> 类型，则必须静态声明或者使用<pre class="crayon-plain-tag">as</pre> 操作符Cast</li>
<li>双引号包围的字符缓存：支持占位符变量替换，占位符形式为：<pre class="crayon-plain-tag">$varName</pre> 或<pre class="crayon-plain-tag">${obj.propName}</pre> </li>
<li>三引号包围的字符串：支持多行。如果是三个双引号，则支持占位符替换</li>
<li>斜杠包围的字符串：不需要对反斜杠进行转义，主要用于正则式</li>
</ol>
<p>Groovy为字符串引入了一些新方法，包括：<pre class="crayon-plain-tag">toInteger, toLong, toFloat, toDouble</pre> 等。</p>
<p>可以对字符串进行追加、修改等操作：</p>
<pre class="crayon-plain-tag">greeting = 'Hello'
greeting &lt;&lt;= ' Groovy' //可以对字符串进行内容追加
//追加后自动转换为StringBuffer
assert greeting instanceof java.lang.StringBuffer
greeting &lt;&lt; '!'
assert greeting.toString() == 'Hello Groovy!' //不会自动转回String，因此需要调用toString()
greeting[1..4] = 'i' //可以用这种语法替换范围指定的子串
assert greeting.toString() == 'Hi Groovy!'</pre>
<div class="blog_h3"><span class="graybg">正则表达式</span></div>
<p>Groovy引入了三个操作符，专用于正则式处理 </p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">操作符</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>=~</td>
<td>正则式匹配操作符</td>
</tr>
<tr>
<td>==~ </td>
<td>正则式匹配整串操作符 </td>
</tr>
<tr>
<td>~String</td>
<td>正则式模式（Pattern）操作符，把字符串转换为java.util.regex.Pattern类型</td>
</tr>
</tbody>
</table>
<p>下面是一些示例代码：</p>
<pre class="crayon-plain-tag">import java.util.regex.Matcher;

def pattern = /W.ng/
def name = 'Alex Wong Wang'
def lname = 'Wang'

//完整匹配，返回值是一个Boolean对象
matcher =  lname ==~ pattern
assert matcher instanceof Boolean

//子串匹配，返回值是一个matcher对象
matcher = ( name =~ pattern )
assert matcher instanceof Matcher
//支持使用索引语法访问匹配的子串
assert matcher[0] == 'Wong'
assert matcher.count == 2

//预编译正则式
pattern = ~/W.ng/
pattern.matcher( 'Alex Wong' )[0]</pre>
<div class="blog_h3"><span class="graybg">数字处理和自动转型</span></div>
<p>Groovy中的数字处理更加直观和符合预期，例如<pre class="crayon-plain-tag">1/2</pre> 的结果会是0.5而非0。</p>
<p>在Groovy中，<pre class="crayon-plain-tag">+ - *</pre> 运算满足下列规则：</p>
<ol>
<li>如果有一个数为Float或者Double，那么结果是Double</li>
<li>否则，如果一个操作数为BigDecimal，结果为BigDecimal</li>
<li>否则，如果一个操作数为BigInteger，结果为BigInteger</li>
<li>否则，如果一个操作数为Long，那么结果为Long</li>
<li>否则，结果为一个Integer</li>
</ol>
<p>对于<pre class="crayon-plain-tag">/</pre> 运算：</p>
<ol>
<li>如果任何一个数是Float（或者Double）类型，那么运算结果为Double类型</li>
<li>否则，结果为BigDecimal类型，精度以两个数的精度值较大的为准，采用四舍五入的方式，结尾没有无效的0</li>
<li>整数除法可以使用<pre class="crayon-plain-tag">as</pre> 转换，或者使用<pre class="crayon-plain-tag">intdiv()</pre> 方法</li>
</ol>
<p>当运算结果超过类型表示范围后，<span style="background-color: #c0c0c0;">不会自动提升类型</span>，<span style="background-color: #c0c0c0;">幂运算除外</span>。幂运算根据需要依<pre class="crayon-plain-tag">Integer,Long,Double</pre> 的次序提升。</p>
<div class="blog_h3"><span class="graybg">范围（Range）</span></div>
<p> Groovy引入一个groovy.lang.Range类，表示一个连续的对象范围，并在语法级别上支持范围的直接量：</p>
<pre class="crayon-plain-tag">left = 0
right = 10
Range r = left..right
r = (left..right)
//小于号表示右排除范围，即范围不包含right这个值
r = (left..&lt;right)
//起始值大于结束值的范围称为“反向范围”
r = 10..0</pre>
<p><span style="background-color: #c0c0c0;">范围的元素不一定是数字</span>。 日期、字符也可以：</p>
<pre class="crayon-plain-tag">assert ('a'..'d').size() == 4

def today = new Date()
def tomorrow = today + 1
assert (today..tomorrow).size() == 2</pre>
<p>Range的<pre class="crayon-plain-tag">toList()</pre> 方法用于生成对应的列表。</p>
<p>可以使用下面的方式遍历Range：</p>
<pre class="crayon-plain-tag">//遍历Range
for ( day in today..tomorrow) println day
//使用闭包遍历
(today..tomorrow).each { day -&gt; println day }</pre>
<div class="blog_h3"><span class="graybg">列表（List）</span></div>
<p>Groovy把Java数字和List的优点融合到了一起：基于索引的操作、动态增减元素。Groovy在语法上修改了List直接量的声明方式：</p>
<pre class="crayon-plain-tag">list = [1, 2, 3]</pre>
<p> Groovy缺省使用的List实现是<pre class="crayon-plain-tag">java.util.ArrayList</pre> ，你可以显示声明为其它类型。 Groovy为List重载了<pre class="crayon-plain-tag">[]</pre> 操作符，支持多种下标访问风格：</p>
<pre class="crayon-plain-tag">list = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
//基于Range的范围访问
assert list[2..4]  == [2, 3, 4] //getAt
//基于索引集合的访问
assert list[2, 3, 4]  == [2, 3, 4]

list[2..4]  = [2, 3, 4] //setAt 设置元素
list[5..9] = []  //移除元素
println list // [0, 1, 2, 3, 4]
list[1..&lt;1] = [9, 9, 9] //在索引1上插入多个元素
println list // [0, 9, 9, 9, 2, 3, 4]</pre>
<p>List支持多种操作符：</p>
<pre class="crayon-plain-tag">list = []
list += 1 // [1]
list += [2, 3] // [1, 2, 3]
list &lt;&lt; 4 &lt;&lt; 5 // [1, 2, 3, 4, 5]
list -= [1, 2, 3, 4] // [5]
list * 3 // [5, 5, 5]</pre>
<div class="blog_h3"><span class="graybg">映射（Map）</span></div>
<p>与List类似，Groovy也从语法上支持Map。和其它语言使用的花括号不同，Groovy用方括号界定Map：</p>
<pre class="crayon-plain-tag">map = [
    'Alex' : 'Wong',
    'Meng' : 'Lee'
]</pre>
<p>如果要声明一个空的Map，使用<pre class="crayon-plain-tag">[:]</pre>  。Groovy默认使用java.util.HashMap。由于Map的Key往往是字符串，因此，Key的字符串标记可以省略：<pre class="crayon-plain-tag">assert ['a':1] == [a:1]</pre> ，但是Key不能是Groovy关键字或者包含特殊符号。</p>
<p>除了数组语法外，还可以使用点号导航来访问Map元素：</p>
<pre class="crayon-plain-tag">myMap = ['a.b':1] 
assert myMap.'a.b' == 1</pre>
<div class="blog_h3"><span class="graybg">闭包</span></div>
<p>所谓闭包（Closure），就是<span style="background-color: #c0c0c0;">绑定了上下文中变量的匿名函数</span>。闭包在很多语言中都可以见到，但是在Java领域，直到Java 8才通过Lambda语法提供闭包特性。Groovy引入<pre class="crayon-plain-tag">groovy.lang.Closure</pre> 表示闭包，并在语法层面上支持闭包的声明。闭包是普通对象，可以被传递。声明一个闭包很简单：</p>
<pre class="crayon-plain-tag">list = [1, 2, 3]
counter = 0
// 闭包语法： {入参列表 -&gt; 语句列表}
Closure  c = {val -&gt; counter++; ++val} //闭包的最后一个表达式，作为默认的返回值，任何时候可以显式的return
list.each c

// 匿名的闭包
list.each {val -&gt; ++val}
// 小箭头也可以省略
def c = { println it }

// 作为参数的闭包，可以使用注解说明其入参类型，这些注解主要供IDE的智能提示
static int shell(@ClosureParams( value = SimpleType.class, options = "java.io.Reader" ) Closure stdoutReader){}
// 多参数闭包入参类型提示
class Lists {
  static &lt;T,U&gt; List&lt;T&gt; randomInstances(List&lt;U&gt; listToIterate, final Builder&lt;T&gt; builder, 
        @ClosureParams(value = FromString, options = ["T,U", "T"]) final Closure&lt;T&gt; postProcessor = null) {
    }
  }
}

// 闭包的返回值，可以在其泛型参数中声明
public abstract class Closure&lt;V&gt; extends GroovyObjectSupport{}</pre>
<p>注意，闭包具有访问上下文中变量的能力（上面第4行），这意味着上下文变量<span style="background-color: #c0c0c0;">以引用的形式（而不是值）</span>传递给闭包。特别是，<span style="background-color: #c0c0c0;">即使上下文变量的Scope已经终结，被闭包引用的那些变量依然存在</span>：</p>
<pre class="crayon-plain-tag">def genCloseure(){
    def i = 10
    return {println i++}
}
def c = genCloseure() //i作为局部变量，正常情况下应该在调用完毕即销毁
c() // 10，i还存活着
c() // 11</pre>
<p>在闭包中，关键字：</p>
<ol>
<li><pre class="crayon-plain-tag">this</pre> 表示声明闭包的对象或者类</li>
<li><pre class="crayon-plain-tag">owner</pre> 表示直接包含闭包的对象或类</li>
<li><pre class="crayon-plain-tag">delegate</pre> 默认值为owner，该变量用于重新定位闭包体中引用的上下文变量，将它们解析为自己的属性/方法</li>
</ol>
<p><a id="diff-between-closure-props"></a>下面的例子说明这三者的关系：</p>
<pre class="crayon-plain-tag">package cc.gmem.study.groovy
def cc = { // 外层闭包
    return {  // 内层闭包
        if (printhis){
            println this      //当前对象cc.gmem.study.groovy.GroovyStudy@4485af5c
            println owner     //外层闭包cc.gmem.study.groovy.GroovyStudy$_run_closure1@15b734b
            println delegate  //默认就是owner，可以运行时替换
        }else{
            println name      //自动解析的上下文变量
        }
    }
}
def c = cc()

printhis = true
c()
printhis = false

// 修改delegate对象
c.delegate = [name : 'DelegateObject']
c() // 打印DelegateObject
c.name = 'InnerClosure' //设置内层闭包的属性
/**
 * 下面两行打印：
 * name of inner closure is InnerClosure
 * name of enclosing closure is InnerClosure
 * name of current object is InnerClosure
 * 可以看到，设置闭包的xxx属性，那么owner.xxx也被设置，且这一过程会递归进行
 * 在闭包里，this.xxx和owner.xxx是同义词，但是this和owner则可能不是一个对象
 */
println "name of inner closure is $c.name"
println "name of enclosing closure is $cc.name"
println "name of current object is $name"

c() // 打印InnerClosure</pre>
<p>设置闭包的<pre class="crayon-plain-tag">resolveStrategy</pre> 属性可以改变默认的上下文变量搜索规则：</p>
<ol>
<li><pre class="crayon-plain-tag">OWNER_FIRST</pre> ：默认，优先搜索owner</li>
<li><pre class="crayon-plain-tag">OWNER_ONLY</pre> ：仅搜索owner</li>
<li><pre class="crayon-plain-tag">DELEGATE_FIRST</pre> ：优先搜索delegate</li>
<li><pre class="crayon-plain-tag">DELEGATE_ONLY</pre> ：仅搜索delegate</li>
<li><pre class="crayon-plain-tag">TO_SELF</pre> ：owner、delegate都不搜索</li>
</ol>
<p>当然，你随时可以使用<pre class="crayon-plain-tag">this</pre> 、<pre class="crayon-plain-tag">owner</pre> 、<pre class="crayon-plain-tag">delegate</pre> <span style="background-color: #c0c0c0;">显式的访问特定上下文对象</span>的属性，不受上述搜索规则限制。此外，<span style="background-color: #c0c0c0;">局部变量的优先级比上面的上下文对象高</span>：</p>
<ol>
<li>当前闭包自己定义的局部变量优先级最高</li>
<li>定义了当前闭包的函数/闭包——即owner——其局部变量的优先级次之。这是闭包的重要特点：<span style="background-color: #c0c0c0;">在声明时绑定（back reference）owner定义的局部变量</span></li>
<li>多层函数/闭包嵌套时，越外层的局部变量，优先级越低</li>
<li>找不到局部变量，则在this、owner、delegate等上下文对象中寻找属性</li>
</ol>
<p>如果闭包只有一个参数，那么<span style="background-color: #c0c0c0;">不需要显式声明</span>，Groovy允许<span style="background-color: #c0c0c0;">使用<pre class="crayon-plain-tag">it</pre>  来访问唯一入参</span>。</p>
<p>可以把对象方法（不支持静态方法）作为闭包引用：<pre class="crayon-plain-tag">def closure = reference.&amp;methodName</pre> 。Groovy支持在<span style="background-color: #c0c0c0;">运行时进行重载闭包引用</span>：</p>
<pre class="crayon-plain-tag">//方法闭包限制在实例方法上
class Lad{
    def sayHello(){
        sayHello('Hello')
    }
    def sayHello(def hello){
        println hello
    }
}
def c = new Lad().&amp;sayHello //c引用哪个方法在此并不确定

// 运行时重载示意：
// 闭包支持直接“调用”，这又是操作符重载的威力。相比之下Java 8 Lambda则不能直接“调用”
c()     // 零参版本
c('Hi') // 一参版本</pre>
<p>Groovy为闭包提供了以下重要方法：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>call()</td>
<td>调用闭包，与()操作符一样 </td>
</tr>
<tr>
<td>curry()</td>
<td>
<p>返回当前闭包绑定了若干参数（从左向右）的副本闭包：</p>
<pre class="crayon-plain-tag">def add = {x, y -&gt; return x + y}
def addOne = add.curry( 1 ) //绑定第一个参数
println addOne(100) // 101</pre>
</td>
</tr>
<tr>
<td>isCase()</td>
<td>
<p>闭包实现了该方法，可以作为switch语句的分类器使用：
<pre class="crayon-plain-tag">def i = 10
switch(i)
{
    //闭包作为分类器
    case { it%2 == 0 }: println true; break
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">注解</span></div>
<p>在Groovy中可以和Java一样，声明和使用注解。下表列出重要的内置注解：
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 20%; text-align: center;">注解</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>@Immutable</td>
<td>
<p>标记一个类型为不变类型：为class和所有字段添加final标记，对于那些本身可变的对象，Groovy会强制defensive copying。例如：</p>
<pre class="crayon-plain-tag">import groovy.transform.Immutable
@Immutable class ClassRoom
{
    Map&lt;Integer,String&gt; students = new HashMap();
}
ClassRoom cr = new ClassRoom();

//Caught: java.lang.UnsupportedOperationException
cr.students.put( 020, "Alex Wong" );</pre>
</td>
</tr>
<tr>
<td>@Grab</td>
<td>
<p>在脚本中显式的声明依赖，使其自包含。
<pre class="crayon-plain-tag">@Grab('commons-lang:commons-lang:2.4')
import org.apache.commons.lang.ClassUtils</pre>
<p> IDE或者Maven、Ivy等工具负责识别和定位依赖</p>
</td>
</tr>
<tr>
<td>@Category</td>
<td>
<p>用于声明一个Category，注解参数说明this指向的类型：</p>
<pre class="crayon-plain-tag">@Category(Integer)
class IntegerMarshal
{
    String marshal()
    {
        toString()
    }
}
use (IntegerMarshal)
{
    assert 1.marshal() == "1"
}</pre>
</td>
</tr>
<tr>
<td>@Mixin</td>
<td>
<p>轻松的把其他类定义的方法添加到某个类中：
<pre class="crayon-plain-tag">@Mixin(MessageFeature)
class Test
{
    void testWithMixinUsage()
    {
        send "Called from Test"
    }
}
class MessageFeature
{
    void send(String msg)
    {
    }
}</pre>
<p>该特性自2.3开始已经废弃，由<pre class="crayon-plain-tag">trait</pre> 代替</p>
</td>
</tr>
<tr>
<td>@Delegate</td>
<td>
<p>自动添加被代理对象的全部行为到当前类：</p>
<pre class="crayon-plain-tag">class CountingList
{
    int counter = 0
    //获得list的全部行为，即，默认的把所有调用转发给list
    @Delegate LinkedList list = new LinkedList&lt;&gt;()
    //覆盖下面的行为
    boolean addAll(Collection&lt;? extends E&gt; c)
    {
        counter += c.size()
        list.addAll(c)
    }
    boolean addAll(int index, Collection&lt;? extends E&gt; c)
    {
        counter += c.size()
        list.addAll(index, c)
    }
}</pre>
<p>在实现装饰、代理等模式时，可以避免写大量重复性代码</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">程序控制结构</span></div>
<div class="blog_h3"><span class="graybg">布尔值转换规则</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">运行时数据类型</td>
<td style="text-align: center;">何时作为true处理</td>
</tr>
</thead>
<tbody>
<tr>
<td>Boolean </td>
<td>如果对应的布尔值为true </td>
</tr>
<tr>
<td>Matcher</td>
<td>如果存在至少一个匹配 </td>
</tr>
<tr>
<td>Collection</td>
<td>如果集合非空</td>
</tr>
<tr>
<td>Map</td>
<td>如果映射非空</td>
</tr>
<tr>
<td>String, GString</td>
<td>如果字符串非空</td>
</tr>
<tr>
<td>Number, Character</td>
<td>如果值为非0</td>
</tr>
<tr>
<td>其它所有类型</td>
<td>如果为非null</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">分支结构</span></div>
<p>需要注意的是，在if语句中，赋值不能作为顶级表达式。这样会导致编译错误：<pre class="crayon-plain-tag">def var ; if( var = 1 ){}</pre> 。除了上述布尔值转换规则以外，Groovy的if/else语句完全一样。三元操作符也被支持，和Java的三元操作符类似。</p>
<p>但是，Groovy<span style="background-color: #c0c0c0;">优雅的增强</span>了Java中的switch-case语句。其引入了灵活分类器机制——任何实现了<pre class="crayon-plain-tag">isCase()</pre> 方法的对象，都可以作为<pre class="crayon-plain-tag">case</pre> 子句的分类器，而Java中仅仅支持常量作为分类器：</p>
<pre class="crayon-plain-tag">switch (candidate)
{
    case classifier1 : handle1() ; break
    case classifier2 : handle2() ; break
    default : handleDefault()
}
//上面的语句等价于：
if (classifier1.isCase(candidate)) handle1() 
else if (classifier2.isCase(candidate)) handle2() 
else handleDefault()</pre>
<p>只要isCase()返回true，那么当前case就匹配。Groovy中下面的类实现了isCase()： </p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">类型</td>
<td style="text-align: center;">实现方式 </td>
</tr>
</thead>
<tbody>
<tr>
<td>Object</td>
<td>a.equals(b) </td>
</tr>
<tr>
<td>Class</td>
<td>a.isInstance(b)</td>
</tr>
<tr>
<td>Collection</td>
<td>a.contains(b)</td>
</tr>
<tr>
<td>Range</td>
<td>a.contains(b)</td>
</tr>
<tr>
<td>Pattern</td>
<td>a.matcher(b.toString()).matches()</td>
</tr>
<tr>
<td>String</td>
<td>(a==null &amp;&amp; b==null) || a.equals(b)</td>
</tr>
<tr>
<td>Closure</td>
<td>a.call(b)</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">循环结构</span></div>
<p>Groovy中没有Java的<pre class="crayon-plain-tag">do{}while(condition)</pre> 或者<pre class="crayon-plain-tag">for(;;)</pre> 循环语法。支持<pre class="crayon-plain-tag">while(condition)</pre> 语法，并且引入<pre class="crayon-plain-tag">for (variable in iterable) { body }</pre> 语法（与Java5引入的新循环语法类似）。此外，基于闭包的循环也很常用：</p>
<pre class="crayon-plain-tag">def list = [0, 1, 2, 3, 4]

//while循环，注意任何对象都可以作为测试条件
while(list) list.remove( 0 ) //移除直到为空
//for循环
for(item in list) println item 

//下面的for循环与Java的for(int i = 0;i &lt; 10; i++)类似
for(i in 0..&lt;10) println i

//基于闭包的循环
(0..&lt;10).each { println it }</pre>
<div class="blog_h2"><span class="graybg">OO编程</span></div>
<div class="blog_h3"><span class="graybg">类、脚本和文件</span></div>
<p>在Groovy中，文件和类声明的关系不像Java那样的固定。一个Groovy文件可以包含多个public类声明：</p>
<ol>
<li>如果.groovy文件中不包含类声明，它被<span style="background-color: #c0c0c0;">作为脚本处理</span>。Groovy会生成一个名称与文件名一致的Script类，脚本内容被包含在<pre class="crayon-plain-tag">run()</pre> 方法中，并且新建一个main方法调用之</li>
<li>如果.groovy文件中只包含一个类声明，并且起名称与文件名相同，那么和Java一样，具有一一对应关系</li>
<li>如果.groovy文件中包含多个不同访问限定符的类，Groovy会分别生成.class</li>
<li>如果.groovy文件中混合了脚本和类声明，脚本代码将变成和文件同名的类被执行。此时声明的类不应当和文件同名</li>
</ol>
<div class="blog_h3"><span class="graybg">增强的导入支持</span></div>
<pre class="crayon-plain-tag">import javax.lang.*
//使用import as可以定义别名，别名可以避免名字冲突，或者避免不合理的名字
import java.lang.AssertionError as AErr </pre>
<div class="blog_h3"><span class="graybg">定义新类型</span></div>
<p>Groovy的类声明语法和Java很类似，但是需要注意：</p>
<ol>
<li>属性、本地变量在使用前必须声明（注意在Groovy脚本中这不是必须的）</li>
<li>支持Java的访问限定符<pre class="crayon-plain-tag">private protected public</pre> ，如果不加限定符：
<ol>
<li>对于成员变量，默认限定符为<pre class="crayon-plain-tag">private</pre>，Groovy会自动生成Getter/Setter方法</li>
<li>对于成员方法，默认作为<pre class="crayon-plain-tag">public</pre> 的</li>
</ol>
</li>
<li>成员变量或者方法返回值可以声明为动态类型，使用关键字def</li>
<li>静态变量或者方法返回值可以声明为动态类型，使用关键字static</li>
<li>方法参数的类型是可选的，如果不指定，默认Object</li>
<li>方法参数可以提供默认值</li>
<li>方法参数可以声明为单个Map，调用时使用<span style="background-color: #c0c0c0;">命名参数</span>方式传递</li>
</ol>
<p>下面是一个类定义的例子：</p>
<pre class="crayon-plain-tag">package cc.gmem.study.groovy
class Person
{
    def name
    def age
    //构造器
    Person(name = 'Alex', age = 29) //参数默认值
    {
        this.name = name
        this.age = age
    }
    def listArgs(List l){ }
    def namedArgs(Map m){ }
}</pre>
<div class="blog_h3"><span class="graybg">使用类和对象</span></div>
<p>这里我们使用前面定义的新类：</p>
<pre class="crayon-plain-tag">def p = new Person()
p.namedArgs( key1 : 0, key2 : 1 ) //命名参数方式传参
p.listArgs( [1, 2, 3])
p.listArgs() //任何参数都是可选的</pre>
<p> 在Groovy中，对象属性/方法的访问，可以使用<span style="background-color: #c0c0c0;">点号导航或者下标语法</span>，甚至，点号导航中可以使用变量：</p>
<pre class="crayon-plain-tag">def methodName = 'listArgs'
new Person()."$methodName"()."$methodName"()</pre>
<p>构造Groovy对象，除了使用Java的构造器调用外，还有更多方式：</p>
<pre class="crayon-plain-tag">//as关键字进行强制转型列表为对象，列表为构造器入参
Person p = ['Alex', 29] as Person
//使用命名参数的方式访问构造器
p = new Person(age : 29, name : 'Alex')
//隐式构造
Person p = ['Alex', 29]</pre>
<div class="blog_h3"><span class="graybg">空指针安全的引号操作符</span></div>
<p>在进行多级点号导航的访问时，一旦路径上的对象为null，就会导致NullPointerException（NPE）的出现。在Java中这需要很繁琐的处理，在入参规格无法确定的情况下，方法实现者可能需要进行多次空指针检查。</p>
<p>Groovy引入特殊的操作符<pre class="crayon-plain-tag">?.</pre> 来允许安全的属性导航——当路径中出现null时，当前表达式的估算中止，并返回null。下面是一个例子：</p>
<pre class="crayon-plain-tag">class Contact{
    class Address{
        class Tel{
            def zoneCode
        }
        Tel tel
    }
    Address address 
}
def c = new Contact()
assert c?.address?.tel?.zoneCode == null</pre>
<div class="blog_h3"><span class="graybg">继承、接口和特征</span></div>
<p>Groovy完整的支持Java的继承和接口机制，并且两者的代码可以几乎完美的交互。</p>
<p>但是Groovy提供了更加动态的能力，你可以把Map或者Closure强制转型为目标接口：</p>
<pre class="crayon-plain-tag">interface Person
{
    def sayHello(String content);
}
interface Greeter extends Person
{
    def getName();
}
// 对于单方法接口，可以从Closure转型得到
Person p = { content -&gt; println content } as Person
// 对于多方法接口，可以从Map转型得到
Greeter g = [
    sayHello : { content -&gt; println content },
    getName : { 'Alex' }
] as Person

// 注意：仅仅调用的方法才需要实现，其它方法不实现也不影响编译</pre>
<p>trait用于代替旧的Mixin机制，实现（而不是仅仅像接口那样声明）可共享的、可组合的行为，和Java 8 的Default method类似：</p>
<pre class="crayon-plain-tag">trait HasId
{
    long id
}
trait HasVersion
{
    long version
}
trait Persistent
{
    boolean save()
    {
        println "saving ${this.dump()}"
    }
}
//trait与接口很类似，但是它可以包含字段、方法实现
trait Entity implements Persistent, HasId, HasVersion
{
    //覆盖父trait的行为
    boolean save()
    {
        version++
        Persistent.super.save()
    }
}
//类可以实现trait，就像可以实现interface一样
class Publication implements Entity
{
    String title
}
class Book extends Publication
{
    String isbn
}</pre>
<div class="blog_h3"><span class="graybg">动态重载</span></div>
<p>我们知道，Java的重载是静态的——调用哪个重载版本在编译期就确定，依据方法入参的声明类型来决定链接到哪个方法：</p>
<pre class="crayon-plain-tag">public class HelloJava
{
    public void m( Object o )
    {
        System.out.println( "Object" );
    }

    public void m( String s )
    {
        System.out.println( "String" );
    }

    public static void main( String[] args )
    {
        HelloJava hj = new HelloJava();
        Object o0 = new Object();
        Object o1 = new String();
        hj.m( o0 ); //Object
        hj.m( o1 ); //Object
    }
}</pre>
<p>Groovy在这一点上与之完全相反，重载方法的选择是<span style="background-color: #c0c0c0;">依据变量的运行时类型，动态决定的</span>：</p>
<pre class="crayon-plain-tag">class HelloGroovy
{
    def m( Object o )
    {
        System.out.println( "Object" );
    }

    def  m( String s )
    {
        System.out.println( "String" );
    }

    static  main( String[] args )
    {
        HelloGroovy hg = new HelloGroovy();
        Object o0 = new Object();
        Object o1 = new String();
        hg.m( o0 ); //Object
        hg.m( o1 ); //String
    }
}</pre>
<div class="blog_h3"><span class="graybg">GroovyBean</span></div>
<p>前面已经提到过，Groovy会默认访问限定符的实例变量自动生成getter/setter。另外需要注意：</p>
<ol>
<li>在类外部，<pre class="crayon-plain-tag">ref.fieldName</pre> 会自动映射为getter/setter调用，要想规避这一点，必须使用特殊操作符：<pre class="crayon-plain-tag">ref.@fieldName</pre> </li>
<li>在类内部，<pre class="crayon-plain-tag">ref.fieldName</pre> 直接访问字段</li>
</ol>
<p>任何类都可以实现<pre class="crayon-plain-tag">getProperty(name)</pre> 和<pre class="crayon-plain-tag">setProperty(name,value)</pre> ，改变Bean属性访问时的默认逻辑。</p>
<div class="blog_h2"><span class="graybg">高级语言特性</span></div>
<div class="blog_h3"><span class="graybg">Gpath</span></div>
<p>类似于Xpath，Gpath是Groovy语言强大的对象导航结构，它引入一个<span style="background-color: #c0c0c0;">展开点操作符</span>：<pre class="crayon-plain-tag">*.</pre> 。</p>
<p>对于列表来说：<pre class="crayon-plain-tag">list*.property</pre> 等价于<pre class="crayon-plain-tag">list.collect{ item -&gt; item?.property }</pre> ，下面是一段示例代码：</p>
<pre class="crayon-plain-tag">println this //当前类：HelloGroovy@14a2f921
println this.class //调用getClass()：class HelloGroovy
println this.class.methods //调用getClass().getMethod()：很长的一个list
//*.操作符，对集合的每个元素调用.name
println this.class.methods*.name
//上述*.操作符可以简写为.
println this.class.methods.name

println this.class.methods.name.grep(~/get.*/).sort() //[getBinding, getClass, getMetaClass, getProperty]</pre>
<div class="blog_h3"><span class="graybg">集合展开（spread）操作符</span></div>
<p>可以使用<pre class="crayon-plain-tag">*</pre> 在当前位置把集合展开：</p>
<pre class="crayon-plain-tag">def sum (a,b,c)
{
    a + b + c
}
assert 6 == sum( *[1, 2, 3])

def range = (1..3)
assert [0, 1, 2, 3] == [0, *range]

def map = [a:1,b:2] 
assert [a:1, b:2, c:3] == [c:3, *:map]</pre>
<div class="blog_h3"><span class="graybg">use关键字</span></div>
<p>Groovy为Object类添加了一个<pre class="crayon-plain-tag">use()</pre> 方法，用于<span style="background-color: #c0c0c0;">扩展/修改一个类的可用实例方法</span>。use的入参是一种被<span style="background-color: #c0c0c0;">称为Category的特殊类</span>，这种类包含一组静态方法，use<span style="background-color: #c0c0c0;">使得这些方法可以作为第一个入参的实例方法使用</span>。下面是一个简单示例：</p>
<pre class="crayon-plain-tag">class StringPlusCategory
{
    static def plus(String self, String operand)
    {
        return self.toInteger() + operand.toInteger()
    }
}
assert '33' == '3' + '3'
//临时改变String的+操作符的行为
use(StringPlusCategory) 
{ 
    assert 6 == '3' + '3' 
}

assert '33' == '3' + '3'</pre>
<p>需要注意的是，use导致的扩展/修改是<span style="background-color: #c0c0c0;">临时的</span>，不会产生全局性的影响。 </p>
<div class="blog_h3"><span class="graybg">调用简写法与DSL</span></div>
<p>如果一个链式调用（chain-of-method）类似：<pre class="crayon-plain-tag">link(producer).to(consumer)</pre> </p>
<p>则Groovy允许你将其编写为：<pre class="crayon-plain-tag">link producer to consumer</pre> 。这非常类似于自然语言的语法，在DSL（领域特定语言）中非常有用。下面是更多的例子：</p>
<pre class="crayon-plain-tag">move(10, forward).painting(color:blue)
//支持多参数调用，支持命名参数
move 10, forward painting color:blue</pre>
<p>Groovy还允许把作为最后一个入参的闭包置于括号外面，有时可以达到很奇特的效果：</p>
<pre class="crayon-plain-tag">def when(expr,closure)
{
    if(expr) closure()
}

//这里好像我们自己发明了新的控制结构一样
when(true)
{
    println 'OK' ;
}</pre>
<div class="blog_h1"><span class="graybg">Groovy动态编程</span></div>
<p>所谓动态编程，是指在运行时修改对象结构（添加状态、改变行为）的编程风格，是动态语言的优势所在。</p>
<div class="blog_h2"><span class="graybg">元对象协议</span></div>
<p>当Groovy调用方法时，它不会直接调用，而是总是把调用请求转交给一个中间层——MOP——处理。MOP由相应的API定义。其一系列规则，这些规则限定了：</p>
<ol>
<li>Groovy运行时如何处理方法调用请求</li>
<li>如何控制中间层</li>
</ol>
<p>为了寻找正确的目标方法，MOP需要很多信息，这些信息存放在元类中。元类可以在运行时改变或者替换，从而改变对象的行为。</p>
<p>即使不改变元类，Groovy也允许通过一系列钩子方法改变对象的行为。</p>
<div class="blog_h2"><span class="graybg">AST转换器</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">@groovy.transform.ToString</td>
<td>生成人类可读的toString()方法</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.EqualsAndHashCode</td>
<td>生成equals()和hashCode()方法</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.TupleConstructor</td>
<td>
<p>生成基于各字段组合的构造函数：</p>
<pre class="crayon-plain-tag">@TupleConstructor(excludes=['lastName'])
class Person {
    String firstName
    String lastName
}</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.MapConstructor</td>
<td>生成入参为map的构造函数，键为字段名</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.Canonical</td>
<td>ToString + EqualsAndHashCode + TupleConstructor</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.InheritConstructors</td>
<td>生成匹配父类构造器的构造器</td>
</tr>
<tr>
<td class="blog_h3">@groovy.lang.Category</td>
<td>简化Category的编写</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.IndexedProperty</td>
<td>
<p>为数组、列表字段生成索引化的Getter/Setter：
<pre class="crayon-plain-tag">class SomeBean {
    @IndexedProperty String[] someArray = new String[2]
    @IndexedProperty List someList = []
}

def bean = new SomeBean()
bean.setSomeArray(0, 'value')
bean.setSomeList(0, 123)</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.lang.Lazy</td>
<td>
<p>自动生成字段的延迟初始化逻辑：
<pre class="crayon-plain-tag">class SomeBean {
    @Lazy LinkedList myField
}

// 等价于
List $myField
List getMyField() {
    if ($myField!=null) { return $myField }
    else {
        $myField = new LinkedList()
        return $myField
    }
}</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.lang.Newify</td>
<td>
<p>增加Python、Ruby风格的构造函数：
<pre class="crayon-plain-tag">@Newify([Tree,Leaf])
class TreeBuilder {
    Tree tree = Tree(Leaf('A'),Leaf('B'),Tree(Leaf('C')))
}

@Newify([Tree,Leaf])
class TreeBuilder {
    Tree tree = Tree.new(Leaf.new('A'),Leaf.new('B'),Tree.new(Leaf.new('C')))
}</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.Sortable</td>
<td>辅助编写Comparable类</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.builder.Builder</td>
<td>
<p>生成Fluent API：
<pre class="crayon-plain-tag">@Builder(builderStrategy=SimpleStrategy)
class Person {
    String first
    String last
    Integer born
}
def p1 = new Person().setFirst('Johnny').setLast('Depp').setBorn(1963)


@Builder(builderStrategy=SimpleStrategy, prefix="")
class Person {
    String first
    String last
    Integer born
}
def p = new Person().first('Johnny').last('Depp').born(1963)</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">类设计注解</span></div>
<p>这些注解用于简化设计模式的实现。
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">@groovy.transform.BaseScript</td>
<td>指定脚本需要继承自特定的自定义脚本，而非groovy.lang.Script</td>
</tr>
<tr>
<td class="blog_h3">@groovy.lang.Delegate</td>
<td>
<p>代理模式：</p>
<pre class="crayon-plain-tag">class Event {
    // 生成一个Date when字段，并将对Date方法的调用转发给when
    @Delegate Date when
    String title
}</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.Immutable</td>
<td>实现不变模式</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.TailRecursive</td>
<td>将尾递归转换为迭代风格，避免栈过深</td>
</tr>
<tr>
<td class="blog_h3">@groovy.lang.Singleton</td>
<td>
<p>实现单例模式：
<pre class="crayon-plain-tag">@Singleton
class GreetingService {
    String greeting(String name) { "Hello, $name!" }
}

assert GreetingService.instance.greeting('Bob') == 'Hello, Bob!'</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">声明式并发</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">@groovy.transform.Synchronized</td>
<td>
<p>实现类似于synchronized的功能，但是可以针对不同对象进行锁定，默认情况下创建$lock、$LOCK（用于静态方法）锁对象
<pre class="crayon-plain-tag">class Counter {
    int cpt
    @Synchronized
    int incrementAndGet() {
        cpt++
    }
}

// 生成
class Counter {
    int cpt
    private final Object $lock = new Object()

    int incrementAndGet() {
        synchronized($lock) {
            cpt++
        }
    }
}</pre>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.WithReadLock  </td>
<td rowspan="2">读写锁  </td>
</tr>
<tr>
<td class="blog_h3"> @groovy.transform.WithWriteLock</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">简化克隆和串行化</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">@groovy.transform.AutoClone</td>
<td>
<p>辅助实现@java.lang.Cloneable接口，可以通过style自动克隆策略：</p>
<ol>
<li>AutoCloneStyle.CLONE，默认，先调用super.clone()，再针对每个可克隆属性调用clone()</li>
<li>AutoCloneStyle.SIMPLE，调用构造器，并拷贝属性</li>
<li>AutoCloneStyle.COPY_CONSTRUCTOR 创建并使用拷贝构造器</li>
<li>AutoCloneStyle.SERIALIZATION，使用串行化机制克隆对象</li>
</ol>
</td>
</tr>
<tr>
<td class="blog_h3">@groovy.transform.AutoExternalize</td>
<td>指定外部化包括/排除的字段</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">编译器指令</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class=" blog_h3">@groovy.transform.Field</td>
<td>
<p>仅仅在脚本的上下文中有意义，将一个变量声明为脚本类的字段，而非run方法的局部变量</p>
<pre class="crayon-plain-tag">@Field def x</pre>
</td>
</tr>
<tr>
<td class=" blog_h3">@groovy.transform.PackageScope</td>
<td>包内可见的字段，不生成属性</td>
</tr>
<tr>
<td class=" blog_h3">@groovy.transform.AutoFinal </td>
<td>提示编译器自动插入final关键字</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">内嵌依赖管理</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">注解</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td class="blog_h3">@Grab</td>
<td>
<p>添加一个依赖：
<pre class="crayon-plain-tag">@Grab(group='org.springframework', module='spring-orm', version='3.2.5.RELEASE')
import org.springframework.jdbc.core.JdbcTemplate</pre>
<p> 也可以使用下面的简写法：</p>
<pre class="crayon-plain-tag">@Grab('org.springframework:spring-orm:3.2.5.RELEASE')</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@GrabResolver</td>
<td>
<p>指定仓库：
<pre class="crayon-plain-tag">@GrabResolver(name='restlet', root='http://maven.restlet.org/')</pre>
</td>
</tr>
<tr>
<td class="blog_h3">@GrabConfig </td>
<td>
<p>使用系统类加载器加载：
<pre class="crayon-plain-tag">@GrabConfig(systemClassLoader=true)
@Grab(group='mysql', module='mysql-connector-java', version='5.1.6')</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">利用钩子方法定制MOP</span></div>
<div class="blog_h3"><span class="graybg">处理缺失的方法和属性</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">钩子方法</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>methodMissing</td>
<td>
<p>调用任何对象不支持的方法，转交给该钩子处理：
<pre class="crayon-plain-tag">class Pretender
{
    Object methodMissing(String name, Object arguments){
        println "Method $name called"
    }
}
def p = new Pretender();
p.hello()</pre>
</td>
</tr>
<tr>
<td>propertyMissing</td>
<td>
<p>与上面类似， 当访问不存在的属性时，交由该钩子处理，下面的例子演示了一个DSL风格的二进制计算器：
<pre class="crayon-plain-tag">def propertyMissing(String name)
{
    int result = 0
    name.each
    {
        result &lt;&lt;= 1
        if (it == 'I') result++
    }
    return result
}
assert IIOI + IOI == IOOIO</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">动态钩子</span></div>
<p>通过把逻辑委托给一个闭包，可以针对一个对象实例（而不是类），在运行时动态修改逻辑：
<pre class="crayon-plain-tag">class DynamicPretender
{
    Closure whatToDo = { name -&gt; "accessed $name"} //默认钩子实现
    def propertyMissing(String name)
    {
        whatToDo(name)
    }
}
def one = new DynamicPretender()
assert one.hello == 'accessed hello'
//修改钩子实现
one.whatToDo = { name -&gt; name.size() }
assert one.hello == 5</pre>
<div class="blog_h3"><span class="graybg">定制GroovyObject的方法</span></div>
<p>所有由Groovy类都实现了下面的接口（继承了<pre class="crayon-plain-tag">GroovyObjectSupport</pre> ）：</p>
<pre class="crayon-plain-tag">public interface GroovyObject
{
    Object invokeMethod(String methodName, Object args);
    Object getProperty(String propertyName);
    void setProperty(String propertyName, Object newValue);
    MetaClass getMetaClass();
    void setMetaClass(MetaClass metaClass);
}</pre>
<p>任何该接口的子类，都遵守下面的规则：</p>
<ol>
<li>对其属性的访问，委托给getter/setter</li>
<li>对任何不存在的方法的调用，委托给invokeMethod()</li>
<li>如果子类实现了GroovyInterceptable，那么对任何方法的调用委托给invokeMethod()</li>
</ol>
<p>缺省适配<pre class="crayon-plain-tag">GroovyObjectSupport</pre> 的方法实现，通过委托调用给对象的元类，应用上述规则：</p>
<pre class="crayon-plain-tag">public Object invokeMethod(String name, Object args)
{
    return getMetaClass().invokeMethod(this, name, args);
}</pre>
<div id="omit-par-0arg-mthd">下面的例子演示了如何通过覆盖GroovyObject的方法，突破“只有非0参方法调用才允许省略括号”的限制：</div>
<pre class="crayon-plain-tag">class NoParens
{
    def getProperty(String propertyName)
    {
        if (metaClass.hasProperty(this, propertyName))
        {
            return metaClass.getProperty(this, propertyName)
        }
        invokeMethod propertyName, null
    }
}
class PropUser extends NoParens
{
    boolean existingProperty = true
}

def user = new PropUser()
assert user.existingProperty
//现在可以免括号调用0参方法了
assert user.toString() == user.toString</pre>
<div class="blog_h2"><span class="graybg">通过元类定制行为</span></div>
<p>上面演示的动态编程方式，都是“侵入性”的——需要修改目标类的源码。Groovy还允许修改元类本身，来实现“非侵入性”的动态编程。</p>
<div class="blog_h3"><span class="graybg">元类简介</span></div>
<p>Groovy为任何一个类<span style="background-color: #c0c0c0;">维护一个元类（metaclass）信息</span>，元类使用类型<pre class="crayon-plain-tag">groovy.lang.MetaClass</pre> 表示。元类持有类的所有属性、方法信息，包括Groovy附加的方法。</p>
<p>一般情况下，类的所有实例共享一个元类，但是Groovy支持通过<pre class="crayon-plain-tag">setMetaClass( metaClass )</pre> 修改单个实例的元类。</p>
<p>你可以很容易的和元类交互，例如：</p>
<pre class="crayon-plain-tag">MetaClass mc = String.metaClass
final Object[] NO_ARGS = []
//检查方法的可用性
assert 1 == mc.respondsTo("toString", NO_ARGS).size()
//检查属性、方法的数量
assert 3 == mc.properties.size()
assert 74 == mc.methods.size()
//检查Groovy动态添加的元方法的数量
assert 176 == mc.metaMethods.size()
//调用方法、静态方法、构造器
assert "" == mc.invokeMethod("","toString", NO_ARGS)
assert null == mc.invokeStaticMethod(String, "println", NO_ARGS)
assert "" == mc.invokeConstructor(NO_ARGS)</pre>
<p>默认情况下，<pre class="crayon-plain-tag">getMetaClass()</pre> 通过元类注册表<pre class="crayon-plain-tag">MetaClassRegistry</pre> 来查找元类，除非你覆盖前述方法的实现。该注册表维护了类到元类的映射关系。</p>
<p>Groovy内置了若干元类实现：</p>
<ol>
<li>默认实现<pre class="crayon-plain-tag">MetaClassImpl</pre> ，大部分情况下使用</li>
<li><pre class="crayon-plain-tag">ExpandoMetaClass</pre> 用于扩展状态和行为</li>
<li><pre class="crayon-plain-tag">ProxyMetaClass</pre> 装饰一个元类，作为拦截器使用</li>
<li>其它内部、测试用的元类</li>
</ol>
<div class="blog_h3"><span class="graybg">使用ProxyMetaClass</span></div>
<pre class="crayon-plain-tag">class InspectMe
{
    def method()
    {
    }
}
def tracer = new TracingInterceptor(writer: new StringWriter())

def proxyMetaClass = ProxyMetaClass.getInstance(InspectMe)
proxyMetaClass.interceptor = tracer //设置拦截器

InspectMe inspectMe = new InspectMe()
inspectMe.metaClass = proxyMetaClass //运行时修改元类

inspectMe.method()
println tracer.writer.toString()</pre>
<div class="blog_h3"><span class="graybg">使用ExpandoMetaClass</span></div>
<p>为元类添加状态/行为后，元类自动成为<pre class="crayon-plain-tag">ExpandoMetaClass</pre> ，类的所有实例获得新的状态/行为：</p>
<pre class="crayon-plain-tag">assert String.metaClass =~ /MetaClassImpl/
//为元类添加方法
String.metaClass.low = {-&gt; delegate.toLowerCase() } //闭包的delegate自动指定指向当前对象
assert String.metaClass =~ /ExpandoMetaClass/ //元类已经自动修改
assert "ALEX".low() == "alex"</pre>
<p>使用<pre class="crayon-plain-tag">Expando</pre> 可以在对象级别上动态添加状态/行为：</p>
<pre class="crayon-plain-tag">class Person extends Expando
{}
def person = new Person()
person.name = 'Alex'
person.sayHello = { -&gt; println 'Hello ' + delegate.name }
person.sayHello()</pre>
<div class="blog_h3"><span class="graybg">修改元类</span></div>
<pre class="crayon-plain-tag">//修改元类，同时添加多个状态/行为
String.metaClass
{
    shift = -1
    low {-&gt; delegate.toLowerCase() }
}
assert "".shift == -1
assert "ALEX".low() == 'alex'

//修改元类，添加静态成员
Integer.metaClass.static.answer = {-&gt; 42}
assert Integer.answer() == 42</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/groovy-study-note">Groovy学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/groovy-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
