【问题标题】:How to Make a Classloader evaluate the classpath before initlializing static objects?如何让类加载器在初始化静态对象之前评估类路径?
【发布时间】:2011-03-27 16:08:40
【问题描述】:

我正在开发一个使用 jar 库和本机系统库的应用程序。我的问题是默认类加载器会在调用 main 之前很久就尝试加载静态类。因为类路径还不包含静态类所需的本机库,所以在第一个评估的静态引用处抛出 java.lang.NoClassDefFoundError。

这是我无法访问的库加载方法的样子:

private static void load_libraries() {
    try {
        String osname = getOSName();

        if (osname == null) {
            throw new RuntimeException("The system you are running on is not supported");
        }
        URL u = NativeLibs.class.getResource(osname);

        URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Class urlClass = URLClassLoader.class;
        Method method = urlClass.getDeclaredMethod("addURL", new Class[]{URL.class});
        method.setAccessible(true);
        method.invoke(urlClassLoader, new Object[]{u});
    } catch (IllegalAccessException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to load native libs", ex);
    } catch (IllegalArgumentException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to load native libs", ex);
    } catch (InvocationTargetException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to load native libs", ex);
    } catch (NoSuchMethodException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to load native libs", ex);
    } catch (SecurityException ex) {
        Logger.getLogger(Main.class.getName()).log(Level.SEVERE, "Failed to load native libs", ex);
    }

}

所以,让我解释一下这里发生了什么。首先 getOSName() 所做的就是返回一个代表我正在运行的操作系统系列的字符串。如果操作系统不受支持,则返回 null。以防万一我在该方法中遇到问题,我会继续发布它:

private static String getOSName() {
    String os = System.getProperty("os.name").toLowerCase(Locale.US);
    if (os.indexOf("win") >= 0) {
        return "windows";
    } else if (os.indexOf("mac os x") >= 0) {
        return "macosx";
    } else if (os.indexOf("nux") >= 0) {
        return "linux";
    } else if (os.indexOf("solaris") >= 0) {
        return "solaris";
    } else {
        return null;
    }
}

它返回的字符串是方法 load_libraries() 用来定义相对 URL 的目录名称,我反射性地传递给类加载器以加载库。我的问题是运行时执行永远不会到达 load_libraries(),即使这是 main 中的第一个方法。

一个显而易见的解决方案是使用自定义 jar 静态加载本机库。如果可以避免的话,我不想静态链接本机库,因为这违背了 Java 平台可移植性的目的。我已经推断出的另一个解决方案是一个系统可执行文件,它可以使用系统可执行文件中确定的自动类路径运行应用程序 jar,但该解决方案仍然需要多个特定于平台的可执行文件。

所以,这是我的问题:

我可以强制类加载器在初始化静态对象之前运行我的库加载方法吗?这需要定制的 ClassLoader 吗?如果它确实需要一个自定义的 ClassLoader,那么简单地传递代表默认类加载器的系统属性会解决问题吗?

谢谢,我希望我写了一个足够详细的问题!

【问题讨论】:

    标签: java dynamic static default classloader


    【解决方案1】:

    “默认类加载器在调用 main 之前很久就尝试加载静态类”的原因是因为您的主类具有静态成员变量和/或静态初始化程序。当类加载器初始化你的主类时,它还必须初始化这些静态变量。如需更多信息,请参阅VM Spec

    解决方案——唯一的解决方案——是消除静态初始化。在 main() 中初始化静态成员变量,或使用 Spring 等框架进行初始化。

    一般来说,静态成员变量很少有好的用途。它们中的大多数最终都成为了从程序中的任意点提供对资源的访问权限的黑客。这不仅使程序更难测试,而且会导致意外的初始化链。

    【讨论】:

    • 我无法摆脱静态引用。它们本质上链接到对静态 C 代码的 JNI 静态调用。所以,我必须保留它们。我将尝试制作一个加载程序存根,将 library.path 传递给基于操作系统的真实应用程序
    猜你喜欢
    • 1970-01-01
    • 2018-01-17
    • 2016-12-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多