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 方法中进行方法拦截 )