Groovy简介——编译时方法注入

一般只用于编写一些插件或者模板方法的时候使用,编译后的代码还是会合成为静态代码,会比运行时的处理方式效率更高。需要根据AST来添加对应的语法树。Groovy编译器允许我们进入其变异截断,一窥其所处理的AST(Abstract Syntax Tree 抽象语法树)。

 

AST:

这就是生成的语法树:

 

Groovy支持开发者在任何阶段介入:初始化、解析、转换、语义分析、规范化、指令选择、class生成、输出和结束

AST在语义分析阶段之后生成,如果想使用信息更多的AST,可以再之后的阶段介入。

 

CodeCheck

package CodeAnalysis

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

import org.codehaus.groovy.control.SourceUnit

import org.codehaus.groovy.syntax.SyntaxException

import org.codehaus.groovy.transform.ASTTransformation

import org.codehaus.groovy.transform.GroovyASTTransformation



@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)

class CodeCheck implements ASTTransformation {

    @Override

    void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

        sourceUnit.ast.classes.each { classNode ->

            classNode.visitContents(new OurClassVisitor(sourceUnit))

        }

    }

}

class OurClassVisitor implements GroovyClassVisitor{

    SourceUnit sourceUnit

    OurClassVisitor(theSourceUnit){

        sourceUnit = theSourceUnit

    }

    @Override

    void visitClass(ClassNode classNode) {

    }





    @Override

    void visitConstructor(ConstructorNode constructorNode) {

    }

    private void reportError(message, lineNumber, columnNumber){

        sourceUnit.addError(new SyntaxException(message, lineNumber, columnNumber))

    }

    @Override

    void visitMethod(MethodNode methodNode) {

        if (methodNode.name.size() == 1){

            reportError "Make method name descriptive, avoid single letter names",

                    methodNode.lineNumber, methodNode.columnNumber

        }

        methodNode.parameters.each {parameter ->

            if (parameter.name.size() == 1){

                reportError "Single letter parameters are morally wrong!",

                    parameter.lineNumber, parameter.columnNumber

            }

        }

    }





    @Override

    void visitField(FieldNode fieldNode) {

    }

    @Override

    void visitProperty(PropertyNode propertyNode) {

    }

}

 

OurClassVisitor

 

package CodeAnalysis

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

import org.codehaus.groovy.control.SourceUnit

import org.codehaus.groovy.syntax.SyntaxException

import org.codehaus.groovy.transform.ASTTransformation

import org.codehaus.groovy.transform.GroovyASTTransformation



@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)

class CodeCheck implements ASTTransformation {

    @Override

    void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

        sourceUnit.ast.classes.each { classNode ->

            classNode.visitContents(new OurClassVisitor(sourceUnit))

        }

    }

}

class OurClassVisitor implements GroovyClassVisitor{

    SourceUnit sourceUnit

    OurClassVisitor(theSourceUnit){

        sourceUnit = theSourceUnit

    }

    @Override

    void visitClass(ClassNode classNode) {

    }

    @Override

    void visitConstructor(ConstructorNode constructorNode) {

    }





    private void reportError(message, lineNumber, columnNumber){

        sourceUnit.addError(new SyntaxException(message, lineNumber, columnNumber))

    }



    @Override

    void visitMethod(MethodNode methodNode) {

        if (methodNode.name.size() == 1){

            reportError "Make method name descriptive, avoid single letter names",

                    methodNode.lineNumber, methodNode.columnNumber

        }

        methodNode.parameters.each {parameter ->

            if (parameter.name.size() == 1){

                reportError "Single letter parameters are morally wrong!",

                    parameter.lineNumber, parameter.columnNumber

            }

        }

    }

    @Override

    void visitField(FieldNode fieldNode) {

    }

    @Override

    void visitProperty(PropertyNode propertyNode) {

    }

}

配置文件

 

manifest/META-INF/services/org.codehaus.groovy.transform.ASTTransformation

groovyc -classpath checkcode.jar smelly.groovy    

 

groovyc -d classes CodeAnalysis/CodeCheck.groovy  

jar -cf checkcode.jar -C classes CodeAnalysis -C manifest .

groovyc -classpath checkcode.jar smelly.groovy    

 

 

新建InjectAudit.groovy


 

package AST.InterceptingCalls.com.aglledeveloper

import org.codehaus.groovy.ast.ASTNode

import org.codehaus.groovy.ast.expr.ArgumentListExpression

import org.codehaus.groovy.ast.expr.MethodCallExpression

import org.codehaus.groovy.ast.expr.VariableExpression

import org.codehaus.groovy.ast.stmt.ExpressionStatement

import org.codehaus.groovy.control.CompilePhase

import org.codehaus.groovy.control.SourceUnit

import org.codehaus.groovy.transform.ASTTransformation

import org.codehaus.groovy.transform.GroovyASTTransformation





@GroovyASTTransformation(phase = CompilePhase.SEMANTIC_ANALYSIS)

class InjectAudit implements ASTTransformation {

    @Override

    void visit(ASTNode[] astNodes, SourceUnit sourceUnit) {

        def checkingAccountClassNode =

                astNodes[0].classes.find {

                    it.name == 'AST.InterceptingCalls.CheckingAccount'

                }

        injectAuditMethod(checkingAccountClassNode)

    }



    static void injectAuditMethod(checkingAccountClassNode){

        def nonAuditMethods =

                checkingAccountClassNode?.methods.findAll{ it.name != 'audit'}

        nonAuditMethods?.each { injectMethodWithAudit(it) }

    }



    static void injectMethodWithAudit(methodNode){

        def callToAudit = new ExpressionStatement(

                new MethodCallExpression(

                        new VariableExpression('this'),

                        'audit',

                        new ArgumentListExpression(methodNode.parameters)

                )

        )

        methodNode.code.statements.add(0, callToAudit)

    }

}

 

groovyc -d classes AST/InterceptingCalls/com/aglledeveloper/InjectAudit.groovy    

jar -cf injectAudit.jar -C classes AST -C manifest .

生成对应的jar包

 

UsingCheckingAccount.groovy

package AST.InterceptingCalls

class CheckingAccount {

    def audit(amount) {

        if (amount > 10000){

            print "auditing ..."

        }

    }





    def deposit(amount){

        println "depositing ${amount} ..."

    }

    def withdraw(amount){

        println "withdrawing ${amount} ..."

    }

}

def account = new CheckingAccount()

account.deposit(1000)

account.deposit(12000)

account.withdraw(11000)





groovy src/groovy/AST/InterceptingCalls/UsingCheckingAccount.groovy



depositing 1000 ...

depositing 12000 ...

withdrawing 11000 ...



depositing 1000 ...

auditing ...depositing 12000 ...

auditing ...withdrawing 11000 ...

 

执行:

groovyc -d classes src/groovy/AST/InterceptingCalls/com/aglledeveloper/InjectAudit.groovy

jar -cf injectAudit.jar -C classes AST -C manifest .    

groovy -classpath injectAudit.jar src/groovy/AST/InterceptingCalls/UsingCheckingAccount.groovy

 

 

结果:

depositing 1000 ...

auditing ...depositing 12000 ...

auditing ...withdrawing 11000 ...

 

*audit方法被应用在最前方,当对于10000的时候会自动审计

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值