Custom URLClassLoader, NoClassDefFoundError when run

前端 未结 3 1732
忘掉有多难
忘掉有多难 2021-01-21 03:03

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 clas

相关标签:
3条回答
  • 2021-01-21 03:43

    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
    
    0 讨论(0)
  • 2021-01-21 03:50

    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.

    0 讨论(0)
  • 2021-01-21 03:53

    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.

    0 讨论(0)
提交回复
热议问题