3

I've created my own URLClassLoader, and set it as the system classloader via java.system.class.loader. It's initialized and everything, but the classes I'm trying to load aren't found. Here's the URLClassLoader:

public class LibraryLoader extends URLClassLoader
{
    public LibraryLoader(ClassLoader classLoader)
    {
        super(new URL[0], classLoader);
    }
    synchronized public void addJarToClasspath(String jarName) throws MalformedURLException, ClassNotFoundException
    {
        File filePath = new File(jarName);
        URI uriPath = filePath.toURI();
        URL urlPath = uriPath.toURL();

        System.out.println(filePath.exists());
        System.out.println(urlPath.getFile());

        addURL(urlPath);
    }
}

I've confirmed that the jar exists, and that the path is correct. This is how I call it in my program:

LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();
loader.addJarToClasspath("swt.jar");

This is the exception that I get (line 166 refers to the line at which I try to create a new Point:

Exception in thread "main" java.lang.NoClassDefFoundError: org/eclipse/swt/graphics/Point
        at mp.MyProgram.loadArchitectureLibraries(MyProgram.java:116)
        at mp.MyProgram.main(MyProgram.java:90)
Caused by: java.lang.ClassNotFoundException: org.eclipse.swt.graphics.Point
        at java.net.URLClassLoader$1.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        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)
        ... 2 more

I even tried explicitly loading the class like so:

Class.forName("org.eclipse.swt.graphics.Point", false, loader);

What might be causing this? Shouldn't it "just work"?


Update: Here's the important code from MyProgram

public class MyProgram
{
    // ...

    public static void main(String[] args)
    {
        loadArchitectureLibraries();

        // ...
    }

    public static void loadArchitectureLibraries()
    {
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
    }
}

Update 2: Here's an SSCCE: http://nucleussystems.com/files/myprogram.zip . Call java -Djava.system.class.loader=mp.LibraryLoader -jar myprogram.jar.

Jonah
  • 9,991
  • 5
  • 45
  • 79
  • 1
    Could you please post the code of the MyProgram class, too? – Sebastian Zarnekow Apr 27 '11 at 19:07
  • Does your class loader look in jre/ext? Does your jre/ext have anything important? What exception do you get if you do Class.forName("org.eclipse.swt.graphics.Point", *true*, loader); – Dilum Ranatunga Apr 27 '11 at 19:19
  • @Jonah: "I've confirmed that the jar exists, and that the path is correct." Nowhere in those code snippets does it call `File.exists()` or `System.out.println(File.getCanonicalPath())` or similar, so we'll just have to take your word for it. (considers) Actually, no we don't have to do any such thing. Why don't you post an [SSCCE](http://pscode.org/sscce.html)? – Andrew Thompson Apr 27 '11 at 19:23
  • @Andrew: I actually did have that code in there, I just removed it :) I'll put it back in. – Jonah Apr 27 '11 at 19:41
  • 1
    Do you get the NoClassDefFoundError on line org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0); ? Or is it thrown in the Class.forName(..) call? Could you please verify that MyProgram.class.getClassLoader() returns your LibraryClassLoader? – Sebastian Zarnekow Apr 27 '11 at 20:06
  • @Andrew: I added the SSCCE to the question. – Jonah Apr 27 '11 at 20:09
  • @Sebastian: Yes, it's coming from the line where I try to initialize the `Point`. And yup, it's definitely my class loader. – Jonah Apr 27 '11 at 20:11
  • @Dilum: The error is the same when it's set to true. – Jonah Apr 27 '11 at 20:14

3 Answers3

2

I would have to agree with the comments on this question. Based on the code you have provided, it would appear that you are getting the error due to the JAR files not being where you expect them to be. As mentioned by @Andrew, you are not checking the existence of the file in your addJarToClasspath method. As a result, if the file does not exist, you will receive a ClassNotFound exception as you are seeing. I verified this problem by taking your ClassLoader logic and passing it a valid and an invalid JAR. When a valid JAR/path was provided, the ClassLoader loaded the class as expected. When an invalid JAR/path was specified, I received the error you mentioned. The URLClassLoader does not throw an exception if an URL is specified that does not point to a valid file.

