【问题标题】:Problems with loading resources during execution执行过程中加载资源的问题
【发布时间】:2011-04-26 22:49:03
【问题描述】:

这是潜在问题的背景,我正在与一个小组合作开展一个使用 Swt 库的项目,并且我正在尝试打包软件以进行部署。事实证明SWT 非常依赖于平台/架构。我希望能够将所有六个jars(linux、mac、win 和 32/64 位)打包到同一个包中,并根据系统使用适当的库。我意识到这是一个艰巨的挑战,但是现在切换到Swing(或其他任何东西)并不是一个真正的选择。

我发现了许多相关的线程(@Aaron Digulla's thread@mchr's thread),它们为我提供了有关手头问题的宝贵见解。我试图实施@Alexey Romanov here 提出的解决方案。有一个区别,因为他提出的loadSwtJar() 方法不是静态的,我实例化了对象,然后立即运行该方法,然后对对象进行任何其他操作。

显示为加载过程无法正常工作。我对这种说法的推理如下:

  • 如果所有Swt jars 都从可执行jar 文件的classpath 中删除,则Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/events/MouseListener 被抛出,这是由于:java.lang.ClassNotFoundException: org.eclipse.swt.events.MouseListener 引起的

对我来说,这意味着在类路径中找不到库,我弄错了吗?

  • 如果swt jar 留在类路径中,则系统在执行期间将使用第一个 jar 文件。这意味着如果 gtk-linux-x86_64 恰好是 jar 列表中的第一个 swt jar,那么系统会尝试使用它,无论系统是 win32 还是 Mac OSX。

我尝试添加一些输出以查看loadSwtJar() 方法是否选择了正确的 jar,并且在我尝试过的所有平台上输出似乎都是正确的,因为选择了正确的包(并且文件确实存在于可运行的 jar)。但是仍然没有加载正确的库,因此会发生执行错误: Exception in thread "main" java.lang.reflect.InvocationTargetException 由前任引起:Caused by: java.lang.UnsatisfiedLinkError: Cannot load 32-bit SWT libraries on 64-bit JVM (请注意,如果我更改 build.xml 文件中 64 位和 32 位 swt 库的出现顺序,这是我在 Linux 机器上遇到的错误)

那么,这里的问题似乎是什么?我是否遗漏了一些细节,或者根本无法检查系统属性并相应地加载适当的库?

最后是我的构建文件的摘录,认为它可能有助于找到问题的根源。

提前致谢,


编辑:在与同事进行长时间的调试会话后,问题得到解决(除了我提到的有关 MacOS 上的线程管理的恼人错误 here)。它涉及调整 ANT 构建以及编写主类的方式。 (事实证明,主类正在扩展和实现来自 SWT 库的引用,这意味着代码根本无法编译,用另一个类包装主类并从那里加载 SWT jar,这似乎就足够了解决问题)

感谢和问候所有做出贡献的人,尤其是@Aaron。真的很感激!

