How to Use Your Custom Gradle Plugin

Gradle Plugin for MyBatis Generator一文中描述了如何自定义一个插件,下面我们就应该考虑该如何使用我们的插件了。相信大家都知道通过apply plugin:插件名来使用插件,但是要使用自定义插件的话,有一个问题就是Gradle脚本去哪里找我们自定义的插件。我们可以通过下面三种方式来使用自定义插件:
1. 将插件和Gradle脚本放在同一个文件里
2. 上传到Maven Repository里去
3. 上传到Github上
讲插件的定义代码放在build.gradle文件里,这个没有什么好讲的,在插件里直接通过apply就可以。但是我们自定义插件的目的就是为了让插件能够被其他项目使用到,要是放在build.gradle文件里就不能被其他项目使用了,也就失去了我们自定义插件的意义了,所以我们着重讲下后面两种情况。

上传到Maven Repository

Gradle天然和Maven融合的很好,除了可以使用Maven Repository里的依赖,还可以在Gradle里使用在Maven Repository的插件。说到Maven Repository,想必大家都知道有本地和远程两种Maven Repository之分。在上传时上没有什么不一样的配置,只是使用的时候,本地的Maven Repository只能在本机上使用,而远程的可以给整个公司乃至全球的人使用。
要上传到Maven Repository,我们首先要在自定义插件项目的build.gradl文件里使用maven插件:

1
apply plugin: 'maven'

这样我们执行gradle install就能把插件安装到Local Maven Repository的默认目录(一般在home下的.m2文件下),在使用插件的Gradle脚本里,通过如下的方式引用:

Local Maven Repository插件的使用
1
2
3
4
5
6
7
8
9
buildscript {
    repositories {
        mavenLocal()
    }
    dependencies {
        classpath('com.rever:mybatis:1.0-SNAPSHOT')
    }
}
apply plugin: 'mybatis'

继续阅读 →

Comments

Gradle Plugin for MyBatis Generator

因为项目原因,最近又重新看了一下MyBatis,发现MyBatis只有Ant和Maven的Generator,没有Gradle的插件。而现在Gradle已经越来越流行,没有Gradle插件怎么行。
编写Gradle插件,可以从头自己实现,也可以调用现有的Ant Task。MyBatis已经有Ant的Generator了,所以我决定不重造轮子,调用Ant的Task就行了。

定义插件Task的类型

编写插件就是为了重用一些Task,比如Gradle的Java插件,就提供了编译、测试以及打包等Task,这样在构建脚本里使用:

1
apply: 'java'

就可以使用这些Task。所以我们自定义插件,就难免要为插件定义Task。Gradle中的Task也是有类型的,比如Copy的Task。自定义Task也可以继承已经有的Task,但是我们这里要使用的Task是Ant提供的,所以我们需要继承ConventionTask。

Task的定义
1
2
3
4
5
6
7
class MyBatisGeneratorTask extends ConventionTask {
    //Define some properties
    @TaskAction
    void executeCargoAction() {
  //Implement the task action
    }
}

申明了Task之后,就需要实现Task的Action了,就是该Task都做哪些事情。我们这里要调用MyBatis的Ant任务,所以需要使用IsolatedAntBuilder。代码如下:

Task的Action实现
1
2
3
4
5
6
7
8
9
services.get(IsolatedAntBuilder).withClasspath(getMyBatisGeneratorClasspath()).execute {
       ant.taskdef(name: 'mbgenerator',
                       classname: 'org.mybatis.generator.ant.GeneratorAntTask')

       ant.properties['generated.source.dir'] = getTargetDir()
       ant.mbgenerator(overwrite: getOverwrite(),
                          configfile: getConfigFile(),
                          verbose: getVerbose()) {}
}

继续阅读 →

Comments

Get All Books Name From Amazon

到年底了,Training Budget需要报销。在一年里陆陆续续买了些书,但是也有很多书会在最后一起买,因为怕Budget会花在别的地方。
选了一些书之后,下一个任务就是要把书名拷贝到邮件里,一本书这样做当然无妨,但是要书多的话,这就不是一个程序员该做的事情了。因为我是在Amazon买的书,所以下面提供一个从Amazon订单里提取书名的方法:

