Groovy11_编译时元编程

      • 1. ASTTransformation 介绍
      • 2. 拦截一个方法 , 修改 方法的实现
      • 3. 利用注解来进行ast转换

利用ASTTransformation 来实现

1. ASTTransformation 介绍

lsn11_0.groovy

class Content{
    def a;
    def b(){

    }
}

println()

Groovy11_编译时元编程_第1张图片

MyASTTansformation.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

// 如果在运行编译将我们的MyASTTansformation 添加到classPath里面
// 就能够在 这个自定的的实现了这个接口的类当中visit方法里面来
// 获得所有编译的节点,所有编译的源文件代码,还有类的方法 都可以获得
@GroovyASTTransformation
class MyASTTansformation implements ASTTransformation{

    /**
     *
     * @param nodes  :节点 ast抽象语法数节点(如果安装好了Groovy环境变量可以,
     * 在命令行当中来输入groovyConsole,在这个工具当中,然后将 Content这个类放到这个
     * 工具中,这个工具能帮我们分析ast,点击Script 的 Inspect Ast,这就是ast语法树,有
     * Methods,Fields,Properties拿到这个方法树,就意味着我们拿到这这个放到每一个节点,
     * 能拿到b方法的实现,能拿到a属性的值,都能获得)
     * @param source :源单元 (能拿到源文件)
     */
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {
        // 通过source 拿到源文件
        // 执行MyASTTansformation 必须创建META-INF
        // 为了方便,用命令行的方式
        // 先在groovy目录下创建resources目录
        // 然后创建META-INF.services目录
        // 然后在这个目录下创建一个org.codehaus.groovy.transform.ASTTransformation文件
        // 在这个文件当中写入 ASTTransformation 的实现类的全类名
        // 1.然后通过命令行groovyc -d classes MyASTTansformation.groovy 进行编译
        // 2. 然后打包 jar -cf test.jar -C classes . -C resources .
        // (然后就会在groovy目录下有一个test.jar)
        //3. 然后执行 groovy -classpath test.jar lsn11_0.groovy
        // 将刚才打出来的包 加到classpath当中来执行我们的
        println nodes   // [org.codehaus.groovy.ast.ModuleNode@61009542]
                        // (ModuleNode就是lsn11_0.groovy的节点)
                        // Groovy源文件可以创建很多 class,所以ModuleNode类里面
                        // 有一个List ,还有 List 方法
                        // List starImports = new ArrayList();
                        // Map staticImports = new LinkedHashMap();
                        // 就是 import节点
        println source          // org.codehaus.groovy.control.SourceUnit@77e9807f
        println source.AST      // org.codehaus.groovy.ast.ModuleNode@61009542
        source.AST.classes.each {
            //println it.name  // Content   可以拿到类名
            it.visitContents(new GroovyClassVisitor() {  // 分析访问 我们的 类
                @Override
                void visitClass(ClassNode node) {  // 分析类

                }

                @Override
                void visitConstructor(ConstructorNode node) {  // 分析构造方法

                }

                @Override
                void visitMethod(MethodNode node) {  // 分析方法
                    if (node.name.length() == 1){
                        println "$node.name too short"  // b too short
                    }
                }

                @Override
                void visitField(FieldNode node) {  // 分析 属性
                    if (node.name.length() == 1){
                        println "$node.name too short"  // a too short
                    }
                }

                @Override
                void visitProperty(PropertyNode node) {

                }
            })
        }
        println source.source.reader.text
//        class Content{
//            def a;
//            def b(){
//
//            }
//        }
//
//        println()


    }
}

org.codehaus.groovy.transform.ASTTransformation

MyASTTansformation

2. 拦截一个方法 , 修改 方法的实现

如果希望在编译时候,来拦截一个方法,或者在这个方法当中注入代码的话
也可以通过ASTTransformation 来实现
先创建一个新的模块 叫 inject, 在inject当中来拦截一个方法

source.groovy

// 希望通过Transformation 来修改 soutMsg的实现
class Content {

    def soutMsg(){
        println('ms')
    }
}

new Content().soutMsg()

