赞
踩
在上一篇中,主要讲解了Groovy类、方法与闭包详解,在这一篇中将会对Groovy 动态特性及元编程进行详解。
说起动态特性,在第一篇了解Gradle及自动化构建里,仅仅是一笔带过,没有详解,在这里就要先从动态语言与静态语言的区别开始说起:
动态类型语言
动态类型语言是指在运行期间才去做数据类型检查的语言,也就是说,在用动态类型的语言编程时,可以不用给变量指定数据类型,该语言会在你第一次赋值给变量时,在内部将数据类型记录下来。Python和Ruby这些就是一种典型的动态类型语言
静态类型语言
静态类型语言与动态类型语言刚好相反,它的数据类型是在编译其间检查的,也就是说在写程序时要声明所有变量的数据类型,C/C++是静态类型语言的典型代表,其他的静态类型语言还有C#、Java等。
各种类型语言
一大堆理论说完了,接下来我们直接开始愉快的撸码环节
先看咱们所熟悉的java代码
public class JavaTest2 { static class User { String userName = "hqk"; int age; void setName(String name) { System.out.println("setName(String name)"); this.userName = name; } void setName(Object name) { System.out.println("setName(Object name)"); this.userName = name.toString(); } } public static void main(String[] args) { User user=new User(); user.setName(123); user.setName("zzz"); } }
这是一段非常简单的java代码,我们可以看到,在函数入口那,分别传入两种不同类型的数据源,为了代码不报错,额外重载了方法setName,但最终userName属性类型还是String。换句话说,变量在定义的时候(运行前),就已经决定了它的类型,要是不满足它,编译就不会让你通过。
那继续看看我们现在所学的Groovy。
class User { def userName = 'hqk' 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 "赋值前" println user.userName.class println "\n" println "赋值为object类型" user.userName = new Object() println user.userName.class println "\n" println "赋值为int类型" user.userName = 123 println user.userName.class println "\n" println "赋值为User类型" user.userName = new User() println user.userName.class println "\n"
我们可以看到,用相同的逻辑也在Groovy这里也实现一遍,赋了不同值后也依次打印了该变量的类型,这样写代码没有错误提示信息,那么运行来看看效果。
赋值前
class java.lang.String
赋值为object类型
class java.lang.Object
赋值为int类型
class java.lang.Integer
赋值为User类型
class com.zee.gradle.groovy.User
因为Groovy它是可以直接通过对象.变量直接赋值的,所以这里暂时没通过定义好的setName方法赋值,不过也能够看出这里通过def定义的userName这个变量将会随着值类型的改变而改变,它是动态的。这里直接赋值是这样的结果,那我们也和刚刚java方式一样调用setName方法试试。
println "下面将是通过setName方法赋值\n"
Object name = "hqk"
println "定义Object变量,值为String"
println name.class
user.setName(name)
println user.userName.class
println "\n"
println "定义Object变量,值为int"
name = 123
println name.class
user.setName(name)
println user.userName.class
这里定义了一个Object类型的变量name,先是赋值了String类型的值,将对应的值通过setName方法赋值;随后将Object类型的变量name赋值了int类型的值,再次将对应的值通过setName方法赋值。我们来看看运行效果。
下面将是通过setName方法赋值
定义Object变量,值为String
class java.lang.String
setName(String name)
class java.lang.String
定义Object变量,值为int
class java.lang.Integer
setName(Object name)
class java.lang.Integer
现在我们看到,一个Object类型的值,在赋了不同值的情况下,它的类型也发生了改变,随后调用了不同的setName方法。
那么现在问题来了,既然在Groovy里面变量能够随意变,难道这Groovy和JS一样是弱类型变量? 带着这样的问题,我们再次研究下Groovy代码。
class User { def userName = 'hqk' 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() user.age = "123" println user.age.class user.age = 123 println user.age.class user.age=new Object() println user.age.class
在这里我在Class里面定义了age变量,类型也完全确认为String类型,但是在下面赋值的时候,我赋值了不同类型的值,最终打印了赋值好的变量类型,接下来我们看看运行效果。
class java.lang.String
class java.lang.String
class java.lang.String
这里我们看出当确定了是什么类型的时候,无论赋值类型怎么变,它都不会发生任何改变。这就说明了Groovy它是强类型变量。而通过def、Object定义变量,它自身的类型将会自动转为对应赋值对象的类型,并非弱类型变量。
在这里总结一下Groovy的动态特性:
这里提到了MOP元编程,那么在这就要对应MOP元编程进行详解。
说起元编程,先说说纯Java能做的事:
在Java中可以通过反射,在运行时动态的获取类的属性,方法等信息,然后反射调用。但是没法直接做到往内中添加属性、方法和行为。( 需要通过动态字节码技术如ASM、javassist等技术来实现动态的修改class )
而在Groovy中可以直接使用MOP进行元编程,我们可以基于应用当前的状态,动态的添加或者改变类的方法和行为。比如在某个Groovy类中并没有实现某个方法,这个方法的具体操作由服务器来控制,使用元编程,为这个类动态添加方法,或者替换原来的实现,然后可以进行调用。
这里归纳总结就是一句话,Java能做的Groovy也能做,Groovy能做的Java不一定能做。而Groovy能做的事无非就是动态修改和动态注入,动态修改就涉及到拦截。所以接下来就从Groovy的拦截以及注入这两个方面详解。
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()
代码解析
一看到这代码,不就是之前我讲解的静态代理与动态代理详解,简直是一模一样,只不过是以Java方式实现的(感兴趣的小伙伴阔以去看一下)。我们继续看看运行效果:
func invokeMethod
func 方法存在
func 执行前..
I have a dream!
func 执行后..
注意:如果要使用这种方式拦截,必须要实现GroovyInterceptable 接口,重写invokeMethod方法。这种方式太麻烦了,那有没有不实现这个接口就能拦截的方式呢?所以就有了第二种
class Person3 {
def func() {
System.out.println "I have a dream!"
}
}
def person3 = new Person3()
person3.func()
// 这里拦截某个对象的某一个方法
person3.metaClass.func = {
println "I have a new dream !!!"
}
person3.func()
运行效果
I have a dream!
I have a new dream !!!
从这里可以看出,通过 metaClass 可以直接修改对应方法里面的逻辑。它这样的方式等价于:
class Person3 { def func() { System.out.println "I have a dream!" } } def person3 = new Person3() // 等价与实现拦截接口 person3.metaClass.invokeMethod = { String name, Object args ->// invokeMethod(String name, Object args) println "$name 被拦截" } person3.func() new Person3().func() new Person3().func() new Person3().func() new Person3().func() new Person3().func()
运行效果
func 被拦截
I have a dream!
I have a dream!
I have a dream!
I have a dream!
I have a dream!
我们发现,这里仅仅是拦截了一次,并没有做到全局拦截,那么要想做到全局拦截应该怎样呢?
class Person3 { def func() { System.out.println "I have a dream!" } } def person3=new Person3() person3.func() Person3.metaClass.invokeMethod = { String name, Object args ->// invokeMethod(String name, Object args) println "$name 被拦截" } person3=new Person3() person3.func() new Person3().func() new Person3().func() new Person3().func() new Person3().func() new Person3().func()
这里我们拦截前后,也多次实例化调用了对应方法,现在看看效果
I have a dream!
func 被拦截
func 被拦截
func 被拦截
func 被拦截
func 被拦截
func 被拦截
这里我们能看出,拦截前,运行的之前的代码,拦截后就变成最新的逻辑了。到现在为止,我们拦截的都是自己创建的代码,那么系统类能否拦截呢?
String.metaClass.invokeMethod = {
String name, Object args ->
println "String.metaClass.invokeMethod"
MetaMethod method = delegate.metaClass.getMetaMethod(name)
if (method != null && name == 'toString') {
"你被我拦截了,还想toString"
}
}
println "hqk".toString()
这里我们看到直接将String的toString方法给拦截了,看看运行效果
String.metaClass.invokeMethod
你被我拦截了,还想toString
这里我们看出,并没有打印我们想要的字符串,而是打印了已经被拦截后的字符串。到这,Groovy拦截方式差不多讲完了,现在该讲解Groovy注入了。
class Person4 { def func() { System.out.println "I have a dream!" } } // 注入前:org.codehaus.groovy.runtime.HandleMetaClass println Person4.metaClass Person4.metaClass.newFunc = { println "newFunc调用" //方法返回字符串 "注入后,后续依然能够访问" } // 注入后:groovy.lang.ExpandoMetaClass println Person4.metaClass def person=new Person4() //注入后 尝试是否能够调用注入后的方法 println person.newFunc()
这里我们看到,在Person4 里面并没有 newFunc 方法,于是我们通过 metaClass方式注入了新方法newFunc ,并且实现了对应的逻辑。来看看运行效果
org.codehaus.groovy.runtime.HandleMetaClass@ea27e34[groovy.lang.MetaClassImpl@ea27e34[class com.zee.gradle.groovy.Person4]]
groovy.lang.ExpandoMetaClass@27a0a5a2[class com.zee.gradle.groovy.Person4]
newFunc调用
注入后,后续依然能够访问
这里我们看到,注入前和注入后的Class类型发生了改变,它Class类型变成了groovy.lang.ExpandoMetaClass类型,那么是否能直接通过ExpandoMetaClass来实现注入?
class Person4 {
def func() {
System.out.println "I have a dream!"
}
}
def emc = new ExpandoMetaClass(Person4)
emc.func2 = {
println "func2"
}
emc.initialize()
Person4.metaClass = emc
new Person4().func2()
这里直接实例化了 ExpandoMetaClass 对象,然后通过该对象进行了一系列操作。看看运行效果
func2
这里依然能够正常调用注入后的方法
class StringUtils { 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 //因为 println 没有返回值,所以这里返回为null,因此下面打印为null } } use(StringUtils, StringUtils2) { println "".isNotEmpty() println 1.isNotEmpty() }
运行效果
isNotEmpty
false
isNotEmpty2
null
这里定义了对应的工具类,里面有对应的非空判断逻辑,通过use方式,直接将这两个类里面的方法注入到对应的系统类当中(String/Integer)。然后就可以在闭包里面就能直接调用对应注入好的方法。
除此之外还有木有其他方式呢?
@Category(Object) class StringUtils2 { def isNotEmpty() { println "isNotEmpty2" if (this instanceof Integer){ println "数字类型判断" return true } println "非数字类型判断" this != null && this.length() > 0 } } use(StringUtils2) { println "".isNotEmpty() println "\n" println 1.isNotEmpty() }
这里我们看到使用了 @Category 注解里面放入了Object,表示在use闭包内,任意类型变量都拥有新的方法isNotEmpty。看看运行效果
isNotEmpty2
非数字类型判断
false
isNotEmpty2
数字类型判断
true
这里我们看到通过注解的方式,依然能够成功注入,但是这两种写法只能在use闭包里面,通常用于临时判断,用完就丢的那种。
因为Groovy是动态语言,那么在程序运行中,如果对象里面没有对应的方法和成员变量,在外面强制访问是肯定会报错的。那么该如何避免呢?
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 "\n" println p.age println "\n" println p.getFather()
运行效果
propertyMissing age : arg12
12
propertyMissing
19
getFather methodMissing
zzzz
这里可以看出,当对象里面没有对应方法和变量时,可以通过这样的方式避免报错,也可以针对不同的情况做不同的逻辑处理。
因为Gradle大多数都是用Groovy语言写的,所以学习Gradle必须要掌握部分Groovy语法。到这里,整个Groovy语法已经全部讲解完毕了。在下一篇文章中,将会真正开始Gradle讲解了。
赞
踩
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。