如何从jar文件中加载Groovy脚本?

3
我正在使用Groovy执行一组Groovy脚本,当运行时JAR与Web应用程序一起部署并且运行时执行位于C:\ jboss-eap-7.4 \ bin(Java工作目录)下的Groovy脚本时,这些脚本可以正常工作。由于可移植性和其他限制,现在我们需要将这些Groovy脚本作为运行时JAR的一部分移动,然后从类路径加载和执行这些脚本。

有人可以帮助运行运行时JAR文件内部的Groovy脚本吗(在Web应用程序中)?

enter image description here

目前的实现是从C:\jboss-eap-7.4\bin(Java工作目录)执行Groovy脚本。

**在GPT-3的回答后更新**

final String PLUGIN_DESCRIPTOR = "Plugins.groovy"
    final class PluginBootStrapper 
{
    private GroovyScriptEngine scriptEngine = null;
    private GroovyShell shell;
    private GroovyClassLoader classLoader = null;
    private final boolean loadFromClasspath;

    private final List<Plugin> allPlugins = null;
    
    private static final Logger logger = LogManager.getLogger(PluginBootStrapper.class);
    
    public PluginBootStrapper() 
    {
        System.out.println("Inside PluginBootStrapper")
        logger.info("Inside PluginBootStrapper")
        String pluginsDir = System.getProperty(CSMProperties.endorsed_plugins_dir)//".\\plugins"//System.getProperty(CSMProperties.endorsed_plugins_dir)
        loadFromClasspath = true
        shell =  new GroovyShell();
        //scriptEngine = new GroovyScriptEngine(CommonUtils.getAllDirectories(pluginsDir))

        logger.info "Plugins Directory:"+pluginsDir
        println "Plugins Directory:"+pluginsDir
        
        allPlugins = loadDescriptor().invokeMethod("getAllPlugins", null)       
    }
    
    private Object loadDescriptor()
    {
        Object pluginDescriptor = bootStrapScript(CSMProperties.get(CSMProperties.PLUGIN_DESCRIPTOR))                                                                
        pluginDescriptor                         
    }
        
    Object bootStrapScript(String script)
    {
        String pluginsDir = System.getProperty(CSMProperties.endorsed_plugins_dir)
        if (pluginsDir != null) {
            script = pluginsDir + script
        }
        
        printClassPath(this.class.getClassLoader())

        Object pluginScript = null
        //logger.info "script: "+ script
        //String path = this.getClass().getClassLoader().getResource(script).toExternalForm()
        //logger.info "bootStrapScript script "+ script
        logger.info "bootStrapScript script: "+ script + ", path: "+ new File(script).absolutePath
        println "bootStrapScript script: "+ script + ", path: "+ new File(script).absolutePath
        if (this.loadFromClasspath) {
            pluginScript = new GroovyShell(this.class.getClassLoader()).evaluate(new File(script));     //<-- Line no:60
            /* classLoader = new GroovyClassLoader(Thread.currentThread().getContextClassLoader());
            pluginScript = classLoader.parseClass(new File(script)); */
            return pluginScript
        } else {
            pluginScript = scriptEngine.loadScriptByName(script).newInstance()
            return pluginScript
        }
        
        return pluginScript
    }
    
    public List<Plugin> getAllPlugins()
    {
        return allPlugins
    }

    def printClassPath(classLoader) {
        println "$classLoader"
        classLoader.getURLs().each {url->
            println "- ${url.toString()}"
        }
        if (classLoader.parent) {
            printClassPath(classLoader.parent)
        }
    }
    
}

CommonUtils.getAllDirectories 方法

public static String[] getAllDirectories(String directory)
{
    logger.info("Inside CommonUtils"+directory)
    def dir = new File(directory)
    def dirListing = []
    if (dir.exists()) {
        logger.info "Looking for plugins in "+dir.absolutePath+" directory."
        
        dir.eachFileRecurse {file->
             if(file.isDirectory()) {
                 dirListing << file.getPath()
                 logger.info "Using "+file.getPath()+" plugin folder."
             } else {
                 logger.info "Using "+file.getPath()+" plugin file."
             }
        }
    } else {
        logger.error directory+" folder does not exist. Please provide the plugin files."
    }
    
    dirListing.toArray() as String []
}

测试运行命令:java -cp runtime-2.0-jar-with-dependencies.jar;.runtime-2.0-jar-with-dependencies.jar; -Dendorsed_plugins_dir=runtime-2.0-jar-with-dependencies.jar\ com.Main %*

输出 enter image description here

**旧更新**