引入JQuery

去年的Amazon还有JQuery在页面上,但是很不幸,今年Amazon上已经没有JQuery的引用了,那么我们需要从外部引入JQuery。最简单的办法当然就是在Chrome的Console里从Google的CDN加载JQueery,在Console里输入如下的代码:

1
2
3
4
var jq = document.createElement('script');
jq.src = "https://ajax.googleapis.com/ajax/libs/jquery/1.9.0/jquery.min.js";
document.getElementsByTagName('head')[0].appendChild(jq);
jQuery.noConflict();

获取书名

有了JQuery之后,我们就可以使用JQuery牛B的selector选择我们的书名了,script如下:

1
$('.product-title a').each(function(i, e){console.log($(e).text())})

大功告成,书名就都输出在Console里了,拷贝出来,放到邮件即可。 modify

Comments

HBase——Hadoop Introduce

这算是一篇Hadoop的八卦,干货不多。


Hadoop历史

在介绍Hadoop之前,我们先来看看Hadoop的历史,这样能够更好的理解Hadoop的来龙去脉。

Hadoop之父

要说Hadoop的历史,我们不能不先说Hadoop之父Doug Cutting。说到Doug Cutting知道的人可能不多,但是说到他发起的Lucence,可能知道的人就多了。为了能够还清助学贷款以及为以后的生活考虑,Cutting决定开始从事IT相关的工作。1985年,Cutting毕业于美国斯坦福大学,加入Xerox(施乐)公司。说道施乐公司大家未必知道,但是如果告诉大家是施乐发明的东西,大家就会知道了:

  • 第一台PC
  • 最先发明了图形用户界面,乔布斯就是从施乐那里偷的图形用户界面的概念
  • 最先发明了所见即所得文字处理程序
  • 发明了因特网

就可见施乐公司有多么牛b,施乐对Cutting后来研究搜索技术起到了决定性的影响。在施乐的这段时间让他在搜索技术的知识上有了很大提高。他花了四年的时间搞研发,这四年中,他阅读了大量的论文,同时,自己也发表了很多论文,用Cutting自己的话说——“我的研究生是在Xerox读的。” 继续阅读 →

Comments

Smart GCIS

在大数据如此活跃的今天,我们这两天也跟大数据来了一次亲密接触。我们六个人的小组在一天时间里完成了一个简单的产品智能推荐系统,我们称为Smart GCIS

项目结构

我们决定提供Micro-Service,所以在项目里面我们分了好如下几个模块:

  • smart-persistence
  • smart-match
  • smart-recommendation
  • smart-web
    其中smart-persistence提供持久层服务,我们使用Hibernate来访问Mysql;smart-match提供match功能,通过Spring-Batch调用Match引擎来匹配相同的用户;smart-recommendation为客户推荐可能要购买的产品;smart-web提供一个界面用于市场人员查看客户以及其推荐产品,同事smart-web还提供RESTd的Micro-Service。

构建脚本

针对这样的一个多模块项目,我们使用Gradle作为我们项目的构建脚本。

问题

在项目的初期,搭建项目的时候,我犯了一个致命的疏忽。就是在配置多模块的项目时需要一个settings.gradle的文件,我少写了一个s,导致子模块在Idea里不是Java项目。这个废了我不少时间,最后还是dreamhead帮助才发现问题。
我要跟s做斗争

进步

我们在项目里面使用DropWizard作为Micro-Service的web开发,而Dropwizard的一个特点就是它通过一个main函数启动并提供web服务。在DropWizard官方文档里是要让打成jar包,然后通过Java -jar来运行。这样是很方便,但是在开发过程中就不方便了,因为要打Jar包之后运行,才能看见结果。
所以我引入了Gradle的application插件,在smart-web的build.gradle文件中apply这个插件,并且指定要运行的main之后就可以在命令行里运行:

1
gradle :smart-web:run

来运行DropWizard了。不过这里还有个问题就是在运行DropWizard的时候,需要为main函数提供一个server和configuration.yml的参数,但是在gradle :smart-web:run后面加上这两个参数有会报错。好在Gradle的appliation插件提供了相应的配置:

