Question

I ran across an issue when attempting to port an application over to JApplet so it can run on a browser.

Program Contents:

  1. Jar file. Contains my CustomClassLoader implementation. Stored on website.
  2. Content directory. Filled with compiled classes. Stored on the users computer.

Issue:

I am getting a NoClassDefFoundError when attempting to load .class files in the content directory with my CustomClassLoader.

The error, although unattainable, relates back to a class inside the jar. The class is abstract. All the .class files in the content directory extend this class and fill all the required methods. Upon loading these classes, the error is thrown. The program, when ran normally java -jar file.jar, works perfectly fine.

This makes me believe it has to do with the classpath.

Security Setup:

I am running the applet through the appletviewer command like so:

 appletviewer -J-Djava.security.policy=policy file.html

In the same directory is my policy file:

grant {
  permission java.lang.RuntimePermission "getenv.APPDATA";
  permission java.io.FilePermission "<<ALL FILES>>", "read, write, delete, execute";
  permission java.lang.RuntimePermission "exitVM";
  permission java.util.PropertyPermission "user.name", "read";
  permission java.lang.RuntimePermission "createClassLoader";
};

As far as I know, no other security exceptions are being thrown. The applet is signed.

HTML File Used To Load Applet:

<!DOCTYPE html>
<html>
    <body>
        <object width="1000" height="600" classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"
            codebase="http://java.sun.com/products/plugin/autodl/jinstall-1_4-windows-i586.cab#Version=1,4,0,0">
            <param name="archive" value="file.jar"/>
            <param name="code" value="package.to.Boot"/>
        </object>
    </body>
</html>

Any help towards fixing this problem is greatly appreciated.

CustomClassLoader.java:

package org.obicere.cc.methods;

import java.io.File;

public class CustomClassLoader extends ClassLoader {
    //...
    private Class<?> loadClass(final File file) {
        try {
            final byte[] data = IOUtils.readData(file);
            return super.defineClass(file.getName().substring(0, file.getName().length() - 6), data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }
}

Example Runner: CanReachRunner.java

import java.lang.reflect.Method;
import java.util.Random;

import org.obicere.cc.executor.Result;
import org.obicere.cc.tasks.projects.Runner;

public class CanReachRunner extends Runner {

    @Override
    public Result[] getResults(Class<?> clazz) {
        try {
            final Method method = clazz.getMethod("canReach", int.class, int.class, int.class);
            final Random ran = new Random();
            final Result[] results = new Result[10];
            for (int i = 0; i < 10; i++) {
                final int small = ran.nextInt(5) + 5;
                final int large = ran.nextInt(5);
                final int goal = (small + large * 5) + 5 + ran.nextInt(6);
                results[i] = new Result(method.invoke(clazz.newInstance(), small, large, goal), (goal <= small + large * 5) && goal % 5 <= small, small, large, goal);
            }
            return results;
        } catch (Exception e) {
            return new Result[] {};
        }
    }
}
Was it helpful?

Solution

There are several things wrong with the class loader. The first is that the loadClass method uses an argument of a String rather than a File, the string being the name of the class to load. This is because the class to load might not be in a file, it might be on a network connection, and anyway the JVM doesn't know how to find the file. The second is that it is bad practice to override loadClass, because if you do, it interferes with the default behavior, which first tries to load classes the normal way, and only resorts to calling the findClass method if that doesn't work. So, you should override findClass instead of defineClass. Here's the updated code:

public class CustomClassLoader extends ClassLoader {
    private Class<?> findClass(String class) {
        try {
            File contentDir = ...; // You have to fill this in with the location of the content dir
            final byte[] data = IOUtils.readData(new File(contentDir, class + ".class");
            return defineClass(class, data, 0, data.length);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}

You must find the content directory somehow and use that to initialize contentDir.

The reason this works when run as a jar is cause it is then capable of loading the classes without needing a custom class loader.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top