Groovy 1.8 新特性: 增强的 AST
编译器在生成字节码前,会先将代码转换为抽象代码树(Abstract Syntax Tree)。在这个转换过程中,我们可以让机器替我们自动插入很多代码。在新的版本中,Groovy 提供了更多的 AST 转换选项,从而进一步减少了那些重复而乏味的例行代码。 @Log 注释通过使用该注释(或使用 @Commons/@Log4j/@Slf4j 选择其它日志框架),可以在 AST 转换过程中注入日志代码。 1: import groovy.util.logging.* 2:?
3: @Log
4: class Car { 5: { log.info 'Static Initiation' } 6: }
7: new Car() 以上代码执行时,会生成级别为 INFO 的日志。(很久很久以前,我们都是靠 IDE 来插入日志代码的,因为很无趣,是吧) @Field这是个让我困惑不已的特性,以下是官方的说明: 在写脚本的时候,很容易混淆的是变量的类型,如下代码中 list 在没有修饰的时候其实是脚本对象 run() 方法的本地变量,因此无法在 func 中被调用。但使用 @Field 修饰后则成为了脚本对象的 private field,从而可以被调用。 1: list = [1,2]
2: def func() { list.max() }
3:?
4: println func()
很简单对不?等我在 GroovyConsole 中进行验证的时候就不简单了。(不单是 GroovyConsole,我试过了包括命令行和诸如 GroovyShell 的 evaluate 方法,都不正常) 首先,上述代码在没有加上 @Field 注释的时候也可以运行 1: public class script1305201472799 extends groovy.lang.Script {
3: private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 4: public static transient boolean __$stMC 5: public static long __timeStamp 6: public static long __timeStamp__239_neverHappen1305201472803 7: final private static java.lang.Long $const$0 8: final private static java.lang.Long $const$1 9: private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo$ 10:?
11: public script1305201472799() { 12: }
13:?
14: public script1305201472799(groovy.lang.Binding context) { 15: super.setBinding(context) 16: }
17:?
18: public static void main(java.lang.String[] args) { 19: org.codehaus.groovy.runtime.InvokerHelper.runScript(script1305201472799,args)
20: }
21:?
22: public java.lang.Object run() { 23: list = [1,monospace; direction: ltr; color: black; font-size: 8pt; overflow: visible; border-style: none; padding: 0px;"> 24: return this.println(this.func()) 25: }
可以看到,list 的范围被局限在了 run() 内。 不要关闭 GroovyConsole,“直接加上”注释后,代码依旧可以运行。查看 AST,却发现 list 并没有提升为 field。奇了怪了…… 关闭 GroovyConsole,重新打开这段代码……这一次,提示错误:
这是什么情况?因为第一次运行的内容作为上下文缓存了下来,因此在第二次运行的时候其实根本就没有处理 @Field 这个注释!而在第三次运行的时候,没有上下文,而编译器无法找到 Field 这个注释的来源,就没有找到 list 这个变量。所以飞哥说:
不过问题并没有解决,为啥在没有注释的情况下也能运行?我实在是想不出来了。(需要指出的是,在 Windows 下报错信息是错误的,因为问题不在于有没有 list 这个变量,而是没能正确处理 Field 这个注释。相对的在 Linux 版本下,我得到的报错则是无法找到 Field 这个类。) 为了像官方文档描述的那样使用 @Field 注释,我们需要添加一条 import 语句 1: import groovy.transform.Field
这样,转换后的 AST 变成了如下代码 1: public class script1305202926907 extends groovy.lang.Script {
3: java.lang.Object list
4: private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo 5: public static transient boolean __$stMC 6: public static long __timeStamp 7: public static long __timeStamp__239_neverHappen1305202926915 8: final private static java.lang.Long $const$0 9: final private static java.lang.Long $const$1 10: private static org.codehaus.groovy.reflection.ClassInfo $staticClassInfo$ 11:?
12: public script1305202926907() { 13: list = [1,monospace; direction: ltr; color: black; font-size: 8pt; overflow: visible; border-style: none; padding: 0px;"> 14: } 15:?
16: public script1305202926907(groovy.lang.Binding context) { 17: list = [1,monospace; direction: ltr; color: black; font-size: 8pt; overflow: visible; border-style: none; padding: 0px;"> 18: super.setBinding(context) 19: }
20:?
21: public static void main(java.lang.String[] args) { 22: org.codehaus.groovy.runtime.InvokerHelper.runScript(script1305202926907,args)
23: }
24:?
25: public java.lang.Object run() { 26: null
27: return this.println(this.func()) 28: }
29:?
30: public java.lang.Object func() { 31: return list.max() 32: }
注意在第三行,list 确实变成了 field。 @PackageScope对于多数用户而言,这应该不会是特别重要的特性。考虑以下代码: 1: class T {
2: @PackageScope int i 3: int j 4: def p() { println 'hell world' } 5: }
在默认情况下,Groovy 会自动为 i/j 生成 setter/getter 方法,并将其设为 private,但是在使用 @PackageScope 进行修饰后,i 变成了 friendly 而不是 private,accessor 也没有自动生成,其 AST 大致如下: 1: public class T implements groovy.lang.GroovyObject extends java.lang.Object {
3: @groovy.lang.PackageScope
4: int i 5: private int j 6: public java.lang.Object p() { 7: return this.println('hell world') 8: }
9: public int getJ() { 10: }
12: public void setJ(int value) { 13: }
14:?
15: ...
16: }
如果对 class 进行修饰的话: 1: @PackageScope class T {
2: def i,j
3: }
则 i/j 自动变成 friendly。 但再次使人困惑的是,这个注释似乎在处理 method 的时候出现了问题,而且试图像注释提供参数([CLASS,FIELDS])的尝试也宣告失败……我想和先前的 Field 注释一样,这应该是个未完成的作品。因此,飞哥又说:
(还是那句话,除非你正在被一些奇特的第三方框架所折磨,大可忽略这个问题。事实上 Scala 的语法也提供了类似的粒度及细的访问控制机制,但除非你在做非常底层的开发,否则不太可能用到这种程度的控制吧?) @AutoClone这将是一个非常重要的注释。 1: import groovy.transform.AutoClone
2: @AutoClone class T { 3: int i 4: List strs
5: }
以上代码将被扩展为: 1: public class T implements java.lang.Cloneable,groovy.lang.GroovyObject extends java.lang.Object {
3: public java.lang.Object clone() throws java.lang.CloneNotSupportedException { 4: java.lang.Object _result = super.clone() 5: if ( i instanceof java.lang.Cloneable) { 6: _result .i = i.clone()
7: }
8: if ( strs instanceof java.lang.Cloneable) { 9: _result .strs = strs.clone()
10: }
11: return _result 14: ...
如果这个类的全部成员都是 final 或是 Cloneable,那么你可以这么写: 2: import static groovy.transform.AutoCloneStyle.*
3: @AutoClone(style=COPY_CONSTRUCTOR) class T { 4: final int i 5: }
现在 Groovy 将使用构造函数来完成 clone 1: protected T(T other) {
2: if ( other .i instanceof java.lang.Cloneable) { 3: this .i = other .i.clone() 4: } else { 5: this .i = other .i 6: }
7: }
8:?
9: public java.lang.Object clone() throws java.lang.CloneNotSupportedException { 10: return new T(this) 11: }
如果类已经被实现为 Serializable / Externalizable,那么还可以使用 style = SERIALIZATION 的参数来使用 Serializable 机制进行 clone。 (飞哥:总算这个重要功能没有掉链子……) @AutoExternalize(该死的官方文档居然在这里拼写错误,害我莫名了十二秒钟 这个注释比较简单 1: import groovy.transform.*
2: @AutoExternalize class T { 3: String name
4: int age 5: transient String nickname 6: }
以上代码会被注入以下方法,同时类 T 会被标记为 Externalizable 1: public void writeExternal(java.io.ObjectOutput out) throws java.io.IOException {
2: out.writeObject( name )
3: out.writeInt( age )
4: }
5:?
6: public void readExternal(java.io.ObjectInput oin) { 7: name = oin.readObject()
8: age = oin.readInt()
9: }
注意,transient 成员被无视了 @TimedInterrupt这一注释将有助于扼杀那些由患有BRAIN-DAMAGE综合症的用户所制造了巨耗时的任务,比如包含了死循环的代码 1: @TimedInterrupt(5)
2: import groovy.transform.* 4: while(true) {} 5: 'Done' 这是个死循环,但是在使用注释后,运行该脚本将抛出异常: java.util.concurrent.TimeoutException: Execution timed out after 5 units. @ThreadInterrupt我非常喜欢的一个做法是在系统中提供脚本接口,这样的话,业务逻辑变化的时候就不用升级整个系统,而只是写一个 DSL 片段就可以了。但如果这个 DSL 写的有问题,像 System.exit(-1) 这样的退出代码,那么很可能每次新的业务逻辑下来,CEO就要和我谈一次心……但是现在,参考以下代码: 2: @ThreadInterrupt
4: def f() {
5: println 'Hell' 6: }
在加上注释后,执行任何代码前都会检查相关进程有没有结束,检查 f() 的 AST 代码: 1: public java.lang.Object f() {
2: if (java.lang.Thread.currentThread().isInterrupted()) { 3: throw new java.lang.InterruptedException('Execution interrupted. The current thread has been interrupted.') 4: }
5: return this.println('Hell') 6: }
这样总安全了吧。 @ConditionalInterrupt 1: @ConditionalInterrupt({ counter++> 10})
4: counter = 0
6: 2.times { println 'Oh...' } 7:?
8: def scriptMethod() {
9: 14.times { t ->
10: 3.times {
11: println counter
12: println "executing script method...$t * $it" 13: }
15: }
16:?
17: println counter
18: scriptMethod()
有一个使用了闭包的注释。但这种做法有些奇怪,至少我觉得很少有人能在以上代码中以直觉来判断 counter 在每行代码中的值。根据输出,大致可以看出每次进入一个代码块,就会进行一次条件判断。总的来说,这样的用法多少带了点不好的“味道”。 @ToString 3: @ToString
4: class T { 5: int i,j 8: println new T(i: 12,j: -1) 9: // => T(12,-1)
好吧,到这里我有点觉得 Groovy 的注释快走火入魔了……不过这个功能让我想起了 Scala 的 case 类和 Groovy 的 Expando。虽然输出很简单,但是看看 AST 的 toString 就觉得头大了。 1: public java.lang.String toString() {
2: java.lang.Object _result = new java.lang.StringBuffer() 3: _result.append('T') 4: _result.append('(') 5: if ( $print$names ) { 6: _result.append('i') 7: _result.append(':') 9: _result.append(org.codehaus.groovy.runtime.InvokerHelper.toString( i ))
10: _result.append(',') 11: if ( $print$names ) { 12: _result.append('j') 13: _result.append(':') 15: _result.append(org.codehaus.groovy.runtime.InvokerHelper.toString( j ))
16: _result.append(')') 17: return _result.toString() 18: }
复杂吧……在这里,飞哥的意见是:尽量使用这个功能自动生成以 debug 为目的的 toString 代码,其它就别多想了。 @EqualsAndHashCode如果你的比较逻辑是基于比较所有成员的值,那么可以使用这个注释 3: @EqualsAndHashCode
6: }
对应的头大代码是: 1: public int hashCode() {
2: java.lang.Object _result = org.codehaus.groovy.util.HashCodeHelper.initHash()
3: _result = org.codehaus.groovy.util.HashCodeHelper.updateHash( _result,i )
4: _result = org.codehaus.groovy.util.HashCodeHelper.updateHash( _result,j )
5: return _result 8: public boolean equals(java.lang.Object other) { 9: if ( other == null) { 10: return false 11: }
12: if (T != other.getClass()) { 13: return false 15: if (this.is(other)) { 16: return true 17: }
18: other = (( other ) as T)
19: if ( i != other .i) { 20: return false 21: }
22: if ( j != other .j) { 23: return false 24: }
25: return true 26: }
这个功能应该也会属于常用功能吧。 @TupleConstructor 4: @TupleConstructor
5: class T { 6: int i,monospace; direction: ltr; color: black; font-size: 8pt; overflow: visible; border-style: none; padding: 0px;"> 9: println new T(1,-12) 10: // => T(1,-12)
基于元组的构造函数。 @Canonical注意在上述代码中共叠放了两个注释。从我的角度来看,ToString,TupleConstructor以及Equals/HashCode 都是最常用的功能,而 Canonical 正是它们的组合。当然,也可以个别指定,就好象如下的 toString: 3: @Canonical
4: @ToString(includeNames = true)
10: // => T(i:1,j:-12)
@InheritConstructors在生成子类的时候,往往不得不一个一个的将超类的构造函数“抄写”一遍,以后不用了 2: @InheritConstructors class MyException extends Exception {}
在这里,MyException 将照搬 Exception 的所有构造函数,AST 代码如下: 1: public MyException() {
2: super() 3: }
4:?
5: public MyException(java.lang.String param0) { 6: super(param0) 9: public MyException(java.lang.String param0,java.lang.Throwable param1) { 10: super(param0,param1) 11: }
12:?
13: public MyException(java.lang.Throwable param0) { 14: super(param0) 15: }
@WithReadLock / @WithWriteLock 2: public class ResourceProvider {
4: private final Map data = new HashMap(); 6: @WithReadLock
7: public String getResource(String key) throws Exception { 8: return data.get(key); 9: }
11: @WithWriteLock
12: public void refresh() throws Exception { 13: //reload the resources into memory 15: }
在以上代码中,如果没有调用 refresh(),那么可以有多个线程同时调用 getResource(key),一旦有任何线程开始调用 refresh(),那么锁就会生效,所有调用 getResource 的线程都将进入等待状态。 @ListenerList这是针对 Collection 类的绑定机制。 2: import groovy.beans.*
4: interface MyListener { 5: void eventOccurred(MyEvent event) 8: @Canonical
9: class MyEvent { 10: def source
11: String message
12: }
14: class MyBean { 15: @ListenerList List listeners
16: }
对于 MyBean 而言,AST 将为其添加如下方法: 1: public void addMyListener(MyListener listener) {
2: if ( listener == null) { 3: return null 5: if ( listeners == null) { 6: listeners = []
7: }
8: listeners.add(listener)
9: }
11: public void removeMyListener(MyListener listener) { 12: if ( listener == null) { 13: return null 15: if ( listeners == null) { 16: listeners = []
18: listeners.remove(listener)
19: }
21: public void fireEventOccurred(MyEvent event) { 22: if ( listeners != null) { 23: java.util.ArrayListextends java.lang.Object> __list = new java.util.ArrayListextends java.lang.Object>(listeners) 24: for (java.lang.Object listener : __list ) { 25: listener.eventOccurred(event)
26: }
27: }
28: }
这一注释可以简化 Bean 的编写(不过老实说,我倒是没怎么用 Groovy 来写 Bean)。 PS:总的来说,1.8 版本的 AST 新特性还是很让人满意的,但是大家也看到了 AST 生成的代码了,毕竟是机器生成的,效率上稍稍打了折扣。 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |