【问题标题】:Read xml from an external jar not included in classpath从未包含在类路径中的外部 jar 中读取 xml
【发布时间】:2015-11-09 22:12:38
【问题描述】:

我使用 Netbeans 创建了一个 javafx 项目,该项目本身运行良好。

我现在正在尝试实现一个自定义的轻量级插件系统,插件是位于主项目的plugins/ 目录内的外部 JAR。我正在使用javax.security 包对插件进行沙箱处理。

这是主要项目的结构:

MainProject
  |
  |---plugins/
  |   |---MyPlugin.jar
  |
  |---src/
  |   |---main.app.plugin
  |       |---Plugin.java
  |       |---PluginSecurityPolicy.java
  |       |---PluginClassLoader.java
  |       |---PluginContainer.java
  ....

还有插件的一个:

Plugin
  |
  |---src/
  |   |---my.plugin
  |   |   |---MyPlugin.java
  |   |--settings.xml
  |
  |---dist/
      |---MyPlugin.jar
          |---META-INF/
          |   |---MANIFEST.MF
          |---my.plugin
          |   |---MyPlugin.class
          |---settings.xml

为了将插件加载到程序中,我创建了一个 PluginContainer 类,该类从插件目录中获取所有 jar 文件,列出 jar 中的所有文件并查找插件文件和设置文件。

我可以加载并创建插件类的实例,但是当涉及到 XML 时,我什至无法在 jar 内容中列出它。

这是代码,也许有人能看出我哪里做错了。

PluginSecurityPolicy.java

import java.security.AllPermission;
import java.security.PermissionCollection;
import java.security.Permissions;
import java.security.Policy;
import java.security.ProtectionDomain;

public class PluginSecurityPolicy extends Policy {

    @Override
    public PermissionCollection getPermissions(ProtectionDomain domain) {
        if (isPlugin(domain)) {
            return pluginPermissions();
        } else {
            return applicationPermissions();
        }        
    }

    private boolean isPlugin(ProtectionDomain domain) {
        return domain.getClassLoader() instanceof PluginClassLoader;
    }

    private PermissionCollection pluginPermissions() {
        Permissions permissions = new Permissions();
        //
        return permissions;
    }

    private PermissionCollection applicationPermissions() {
        Permissions permissions = new Permissions();
        permissions.add(new AllPermission());
        return permissions;
    }
}

PluginClassLoader.java

import java.net.URL;
import java.net.URLClassLoader;

public class PluginClassLoader extends URLClassLoader {

    public PluginClassLoader(URL jarFileUrl) {
        super(new URL[] {jarFileUrl});
    }
}

PluginContainer.java,#load 方法就是其中之一

import main.app.plugin.PluginClassLoader;
import main.app.plugin.PluginSecurityPolicy;
import java.io.File;
import java.net.URL;
import java.security.Policy;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class PluginContainer {
    private ArrayList<Plugin> plugins;
    private ManifestParser parser;

    public PluginContainer() {
        Policy.setPolicy(new PluginSecurityPolicy());
        System.setSecurityManager(new SecurityManager());
        plugins = new ArrayList<>();
        parser = new ManifestParser();
    }

    public void init() {
        File[] dir = new File(System.getProperty("user.dir") + "/plugins").listFiles();
        for (File pluginJarFile : dir) {
            try {
                Plugin plugin = load(pluginJarFile.getCanonicalPath());
                plugins.add(plugin);
            } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
            }
        }
    }

    public <T extends Plugin> T getPlugin(Class<T> plugin) {
        for (Plugin p : plugins) {
            if (p.getClass().equals(plugin)) {
                return (T)p;
            }
        }
        return null;
    }

    private Plugin load(String pluginJarFile) throws Exception { 
        PluginManifest manifest = null;
        Plugin plugin = null;

        // Load the jar file
        ZipFile jarFile = new ZipFile(pluginJarFile);            

        // Get all jar entries
        Enumeration allEntries = jarFile.entries();
        String pluginClassName = null;

        while (allEntries.hasMoreElements()) {

            // Get single file
            ZipEntry entry = (ZipEntry) allEntries.nextElement();
            String file = entry.getName();

            // Look for classfiles
            if (file.endsWith(".class")) {

                // Set class name
                String classname = file.replace('/', '.').substring(0, file.length() - 6);

                // Look for plugin class
                if (classname.endsWith("Plugin")) {

                    // Set the class name and exit loop
                    pluginClassName = classname;
                    break;
                }
            }
        }

        // Load the class
        ClassLoader pluginLoader = new PluginClassLoader(new URL("file:///" + pluginJarFile));
        Class<?> pluginClass = pluginLoader.loadClass(pluginClassName);

        // Edit as suggested by KDM, still null
        URL settingsUrl = pluginClass.getResource("/settings.xml");
        manifest = parser.load(settingsUrl);

        // Check if manifest has been created
        if (null == manifest) {
            throw new RuntimeException("Manifest file not found in " + pluginJarFile);
        }

        // Create the plugin
        plugin = (Plugin) pluginClass.newInstance();
        plugin.load(manifest);

        return plugin;
    }
}

还有自动生成的 MANIFEST.MF