To validate the scenario, print out the path of the full path of your File and see if it is correct for the execution environment.

Edit


It appears that even if you override the system ClassLoader, the VM will still use the default sun.misc.Launcher$AppClassLoader to load some classes. In my testing this includes the classes that are referenced from the main application. I'm sure there is a reason for this process, however, I am unable to ascertain it at this time. I have come up with a few solutions for you:
  • Use a script to detect the environment and set the classpath accordingly. This is perhaps the simplest solution, but one you may or may not want to take based on your particular requirements.
  • Similar to what was mentioned in other answers, specifically load and execute your application using your custom ClassLoader. This does not mean creating a single class that will be loaded and then invoke your application. It means that any class that needs to interact with the dynamically loaded swt libraries and any classes that need to reference your application classes should be loaded from your custom ClassLoader. Any application dependencies, such as log4j, etc, can be referenced by the default application ClassLoader. Here is an example of how this would work:

JAR 1 (launcher.jar):

public class AppLauncher {
    public static void main(String… args) throws Exception {
        ClassLoader loader = initClassLoader();
        Class<?> mpClass = loader.loadClass("mp.MyProgram");

        // using Runnable as an example of how it can be done
        Runnable mpClass = (Runnable) mpClass.newInstance();
    }
    public static ClassLoader initClassLoader() {
        // assuming still configured as system classloader, could also be initialized directly
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        // add the main application jar to the classpath.  
        // You may want to dynamically determine this value (lib folder) or pass it in as a parameter
        loader.addJarToClasspath("myapp.jar");

        String architecture = System.getProperty("os.arch");
        try {
            if (architecture.contains("64")) {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86_64.jar");
            } else {
                loader.addJarToClasspath("swt-3.6.1-win32-win32-x86.jar");
            }

            Class.forName("org.eclipse.swt.graphics.Point", false, loader);
            org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);

        } catch (Exception exception) {

            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
        return loader;
    }

JAR 2 (myapp.jar): Includes all class which depend on swt

public class MyProgram implements Runnable {
    //…
    public void run() {
    // perform application execution

           // this logic should now work
           org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0,0);
    }
}

The AppLauncher class would be executed by the VM without the rest of your application being included in the execution Jar.

java -Djava.system.class.loader=test.LibraryLoader -cp <dependency jars>:launcher.jar mp.AppLauncher

I see that there have been updates to the other answers. Since I already had typed up the above comments, I figured that I should still post it for your perusal.

Kris Babic
  • 6,254
  • 1
  • 29
  • 18
1

Since the offending line is not the Class.forName but the actual initialization of an instance of Point, we'll have to make sure that the class, that tries to load the Point class, was created by the Library class loader. Therefore, I made some minor changes in the LibraryLoader accordingt to this blog entry

public class LibraryLoader extends URLClassLoader {

    public LibraryLoader(ClassLoader classLoader) {
        super(new URL[0], classLoader);
    }

    synchronized public void addJarToClasspath(String jarName)
            throws MalformedURLException, ClassNotFoundException {
        File filePath = new File(jarName);
        URI uriPath = filePath.toURI();
        URL urlPath = uriPath.toURL();

        System.out.println(filePath.exists());
        System.out.println(urlPath.getFile());

        addURL(urlPath);
    }