目前,如果我将插件文件夹(Groovy脚本)放在jar包中,它会说找不到C:\jboss-eap-7.4\bin文件夹下的Plugins.groovy

Jar包内war文件的截图:C:\Users\ricky\Desktop\siperian-mrm.ear\mysupport.war\WEB-INF\lib\runtime.jar\ enter image description here


听起来你的问题不同 - 如何在jar包中列出特定文件夹下的资源... - daggett
根据你的代码,你已经在Groovy内部了 - 为什么要使用新的GroovyScriptEngine来加载插件类/脚本,而不是使用new plugins.desc.Plugins()来实例化/plugins/desc/Plugins.groovy呢? - daggett
@daggett:这个代码库用于两个项目:一个是基于Java的,允许我们根据需要更改插件(Groovy脚本)而无需重新启动服务器;另一个项目则需要将其打包到JAR文件中。因此正在尝试创建一个通用工具。 - Ricky
但是您的内存中已经有了Groovy运行时。因此,问题仍然存在-您为什么不使用现有的Groovy运行时与简单的new a.b.Plugins()语句?它甚至可以从jar文件中加载Groovy类... - daggett
这里是问题:官方上你不能列出JAR中的类/资源。你只能通过名称访问它们。作为解决方法,你可以使用someClass.getClass().protectionDomain.codeSource.location获取JAR位置,然后尝试使用https://docs.oracle.com/javase/8/docs/api/java/util/zip/ZipFile.html列出JAR内容。你的其余代码应该可以工作,但从ZipEntry获取脚本内容或使用ClassLoader作为资源,或者像我说的那样,你可以直接使用`currentGroovyClassLoader.loadClass('plugins.desc.Plugins')` - 但在这种情况下,它应该有声明的包... - daggett
显示剩余4条评论
2个回答

2
假设你有一个名为plugins.jar的文件,其中包含以下Groovy文件:/a/b/C.groovy
def f(x){
    println "${this.getClass()} :: ${x}"
}

option 1

//in this case you don't need plugins.jar to be in classpath
//you should detect somehow where the plugins.jar is located for current runtime
URL jar = new URL('jar:file:./plugins.jar!/') //jar url must end with !/
def groovyScriptEngine = new GroovyScriptEngine(jar)
def C = groovyScriptEngine.loadScriptByName('a/b/C.groovy')
def c = C.newInstance()
c.f('hello world')

option 2

//plugins.jar must be in current classpath
//C.groovy must have a `package` header if it's located in /a/b folder: package a.b
def C = this.getClass().getClassLoader().loadClass('a.b.C')
def c = C.newInstance()
c.f('hello world')

option 3

//plugins.jar must be in current classpath
def url = this.getClass().getClassLoader().getResource('a/b/C.groovy')
def gshell = new GroovyShell()
def c = gshell.parse(url.toURI())
c.f('hello world')

在jar中迭代文件

理论上可以列出jar内部的文件,但应用服务器可能会对此进行限制。

在获取入口点 - 类/脚本实例 a/b/C.groovy(在您的情况下为“Plugins.groovy”)之后,您可以引用jar并迭代条目:

URL jar = c.getClass().protectionDomain.codeSource.location //you don't need this for option #1

JarURLConnection jarConnection = (JarURLConnection)url.openConnection()
jarConnection.getJarFile().with{jarFile->
    jarFile.entries().each{java.util.jar.JarEntry e->
        println e.getName()
        if(!e.isDirectory()){
            //you can load/run script here instead of printing it
            println '------------------------'
            println jarFile.getInputStream(e).getText()
            println '------------------------'
        }
    }
}

1
上述提供的答案也是正确的,特别是 @daggett 提供的答案,因为它们包括从 jar 文件中读取/访问资源的多种方式。我分享我的版本,已在此 codebase 上测试过。
我进行了以下更改:
  1. 将所有脚本放入 "plugins" 文件夹中。

enter image description here

修改了 pom.xml 文件,将“plugins”文件夹包含在内,而不是单独的 groovy 脚本。使用 <include>plugins/</include>
使用类似下面的代码通过 getResourceAsStream 读取脚本。
``` StringBuilder pluginsDir = new StringBuilder(System.getProperty("plugins_dir").toString()) String script = pluginsDir.append(script) InputStream inputStream = getClass().getResourceAsStream(script) BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream)) pluginScript = groovyShell.parse(reader); return pluginScript ```
使用以下命令运行:
``` java -cp mvn-groovy-test-example-1.0-jar-with-dependencies.jar; -Dplugins_dir=/plugins/ com.example.App ```

enter image description here


谢谢,这个完美地运行了。 - Ricky

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接