在非MACOS JRE上捕获java.lang.NoClassDefFoundError

时间:2017-03-15 18:57:10

标签: java swing java-8 macos-sierra

我正在使用java swing开发一个简单的应用程序,我希望在Windows,MacOS和Linux中使用它。 当然,我正试图尽可能地将它与操作系统集成。

对于MacOS,我有这个代码,允许我在全局菜单中设置应用程序名称,并为“关于”按钮设置操作。

我正在使用以下代码:

  if(System.getProperty("os.name").toUpperCase().startsWith("MAC")){
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My app name");
        System.setProperty("apple.awt.application.name", "My app name");
        //Need for macos global menubar
        System.setProperty("apple.laf.useScreenMenuBar", "true");
        try{
        com.apple.eawt.Application app = com.apple.eawt.Application.getApplication();
        app.setDockIconImage(Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png")));
        app.setAboutHandler(new com.apple.eawt.AboutHandler() {
            @Override
            public void handleAbout(com.apple.eawt.AppEvent.AboutEvent aboutEvent) {
                AboutDialog a = new AboutDialog();
                a.setTitle("About");
                a.pack();
                a.setResizable(false);
                centerDialogInScreen(a);
                a.setVisible(true);
            }
        });
        } catch (Throwable e){
            //This means that the application is not being run on MAC OS.
            //Just do nothing and go on...
        }
    }

当我在非macos JAVA上运行我的应用程序,因为它的JRE没有com.apple.eawt。*类时,JVM应该抛出一个NoDefClassFoundError,我正在捕捉并继续,对吗?

似乎没有这样做,当我启动我的应用程序“.jar”时,我得到以下内容(在Windows上):

Error: A JNI error has occurred, please check your installation and  try again
Exception in thread "main" java.lang.NoClassDefFoundError:  com/apple/eawt/AboutHandler
       at java.lang.Class.getDeclaredMethods0(Native Method)
       at java.lang.Class.privateGetDeclaredMethods(Unknown Source)
       at java.lang.Class.privateGetMethodRecursive(Unknown Source)
       at java.lang.Class.getMethod0(Unknown Source)
       at java.lang.Class.getMethod(Unknown Source)
       at sun.launcher.LauncherHelper.validateMainClass(Unknown Source)
       at sun.launcher.LauncherHelper.checkAndLoadMain(Unknown Source)
Caused by: java.lang.ClassNotFoundException: com.apple.eawt.AboutHandler
       at java.net.URLClassLoader.findClass(Unknown Source)
       at java.lang.ClassLoader.loadClass(Unknown Source)
       at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
       at java.lang.ClassLoader.loadClass(Unknown Source)
       ... 7 more

我在这里缺少什么?

4 个答案:

答案 0 :(得分:2)

抛出NoClassDefFoundError的确切时间并不固定,但可能取决于JVM实现细节。在您的情况下,这是课程的验证。在HotSpot中,Verifier不会加载所有引用的类,但显然它试图在你的情况下加载AboutHandler,这可能是因为你的类有一个实现AboutHandler的内部类而Verifier想要的检查类型层次结构的一致性,即AboutHandler是否真的是一个接口。确切的细节并不是那么重要,即使你设法解决它,结果也会很脆弱,可能会在其他版本或替代JVM实现中中断。

如果您想要安全起见,则不得直接引用可能不存在的类。因此,您可以使用Class.forNameMethod.invoke等反射性地执行整个操作,但这会使任何重要的代码变得繁琐。更简单的解决方案是将整个MacOS特定代码放入自己的类中,比如说MacSetup。然后,您只需通过Class.forName加载此类(仅在检查您在MacOS上运行后)才能将其分离。

public class MacSetup implements Runnable {
    @Override
    public void run() {
        System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My app name");
        System.setProperty("apple.awt.application.name", "My app name");
        //Need for macos global menubar
        System.setProperty("apple.laf.useScreenMenuBar", "true");
        com.apple.eawt.Application app = com.apple.eawt.Application.getApplication();
        app.setDockIconImage(Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png")));
        app.setAboutHandler(new com.apple.eawt.AboutHandler() {
            @Override
            public void handleAbout(com.apple.eawt.AppEvent.AboutEvent aboutEvent) {
                AboutDialog a = new AboutDialog();
                a.setTitle("About");
                a.pack();
                a.setResizable(false);
                centerDialogInScreen(a);
                a.setVisible(true);
            }
        });
    }
}

主要课程:

if(System.getProperty("os.name").toUpperCase().startsWith("MAC")) {
    try {
        Class.forName("MacSetup").asSubclass(Runnable.class).newInstance().run();
    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException ex) {
        // since this code is only executed when we *are* on MacOS, we should report it
        Logger.getLogger("MacSetup").log(Level.SEVERE, null, ex);
    }
}

因此,主类与MacSetup类及其所有引用分离,因为它反射性地加载此类并通过始终存在的Runnable接口调用其实现方法,从而减少了反射操作达到必要的最低限度。

答案 1 :(得分:1)

apple类不是标准Java类,因此在Mac平台之外不可用。如果您希望您的应用程序是跨平台的,最好的选择是避免使用这些类,即使这意味着与MacOS的集成更少。

另一方面,您可以创建一个特定的'关于'用于不同操作系统的类,并针对特定操作系统进行了扩展(例如MacAbout,WinAbout,....)

确定班级'命名运行时,并使用Class.forName动态加载类,避免JRE需要解析不存在的apple类。

但是,老实说,我会选择第一个选项并制作一个真正的平台独立应用程序。

答案 2 :(得分:0)

好吧,我设法用反射来解决部分问题。图标部分工作正常,我认为" AboutHandler"如果我能够将代码转换为反射,那么part会工作得很好。 因为它并不重要,我有一些更大的鱼来煎炸我没有太多困扰。 如果有人管理如何使用反射获取AboutHandler,我会将其标记为正确。

if(System.getProperty("os.name").toUpperCase().startsWith("MAC")) {
System.setProperty("com.apple.mrj.application.apple.menu.about.name", "My App Name");
System.setProperty("apple.awt.application.name", "My App Short name");
//Need for macos global menubar
System.setProperty("apple.laf.useScreenMenuBar", "true");

try {
    Class application = Class.forName("com.apple.eawt.Application");
    Method getApplication = application.getMethod("getApplication");
    Object instance = getApplication.invoke(application);

    Class[] params = new Class[1];
    params[0] = Image.class;
    Method setIcon = application.getMethod("setDockIconImage",params);
    setIcon.invoke(instance,Toolkit.getDefaultToolkit().getImage(MainGUI.class.getResource("images/icon.png")));

} catch (ClassNotFoundException | NoSuchMethodException |
        SecurityException | IllegalAccessException |
        IllegalArgumentException | InvocationTargetException exp) {
    exp.printStackTrace(System.err);
}
}

答案 3 :(得分:0)

com.apple java包在java9中已弃用并删除(现在默认在MacOS high sierra上)

在新api上查看http://openjdk.java.net/jeps/272以使用

package java.awt;

public class Desktop {

    /* ... */

    /**
     * Adds sub-types of {@link AppEventListener} to listen for notifications
     * from the native system.
     *
     * @param listener
     * @see AppForegroundListener
     * @see AppHiddenListener
     * @see AppReOpenedListener
     * @see AppScreenSleepListener
     * @see AppSystemSleepListener
     * @see AppUserSessionListener
     */

    public void addAppEventListener(final AppEventListener listener) {}

    /**
     * Requests user attention to this application (usually through bouncing the Dock icon).
     * Critical requests will continue to bounce the Dock icon until the app is activated.
     *
     */
    public void requestUserAttention(final boolean critical) {}

    /**
     * Attaches the contents of the provided PopupMenu to the application's Dock icon.
     */
    public void setDockMenu(final PopupMenu menu) {}

    /**
     * Changes this application's Dock icon to the provided image.
     */
    public void setDockIconImage(final Image image) {}


    /**
     * Affixes a small system provided badge to this application's Dock icon. Usually a number.
     */
    public void setDockIconBadge(final String badge) {}

    /**
     * Displays or hides a progress bar or other indicator in
     * the dock.
     *
     * @see DockProgressState.NORMAL
     * @see DockProgressState.PAUSED
     * @see DockProgressState.ERROR
     *
     * @see #setDockProgressValue
     */
    public void setDockProgressState(int state) {}

    /**
     * Sets the progress bar's current value to {@code n}.
     */
    public void setDockProgressValue(int n) {}

    /**
     * Tests whether a feature is supported on the current platform.
     */

    public boolean isSupportedFeature(Feature f) {}

    /* ... */
}