    @Override
    public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        if ("mp.MyProgram".equals(name)) {
            return getClass(name);
        }
        return super.loadClass(name, resolve);
    }

    private Class<?> getClass(String name) throws ClassNotFoundException {
        String file = name.replace('.', File.separatorChar) + ".class";
        byte[] b = null;
        try {
            b = loadClassData(file);
            Class<?> c = defineClass(name, b, 0, b.length);
            resolveClass(c);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    private byte[] loadClassData(String name) throws IOException {
        InputStream stream = getClass().getClassLoader().getResourceAsStream(
                name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        in.readFully(buff);
        in.close();
        return buff;
    }
}

In the program itself, we have to extract a new method since all the classes, that are used from within a method, seem to be loaded up-front:

public class MyProgram {
    public static void main(String[] args) {
        LibraryLoader loader = (LibraryLoader) ClassLoader.getSystemClassLoader();

        String architecture = System.getProperty("os.arch");
        try {
            loader.addJarToClasspath("swt.jar");
            otherMethod();

        } catch (Throwable exception) {
            // println instead of logger because logging is useless at this level
            exception.printStackTrace();
            System.out.println("Could not load SWT library");
            System.exit(1);
        }
    }

    protected static void otherMethod() {
        org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
        System.out.println("Works!");
    }
}

That should work for you.

Sebastian Zarnekow
  • 6,609
  • 20
  • 23
  • @Sebastian: I tried it, but I get the same error. Strangely, when I try to access the class loader from inside of the thread, it says it's not my LibraryLoader. So I got it by setting the loader variable to `final`. – Jonah Apr 27 '11 at 20:34
  • this will not work, the CCL is used entirely differently. You need the Runnable impl to be loaded by the libraryLoader – bestsss Apr 27 '11 at 21:15
  • @bestsss: what exactly do you mean by "load the runnable with libraryLoader"? – Jonah Apr 27 '11 at 21:23
  • Unfortunately, the LibraryLoader will first delegate to the original system classloader which will load the runnable. The runnable's class will be associated with that 'system' class loader and thereby not see the swt.Point. – Sebastian Zarnekow Apr 27 '11 at 21:25
  • @Sebastian: so you're saying that this particular method won't work? – Jonah Apr 27 '11 at 21:35
  • @Sebastian, LibraryLoader is done exactly how you should not do it, and I used similar cases to hack only. – bestsss Apr 27 '11 at 22:26
  • @bestsss: how would you suggest it be done better? @Sebastian: I'm working on implementing that right now. – Jonah Apr 27 '11 at 22:29
  • @Sebastian: YOU TOTALLY ROCK MAN!!!!!! IT WORKS NOW! Man, I've been working on this thing for two days straight. I just had to make one change: instead of `File.separatorChar`, it has to be `'/'`. It says in the documentation for `ClassLoader.getResource`. Before the change, the stream in `loadClassData` would come up as null. What is the significance of the `mp.MyProgram` equality check? Do I need to do that for all the other classes in my jar too? – Jonah Apr 27 '11 at 22:47
  • The equalitiy check is used to ensure that the custom class loader is used to load the main class. IIRC it is not necessary to use that for other classes from your jar since the will be associated with the class loader of your main class. If you want to get rid of the command line argument, you could give bestsss approach a second look. It should be usable with a newly created class loader that is not necessarily set as the system class loader. – Sebastian Zarnekow Apr 27 '11 at 22:56
  • @Sebastian: the SSCCE I made works fine, and my real program works fine, but only loading SWT classes in the main class. If I call another class and it tries to create a SWT object, the whole thing blows up. Possibilities? – Jonah Apr 27 '11 at 23:26
  • @Sebastian: I'll have to continue work on this tomorrow. Sounds like I need to do what @Kris and @bestsss are saying too, and create a launcher jar. – Jonah Apr 27 '11 at 23:30
  • This is quiet funny: loader.addJarToClasspath("swt.jar"); here you load a jar, and here org.eclipse.swt.graphics.Point pt you are accessing a class from the jar. This shouldn't even compile except when linking against the jar you are loading and therefor the class is already known to your application before loading the jar. – Peter Dec 23 '13 at 07:55
  • @Peter, you're right, you have to compile your code at least against stubs of the APIs that will be provided dynamically at runtime. In case of swt, there are many different implementations / ports for different windowing toolkits. That's why it may be necessary to load the jar dynamically without putting it on the classpath of the application. – Sebastian Zarnekow Dec 30 '13 at 12:43
1

It's visible from a (few) mile(s) away you are not using the custom classloader beside Class.forName

The ClassNoDefFoundError occurs since the classloader that has loaded current class MyProgram attempts to load org.eclipse.swt.graphics.Point.

You need to load another class (call it launcher) via Class.forName and then start from there - implement some interface (even runnable will do) and call it.


edit

How to do it, a simplistic scenario.
1. Create another class called mp.loader.Launcher that implements Runnable like that.

public class Launcher implements Runnable{
public void run(){
  org.eclipse.swt.graphics.Point pt = new org.eclipse.swt.graphics.Point(0, 0);
  //whatever, start from here.
}
}

2. Place it in another jar called swt-loader.jar.

in MyProgram class use:

loader.addJarToClasspath("swt-loader.jar");
Runnable r = (Runnable) Class.forName("mp.loader.Launcher", true, loader);
r.run();//there you have
bestsss
  • 11,796
  • 3
  • 53
  • 63
  • @bestsss: it is in fact using my loader. I'm not sure what you mean in the second sentence. Could you explain why it needs another class to start? – Jonah Apr 27 '11 at 21:22
  • 1
    Since the libary loader is the system class loader, it should be used to load the MyProgram class, too, shouldn't it? – Sebastian Zarnekow Apr 27 '11 at 21:32
  • 1
    @Sebastian *Since the libary loader is the system class loader...* it is not. The system loader is the one that has loaded the class w/ the main method (to put it simply) – bestsss Apr 27 '11 at 22:24
  • 1
    @Jonah, I guess you miss the aspect how class loading mechanism works in java, the classloader that has loader the currently executing class will be used to load new classes. I will edit the code to show how to do it properly. But the general idea is that you should place, yet another jar to bootstrap and operate the `swt.jar` one. Basically you want to access the classes in `swt.jar` ONLY from the very same loader. Failing to do so is basically why NoClassDefFoundError (rightfully) occurs. – bestsss Apr 27 '11 at 22:28
  • 1
    The command line arg -Djava.system.class.loader=mp.LibraryLoader was used to set the system class loader to the library loader. – Sebastian Zarnekow Apr 27 '11 at 22:30
  • 1
    @Jonah, if you have experience w/ web-apps they are a good example. [Well almost, since they web-apps loading breaks some standards but it's not important.] But practically the classes outside the web-app may not access any class residing into it. Indeed, you can export common interfaces to carry some task and that's the idea of using interfaces. – bestsss Apr 27 '11 at 22:32
  • @Sebastian, then you are not using a custom classloader at all, you'd be TONS better off just using -cp :) – bestsss Apr 27 '11 at 22:33
  • @bestsss You are right, if one can decide statically which jars to put on the classpath, -cp is your friend. However, swt is distributed in a platform dependant manner. That is, you have multiple jars with basically the same class signatures but different, platform dependent implementations. That's why Jonah wants to decide at runtime which jar to use. Since he's not using OSGi, a simple custom classloader seems like a feasable solution to me. Using some kind of bootstrapping.jar and a completely dynamic URLClassLoader should be possible, too. – Sebastian Zarnekow Apr 27 '11 at 22:40
  • @Sebastian, I edited my answer to include how to do it, doing module (osgi) stuff is pretty simple actually and it's a good thing to master. I am using custom classloaders for various tasks and they work like charm, all you have to remember is that you cannot access classes loaded by the child loaders by a class coming from the parent one. That's all. – bestsss Apr 27 '11 at 22:43
  • @bestsss: does it have to be in another jar, or can it be just a different class? – Jonah Apr 27 '11 at 23:30
  • @Jonah, different jar, I did try to explain the concept. You want the same classloader for the swt.jar and your bootstrap class(es). – bestsss Apr 28 '11 at 07:27
  • @bestsss: Oh, I understand now. I'll try to implement that. – Jonah Apr 28 '11 at 14:50
  • @bestsss: woot! It works! Yours and @Sebastian's answers together did the trick. Thanks a lot! – Jonah Apr 28 '11 at 16:28