Groovy11_编译时元编程_第2张图片

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

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

        source.AST.classes.find{
            // 找到Content类
            it.name == "Content"
        }?.methods?.find{
            // 找到soutMsg方法
            it.name == "soutMsg"
        }?.with {
            // 通过groovyConsole分析节点
            // 发现 code其实是个BlockStatement
            // 获得methodnode中的代码块实现
            BlockStatement block = code
            // BlockStatement 有一个 ExpressionStatement
            // block.getStatements()
            //  public List getStatements() 这个集合就是每一个实现节点


            // ====  1 . 方法的拦截 ====
            // 替换实现,先把这个方法节点里面所有的节点清空
            // 清空原始的实现
//            block.statements.clear()
            // 然后添加自己的实现
            // 创建自定义实现
            // 有三种创建,buildFromSpec,buildFromCode,buildFromString
            // 1.buildFromSpec
            def methods = new AstBuilder().buildFromSpec {
                //dsl 配合 groovyConsole使用
                expression{
                    methodCall {
                        variable('this')
                        constant('println')
                        argumentList {
                            constant('replace')
                        }
                    }
                }
            }
            block.statements.addAll(methods)
//            println methods   // ExpressionStatement
            // 2.buildFromString
//            block.statements.clear()
//            methods = new AstBuilder().buildFromString("""println '123456'""")
//            println methods   // BlockStatement
//            block.statements.addAll(methods[0].statements)
            // 3.buildFromCode
//            block.statements.clear()
            methods = new AstBuilder().buildFromCode {
                println 'zeking'
            }
//            println methods   // BlockStatement
//            block.statements.addAll(methods[0].statements)  // 添加到后面 ,,
//            ms
//            replace
//            zeking

            // ====  2 . 方法的注入 ====
            block.statements.add(0,methods[0].statements[0]) // 添加到前面
            // zeking
        }
    }

    // groovyc -d classes InjectAST.groovy
    // jar -cf inject.jar -C classes . -C resources .
    // groovy -classpath inject.jar source.groovy

    // 打印了 replace
}

org.codehaus.groovy.transform.ASTTransformation

InjectAST

groovyc -d classes InjectAST.groovy
jar -cf inject.jar -C classes . -C resources .
groovy -classpath inject.jar source.groovy

打印 replace

3. 利用注解来进行ast转换

基本上就跟apt一样了

创建新的module : annotationAST

UseAnno.groovy

class Content{

    @Check
    def fun(){
        Thread.sleep(2_000)

    }
}

new Content().fun()

需求: 将上面fun的实现变为

def fun(){
        def start = System.nanoTime()
        Thread.sleep(2_000)
        def use = System.nanoTime()-start
        println(use)
    }

Check.groovy

import org.codehaus.groovy.transform.GroovyASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformationClass

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

@Target(ElementType.METHOD)
@GroovyASTTransformationClass('AptAst')  // 这样就不用创建MEF-INF.services 这个资源了
@interface Check {

}

AptAst.groovy

mport org.codehaus.groovy.ast.ASTNode
import org.codehaus.groovy.ast.MethodNode
import org.codehaus.groovy.ast.builder.AstBuilder
import org.codehaus.groovy.ast.stmt.BlockStatement
import org.codehaus.groovy.ast.stmt.ExpressionStatement
import org.codehaus.groovy.ast.stmt.ReturnStatement
import org.codehaus.groovy.control.SourceUnit
import org.codehaus.groovy.transform.ASTTransformation
import org.codehaus.groovy.transform.GroovyASTTransformation

@GroovyASTTransformation
class AptAst implements ASTTransformation {
    @Override
    void visit(ASTNode[] nodes, SourceUnit source) {
//        source.AST.find{
//            it.name == 'Content'
//        }
//        println nodes
        // [org.codehaus.groovy.ast.AnnotationNode@64d2d351, MethodNode@107456312[java.lang.Object fun()]]
        // AnnotationNode 就是使用的注解,而MethodNode 就是被我们注解声明的方法 也就是fun
        // 所以可以直接在nodes上面来拿MethodNode
        // nodes 就是拿到了使用了@Check 这个注解的 对应的 注解节点与 方法节点
        def methodnodes = nodes.findAll { it instanceof MethodNode }
        methodnodes.each {
            MethodNode node ->
                def startStatement = new AstBuilder().buildFromCode {
                    def start = System.nanoTime()
                }
                def endStatement = new AstBuilder().buildFromCode {
                    def use = System.nanoTime() - start
                    println("use:${use/1.0e9}")
                }
//                println startStatement
                //[org.codehaus.groovy.ast.stmt.BlockStatement@2e377400[org.codehaus.groovy.ast.stmt.ReturnStatement
                // 发现    startStatement  是个BlockStatement 并且 ReturnStatement
                // return 了 后面的内容就不会执行了,也就是endStatement 不会执行了

//                println endStatement

                BlockStatement blockStatement = node.code
                ReturnStatement returnStatement = startStatement[0].statements[0]
                blockStatement.statements.add(0, new ExpressionStatement(returnStatement.expression))
                blockStatement.statements.addAll(endStatement[0].statements)
        }
    }
}

groovyc -d classes Check.groovy AptAst.groovy
jar -cf annotation.jar -C classes .
groovy -classpath annotation.jar UseAnno.groovy

输出 use:2.003856146

你可能感兴趣的:(Groovy,Gradle)