【问题讨论】:

    标签: java build cross-platform swt


    【解决方案1】:

    这是我的 Main 类的最新版本的副本。让我知道这是否适合你。我在 Linux(32/64 位)和 Windows(32 位)上测试过。

    package de.pdark.epen.editor;
    
    import java.io.File;
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Method;
    import java.net.URL;
    import java.net.URLClassLoader;
    
    import org.apache.commons.lang.SystemUtils;
    import org.apache.commons.lang.exception.ExceptionUtils;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import ch.qos.logback.classic.LoggerContext;
    import ch.qos.logback.core.util.StatusPrinter;
    import de.pdark.epen.exceptions.WikiException;
    
    public class Main
    {
        public final static String VERSION = "V0.9 (13.05.2010)"; //$NON-NLS-1$
        private final static Logger log = LoggerFactory.getLogger (Main.class);
    
        private static final String ORG_ECLIPSE_SWT_WIDGETS_SHELL = "org.eclipse.swt.widgets.Shell"; //$NON-NLS-1$
    
        /**
        * @param args
        */
        @SuppressWarnings({"nls", "PMD.SystemPrintln"})
        public static void main (String[] args)
        {
            String msg = "Starting ePen "+VERSION;
            System.out.println (msg);
            log.info (msg);
    
            LoggerContext lc = (LoggerContext)LoggerFactory.getILoggerFactory ();
            StatusPrinter.print (lc);
    
            int rc = 1;
            try
            {
                Main main = new Main ();
                main.run (args);
                rc = 0;
            }
            catch (Throwable t) //NOPMD
            {
                ExceptionUtils.printRootCauseStackTrace (t);
            }
            finally
            {
                System.out.println ("Done.");
                log.info ("Exit {}", rc);
                System.exit (rc); //NOPMD
            }
        }
    
        @SuppressWarnings({"nls", "PMD.SystemPrintln", "PMD.SignatureDeclareThrowsException"})
        private void run (String[] args) throws Exception
        {
            if (!SystemUtils.isJavaVersionAtLeast (150))
            {
                System.out.println ("Version="+SystemUtils.JAVA_VERSION_INT);
                throw new WikiException ("Need at least Java 5 but this Java is only "+SystemUtils.JAVA_VERSION);
            }
    
            loadSwtJar ();
    
            URLClassLoader cl = (URLClassLoader) getClass().getClassLoader(); //NOPMD
            Class<?> c = cl.loadClass ("de.pdark.epen.editor.EPenEditor");
            Class<?> shellClass = cl.loadClass (ORG_ECLIPSE_SWT_WIDGETS_SHELL);
    
            Constructor<?> ctor = c.getConstructor (shellClass);
            Object obj = ctor.newInstance (new Object[] { null });
            Method run = c.getMethod ("run", args.getClass ()); //$NON-NLS-1$
            run.invoke (obj, new Object[] { args });
        }
    
        @SuppressWarnings({"nls", "PMD"})
        private void loadSwtJar ()
        {
            try {
                Class.forName (ORG_ECLIPSE_SWT_WIDGETS_SHELL);
                // Already on classpath
                return;
            } catch (ClassNotFoundException e) {
                // Add the JAR
            }
    
            String osName = SystemUtils.OS_NAME.toLowerCase ();
            String osArch = SystemUtils.OS_ARCH.toLowerCase ();
    
            String swtFileNameOsPart = 
                osName.contains("win") ? "win32" :
                osName.contains("mac") ? "macosx" :
                osName.contains("linux") || osName.contains("nix") ? "linux" :
                null;
            String swtFileNameUiPart = 
                osName.contains("win") ? "win32" :
                osName.contains("mac") ? "cocoa" :
                osName.contains("linux") || osName.contains("nix") ? "gtk" :
                null;
    
            if (null == swtFileNameOsPart)
            {
                throw new RuntimeException ("Can't determine name of SWT Jar from os.name=[" + osName + "] and os.arch=["
                        + osArch + "]");
            }
    
            String swtFileNameArchPart = osArch.contains ("64") ? ".x86_64" : ".x86";
            if(".x86".equals(swtFileNameArchPart) && "macosx".equals(swtFileNameOsPart)) {
                swtFileNameArchPart = "";
            }
    
            String swtFileName = "org.eclipse.swt." + swtFileNameUiPart + "." + swtFileNameOsPart + swtFileNameArchPart + "-3.6.0.jar";
            File file = new File ("swt", swtFileName);
            if (!file.exists ())
            {
                throw new RuntimeException ("Can't locate SWT Jar " + file.getAbsolutePath ());
            }
            try
            {
                URLClassLoader classLoader = (URLClassLoader) getClass ().getClassLoader ();
                Method addUrlMethod = URLClassLoader.class.getDeclaredMethod ("addURL", URL.class);
                addUrlMethod.setAccessible (true);
    
                URL swtFileUrl = file.toURI ().toURL ();
                log.info ("Adding {} to the classpath", swtFileUrl);
                addUrlMethod.invoke (classLoader, swtFileUrl);
            }
            catch (Exception e)
            {
                throw new RuntimeException ("Unable to add the swt jar to the class path: " + file.getAbsoluteFile (), e);
            }
        }
    }
    

    【讨论】:

    • 谢谢亚伦。虽然我不得不说我不太确定你是如何挖掘来找到 swtFiles 的,你的 swt 文件是放在一个带有所有其他依赖项的大 jar 中还是以其他方式?
    • @Aaron:还有一个问题,您是否遇到过 SWT 包未签名的问题?还是只有我……
    • @posdef:我将所有普通 JAR 放入名为 lib/ 的目录中,将六个 SWT JAR 放入 swt/。如果将它们全部放在一个目录中,则主 JAR 清单中的类路径可能会尝试加载错误的目录。
    • @posdef:我强烈建议使用未修改的原始 JAR。否则,DLL 的加载将失败。我不知道为什么。如果我需要更改 SWT 类,我将源代码复制到不同的包中并在那里进行修改。这通常会导致大量复制,但这就是 SWT 的工作原理。
    • 我们使用非常相似的方法:动态加载 SWT 库。每个都在一个单独的目录中,例如,libs/linux/x64/swt.jar。我的理解是,获取主类加载器addURL()设置为可访问或多或少是黑客行为,并且可能在未来作为安全补丁修复。我希望他们不会,并且可以确认此方法目前适用于所有平台上的 JRE 1.5 及更高版本,但觉得可能需要发出警告。
    【解决方案2】:

    您可以使用 Java Web Start 作为多平台 SWT 应用程序的引导机制。请参阅 SWT 常见问题解答中的 corresponding entry

    或者,您可以将每个平台的 SWT 本机库放入单独的文件夹中,并在特定于平台的启动脚本中指定它们 -Djava.library.path。

    【讨论】:

    • 我已经看过 SWT FAQ 条目,并希望避免这种情况,换句话说,有 6 个不同的文件需要管理。但我想如果这最终没有成功,那将必须是 B 计划。顺便一提;澄清一下,FAQ 建议将 SWT 库与其他依赖项分开,并在 JNLP 文件执行时包含它们,对吗?
    • 正确,JNLP 加载器将负责为您加载特定于平台的库和 jar。
    • 我一直在研究这个例子作为备份计划,我不得不说他们没有包括在例子中(甚至包括 SunOS)是相当可疑的。此外,&lt;resources os=... arch=...&gt; 调用是否有完整的可能返回值列表?
    • 别在意我之前的评论,我已经找到了一个很好的参考(mindprod.com/jgloss/properties.html#OSNAME
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-04-22
    相关资源
    最近更新 更多