Groovy11_编译时元编程

Posted 李樟清

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Groovy11_编译时元编程相关的知识,希望对你有一定的参考价值。

利用ASTTransformation 来实现

1. ASTTransformation 介绍

lsn11_0.groovy

class Content
    def a;
    def b()

    


println()

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<ClassNode> ,还有 List<MethodNode> 方法
                        // List<ImportNode> starImports = new ArrayList<ImportNode>();
                        // Map<String, ImportNode> staticImports = new LinkedHashMap<String, ImportNode>();
                        // 就是 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()

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<Statement> 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

以上是关于Groovy11_编译时元编程的主要内容,如果未能解决你的问题,请参考以下文章

Groovy编译时元编程 ( 编译 ASTTransformation | 打包 ASTTransformation 字节码文件 | 编译 Groovy 类同进行编译时处理 )

Groovy编译时元编程 ( 编译时元编程引入 | 声明需要编译时处理的类 | 分析 Groovy 类的 AST 语法树 )

Groovy编译时元编程 ( ASTTransformation#visit 方法简介 | org.codehaus.groovy.ast.ModuleNode 脚本节点 )

Groovy编译时元编程 ( 编译时方法拦截 | 在 MyASTTransformation#visit 方法中进行方法拦截 )

Groovy09_MOP与元编程(方法注入)

Groovy09_MOP与元编程(方法注入)