Friday, April 29, 2011

Writing custom class loader in java

Why write a ClassLoader?

If the JVM has a ClassLoader, then why would you want to write another one? Good question. The default ClassLoader only knows how to load class files from the local filesystem. This is fine for regular situations, when you have your Java program fully compiled and waiting on your computer. But one of the most innovative things about the Java language is that it makes it easy for the JVM to get classes from places other than the local hard drive or network. For example, browsers use a custom ClassLoader to load executable content from a Web site. There are many other ways to get class files. Besides simply loading files from the local disk or from a network, you can use a custom ClassLoader to:

  • Automatically verify a digital signature before executing untrusted code
  • Transparently decrypt code with a user-supplied password
  • Create dynamically built classes customized to the user's specific needs

Anything you can think of to write that can generate Java bytecode can be integrated into your application.

A Custom Class Loader:

We have to extend the java.lang.ClassLoader class and implement some of its crucial methods, like loadClass(String name). This method is run every time somebody requests a class inside the code, and receives as a parameter the full name of the class to load.

In our implementation we will print out this name, so to know when our method was called, and then load the class with our class loader if the class is in “com.vaani” package.(See loadClass method in code below).
 If the class is not in “com.vaani”, we will use the super.loadClass() method, which will pass the request to the parent class loader. (Please note that in this article we omit the “package” and “import” statements to make the code shorter, but every class should be inside the “com.vaani” package).

public class CustomClassLoader extends ClassLoader {

    /**
     * Parent ClassLoader passed to this constructor
     * will be used if this ClassLoader can not resolve a
     * particular class.
     */
    public CustomClassLoader(ClassLoader parent) {
        super(parent);
    }

    /**
     * Loads a given class from .class file just like
     * the default ClassLoader. This method could be
     * changed to load the class over network from some
     * other server or from the database.
     *
     * @param name Fully qualified class name
     */
    private Class<?> getClass(String name)
        throws ClassNotFoundException {
        // We are getting a name that looks like
        // com.vaani.ClassToLoad
        // and we have to convert it into the .class file name like 
        // com/vaani/ClassToLoad.class
        String file = name.replace('.', File.separatorChar)
            + ".class";
        byte[] b = null;
        try {
            // This loads the byte code data from the file
            b = loadClassData(file);
            // defineClass is inherited from the ClassLoader class
            // and converts the byte array into a Class
            Class<?> c = defineClass(name, b, 0, b.length);
            resolveClass(c);
            return c;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Every request for a class passes through this method.
     * If the requested class is in "com.vaani" package,
     * it will load it using the
     * CustomClassLoader.getClass() method.
     * If not, it will use the super.loadClass() method
     * which in turn will pass the request to the parent.
     *
     * @param name
     *            Full class name
     */
    @Override
    public Class<?> loadClass(String name)
        throws ClassNotFoundException {
        System.out.println("loading class '" + name + "'");
        if (name.startsWith("com.vaani")) {
            return getClass(name);
        }
        return super.loadClass(name);
    }

    /**
     * Loads a given file (presumably .class) into a byte array.
     * The file should be accessible as a resource, for example
     * it could be located on the classpath.
     *
     * @param name File name to load
     * @return Byte array read from the file
     * @throws IOException Is thrown when there
     *               was some problem reading the file
     */
    private byte[] loadClassData(String name) throws IOException {
        // Opening the file
        InputStream stream = getClass().getClassLoader()
            .getResourceAsStream(name);
        int size = stream.available();
        byte buff[] = new byte[size];
        DataInputStream in = new DataInputStream(stream);
        // Reading the binary data
        in.readFully(buff);
        in.close();
        return buff;
    }
}

No comments:

Post a Comment

Chitika