GroovyScriptEngine无法从自定义S3类文件系统加载依赖脚本
GroovyScriptEngine类S3存储脚本依赖自动解析问题解决
问题背景
基于GroovyScriptEngine实现从类S3存储加载脚本的执行系统,执行UseHelper.groovy时无法自动解析依赖的Helper.groovy,手动预加载Helper.groovy可正常运行,需实现依赖的自动动态解析。
环境配置
- 自定义
S3FileSystemService模拟S3存储服务 - 为GroovyScriptEngine实现自定义
ResourceConnector - 使用自定义
GroovyClassLoader - S3文件结构:
testClient/root/ testClient/root/scripts/ testClient/root/scripts/Helper.groovy testClient/root/scripts/UseHelper.groovy
脚本内容
UseHelper.groovy
def helper = new Helper() helper.getMessage().toUpperCase()
Helper.groovy
class Helper { def getMessage() { return "Hello from Helper class" } }
错误信息
执行时抛出:
startup failed: file:Helper.groovy: Helper.groovy (No such file or directory)
问题根源
- 原实现中
ByteArrayURLConnection使用file协议,Groovy编译时会尝试访问本地文件系统,而非通过自定义ResourceConnector获取脚本内容 - 自定义
GroovyClassLoader未实现从S3动态加载依赖类的逻辑,无法在类找不到时自动触发脚本加载与编译
解决方案
1. 修改ByteArrayURLConnection使用自定义协议
避免file协议导致的本地文件查找:
class ByteArrayURLConnection extends URLConnection { private final byte[] content ByteArrayURLConnection(URL url, byte[] content) { super(url) this.content = content } @Override void connect() {} @Override InputStream getInputStream() { return new ByteArrayInputStream(content) } }
2. 优化S3ResourceConnector处理逻辑
移除file:前缀处理,改用自定义协议构建URL,确保所有资源请求通过S3加载:
class S3ResourceConnector implements ResourceConnector { private final S3FileSystemService s3FileSystemService private final String clientKey S3ResourceConnector(S3FileSystemService s3FileSystemService, String clientKey) { this.s3FileSystemService = s3FileSystemService this.clientKey = clientKey } @Override URLConnection getResourceConnection(String resourceName) throws ResourceException { try { println "Original resourceName: ${resourceName}" // 处理依赖请求中的特殊格式 if (resourceName.contains('$')) { resourceName = resourceName.substring(resourceName.indexOf('$') + 1) println "ResourceName after handling dependency: ${resourceName}" } // 标准化脚本文件名 String scriptName = resourceName.endsWith(".groovy") ? resourceName : "${resourceName}.groovy" println "Normalized script name: ${scriptName}" // 构建S3完整路径 String fullS3Path = "root/scripts/${scriptName}" println "Loading from S3 path: ${fullS3Path}" String content = s3FileSystemService.getFileContent(clientKey, fullS3Path) println "Loaded content for ${fullS3Path}" // 使用自定义"s3"协议创建URL URL s3Url = new URL("s3", "", -1, fullS3Path) return new ByteArrayURLConnection(s3Url, content.getBytes()) } catch (Exception e) { println "Failed to load resource ${resourceName}: ${e.message}" throw new ResourceException("Failed to load resource: ${resourceName}", e) } } }
3. 增强S3GroovyClassLoader实现类自动加载
重写findClass方法,当类找不到时自动从S3加载对应脚本并编译:
class S3GroovyClassLoader extends GroovyClassLoader { private final S3FileSystemService s3FileSystemService private final String clientKey S3GroovyClassLoader(ClassLoader parent, S3FileSystemService s3FileSystemService, String clientKey) { super(parent) this.s3FileSystemService = s3FileSystemService this.clientKey = clientKey } @Override protected Class<?> findClass(String name) throws ClassNotFoundException { try { // 优先使用父类加载器 return super.findClass(name) } catch (ClassNotFoundException e) { // 类名转换为脚本文件名 String scriptFileName = "${name}.groovy" String s3Path = "root/scripts/${scriptFileName}" try { String scriptContent = s3FileSystemService.getFileContent(clientKey, s3Path) // 编译并加载类 return parseClass(scriptContent, scriptFileName) } catch (Exception ex) { throw new ClassNotFoundException("Could not load class ${name} from S3", ex) } } } }
4. 测试代码无需修改
原测试逻辑保持不变,现在执行UseHelper.groovy时,Groovy会自动触发依赖的Helper类加载,S3GroovyClassLoader会从S3加载Helper.groovy并编译,实现自动依赖解析。
内容的提问来源于stack exchange,提问作者Jorge Guerreiro
相关产品推荐
相关产品推荐