1
2
3
run {
    args 'server', 'src/main/resources/stuart.yaml'
}

这样就解决问题了。 继续阅读 →

Comments

MapReduce in Hadoop

大数据被大家越来越重视,而在大数据的领域里,Hadoop基本上是行业里的事实标准。而MapReduce又是Hadoop中的一个重要特性,这里总结一下MapReduce相关的特性。

MapReduce的Job是如何工作的

客户端提交一个Job到Hadoop的MapReduce之后,Hadoop的JobTracker(Hadoop集群中的Master)会把任务分发到TaskTracker(Hadoop集群中的slave)上,先执行Map Task,再执行Reduce的任务。大体流程如下图所示:
继续阅读 →

Comments

Add cucumber-JVM Into Project With Gradle

众所周知,Cucumber是Ruby的一个用来BDD的测试框架。Cucumber-Java则是Java版的Cucumber,它模拟Ruby里的DSL,使用Annotation创建了一套Java的BDD测试框架。Cucumber-JVM项目中只有通过ant和maven来使用Cucumber-JVM的例子,这里我记录一下Cucumber-JVM如何在Gradle里使用。

为项目引入Cucumber-JVM

首先在build.gradle文件中加入对于Cucumber-JVM的依赖:

1
2
3
4
5
testCompile(
            'info.cukes:cucumber-java:1.1.1',
            'info.cukes:cucumber-junit:1.1.1',
            'junit:junit:4.10'
)

添加task运行Cucumber

添加完stories和steps之后,就可以运行Cucumber-JVM了,看看我们的测试是否通过。虽然在Gradle里文档里说了运行gradle test时,会扫描classpath路径下具有@RunWith annotation的类,并作为JUnit的测试去运行。但是,我在使用的时候(gradlew1.3),运行gradle test找不到标记了@RunWith的JUnit Runner(这个是gradle的一个bug,已经有人报上去了)。
在研究了Cucumber-JVM自带的例子里的ant脚本之后,我通过添加一个task来运行Cucumber-JVM的测试:

cucumber task
1
2
3
4
5
6
7
8
9
10
11
12
13
task cucumber() {
    dependsOn classes
    doLast {
        javaexec {
            main = "cucumber.api.cli.Main"
            classpath configurations.cucumberRuntime
            classpath sourceSets.main.output.classesDir
            classpath sourceSets.test.output.classesDir
            args = ['-f', 'pretty', '--glue', 'cucumber.examples.java.helloworld', 'src/test/resources']

        }
    }
}

  • 这里实际上就是运行Cucumber-JVM提供的Java类cucumber.api.cli.Main去运行@RunWith的JUnit Runner。
  • 需要给cucumber.api.cli.Main类指定classpath:sourceSets.test.output.classesDirsourceSets.main.output.classesDir以及configurations.cucumberRuntime。configurations.cucumberRuntime的配置如下:
1
2
3
4
5
configurations {
    cucumberRuntime {
        extendsFrom testCompile
    }
}

* 通过args指定steps的包(cucumber.examples.java.helloworld)和stories的目录(src/test/resources)

继续阅读 →

Comments

Writing Building Script

在Gradle运行的时候,会创建一个Project的实例。同时每个Script文件也会被编译为一个实现了Script的类,也就意味着在Script接口里声明的属性和方法在你的Script文件里都是有效的。

定义变量

局部变量

Local variables
1
2
3
4
5
   def dest = "dest"
  task copy(type: Copy) {
      from "source"
      into dest
  }

Extra properties

Extra properties
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
apply plugin: "java"

ext {
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}

sourceSets.all { ext.purpose = null }

sourceSets {
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

task printProperties << {
    println springVersion
    println emailNotification
    sourceSets.matching { it.purpose == "production" }.each { println it.name }
}
  • Gradle的每个对象都可以通过ext来定义属性
  • sourceSets.all { ext.purpose = null }sourceSets包含的每个对象设置一个ext.purpose属性(相当于初始化),不要这句的话,build也能正常运行,但是会有Warning
  • sourceSets是Configures the source sets of this project
  • sourceSets.matching { it.purpose == “production” }.each { println it.name }是Groovy语法,匹配上“production”的再遍历

Groovy语法

Property accessor

Groovy自动为属性添加getter和setter方法

Property accessor
1
2
3
4
5
6
7
// Using a getter method
println project.buildDir
println getProject().getBuildDir()

// Using a setter method
project.buildDir = 'target'
getProject().setBuildDir('target')

Optional parentheses on method calls

方法调用的时候括号是可选的

Optional parentheses
1
2
test.systemProperty 'some.prop', 'value'
test.systemProperty('some.prop', 'value')

List

List
1
2
3
4
5
6
7
// List literal
test.includes = ['org/gradle/api/**', 'org/gradle/internal/**']

List<String> list = new ArrayList<String>()
list.add('org/gradle/api/**')
list.add('org/gradle/internal/**')
test.includes = list

Map

Map
1
2
3
4
5
6
// Map literal
apply plugin: 'java'

Map<String, String> map = new HashMap<String, String>()
map.put('plugin', 'java')
apply(map)
  • 这里apply plugin: ‘java’相当于Ruby里的apply plugin=>’java’

    Closures as the last parameter in a method

Closure
1
2
3
4
5
repositories {
    println "in a closure"
}
repositories() { println "in a closure" }
repositories({ println "in a closure" })
Comments

Using the Gradle Command-Line

执行Gradle命令一些用法

  • 一次运行多个task
    gradle dist test
  • 使用-x参数可以指定哪些task不执行
    gradle dist -x test
  • 使用–continue当有错误时继续执行未执行的task
    gradle dist test –continue
  • 使用task的缩写执行task
    gradle cT
    执行compileTest task
  • 使用-b指定build脚本文件
  • gradle projects
  • gradle tasks
  • gradle dependencies
  • gradle properties
  • gradle -m clean compile列出要执行哪些task
Comments

Advance of Task in Gradle

Gradle在执行的时候有三个生命周期:

  1. initialization
  2. configuration
  3. execution

Configuration

configuration一般用于设置Task在运行时所需的变量和数据结构,每次运行Gradle build文件的时候,都会运行一次configuration代码。

Configuration of task
1
2
3
task initializeDatabase
initializeDatabase { print 'configuring ' } 
initializeDatabase { println 'database connection' }

注意这里的configuration块和定义task的块是一样的,不一样的地方就是在task后面没有”<<”活着doLast等。

Task是对象

Gradle为每一个Task都会创建一个具有属性,方法的对象。我们可以控制每个Task对象的访问顺序,类型以及其功能。一般的每个新的Task都是DefaultTask类型(就像Java里的java.lang.Object一样)。DefaultTask没有实际的功能,只提供一些Gradle需要的接口。

DefaultTask的方法

  • dependsOn
不同的方式调用dependsOn
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Declare that world depends on hello
// Preserves any previously defined dependencies as well
task loadTestData {  
  dependsOn createSchema
}

// An alternate way to express the same dependency
task loadTestData {  
  dependsOn << createSchema
}
// Do the same using single quotes (which are usually optional)
task loadTestData {  
  dependsOn 'createSchem
}

// Explicitly call the method on the task object

task loadTestData
loadTestData.dependsOn createSema

// A shortcut for declaring dependencies
task loadTestData(dependsOn: createSchema)
  • doFirst
    可以添加一段在执行task的之前执行的代码,doFirst可以在已经存在的Task上添加代码。当多次定义doFirst的时候,每个代码块都会被累加到doFirst里去,运行的时候是安装先定义的后执行的顺序。

  • doLast
    doLast除了在执行完Task之后执行和doFirst不一样之外,其他都一样。

  • onlyIf
    onlyIf会判断你传入的闭包是否满足条件,如果满足条件就执行Task,否则就不执行。使用onlyIf方法,你可以使用System property来切换Task是否可以被调用。也可以读文件、调用web services、检查安全等来控制。

onlyIf的例子
1
2
3
4
5
6
7
8
9
task createSchema << {
  println 'create database schema'
}
task loadTestData(dependsOn: createSchema) << {
  println 'load test data'
}
loadTestData.onlyIf {
  System.properties['load.data'] == 'true'
}
不传人property执行结果
1
2
3
gradle loadTestData
create database schema
:loadTestData SKIPPED
传人property执行结果
1
2
3
4
5
gradle -Dload.data=true loadTestData
:createSchema
create database schema
:loadTestData
load test data

DefaultTask的属性

  • didWork
    表示Task是否执行完毕的布尔值。不时所有的Task都有didWork这个属性,但是一些内置的Task都有,比如Compile,Copy以及Delete都有didWork这个Property,用于表示是否成功执行完成。
利用compileJava的didWork
1
2
3
4
5
6
apply plugin: 'java'
task emailMe(dependsOn: compileJava) << {
  if(tasks.compileJava.didWork) {
      println 'SEND EMAIL ANNOUNCING SUCCESS'
  }
}
  • enabled
    用于表示Task是否会被执行的布尔值。当被设置为false的时候,task就不会被执行。
设置task的enabled
1
2
3
4
5
6
7
task templates << {
  println 'process email templates'
}
task sendEmails(dependsOn: templates) << {
  println 'send emails'
}
sendEmails.enabled = false

这个build在执行gradle sendEmails的时候,因为sendEmails的enabled设置成了false,所以他不会被执行,但是他依赖的templates会被执行。

  • path 表示Task全路径的字符串。一般的该值就是task的name属性,但是当被其他的build文件调用时,就不一样了。

  • logger Gradle内部logger的引用,它实现了org.slf4j.Logger接口,但是添加了更多额外的logging level(DEBUG, INFO , LIFECYCLE, WARN, QUIET, ERROR)。

logger的level
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
task logLevel << {
  def levels = [
                  'DEBUG',
                  'INFO',
                  'LIFECYCLE',
                  'QUIET',
                  'WARN',
                  'ERROR']
  levels.each { level ->
      logging.level = level
      def logMessage = "SETTING LogLevel=${level}"
      logger.error logMessage
      logger.error '-' * logMessage.size() logger.debug 'DEBUG ENABLED'
      logger.info 'INFO ENABLED'
      logger.lifecycle 'LIFECYCLE ENABLED' logger.warn 'WARN ENABLED'
      logger.quiet 'QUIET ENABLED'
      logger.error 'ERROR ENABLED'
      println 'THIS IS println OUTPUT' logger.error ' '
  }
 }
  • logging
    logging用于访问logger的日志级别,参见上面的例子。

  • description

description的不同设置方式
1
2
3
4
5
6
7
8
9
10
11
12
13
task helloWorld(description: 'Says hello to the world') << {
  println 'hello, world'
}


task helloWorld << {
  println 'hello, world'
}
helloWorld {
  description = 'Says hello to the world'
}
// Another way to do it
helloWorld.description = 'Says hello to the world'
  • temporaryDir
    返回零时目录File的引用

  • 动态Property

Task类型

除了默认的DefaultTask,Gradle也定义了一些通用的task类型。

自定义Task类型

当Gradle提供的Task类型不够用的时候,我们可以自己定义一些我们的Task类型。

Custom Tasks Types in the Build file

description的不同设置方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
task createDatabase(type: MySqlTask) {
  sql = 'CREATE DATABASE IF NOT EXISTS example'
}

class MySqlTask extends DefaultTask {
  def hostname = 'localhost'
  def port = 3306
  def sql
  def database
  def username = 'root'
  def password = 'password'
  @TaskAction
  def runQuery() {
      def cmd
      if(database) {
          cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} ${database} -e "
      } else {
          cmd = "mysql -u ${username} -p${password} -h ${hostname} -P ${port} -e "
      }
      project.exec {
          commandLine = cmd.split().toList() + sql
      }
  }
}
* 自定义Task需要继承自DefaultTask 
* hostname,port等都是Task自定义的属性
* 使用@TaskAction标示一个方法为Task的Action

Custom Tasks in the Source Tree

在project的根目录下建一个buildSrc的文件夹,然后在文件夹里创建groove的源文件,在Gradle运行task的时候,会找该目录下的文件。

Comments