概述
Groovy快速入门
Groovy介绍
Groovy是一种基于JVM(Java虚拟机)的敏捷开发语言,它结合了Python、Ruby和Smalltalk的许多强大的特性,Groovy代码能够与Java代码很好地结合
,也能用于扩展现有代码。由于其运行在 JVM 上的特性,Groovy也可以使用其它非Java语言编写的库
。
Groovy & Java & Kotlin
- Groovy、Java及Kotlin都是基于JVM的开发语言
- Groovy基于Java,在语法上基本相似,但也做了很多自己的扩展。Groovy除了可以面向对象编程,还可以用作纯粹的脚本语言,这一点和Kotlin是一样的
- Goovy 和 Kotlin都有自己支持的DSL,两者有许多共通之处
Groovy特性
- 同时支持静态类型和动态类型
- 支持运算符重载
支持DSL语法特性
- 本地语法列表和关联数组
- 各种标记语言,如
XML和HTML原生支持
对正则表达式的本地支持
- Groovy和Java语法非常相似,可以无缝衔接使用Java
- 支持使用现有的Java库,并做了一定的扩展
Hello, Groovy!
- 编写Groovy,使用Java写法基本是没有问题的。但是Groovy有自己的特性,而且又是一种领域特定语言,会发现Groovy的写法就变得非常的灵活了
- Groovy与Java写法上的一些差异
- 分号可选,除非会导致语法歧义时才使用分号
return语句可选(默认返回最后一个语句)
方法,属性默认都是public的
Groovy不强迫捕获异常,看使用者需求
?.安全导航操作符的使用(kotlin借鉴了这个)
闭包的使用结合DSL语法特性等等
// 1、以java的方式写groovy
class HelloGroovy{
static void main(String[] args) {
for(int i = 0; i< 3 ;i ++){
println(test())
}
}
// 会自动返回最后一个语句,类型会自动判别
def test(){
// return "hello world"
"hello world"
}
}
// 2、以脚本(kotlin)的方式写groovy
3.times {
println("hello world")
}
Groovy基础语法
数据类型
- 基本数据类型
- byte、short、int、long、float、double、char、boolean
- 包装类 (装箱拆箱)
- String、Byte、Short、Integer、Long、Float、Double、Char、Boolean
- 自动装箱
- 因为Groovy具有动态类型特性,所以它从一开始就支持自动装箱。实际上,必要时Groovy会自动将基本类型视作对象
定义变量-动态类型
- Java是一门静态类型的语言,但是也有自己的多态
- 动态类型是一种更高级的多态
- 动态类型放低了对类型的要求,使语言能够根据上下文来判定变量类型
- 使用def关键字定义变量,不过已使用了final (不可变), priviate这样修饰符,def可以省略
class Two {
// 自动变量
def mile
// 只读变量
final year = 2027
// def 可以省略
private month = 12
}
def two = new Two(mile: 1000)
// 因为 year只读,下面这种方式报错
// def twotwo = new Two(mile: 1000, year: 1111)
// 获取自动装箱的类型
println two.mile.class
println two.mile.getClass()
结果
class java.lang.Integer
class java.lang.Integer
字符串
- 单引号字符串是java.lang.String类型,同时不支持插值
- 双引号字符串在没有使用插值表达式情况下是java.lang.String类型,
但如果有插值表达式使用的话,就是groovy.lang.GString类型
- 三引号字符串表示多行的字符串。不必将字符串分割成几块,也不必用连接符或换行符转义字符来将字符串跨行
- 字符串的使用
- 单引号单个字符要表示char类型,需要使用as转换
- ${…} 表达式进行插值,去掉花括号不引起歧义的话,可以去掉
- 可以通过+=, -=操作符添加/减少字符 (会自动匹配)
def name = "carName"
def realName = "$name:AAAA"
println realName
println name.getClass()
println realName.getClass()
结果
carName:AAAA
class java.lang.String
class org.codehaus.groovy.runtime.GStringImpl
// 关于自动装箱
def method(Object obj){
1
}
def method(String str){
2
}
Object o = new Object()
println "method${method(o)} is invoked"
Object obj = "A" // 推断是String类型
println "method${method(obj)} is invoked"
结果
method1 is invoked
method2 is invoked
数组和列表
- 数组和列表都是使用逗号分割列表的值,使用方括号括起来表示
- Groovy中的数组和列可以随意转换
- def定义的变量会自动推断 [ ] 类型是列表
- Groovy列表是普通的JDK java.util.List,因为Groovy中没有定义自己的集合类
// 看起来是数组,实际是ArrayList类型
def array = [1, 2, 3]
println array.getClass()
// 下面这个才是数组类型
int[] array2 = [1, 2, 3]
println array2.getClass()
// 数组的另一个写法
def array3 = [1, 2, 3] as int[]
println array3.getClass()
结果
class java.util.ArrayList
class [I
class [I
数组的自动装箱
String[] strArray = ["1", 2, "3"]
println strArray.getClass()
println strArray[1].getClass()
结果
class [Ljava.lang.String;
class java.lang.String
范围
范围是一种特殊的列表,由序列中的第一个和最后一个值表示,Range可以是包含或排除。包含范围包括从第一个到最后一个的所有值,而独占范围包括除最后一个之外的所有值。也可以使用表达式来表示范围
- 1…10 包含范围的示例
- 1…<10 独占范围的示例 (开区间)
- ‘a’…‘x’ 范围也可以由字符组成
- 10…1 范围也可以按降序排列
- ‘x’…‘a’ 范围也可以由字符组成并按降序排列。同步请求/异步请求实现
(1..3).forEach{
println "hello world : " + it
if(it == 2){
println("2222")
}
}
结果
hello world : 1
hello world : 2
2222
hello world : 3
class groovy.lang.IntRange
映射
- 映射(也称为关联数组,字典,表和散列)是对象引用的无序集合
- Map集合中的元素由键值访问
- Map中使用的键可以是任何类,如果不能推断具体key类型,默认就是字符串
在Groovy中可以使用特定的表述方式来指定映射
- [k1:v1,k2:v2] 具有键值对的集合
- [:] 空映射
def map = [a: 11, b: 12]
map.forEach{k,v ->
println "keyType=${k.class} key=$k, value=$v"
}
println "map['a'] = " + map['a']
结果
keyType=class java.lang.String key=a, value=11
keyType=class java.lang.String key=b, value=12
map['a'] = 11
运算符及控制语句
- Groovy支持运算符重载
- 循环语句
- 除了和Java保持差不多的用法外,还支持结合范围的用来进行循环
- 组合闭包来实现更简化的循环操作
- 条件语句
- 除了和Java保持差不多的用法外,还多了Groovy的一些扩展
- 可以组合闭包实现更灵活的条件语句
Groovy稍微高级语法
Gradle中用到的Groovy最主要的语法
- 闭包
- 元编程(MOP编程)
Integer.metaClass.add1000{
delegate+1000
}
println 250.add1000()
结果
1250
闭包,gradle中有很多这种写法
class Car{
def miles
private year = 2027
def closureTest(def param1, Closure closure){
}
}
new Car().closureTest(1){
}
类与方法 - getter/setter
- 默认会生成getter, setter方法
- 并且可以直接像使用成员变量的方法来自动判断调用getter/setter
- 当进行赋值时调用setter方法
- 当直接访问值时调用的是getter方法
- 使用’.@'才是真正的直接访问变量,跳过默认的getter/setter方法调用
groovy默认会为类中的变量生产getter/setter方法
car.miles = 2000
println car.miles
对变量的赋值和获取,默认是调用的getter/setter方法
Groovy对于private修饰的变量没有强制性
可以重写getter/setter方法(也可以看到privacy
修饰符不起作用)
class Car{
def miles
private year = 2027
def getMiles() {
println "in getMiles"
return miles
}
void setMiles(miles) {
println "in setMiles"
this.miles = miles
}
def getYear() {
println "in getYear"
return year
}
void setYear(year) {
println "in setYear"
this.year = year
}
}
def car = new Car()
car.miles = 2000
println car.miles
car.year = 2028
println car.year
结果
in setMiles
in getMiles
2000
in setYear
in getYear
2028
Groovy中private不被限制
- 所有的变量默认是public的
- 如果要设置为私有禁止直接访问,仅申明private是不行的。依然可以使用’.'直接访问
- 即使把这段代码放入到另外一个package下面也不行
- 重载这个变量的getter/setter方法,并且在调用方法时抛出异常
- Java中如果没有显式的指定访问修饰符(public、protected、private)那么默认是包访问权限,Groovy使用@PackageScope
如何让privacy
修饰符起作用,通过抛出异常(groovy的语言不是为了安全,是为了便捷,解决这种没多大意义
)
class Car{
def miles
private year = 2027
def getMiles() {
println "in getMiles"
return miles
}
void setMiles(miles) {
println "in setMiles"
this.miles = miles
}
def getYear() {
throw new IllegalAccessException("can not get")
println "in getYear"
return year
}
void setYear(year) {
throw new IllegalAccessException("can not set")
println "in setYear"
this.year = year
}
}
def car = new Car()
car.miles = 2000
println car.miles
car.year = 2028
println car.year
不通过getter/setter,直接操作变量
car.@mails = 2000
多种访问get/set方式
Car car = new Car()
car.'miles' = 10000
car['miles'] = 30000
println car.'miles'
def str = 'miles'
car."$str" = 20000
类-创建对象-使用具名参数的坑
构造方法
- 构造方法重载规则跟Java一样
- 但是要注意,如果没有指定具体参数的类型时,默认推断类型是Object
- 在构造方法中传入具名参数,但是要注意:传入的参数都是键值对,实则就是一个Map类型
- 这种方式传入的参数会自动拆解Map并且调用setter方法对应的进行赋值
- 如果参数中还有非键值对的传参,就会把这些键值对当成Map了不会再进行自动拆解赋值。所以要有对应的构造方法才行
正常创建一个对象
class Car{
def miles
private year = 2027
def getMiles() {
println "in getMiles"
return miles
}
void setMiles(miles) {
println "in setMiles"
this.miles = miles
}
def getYear() {
println "in getYear"
return year
}
void setYear(year) {
println "in setYear"
this.year = year
}
}
def car = new Car(miles: 1000, year: 2028)
结果
in setMiles
in setYear
添加一个构造函数,只接收miles
class Car{
def miles
private year = 2027
def getMiles() {
println "in getMiles"
return miles
}
void setMiles(miles) {
println "in setMiles"
this.miles = miles
}
def getYear() {
println "in getYear"
return year
}
void setYear(year) {
println "in setYear"
this.year = year
}
Car(miles){
this.miles = miles
}
}
def car = new Car(miles: 1000, year: 2028)
println car.miles
println car.miles.getClass()
结果
in getMiles
[miles:1000, year:2028]
in getMiles
class java.util.LinkedHashMap
1、可以看到,创建对象时,new Car中传递参数是当做map进行传递的
2、由于 下面构造方式没有指明参数类型,就把miles当做Object对象,接受了传递进来的map对象
Car(miles){
this.miles = miles
}
如果想让只具名参数和只接收一个参数的构造方法生效
- 需要创建一个空的构造方法
- 一个参数的构造方法,必须指定类型,否则还是识别为接收map的object对象
class Car{
def miles
private year = 2027
def getMiles() {
println "in getMiles"
return miles
}
void setMiles(miles) {
println "in setMiles"
this.miles = miles
}
def getYear() {
println "in getYear"
return year
}
void setYear(year) {
println "in setYear"
this.year = year
}
Car(){
}
Car(int miles){
this.miles = miles
}
}
def car = new Car(miles: 1000, year: 2028)
println car.miles
println car.miles.getClass()
def car2 = new Car(miles: 2000)
println car2.miles
println car2.miles.getClass()
结果
in setMiles
in setYear
in getMiles
1000
in getMiles
class java.lang.Integer
in setMiles
in getMiles
2000
in getMiles
class java.lang.Integer
普通类中方法不支持具名参数 - 神经病
class Car {
def miles
def year
void execute(x = 1, y = 2, z=3) {
println "$x $y $z"
}
}
Car c = new Car()
c.execute(x: 1, y: 2, z: 3) // 不支持具名参数,会把传递进来的方法当做map
c.execute(x: 1, y: 2, z: 3, 2222, 3333) // 这个是可以的,正常的(传递了3个参数,一个是map,和两个整数)
c.execute(x: 1, y: 2, z: 3, 2222, c: 111, 3333, a: 111, b: 222) // 这个也是可以的,多有的键值对合并,成为第一个参数
// 下面的结果,xyz结果都是1,2,3 没有用到变量进行赋值
c.execute(x = 1, y = 2, z = 3)
c.execute(x = 1, z = 2, y = 3)
默认参数
class Car {
def miles
def year
void execute(x = 1, y = 2, z=3) {
println "$x $y $z"
}
// 这个会报错,这和kotlin一样
void execute() {
println "$x $y $z"
}
}
Groovy闭包
Groovy中可以理解为闭包就是可执行的代码块,或匿名函数。闭包在使用上与函数与许多共通之处,但是闭包可以作为一个函数的参数
- 默认情况下,闭包能接收一个参数,且参数字段默认使用it
- 在箭头前面没有定义参数,这时候闭包不能传入参数
- 可以定义多个接收的参数
- 使用参数默认值,跟方法使用规则一样!
一个最简单的闭包
def closure = {
// 这个里面和方法体一样
// 默认接收一个参数,参数直接使用it
}
// 有一个默认参数的闭包
def closure = {
println it
}
closure("show this")
// 没有默认参数的闭包
def closure = {
->
println "not arguments"
}
closure()
// 定义多参数的闭包,支持默认值
def closure = {
arg1="ccc", arg2 ->
println "$arg1 and $arg2"
}
closure("aaa", "bbb")
闭包就是Closure对象
def abc = {
}
println abc.class
结果 - 动态运行时生成的
class com.study.class1.TwoThis$_run_closure2
闭包可以作为方法的参数
def func(closure) {
closure()
}
func {
// 闭包执行的代码
println "call"
}
柯里化闭包
- 使用curry方法给闭包参数加默认值,生成的新的闭包,就等于设置了参数默认值的闭包
- def curriedClosure = closure2.curry(“20”) // curry从左到右设置参数默认值
闭包与接口/类进行转换
- 即使定义的方法传入的是闭包,但是如果传入的对象的类型也有call方法,那么,是可以执行这个对象的call方法的,实际上,闭包执行的也是call方法
- 在一个对象上调用(),表示调用这个对象的call方法
- def action = new Action()
- action()
interface Action {
void aaa()
}
def closure = {
println "aaa"
}
Action action = closure
action.aaa()
println closure.class
println action.class // 动态代理对象
interface Action {
void aaa()
void bbb()
void ccc()
}
def addTextWatcher(Action action) {
action.aaa()
}
addTextWatcher({
println "aaa"
} as Action)
// 当有接口有多个方法时,传入的闭包要强转为Action,使用as关键字
// 现在看起来就像是gradle中的内容了
闭包中的内容是通过Closure中的call方法进行调用
interface Action {
void call()
}
def func(closure) {
closure()
}
Action closure = new Action() {
@Override
void call() {
println "call"
}
} as Closure
func(closure)
类当中如果定义了call方法,可以直接使用对象()去调用call方法
class Action {
void call(a) {
println "$a"
}
}
def a = new Action()
a(111)
Closure比较重要的成员变量
private Object delegate;
private Object owner;
private Object thisObject;
// delegate、owner、thisObject
// 在普通闭包中,都是定义这个普通闭包的对象
// 在静态闭包中,都是定义这个静态闭包的类
private int resolveStrategy = OWNER_FIRST;
protected Class[] parameterTypes; // 参数类型
protected int maximumNumberOfParameters; // 参数个数
def closure = {
int a, int b ->
println a.getClass()
println b.getClass()
}
closure(1, 2)
println closure.parameterTypes
println closure.maximumNumberOfParameters
def closure2 = {
println "this is" + this
println "owner is" + owner
println "delegate is" + delegate
}
closure2()
class Test {
def closure2 = {
println "this is" + this
println "owner is" + owner
println "delegate is" + delegate
}
}
class Test {
static def closure2 = {
println "this is" + this
println "owner is" + owner
println "delegate is" + delegate
}
}
class Test {
def closure1 = {
def closure2 = {
// this:Test@2b76ff4e,定义它的时候的类的this,当它是static时就是class对象
println "this is" + this
// owner:Test$_closure1@8c3619e,定义它的时候的类的对象
println "owner is" + owner
// delegate:Test$_closure1@8c3619e,默认就是owner, 但是代理可以修改
println "delegate is" + delegate
}
closure2()
}
}
闭包中代理策略
- delegate默认就是owner,在执行脚本中可以直接调用方法
- 但是如果方法放入类中就不可以了,可以通过修改代理的方式,让它能够调用
- 代理策略,默认的是选择owner
- 闭包有以下代理策略
- groovy.lang.Closure#DELEGATE_FIRST // delegate优先
- groovy.lang.Closure#DELEGATE_ONLY // 只在delegate中找
- groovy.lang.Closure#OWNER_FIRST // owner优先
- groovy.lang.Closure#OWNER_ONLY // 只在owner找
- groovy.lang.Closure#TO_SELF // 只在自身找(闭包内部),意义不大
在脚本中,func不在类中,下面的脚本可以直接访问
def func(){
println "func"
}
def closure = {
func()
}
closure()
当func在类中时,下面闭包访问就报错
class Test2 {
def func() {
println "Test2 func"
}
}
def closure = {
func()
}
closure()
修改代理,就能访问了
class Test2 {
def func() {
println "Test2 func"
}
}
def closure = {
func()
}
closure.delegate = new Test2()
closure()
但是下面的代码结果是Script func
class Test2 {
def func() {
println "Test2 func"
}
}
def func() {
println "Script func"
}
def closure = {
func()
}
closure.delegate = new Test2()
closure()
修改代理,修改策略
class Test2 {
def func() {
println "Test2 func"
}
}
def func() {
println "Script func"
}
def closure = {
func()
}
closure()
closure.delegate = new Test2()// 优先选择的是owner
//* @see groovy.lang.Closure#DELEGATE_FIRST
//* @see groovy.lang.Closure#DELEGATE_ONLY
//* @see groovy.lang.Closure#OWNER_FIRST
//* @see groovy.lang.Closure#OWNER_ONLY
//* @see groovy.lang.Closure#TO_SELF
closure.resolveStrategy=Closure.DELEGATE_ONLY
closure()
动态/静态类型语言
- 动态类型语言:动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,可以不用给变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby这些就是一种典型的动态类型语言
- 静态类型语言:静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、Java等
groovy的动态特性
class User {
def username = 'zee'
String age
void setName(String name) {
println "setName(String name)"
this.username = name
}
void setName(Object name) {
println "setName(Object name)"
this.username = name
}
}
def user = new User()
println user.username.class
user.username = new Object()
println user.username.class
user.username = 123
println user.username.class
user.username = new User()
println user.username.class
def user1 = new User()
Object name = "zee"
println name.class
user1.setName(name)
name = 123
println name.class
user1.setName(name)
def user3 = new User()
user3.age = "123"
user3.age = 123 // groovy是强类型的,动态的,这里int可以转成 String
println user3.age
结果
class java.lang.String
class java.lang.Object
class java.lang.Integer
class com.study.class1.User
class java.lang.String
setName(String name)
class java.lang.Integer
setName(Object name)
123
groovy不会进行安全检查,在运行时候才抛出异常
class Person1 {
def dream() {
println "I have a dream!"
}
}
class Person2 {
def dream() {
println "I have a dream!"
}
}
def func(person){
person.dream()
}
def person1 = new Person1()
def person2 = new Person2()
func(person1)
func(person2)
使用@TypeChecked强制编译检查,但是会损失动态特性
class Person1 {
def dream() {
println "I have a dream!"
}
}
class Person2 {
def dream() {
println "I have a dream!"
}
}
@TypeChecked
def func(Person1 person) { // 这里必须指定类型
person.dream()
}
def person1 = new Person1()
def person2 = new Person2()
func(person1)
func(person2)
@TypeChecked
class Test {
@TypeChecked(TypeCheckingMode.SKIP) // 跳过检查
def func(person) {
person.dream()
}
}
动态特性及元编程
动态特性
- 根据上下文推断具体类型
- def file = new File(“”);
- file.getParent()
- CallSite动态调用节点
- java中要使用继承才能实现多态,而Groovy轻而易举
- 优势:灵活
- 缺点:编译时不会检查类型,运行时报错
元编程
- Java中可以通过反射,可以在运行时动态的获取类的属性,方法等信息,然后反射调用。但是没法直接做到往内中注入变量、方法;不过Java也有动态字节码技术:ASM,JVM TI,javassist等等
- MOP(元对象协议):Meta Object Protocol
- Groovy直接可以使用MOP进行元编程,我们可以基于应用当前的状态,动态的添加或者改变类的方法和行为。比如在某个Groovy类中并没有实现某个方法,这个方法的具体操作由服务器来控制,使用元编程,为这个类动态添加方法,或者替换原来的实现,然后可以进行调用
MOP方法拦截
- 实现GroovyInterceptable接口,重写invokeMethod来进行拦截
- 使用MetaClass拦截方法,覆盖invokeMethod方法
- 使用类的MetaClass,针对的是class对象,所有实例都会被影响
- 使用具体实例的MetaClass,只影响当前对象实例
class Person implements GroovyInterceptable {
def func() {
System.out.println "I have a dream!"
}
@Override
Object invokeMethod(String name, Object args) {
//println "invokeMethod"
System.out.println "$name invokeMethod"
//respondsTo(name) //判断方法是否存在
if (metaClass.invokeMethod(this, 'respondsTo', name, args)) {
System.out.println "$name 方法存在"
System.out.println "$name 执行前.."
metaClass.invokeMethod(this, name, args)
System.out.println "$name 执行后.."
}
}
}
new Person().func()
结果
func invokeMethod
func 方法存在
func 执行前..
I have a dream!
func 执行后..
这里拦截某个对象的某一个方法
class Person3 {
def func() {
System.out.println "I have a dream!"
}
}
def person3 = new Person3()
// 这里拦截某个对象的某一个方法
person3.metaClass.func = {
println "I have a new dream !!!"
}
person3.func()
// 拦截所有的方法
person3.metaClass.invokeMethod = {
String name, Object args ->// invokeMethod(String name, Object args)
println "$name 被拦截"
}
person3.func()
拦截所有实例的方法
Person3.metaClass.invokeMethod = {
String name, Object args ->// invokeMethod(String name, Object args)
println "$name 被拦截"
}
对内置String进行拦截
String.metaClass.invokeMethod = {
String name, Object args ->
println "String.metaClass.invokeMethod"
MetaMethod method = delegate.metaClass.getMetaMethod(name)
if (method != null && name == 'toString') {
"showme"
}
}
println "jason".toString()
MOP方法注入
方法注入:编写代码时知道想要添加到一个或多个类中的方法的名字。利用方法注入,可以动态地向类中添加行为。也可以向任意数目的类中注入一组实现某一特定功能的可服用方法,就像工具函数。有以下几种方式
- 使用分类注入方法
- 使用ExpandoMetaClass注入方法
- 直接使用类或实例的MetaClass注入方法,实际上最终操作的类型是ExpandoMetaClass。手动创建ExpandoMetaClass来进行注入
- 使用Mixin注入方法。( 在类中可以使用多个Mixin )
使用MetaClass来注入方法
class Person4 {
def func() {
System.out.println "I have a dream!"
}
}
println Person4.metaClass // 注入前:org.codehaus.groovy.runtime.HandleMetaClass
Person4.metaClass.newFunc = {
println "newFunc调用"
}
println Person4.metaClass // 注入后:groovy.lang.ExpandoMetaClass
使用groovy.lang.ExpandoMetaClass来注入方法
def emc = new ExpandoMetaClass(Person4)
emc.func2 = {
println "func2"
}
emc.initialize()
Person4.metaClass = emc
new Person4().func2()
使用分类注入方法 - 场景,框架工具类
class StringUtils {
// public static String isEmpty(){
//
// }
static def isNotEmpty(String self) {
println "isNotEmpty"
self != null && self.length() > 0
}
}
class StringUtils2 {
static def isNotEmpty(Integer self) {
println "isNotEmpty2"
//self != null && self.length() > 0
}
}
use(StringUtils, StringUtils2) { // 从后往前找
println "".isNotEmpty()
println 1.isNotEmpty()
}
其他写法
@Category(String)
class StringUtils2 {
def isNotEmpty() {
println "isNotEmpty2"
this != null && this.length() > 0
}
}
use(StringUtils2) {
println "".isNotEmpty()
}
操作不存在的变量、方法
class Person5 {
def username
// 对不存在的变量进行get操作
def propertyMissing(String name) {
println "propertyMissing"
if (name == 'age') {
"19" // 返回默认值
}
}
// 对不存在的变量进行set操作
def propertyMissing(String name, def arg) {
println "propertyMissing ${name} : arg${arg}"
return "default"// 给与返回值
}
def methodMissing(String name, def arg) {
println "$name methodMissing"
if (name.startsWith 'getFather') {
"zzzz"
}
}
}
def p = new Person5()
println p.age = 12
println p.age
println p.getFather()
运算符重载
运算符重载方法列表
a + b a.plus(b)
a – b a.minus(b)
a * b a.multiply(b)
a ** b a.power(b)
a / b a.div(b)
a % b a.mod(b)
a | b a.or(b)
a & b a.and(b)
a ^ b a.xor(b)
a++ or ++a a.next()
a– or –a a.previous()
a[b] a.getAt(b)
a[b] = c a.putAt(b, c)
a << b a.leftShift(b)
a >> b a.rightShift(b)
~a a.bitwiseNegate()
-a a.negative()
+a a.positive()
switch(a) { case(b) : }
b.isCase(a)
a == b
a.equals(b) or a.compareTo(b) == 0
a != b
! a.equals(b)
a <=> b
a.compareTo(b)
a > b
a.compareTo(b) > 0
a >= b
a.compareTo(b) >= 0
a < b
a.compareTo(b) < 0
a <= b
a.compareTo(b) <= 0
class TestA {
def plus(TestA newTestA) {
// 处理业务
}
}
new TestA() + new TestA()
Integer.metaClass.plus = {
int a ->
"$delegate + $a"
}
println 1 + 2
Expando动态生成类
def e = new Expando(name: "Aaa", func: {
println "func"
})
e.a = 123
println e.a
gradle参考
https://blog.csdn.net/nihaomabmt/article/details/112308851
为什么要学习gradle
gradle自身介绍
- Gradle是一款基于Apache的Ant和Maven概念的项目自动化开源构建工具。Gradle的核心是基于Java来实现的,可以把Gradle看成就是一个轻量级的Java应用程序
- Gradle使用Groovy、Kotlin等语言编写自定义脚本,取代了Ant和Maven使用xml配置文件的方式,很大程度简化了开发时对项目构建要做的配置,使用更加灵活和强大
学习gradle的目的
- Gradle是目前Andorid最主流的构建工具
- 不少技术领域如组件化、插件化、热修复,及构建系统 (很多优秀的框架都是在编译时或者打包之前做一些特殊的处理) ,都需要通过Gradle来实现
- 不懂Gradle将无法完成上述事情,所以学习Gradle非常必要
关于项目构建
- 对于Java应用程序,编写好的Java代码需要编译成.class文件才能够执行。所以任何的Java应用开发,最终都需要经过这一步
- 编译好了这些class文件,还需要对其进行打包。打包不仅针对于这些class文件,还有所有的资源文件等。比如web工程打包成jar包或者war包就包含了自己的资源文件,然后放到服务器上运行
- Andorid工程编译好的class文件还要被打包到dex包中,并且所有的资源文件进行合并处理,甚至还需要对最终打包出来的文件进行加密和签名处理等等
Android build
gradle使用安卓插件编译android项目
自动化构建工具的发展
- “石器时代”:自己编写命令脚本,进行编译和打包
- “蒸汽时代”:Make、Ant工具的出现
- “电气时代”:Maven
- “信息时代”:Gradle,更高级的自动构建工具出现
gradle提供了什么
- 对多工程的构建支持非常出色,尤其是工程依赖问题,并支持局部构建
- 多种方式的依赖管理:如远程Maven仓库,nexus私服,ivy仓库或者本地文件系统等
- 支持传递性依赖管理
- 轻松迁移项目工程
- 基于Groovy等语言构建脚本,简便且灵活
- 免费开源,并且整体设计是以作为一种语言为导向的,而非成为一个严格死板的框架
Gradle基础-Gradle构建机制-Gradle任务
win 10 下环境搭建
获取gradle
- 从官网下载 https://gradle.org/releases/
- 如果已经使用过android studio编译过项目,可使用本地用户文件夹下的gradle工具包 C:Usersshowme.gradlewrapperdists
这里使用本地缓存的gradle工具包
C:Usersshowme.gradlewrapperdistsgradle-7.4.2-bin48ivgl02cpt2ed3fh9dbalvx8gradle-7.4.2
系统属性配置
添加 GRADLE_HOME C:Usersshowme.gradlewrapperdistsgradle-7.4.2-bin48ivgl02cpt2ed3fh9dbalvx8gradle-7.4.2
添加 Path %GRADLE_HOME%bin
通过命令行终端输入gradle --version 查看是否配置成功
PS C:Usersshowme> gradle --version
------------------------------------------------------------
Gradle 7.4.2
------------------------------------------------------------
Build time: 2022-03-31 15:25:29 UTC
Revision: 540473b8118064efcc264694cbcaa4b677f61041
Kotlin: 1.5.31
Groovy: 3.0.9
Ant: Apache Ant(TM) version 1.10.11 compiled on July 10 2021
JVM: 17.0.2 (Oracle Corporation 17.0.2+8-LTS-86)
OS: Windows 10 10.0 amd64
Hello Gradle
在工程文件夹下,创建一个build.gradle文件,包含内容
println 'Hello, Gradle!'
build.gradle是构建Project的核心文件,也是入口
- 如果没有该文件,会出现not found in root project ‘xxxxx’ 提示异常
- 必须要有一个可以运行的task,运行后自动生成.gradle文件夹下的内容
打开cmd终端,移动到工程目录下,执行命令:> gradle help
- help是gradle的内置任务
$ gradle help [0:31:04]
Starting a Gradle Daemon (subsequent builds will be faster)
> Configure project :
Hello, Gradle!
> Task :help
Welcome to Gradle 7.4.2.
To run a build, run gradle <task> ...
To see a list of available tasks, run gradle tasks
To see more detail about a task, run gradle help --task <task>
To see a list of command-line options, run gradle --help
For more detail on using Gradle, see https://docs.gradle.org/7.4.2/userguide/command_line_interface.html
For troubleshooting, visit https://help.gradle.org
从输出可以看到
- 生成了一个
Gradle Daemon
- 执行了gradle脚本中的输出语句
- 打印了帮助信息
在看一下,目录下当前没有生产额外的文件
showme@showmedeMBP: ~/workspace/tmmmp/hello_gradle
$ tree . [0:34:09]
.
└── build.gradle
0 directories, 1 file
Gradle Wrapper
- Gradle Wrapper用来配置开发过程中用到的Gradle构建工具版本。避免因为Gradle不统一带来的不必要的问题
- 在工程目录下使用cmd命令生成wrapper:>
gradle wrapper
- 标准的gradle工程目录
gradlew和gradlew.bat分别是Linux和Windows下的可执行脚本
- 具体业务逻辑是在/gradle/wrapper/gradle-wrapper.jar中实现,gradlew最终还是使用Java执行这个jar包来执行相关的Gradle操作的
showme@showmedeMBP: ~/workspace/tmmmp/hello_gradle
$ gradle wrapper [0:37:24]
> Configure project :
Hello, Gradle!
BUILD SUCCESSFUL in 696ms
1 actionable task: 1 executed
showme@showmedeMBP: ~/workspace/tmmmp/hello_gradle
$ tree . [0:37:31]
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
2 directories, 5 files
gradle/wrapper/gradle-wrapper.properties文件的目的:在不同的机器上快速部署gradle执行环境
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-
distributionBase 下载的Gradle压缩包解压后存储的主目录
-
zipStorePath | distributionPath: 本地缓存的gradle可执行文件路径, 默认在 ~/.gradle/wrapper/dists
-
distributionUrl 下载特定gradle版本
-
https://services.gradle.org/distributions/gradle-7.4.2-bin.zip
-
https://services.gradle.org/distributions/gradle-7.4.2-all.zip
-
bin和all的区别,bin只包含了可执行文件相关的内容,all还包含了gradle的源码和帮助文档
-
GRADLE_USER_HOME
- 默认路径在~/.gradle/ ,不建议使用本地maven的m2替代,因为原本的.gradle目录下的模块分的很清晰,功能明确
- 如果启动时,指定参数,使用别的目录代替GradleUserHome ,后果是每次构建需要重新下载插件与依赖到新的目录
- 默认情况下,gradle运行时,除了和项目打交道,还有当前项目构建的全新的GradleUserHome目录,所有jar包都得重新下载
gradlew命令行
- gradlew -?/-h/-help 使用帮助
- gradlew tasks 查看所有可执行Tasks
- gradlew --refresh-dependencies assemble 强制刷新依赖
- gradlew cBC 等价与执行Task cleanBuildCache,这种通过缩写名快速执行任务
- gradlew :app:dependencies 查找app工程依赖树
Gradle构建机制
gradle标准工程
showme@showmedeMBP: ~/workspace/tmmmp/hello_gradle
$ tree . [0:37:31]
.
├── build.gradle
├── gradle
│ └── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
└── gradlew.bat
java中的标准工程
安卓中的标准工程:有一个app模块,在这个模块中包含也包含了一个build.gradle
- 最外部的build.gradle适用于整个工程
- app模块中的build.gradle适用于当前模块
Gradle DSL
- DSL(Domain Specific Language) 领域特定语言,或领域专属语言。简单来说就是专门关注某一领域的语言,它在于专而不是全,最典型的比如HTML
- Gradle可以使用Groovy DSL,专门用来开发Gradle的构建脚本。所以说Gradle整体设计是以作为一种语言为导向的,并非成为一个严格死板的框架
App->build.gradle文件 , 使用的就是groovy 语言
plugins {
id 'com.android.application'
}
android {
compileSdk 32
defaultConfig {
applicationId "com.example.studygradle"
minSdk 26
targetSdk 32
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
settings.gradle
- Gradle支持多工程构建,使用settings.gradle来配置添加子工程(模块)
- settings文件在初始化阶段执行,创建Settings对象,在执行脚本时调用该对象的方法
- Settings.include(String… projectPaths)
- 将给定的目录添加到项目构建中,‘:app’表示文件相对路径,相当于’./app’文件夹
- 多项目架构进行分层,把同层次的子工程放在同一文件夹下便于管理,使用’:xxx:yyy’表示
在android studio中新增java模块(多层次的模块写法 - 这个冒号就相当于文件夹的斜杆
)
可以看到setting.gradle通过include命令引入的模块,已经分层方式
模块不include进来,则该模块不会进行构建
可以点进去看include细节,发现是gradle构建工具的一个方法
rootProject.name 配置的是整个工程最顶层的名字
build.gradle
- build.gradle是项目构建文件,每个工程都有一个build.gradle文件
- build.gradle在配置阶段执行,并创建相应工程的Project对象,执行的代码可以直接调用该对象提供的方法或属性
- 最顶层的build.gradle适用于整个项目,每个模块中的build.gradle适用于模块自身
Daemon(守护进程)
- 项目启动时,会开启一个client,然后启动一个daemon,通过client向daemon收发请求,项目关闭,client关闭,daemon保持启动,有类似项目再次部署时,会直接通过新的client访问已经启动的daemon,所以速度很快,默认daemon不使用3小时后关闭;不同项目兼容性考虑,也可使用–no-daemon 启动项目,就没有速度优势了
- 手动停止daemon:gradle wrapper --stop
Gradle生命周期
- Initialization
- Gradle支持单项目和多项目构建。在初始化阶段,Gradle确定哪些项目将参与构建,并为每个项目创建Project实例,一般我们不会接触到它。(比如解析settings.gradle)
- Configuration
- 配置阶段,解析每个工程的build.gradle文件,创建要执行的任务子集和确定各种任务之间的关系,并对任务的做一些初始化配置
- Execution
- 运行阶段,Gradle根据配置阶段创建和配置的要执行的任务子集,执行任务
配置阶段
解析每个Project中的build.gradle,解析过程中并不会执行各个build.gradle中的task
- 经过Configration阶段,Project之间及内部Task之间的关系就确定了。一个Project包含很多Task,每个Task之间有依赖关系。
Configuration会建立一个有向图来描述Task之间的依赖关系,所有Project配置完成后,会有一个回调project.afterEvaluate(),表示所有的模块都已经配置完了
task
- task是gardle中最小的任务单元,任务之间可以进行复杂的操作(如动态创建任务,多任务间依赖调用等等)。gradle的执行其实就是由各种任务组合执行,来对项目进行构建的
- 使用gradlew help命令,任何gradle项目都有一个该task,可以执行此命令观察taks执行的流程是否如预期
- 可以使用工具查看,还可以通过 gradlew tasks 命令查看可运行任务
- 使用gradlew tasks --all 命令查看所有任务
- 使用gradlew A B 命令表示执行任务A和B,
支持驼峰简写
在build.gradle中自定义任务
- task <任务名>{ … },在Gradle5.x以上已经删除<<操作符这种写法
- { … }执行的是配置阶段的代码,执行阶段要处理的逻辑需要调用doFirst、doLast方法,在闭包中实现。doFirst{}表示任务执行开始时调用的方法,doLast{}表示任务执行结束调用的方法
- task A(dependsOn:[B]){ … } 表示任务A依赖于任务B,那么B执行在A之前
自定义的任务默认分组到other中
自定义task类
// 通过自定义任务类来实现自定义任务
class MyTask extends DefaultTask {
@Input
String filePath // 可以指定filePath来传入n个文件的路径
@OutputFile
File file
MyTask() {
group "自定义任务"
description "自定义任务描述"
}
@TaskAction
void action() {
println "MyTask action()"
}
@TaskAction
void action2() {
println "MyTask action2()"
}
}
// 继承自定义的task类,并创建新的task
task myTask2(type: MyTask) {
filePath = 'xxx/xxx'
file = file('test.txt')
}
// 继承已有的task
task clean(type: Delete) {
delete rootProject.buildDir
}
task之间的集成和依赖关系
// Gradle3.0 << ,在4.x以上就废弃了
task A {
println "configuration A.."
doFirst {
println "doFirst A.."
}
doLast {
println "doLast A1.."
}
doLast {
println "doLast A2.."
}
doLast {
println "doLast A3.."
}
}
A.doLast {
println "doLast A4.."
}
task C {
println "configuration C.."
doLast {
println "doLast C.."
}
}
task B {
println "configuration B.."
doLast {
println "doLast B.."
}
}
task hello2(dependsOn: [A, C, B]) {
doLast {
println "doLast hello2"
}
}
A.dependsOn B
A.mustRunAfter(B)
A.shouldRunAfter B
task finalized {
doLast {
println "清理任务"
}
}
A.finalizedBy finalized
依赖其他build.gradle文件
1)创建一个gradle文件
config.gradle
task CCCC {
doLast {
println "doLast.."
}
}
2)依赖创建的其他gradle文件
apply from: 'config.gradle'
DefaultTask
- task定义的任务其实就是DefaultTask的一种具体实现类的对象
- 可以使用自定义类继承DeaflutTask
- 在方法上使用@TaskAction注解,表示任务运行时调用的方法
- 使用@Input表示对任务的输入参数
- 使用@OutputFile表示任务输出文件
- 使用inputs,outputs直接设置任务输入/输出项
- 一个任务的输出项可以作为另一个任务的输入项 (隐式依赖关系)
class ZipTask extends DefaultTask {
// @Input
// @Optional
// 表示可选
String from
// @OutputFile
// @Optional
// 表示可选
File out
ZipTask() {
outputs.upToDateWhen {
false// 增量构建,每次都会开启,不会跳过任务
}
}
@TaskAction
void fun() {
println " @TaskAction fun()"
println from
println out
//文件进行操作
//inputs.files.first()
println inputs.files.singleFile
def inFile = inputs.files.singleFile
def file = outputs.files.singleFile
file.createNewFile()
file.text = inFile.text
}
}
task myTask(type: ZipTask) {
from = "a/b/c" // 输入
out = file("test.txt") // 输出
inputs.file file('build.gradle')
outputs.file file('test.txt')
}
使用自定义任务,实现zip打包packageDebug输出的内容
task zip(type: Zip) {
archiveName "outputs.zip"// 输出的文件名字
destinationDir file("${buildDir}/custom")// 输出的文件存放的文件夹
from "${buildDir}/outputs"// 输入的文件
}
压缩packageDebug任务的输出文件
afterEvaluate {
println tasks.getByName("packageDebug")
task zip(type: Zip) {
archiveName "outputs2.zip"// 输出的文件名字
destinationDir file("${buildDir}/custom")// 输出的文件存放的文件夹
from tasks.getByName("packageDebug").outputs.files// 输入的文件
tasks.getByName("packageDebug").outputs.files.each {
println it
}
}
}
gradle钩子函数
- gradle在生命周期三个阶段都设置了相应的钩子函数调用
- 使用钩子函数,处理自定义的构建
- 初始化阶段:gradle.settingsEvaluated和gradle.projectsLoaded。(在settings.gradle中生效)
- 配置阶段:project.beforeEvaluate和project.afterEvaluate;gradle.beforeProject、gradle.afterProject及gradle.taskGraph.taskGraph.whenReady
- 执行阶段:gradle.taskGraph.beforeTask和gradle.taskGraph.afterTask
gradle构建监听
- gradle可以设置监听,对各阶段都有相应的回调处理
- gradle.addProjectEvaluationListener
- gradle.addBuildListener
- gradle.addListener:TaskExecutionGraphListener (任务执行图监听),TaskExecutionListener(任务执行监听),TaskExecutionListener、TaskActionListener、StandardOutputListener …
Daemon(守护进程)
- 项目启动时,会开启一个client,然后启动一个daemon,通过client向daemon收发请求,项目关闭,client关闭,daemon保持启动,有类似项目再次部署时,会直接通过新的client访问已经启动的daemon,所以速度很快,默认daemon不使用3小时后关闭;不同项目兼容性考虑,也可使用–no-daemon 启动项目,就没有速度优势了
- 手动停止daemon:gradle wrapper --stop
Project
- build.gradle在配置阶段会生成project实例,在build.gradle中直接调用方法或属性,实则是调用当前工程project对象的方法或属性
- 使用Project提供的Api,在多项目构建设置游刃有余
- project(‘:app’){} 指定的project (这里是app) 配置
- allprojects{} 所有的project配置
- subprojects{} 所有的子project配置
- buildscript {} 此项目配置构建脚本类路径
在根build.gradle文件中,给app模块进行配置
project(':app') {
// plugins {
// id 'com.android.application'
// id 'kotlin-android'
// }
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion 30
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "com.dongnaoedu.gradledemo"
minSdkVersion 28
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.2.0'
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
}
最后
以上就是背后黑米为你收集整理的gradle入门笔记Groovy快速入门gradle参考为什么要学习gradleGradle基础-Gradle构建机制-Gradle任务的全部内容,希望文章能够帮你解决gradle入门笔记Groovy快速入门gradle参考为什么要学习gradleGradle基础-Gradle构建机制-Gradle任务所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复