一般只用于编写一些插件或者模板方法的时候使用,编译后的代码还是会合成为静态代码,会比运行时的处理方式效率更高。需要根据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的时候会自动审计