Groovy语法学习(十)AST的使用

在开发Android项目中,经常会用到AbstractProcessor来构建注解处理器。在编译的时候生成代码,从而减少工作量。使用非常广泛的butterknife就是如此,但是最新版的github貌似是使用gradle插件来尽心生成,不过原理应该都类似,编译的时候通过对应的注解生成一些辅助类,或者主动注入一些方法。Groovy的AST就是类似的用法,由于Groovy的动态性,在AST中能做的事情就更多了。

一、AST的简单使用

在intellij中新建一个gradle项目,然后建立两个文件。

ASTtest.groovy

class ASTTextContent {
    def a
    def  fun(){
        println 'str'
    }
}
println()

TestASTTransformation.groovy

import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation

/*
* Created by TY on 2018/4/20.
*/

@GroovyASTTransformation
class TestASTTransformation implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {
        nodes.each {
            println it
        }
        println source.AST
        println source.source.reader.text
    }
}

接着新建一个META-INF/services目录,在目录下新建一个org.codehaus.groovy.transform.ASTTransformation文件,并在文件中添加下面一行:
TestASTTransformation
最终目录如图:
这里写图片描述

其中classes,以及resource两个目录可以不管,这是为了方便打包jar建立的目录。按照我之前的建立方式可以不需要额外打包jar来进行测试。
接下来我们运行ASTtest.groovy,就会得到结果:

org.codehaus.groovy.ast.ModuleNode@709ba3fb
org.codehaus.groovy.ast.ModuleNode@709ba3fb
/*
* Created by TY on 2018/4/20.
*/

class ASTTextContent {
    def a
    def  fun(){
        println 'str'
    }
}


println()

分析一下,node和AST是同一个对象,ModuleNode,source直接就把源码读取出来了。至于ModuleNode是什么东西,我们先看一看ASTNode的继承关系。
这里写图片描述
既然是编译时候的处理器,自然能获取到什么类的节点,方法节点,成员变量节点之类的。具体的我也没有太仔细查看,常用的也不多。至于ModuleNode通过查看注释,基本就可以理解为script了。Groovy直接执行代码的时候产生的module。

修改一下TestASTTransformation.groovy的代码

import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.ClassNode
import org.codehaus.groovy.ast.ConstructorNode
import org.codehaus.groovy.ast.FieldNode
import org.codehaus.groovy.ast.GroovyClassVisitor
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.PropertyNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation

/*
* Created by TY on 2018/4/20.
*/

@GroovyASTTransformation
class TestASTTransformation implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {

        source.AST.classes.each {
            it.visitContents(new GroovyClassVisitor() {
                @Override
                void visitClass(ClassNode node) {
                    println 'visit class'
                }

                @Override
                void visitConstructor(ConstructorNode node) {
                    println node.name

                }

                @Override
                void visitMethod(MethodNode node) {
                    println node.name

                }

                @Override
                void visitField(FieldNode node) {
                    println node.name

                }

                @Override
                void visitProperty(PropertyNode node) {
                    println node.name

                }
            })
        }
    }
}

这里的source.AST.classes有两个类,一个是脚本script的子类ASTtest另一个就是ASTTextContent。然而戳进源码发现visitClass方法根本没有调用,其实也对,it就是classNode了,调不调用也无所谓了。结果:

<init>
<init>
main
run
a
a
fun

二、使用AST修改方法

首先我们先运行一下groovy的consolo,来分析ast的结构。
运行win10的powershell,当然cmd也行,执行
groovyconsole
出现以下界面
这里写图片描述

然后我们就可以在窗口中分析类的ast结构。然后我们新建一个module,并创建ASTtest.groovy文件。

class ASTTextContent {
    def a
    def  fun(){
        println 'str'
    }
}

new ASTTextContent().fun()

我们将代码粘贴到groovyconsole窗口中,按下ctrl+T ,就可以分析结构了。
这里写图片描述