Manifest-Version: 1.0
Ant-Version: Apache Ant 1.9.4
Created-By: 1.8.0_25-b18 (Oracle Corporation)

Class-Path 指令丢失,但如果我将其强制为../settings.xmlsettings.xml(通过手动编辑MANIFEST.MF 文件)它也不会工作。

这就是我能想到的,提前感谢您的帮助

[EDIT] 我在插件 jar 根目录中创建了一个 images/monitor-16.png,将 #load2 方法添加到 PluginContainer 中。

由于该方法是在循环中调用的,因此我将 Policy.setPolicy(new PluginSecurityPolicy());System.setSecurityManager(new SecurityManager()); 留在了构造函数中。

这是新的插件 jar 结构:

TestPlugin.jar
    |
    |---META-INF/
    |   |---MANIFEST.MF
    |
    |---dev.jimbo
    |   |---TestPlugin.class
    |
    |---images
    |   |---monitor-16.png
    |
    |---settings.xml

新方法代码:

private Plugin load2(String pluginJarFile) throws MalformedURLException, ClassNotFoundException {        
    PluginClassLoader urlCL = new PluginClassLoader(new File(pluginJarFile).toURL());
    Class<?> loadClass = urlCL.loadClass("dev.jimbo.TestPlugin");

    System.out.println(loadClass);
    System.out.println("Loading the class using the class loader object. Resource = " + urlCL.getResource("images/monitor-16.png"));
    System.out.println("Loading the class using the class loader object with absolute path. Resource = " + urlCL.getResource("/images/monitor-16.png"));
    System.out.println("Loading the class using the class object. Resource = " + loadClass.getResource("images/monitor-16.png"));
    System.out.println();

    return null;
}

这是输出

class dev.jimbo.TestPlugin
Loading the class using the class loader object. Resource = null
Loading the class using the class loader object with absolute path. Resource = null
Loading the class using the class object. Resource = null

【问题讨论】:

  • 做了一些实验后,我删除了我的答案。将URLClassLoader 与在“images/load.gif”处具有图像的 JAR 一起使用时,getResource 在使用“/images/load.gif”时失败,但在“images/load.gif”时有效。你能检查一下吗?
  • 好的,我尝试使用像 URL settingsUrl = pluginClass.getResource("settings.xml")URL settingsUrl = pluginClass.getResource("./settings.xml") 这样的相对路径,但它仍然为空。我试图将带有图像的图像文件夹放入插件 jar 中,但我仍然得到空值。所以希望它可能是 netbeans 不喜欢我的 xml 名称我用我自己的姓氏重命名了文件,但它仍然为空:(
  • 将pluginClass改为pluginLoader并检查。
  • 完成,空。我再次尝试了相对路径和绝对路径。我开始考虑将 XML 文件移到插件 jar 之外。我不喜欢它,但在列表中它应该可以工作......

标签: java xml netbeans jar javafx


【解决方案1】:

以下程序:

public static void main(String[] args) throws MalformedURLException, ClassNotFoundException {
    Policy.setPolicy(new PluginSecurityPolicy());
    System.setSecurityManager(new SecurityManager());
    PluginClassLoader urlCL = new PluginClassLoader(new File(
            "A Jar containing images/load.gif and SampleApp class").toURL());
    Class<?> loadClass = urlCL.loadClass("net.sourceforge.marathon.examples.SampleApp");
    System.out.println(loadClass);
    System.out.println("Loading the class using the class loader object. Resource = " + urlCL.getResource("images/load.gif"));
    System.out.println("Loading the class using the class loader object with absolute path. Resource = " + urlCL.getResource("/images/load.gif"));
    System.out.println("Loading the class using the class object. Resource = " + loadClass.getResource("images/load.gif"));
}

产生以下输出:

class net.sourceforge.marathon.examples.SampleApp
Loading the class using the class loader object. Resource = jar:file:/Users/dakshinamurthykarra/Projects/install/marathon/sampleapp.jar!/images/load.gif
Loading the class using the class loader object with absolute path. Resource = null
Loading the class using the class object. Resource = null

所以我认为你的类加载器没有任何问题。将此作为答案,以便代码可以正确格式化。

【讨论】:

  • 首先感谢您的快速回复,我刚刚尝试了您的建议,但它给了我一个很大的空值,我将尝试添加到问题代码中
  • 您可以尝试只使用“settings.xml”吗?
  • 我通过在类包中移动 XML 并使用 pluginLoader.getResource("dev/jimbo/settings.xml") 来做到这一点,但如果我将 XML 放入 jar 根目录并尝试使用 pluginLoader.getResource("settings.xml"),我仍然会得到空值,奇怪... .如果我能找到解决方案,我会发布答案。感谢您的帮助!
【解决方案2】:

成功了!似乎我以前的 Netbeans (8.0) 正在从 Clean and Build 操作中添加的 Jar/Folder Libraries 引用中删除 plugin 目录。我已经下载并安装了 Netbeans 8.0.2,问题就解决了。虽然在他们的跟踪器上找不到该版本的任何相关错误..

无论如何感谢您的帮助:)

【讨论】:

    猜你喜欢
    • 2010-09-16
    • 2012-02-15
    • 2023-03-18
    • 2011-03-30
    • 1970-01-01
    • 2011-04-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多