如何在Groovy类中加载当前运行方法所在文件的源码为字符串?
获取Groovy类中当前方法所在文件的源码字符串
嘿,这个问题我之前也踩过坑——Groovy脚本确实能直接用script.source拿源码,但类的处理逻辑完全不一样。核心思路是通过类的protectionDomain定位到编译后的class文件,再反向找到对应的Groovy源文件,最后读取文件内容。不过得先说明:这个方案只适用于源码文件和编译后的class路径对应的场景(比如开发环境、或源码和class一起打包的情况),如果是只包含class的Jar包,那肯定拿不到源码。
具体实现代码
给你一个可以直接嵌入类里的工具方法,基础场景下够用:
class MyGroovyService { String getCurrentClassSource() { // 获取当前类的代码源位置 def codeSource = this.getClass().protectionDomain.codeSource if (!codeSource) { throw new IllegalStateException("无法获取类的代码源信息,可能是动态生成类或Jar中未包含源码") } // 将URL转换为文件路径,兼容Windows/Unix系统 def classFile = new File(codeSource.location.toURI()) // 替换.class后缀为.groovy,定位到源文件 def sourceFile = new File(classFile.path.replaceAll(/\.class$/, ".groovy")) if (!sourceFile.exists()) { throw new FileNotFoundException("找不到对应的Groovy源文件: ${sourceFile.path}") } // 指定编码读取,避免乱码 return sourceFile.getText("UTF-8") } void businessMethod() { // 调用方法获取整个类的源码 String classSource = getCurrentClassSource() println("当前类的源码内容:\n${classSource}") } }
关键注意事项
- 路径映射调整:如果你的项目用Gradle/Maven构建,class文件会在
target/classes或build/classes目录,而源码在src/main/groovy,那上面的代码需要调整路径映射。比如:
这个得根据你项目的实际目录结构修改。// 适配Maven项目的路径结构 def classPathParts = classFile.path.split(File.separator + "target" + File.separator) if (classPathParts.length == 2) { sourceFile = new File("src/main/groovy/${classPathParts[1]}".replaceAll(/\.class$/, ".groovy")) } - Jar环境限制:如果类被打包成不含源码的Jar,这个方法会直接失效。这种场景下可以把源码作为资源文件打包到Jar里,然后用
this.getClass().getResourceAsStream("/com/your/package/MyGroovyService.groovy")读取。 - 编码问题:读取文件时一定要指定项目使用的编码(比如UTF-8),不然容易出现乱码。
进阶:定位当前方法的代码片段
如果你不仅要整个文件的源码,还想拿到当前方法的具体代码,可以结合栈信息和行号来截取。这里给个简单的实现(适合方法定义不复杂的场景):
String getCurrentMethodSource() { // 获取当前方法的栈追踪信息 def stackTrace = Thread.currentThread().stackTrace def currentMethodEle = stackTrace.find { it.methodName == "getCurrentMethodSource" } if (!currentMethodEle) { throw new IllegalStateException("无法定位当前方法的栈信息") } int startLine = currentMethodEle.lineNumber // 获取源码的行列表 def sourceLines = getCurrentClassSource().readLines() // 定位方法起始行(简单匹配方法定义的开头) int methodStart = sourceLines.findIndexOf(startLine-2) { it.trim().startsWith("def ${currentMethodEle.methodName}") || it.trim().startsWith("${currentMethodEle.methodName}") } methodStart = methodStart == -1 ? startLine - 1 : methodStart // 定位方法结束行(匹配闭合大括号) int braceCount = 0 int methodEnd = sourceLines.findIndexOf(methodStart) { line -> if (line.contains("{")) braceCount++ if (line.contains("}")) braceCount-- braceCount == 0 } return sourceLines.subList(methodStart, methodEnd+1).join("\n") }
这个实现对复杂方法(比如多行参数、内部类嵌套)的支持有限,需要的话可以结合Groovy的AST工具做更精准的解析。
内容的提问来源于stack exchange,提问作者Michal Kordas
相关产品推荐
相关产品推荐

