在非 MACOS JRE 上捕获 java.lang.NoClassDefFoundError
Catching java.lang.NoClassDefFoundError on Non-MACOS JRE
我正在使用 java swing 开发一个简单的应用程序,我想在 Windows、MacOS 和 Linux 中使用它。
当然,我正在尽我所能将它与 OS 集成。
对于 MacOS 我有这段代码可以让我在全局菜单中设置应用程序名称和 "About" 按钮的操作。
我正在使用以下代码:
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
我在这里错过了什么?
apple
classes 不是标准的Javaclasses,因此在Mac 平台之外不可用。如果您希望您的应用程序是跨平台的,最好的选择是避免使用那些 classes,即使这意味着与 MacOS.
的集成较少
另一方面,您可以为不同的操作系统创建特定的 'About' class 并针对特定的操作系统扩展它(例如 MacAbout、WinAbout、.. ..)
确定class'名称运行时,使用Class.forName动态加载class,避免JRE需要解析不存在的apple
classes.
但是,老实说,我会选择第一个选项并制作一个真正的平台独立应用程序。
抛出 NoClassDefFoundError
的确切时间不固定,但可能取决于 JVM 实现细节。在你的例子中,它是 class 的验证。在 HotSpot 中,验证器不会加载所有引用的 classes,但显然它试图在您的情况下加载 AboutHandler
,这可能是由于您的 class 有一个内部 class 实现了 AboutHandler
并且验证者想要检查类型层次结构的一致性,即 AboutHandler
是否真的是一个接口。确切的细节并不重要,因为即使您设法解决它,结果也很脆弱,并且可能会在其他版本或替代 JVM 实现中中断。
如果您想安全起见,您不能直接引用可能不存在的 classes。因此,您可以使用 Class.forName
、Method.invoke
等反射式地执行整个操作,但这会使任何重要的代码变得麻烦。更简单的解决方案是将整个 MacOS 特定代码放入它自己的 class 中,比如 MacSetup
。然后,你只需要通过 Class.forName
加载这个 class (只有在检查你在 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);
}
});
}
}
主要class:
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);
}
}
所以主 class 与 MacSetup
class 及其所有引用分离,因为它反射加载此 class 并通过调用其实现方法始终存在的 Runnable
界面,将反射操作减少到必要的最低限度。
好吧,我设法使用反射解决了部分问题。图标部分工作正常,我认为如果我能够将代码转换为反射,"AboutHandler" 部分会工作得很好。
因为这并不重要,而且我有一些更大的鱼要炸,所以我没有太在意。
如果有人设法使用反射获取 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);
}
}
com.apple java 软件包在 java9 中被弃用并删除(现在是 MacOS high sierra 的默认设置)
见 http://openjdk.java.net/jeps/272 新 api 使用
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) {}
/* ... */
}
我正在使用 java swing 开发一个简单的应用程序,我想在 Windows、MacOS 和 Linux 中使用它。 当然,我正在尽我所能将它与 OS 集成。
对于 MacOS 我有这段代码可以让我在全局菜单中设置应用程序名称和 "About" 按钮的操作。
我正在使用以下代码:
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
我在这里错过了什么?
apple
classes 不是标准的Javaclasses,因此在Mac 平台之外不可用。如果您希望您的应用程序是跨平台的,最好的选择是避免使用那些 classes,即使这意味着与 MacOS.
另一方面,您可以为不同的操作系统创建特定的 'About' class 并针对特定的操作系统扩展它(例如 MacAbout、WinAbout、.. ..)
确定class'名称运行时,使用Class.forName动态加载class,避免JRE需要解析不存在的apple
classes.
但是,老实说,我会选择第一个选项并制作一个真正的平台独立应用程序。
抛出 NoClassDefFoundError
的确切时间不固定,但可能取决于 JVM 实现细节。在你的例子中,它是 class 的验证。在 HotSpot 中,验证器不会加载所有引用的 classes,但显然它试图在您的情况下加载 AboutHandler
,这可能是由于您的 class 有一个内部 class 实现了 AboutHandler
并且验证者想要检查类型层次结构的一致性,即 AboutHandler
是否真的是一个接口。确切的细节并不重要,因为即使您设法解决它,结果也很脆弱,并且可能会在其他版本或替代 JVM 实现中中断。
如果您想安全起见,您不能直接引用可能不存在的 classes。因此,您可以使用 Class.forName
、Method.invoke
等反射式地执行整个操作,但这会使任何重要的代码变得麻烦。更简单的解决方案是将整个 MacOS 特定代码放入它自己的 class 中,比如 MacSetup
。然后,你只需要通过 Class.forName
加载这个 class (只有在检查你在 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);
}
});
}
}
主要class:
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);
}
}
所以主 class 与 MacSetup
class 及其所有引用分离,因为它反射加载此 class 并通过调用其实现方法始终存在的 Runnable
界面,将反射操作减少到必要的最低限度。
好吧,我设法使用反射解决了部分问题。图标部分工作正常,我认为如果我能够将代码转换为反射,"AboutHandler" 部分会工作得很好。 因为这并不重要,而且我有一些更大的鱼要炸,所以我没有太在意。 如果有人设法使用反射获取 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);
}
}
com.apple java 软件包在 java9 中被弃用并删除(现在是 MacOS high sierra 的默认设置)
见 http://openjdk.java.net/jeps/272 新 api 使用
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) {}
/* ... */
}