图中的BlockStatement可以理解为语句块,也就是一行代码吧。分析一下得知我们只需要在BlockStatement 加入自己的statement就行了。
接下来创建我们的AST,注意下面使用了两种方法来添加方法的语句块。
InjectAST.groovy

import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
/**
 * Created by Administrator on 2017/6/23 0023.
 */
@GroovyASTTransformation
class InjectAST implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {

        source.AST.classes.find{
            it.name.contains("ASTTextContent")
        }?.methods?.find{
            it.name=='fun'
        }?.with {

            BlockStatement block = code
            def methods = new AstBuilder().buildFromSpec {
                expression {
                    methodCall {
                        variable('this')
                        constant('println')
                        argumentList {
                            constant('replace')
                        }
                    }
                }
            }
            block.statements.addAll(methods)

            methods =  new AstBuilder().buildFromCode {
                println('third')
            }
            block.statements.addAll(methods[0].statements)
        }
        }
    }
}

最终目录结构
这里写图片描述
然后我们运行ASTtest.groovy,会输出两行字符串。

str
replace
third

那么我们修改方法就成功了。

三、使用AST绑定注解

同样在新的module中我们新建几个类。
AnnotationAST

import org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation
/**
 * Created by Administrator on 2017/6/23 0023.
 */
@GroovyASTTransformation
class AnnotationAST implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {
       if(nodes.size()==2){
           println nodes[0]
           println nodes[1]
       }
    }
}

ASTtest.groovy

class ASTTextContent {
    @BindMethod
    def  fun(){
        sleep(2_000)
    }
}
new ASTTextContent().fun()

BindMethod

import org.codehaus.groovy.transform.GroovyASTTransformationClass

import java.lang.annotation.ElementType
import java.lang.annotation.Target

/*
* Created by TY on 2018/4/20.
*/


@Target(ElementType.METHOD)
@GroovyASTTransformationClass('AnnotationAST')
@interface BindMethod {

}

最终目录结构:
这里写图片描述

然后我们运行ASTtest,观察之前我们普通执行的结果,知道visit中的nodes只有一个ModuleNode,而使用注解绑定,会将注解的方法以及注解一并传过来,所以这里的结果:

org.codehaus.groovy.ast.AnnotationNode@17695df3
MethodNode@878274034[java.lang.Object fun()]

那么我们就可以不用很容易的处理一些东西了。我们把AnnotationAST的visit方法修改一下。目的是将类处理成这样的。
这里写图片描述
使用ast分析
这里写图片描述
与只有单行代码 sleep(2_000)的进行对比
这里写图片描述
那么我们知道了缺失的表达式,创建好填充进去就可以了。

 @Override
    void visit(ASTNode[] nodes, SourceUnit source) {
        def methodNodes = nodes.findAll { it instanceof MethodNode }
        methodNodes.each {
            MethodNode node ->
            //单行语句会生成ReturnStatement
                def startStatement = new AstBuilder().buildFromCode {
                    def start = System.nanoTime()
                }
                //多行代码生成ExpressionStatement
                def endStatement = new AstBuilder().buildFromCode {
                    def use = System.nanoTime()-start
                    println("use:${use/1.0e9}")
                }

                BlockStatement blockStatement = node.code
                ReturnStatement returnStatement = startStatement[0].statements[0]
                //这里添加需要ExpressionStatement在·
                blockStatement.statements.add(0,new ExpressionStatement(returnStatement.expression))
                blockStatement.statements.addAll(endStatement[0].statements)

        }
    }

那么最终结果就是:

use:2.000883712

关于groovy语言的使用已经写了十篇文章了,不能说总结的十分全面,但是基本上和java的区别用法都总结差不多了,如果你熟悉java,应该对groovy的与语法来说不成问题了。接下来准备研究android的gradle插件了。

猜你喜欢

转载自blog.csdn.net/a568478312/article/details/80019128