You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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)

问题根源

  1. 原实现中ByteArrayURLConnection使用file协议,Groovy编译时会尝试访问本地文件系统,而非通过自定义ResourceConnector获取脚本内容
  2. 自定义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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 21:44:52