Groovy DSL开发:如何抛出异常定位DSL代码错误行?
解决Groovy DSL中的委托异常定位与非预期闭包拦截问题
一、定位DSL错误行并抛出特定类型异常
你的当前代码中,当DSL用户调用了MyType不存在的方法时,默认的MissingMethodException可能不会清晰指向DSL脚本的错误行,或者缺少友好的提示信息。可以通过以下步骤优化:
1. 配置闭包解析策略+自定义异常包装
首先将闭包的resolveStrategy设置为DELEGATE_FIRST,确保优先查找委托对象(MyType)的方法,避免误匹配到Script的方法;然后捕获缺失方法异常,包装成自定义异常并提取DSL脚本的错误行号:
abstract class MyScript extends Script { def type(@DelegateTo(MyType) Closure cl) { def myType = new MyType() cl.delegate = myType cl.resolveStrategy = Closure.DELEGATE_FIRST // 优先使用委托对象的方法 try { cl.call() } catch (MissingMethodException e) { wrapAndThrowDSLException(e) } this } private void wrapAndThrowDSLException(MissingMethodException e) { // 从栈轨迹中定位DSL脚本的错误行 def dslErrorLine = e.stackTrace.find { it.className == this.class.name // 匹配DSL脚本生成的类名 } if (dslErrorLine) { throw new DSLValidationException( "DSL错误:在第${dslErrorLine.lineNumber}行调用了不存在的方法'${e.method}'", e ) } else { throw e // 找不到DSL行号时 fallback到原异常 } } } // 自定义特定类型的异常类,方便统一捕获处理 class DSLValidationException extends RuntimeException { DSLValidationException(String message, Throwable cause) { super(message, cause) } }
2. 编译时提前检查(可选)
如果希望在DSL脚本编译阶段就发现错误,可以启用@DelegateTo的严格模式,配合Groovy的静态类型检查:
import groovy.lang.DelegateTo def type(@DelegateTo(value = MyType, strict = true) Closure cl) { // 原有执行逻辑 }
此时,当DSL脚本中调用MyType不存在的方法时,GroovyShell在编译脚本阶段就会抛出错误,直接定位到错误行,无需等到运行时。
二、拦截非预期的闭包结构
如果需要限制DSL闭包中的操作(比如禁止嵌套闭包、禁用某些方法),可以通过以下方式实现:
1. 在委托对象中拦截未授权方法
修改MyType类,重写methodMissing方法,拦截所有未定义的方法调用并抛出异常:
class MyType { // 定义DSL允许的合法方法 def configure(String param) { // 业务逻辑实现 } // 拦截所有未定义的方法调用 def methodMissing(String name, args) { throw new DSLValidationException( "DSL不允许调用方法'${name}',请检查闭包结构是否符合规范", new MissingMethodException(name, MyType, args) ) } }
2. 限制闭包嵌套(可选)
如果需要禁止在type闭包中嵌套其他闭包,可以解析闭包的AST来检查结构,这种方式适合对DSL语法有严格要求的场景:
def type(@DelegateTo(MyType) Closure cl) { def ast = cl.ast def nestedClosures = ast.statements.findAll { it instanceof org.codehaus.groovy.ast.stmt.ExpressionStatement && it.expression instanceof org.codehaus.groovy.ast.expr.ClosureExpression } if (!nestedClosures.isEmpty()) { throw new DSLValidationException("DSL不允许嵌套闭包,请移除第${nestedClosures[0].lineNumber}行的闭包代码") } // 原有闭包执行逻辑 }
三、确保GroovyShell环境的兼容性
由于你的运行环境基于GroovyShell和CompilerConfiguration,需要确保自定义的异常类、MyScript和MyType都在GroovyShell的类加载器范围内:
def config = new CompilerConfiguration(scriptBaseClass: MyScript.name) def shell = new GroovyShell(this.class.classLoader, new Binding(), config) // 执行DSL脚本 shell.evaluate(new File("your-dsl-script.groovy"))
内容的提问来源于stack exchange,提问作者childno͡.de
相关产品推荐
相关产品推荐

