尽管首先检查操作系统,但Mac OS X特定的操作会使应用程序在其他操作系统上崩溃

时间:2013-06-05 17:00:43

标签: java cross-platform

我正在使用以下代码将我的Java应用程序与Mac OS X集成:

if (System.getProperty("os.name").equals("Mac OS X"))
{
    System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText"); // Program name
    com.apple.eawt.Application.getApplication().setDockIconImage(new javax.swing.ImageIcon( JotextMain.class.getResource("icons/Icon.png")  ).getImage()); // Dock icon
    System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
    com.apple.eawt.Application.getApplication().addApplicationListener(new MacAboutAndQuit()); //Make "Quit" and "About" options work
}

在Mac OS X上,它运行正常。但是在Ubuntu上:

Exception in thread "main" java.lang.NoClassDefFoundError: com/apple/eawt/ApplicationListener
    at java.lang.Class.getDeclaredMethods0(Native Method)
    at java.lang.Class.privateGetDeclaredMethods(Class.java:2451)
    at java.lang.Class.getMethod0(Class.java:2694)
    at java.lang.Class.getMethod(Class.java:1622)
    at sun.launcher.LauncherHelper.getMainMethod(LauncherHelper.java:494)
    at sun.launcher.LauncherHelper.checkAndLoadMain(LauncherHelper.java:486)
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.ApplicationListener
    at java.net.URLClassLoader$1.run(URLClassLoader.java:366)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:355)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:354)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:423)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:308)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:356)
    ... 6 more

当我注释掉该代码时,它会在Ubuntu上正常启动。 奇怪,因为它在执行Mac OS X特定操作之前首先检查Mac OS X. 我没有Windows,但是我的朋友使用Windows也报告我的.jar没有启动,而是从Java虚拟机启动器发出“A Java Exception has Ocured”消息。在学校的Windows PC上,同样的问题。

造成这种情况的原因是什么?

1 个答案:

答案 0 :(得分:3)

if块的第二行是问题(以及第四行)。即使您的代码没有调用该函数,JVM仍然必须尝试在包含该com.apple.eawt.Application语句的任何类的初始化时加载if类,这仅仅是因为它的引用。当你不在Mac上时,该类不存在,因此出错。

解决方法是使用Reflection来进行该函数调用。这应该可以防止类加载器在实际调用之前尝试加载该类。

示例

javax.swing.ImageIcon icon = ...
Class c = Class.forName("com.apple.eawt.Application");
Method m = c.getMethod("getApplication");
Object applicationInstance = m.invoke(null);
m = applicationInstance.getClass().getMethod("setDockIconImage", javax.swing.ImageIcon.class);
m.invoke(applicationInstance,icon);

这是与if块中第二行相对的Reflection。面对良好的开发实践,有很多关于此的东西 - 如果类/方法名称发生变化,您将不会收到编译器警告,但它确实允许您对特定类型进行调用,而无需在源代码中直接引用它们。这可以防止类加载器加载那些特定于操作系统的类,直到你实际进入if语句。

为了清理这一点,您可以创建一个特殊的类来处理所有Mac-OS特定的东西,在单个方法中包含该功能,然后使用Reflection调用该方法,就像这个例子一样 - 这将为您节省其中一个Method m = ...风格的电话。

反思警告

除了这段代码丑陋且不易维护之外,反射非常缓慢。它最适用于这样的情况,您可能会在程序期间初始化一次。通常情况下,如果不是出于可维护性问题,反射是您希望远离性能的原因。

阅读本文以获取有关反射性能的更多信息:

Java Reflection Performance

更优雅的解决方案

我不是用反射代码替换if块内的所有内容,而是创建一个接口,如下所示:

interface SpecificPlatform{
    void initialize(javax.swing.ImageIcon icon);
}

然后,我将为Mac实现它:

class MacOSX implements SpecificPlatform{
    @Override
    public void initialize(javax.swing.ImageIcon icon){
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "JoText");
        Application.getApplication().setDockIconImage(new javax.swing.ImageIcon(
            JotextMain.class.getResource("icons/Icon.png")  ).getImage()); // Dock icon
        System.setProperty("apple.laf.useScreenMenuBar", "true"); // Use global menu bar
        Application.getApplication().addApplicationListener(new MacAboutAndQuit());
    }
}

现在......将原始代码更改为以下内容:

javax.swing.ImageIcon icon = ...
SpecificPlatform sp = null;

if (System.getProperty("os.name").equals("Mac OS X"))
    sp = (SpecificPlatform)(Class.forName("pkg.name.MacOSX").getConstructor().
            newInstance());
}

//Check for other OS-specific classes here ...

if(sp != null) sp.initialize(icon);

现在,每个操作系统只有一个丑陋的Reflection调用,而不是为每个特定于操作系统的方法调用做出一堆丑陋的Reflection调用。