简体   繁体   中英

How to Make a Classloader evaluate the classpath before initlializing static objects?

I have an application I am developing that uses both jar libraries and native system libraries. My problem is the default classloader attempts to load static classes long before the main is called. Because the classpath doesn't yet contain the native libraries the static classes require, a java.lang.NoClassDefFoundError is thrown at the first evaluated static reference.

Here is what my unreachable library loading method looks like:

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);
    }

}

So, let me explain what is going on here. First all getOSName() does is return a string representing the OS family I am running on. It returns null if the OS is unsupported. Just in case I have a problem in that method, I'll go ahead and post it as well:

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;
    }
}

The strings it returns are directory names that the method load_libraries() uses to define a relative URL that I pass to the classloader reflectively to load the libraries. My problem is that the runtime execution never reaches load_libraries(), even though that is the first method in main.

One solution that is obvious is to load native libraries statically with customized jars. I don't want to statically link the native libraries if it is at all avoidable, because that defeats the purpose of java's platform portability. Another solution I have already deduced is a system executable that can run the applications jar with a determined automatic classpath from the system executable, but still, that solution requires multiple excutables that are platform specific.

So, here is my question:

Can I force the classloader to run my Library loading method before it intializes static objects? Will this require a customized ClassLoader? If it does require a custom ClassLoader, will simply passing the system property representing the default classloader solve the problem?

Thanks, I hope I wrote a detailed enough question!

The reason that "the default classloader attempts to load static classes long before the main is called" is because your main class has static member variables and/or a static initializer. When the classloader initializes your main class, it must also initialize these static variables. For more information, see the VM Spec .

The solution -- the only good solution -- is to eliminate static initialization. Initialize your static member variables inside your main() , or use a framework such as Spring to do the initialization.

In general, there are very few good uses for static member variables. Most of them end up being a hack to provide access to resources from arbitrary points in the program. This not only makes the program harder to test, but leads to unexpected initialization chains.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM