How to write a custom classloader in java

denglt發表於2013-04-11
轉載一個不錯的classloader入門介紹:

1.whait is a ClassLoader?

In this article we will give a very simple explanation of what ClassLoaders do in Java. This article starts a series, and in following articles we will show some ways of “replacing” the default class loader and what interesting things come out of it.

In contrary to such languages like C++ or Fortran where source code is compiled directly to machine’s native code, Java’s source code is compiled to platform-independent Java bytecode. Normally Java Virtual Machine loads this bytecode for every required class from .class files from the local file system. Java gives us however a possibility to greatly influence the way bytecode is load – customized class loaders.

Every request for a class from inside the code goes through a chain of default class loaders. Normally there are three of them:

  1. Bootstrap class loader – loads core Java classes like java.lang.System or java.lang.Integer
  2. Extensions class loader – loads classes from /lib/ext
  3. System class loader – loads classes from the current CLASSPATH

“Chaining” of class loaders means, that every class loader has a parent class loader, and in most cases it asks its parent to load a requested class before trying to resolve it on its own. If the parent can not find the class, the class loader itself tries to load the class. A rough view of class loading is represented on the image below:



How to write a custom classloader in java

Bootstrap class loader is pretty special, in that it is implemented in native code. All other class loaders are written in Java (apart from some native methods) and extend the java.lang.ClassLoader class. We could get the class loader which was used to load any particular class with class.getClassLoader() method:

 public class ShowClassLoaders {
    /**
     * This main method shows which class
     * loaders are used for loading classes
     * like Integer, BlowfishCipher (lib/ext)
     * and ShowClassLoaders.
     */
    public static void main(String[] args) {
        System.out.println("class loader for Integer: "
            + Integer.class.getClassLoader());
        System.out.println("class loader for BlowfishCipher: "
            + com.sun.crypto.provider
            .BlowfishCipher.class.getClassLoader());
        System.out.println("class loader for this class: "
            + ShowClassLoaders.class.getClassLoader());
    }
}

 

When we run this program, we will see the following output:

class loader for Integer: null
class loader for BlowfishCipher: sun.misc.Launcher$ExtClassLoader@9cab16
class loader for this class: sun.misc.Launcher$AppClassLoader@d9f9c3

java.lang.Integer was loaded using the bootstrap class loader. In most implementations getClassLoader() method returns null for the bootstrap class loader. com.sun.crypto.provider.BlowfishCipher class is stored inside the /lib/ext/sunjce_provider.jar so it was loaded with the extensions class loader. Classes implemented by us, like the class with the main method, was loaded by system class loader.

Java ClassLoader is a powerful tool that can be used in a various of ways to extend the possibilities of Java applications. One of the most well-known usages of custom class loaders are Java Applets, where a class loader is used to dynamically download code from the webpage. Customized ClassLoaders are also the base element of Java Application Servers and Web Containers. ClassLoaders are one of things that make the Java platform. so extensible and flexible.

More informations on ClassLoader (for example how to create your own one, or how it interacts with namespaces in Java) coming up in the next articles, so stay tuned!

You can also check out such places like ClassLoader’s javadoc on http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ClassLoader.html or Wikipedia http://en.wikipedia.org/wiki/Java_Classloader. There are also great articles available on http://mindprod.com/jgloss/classloader.html, http://www.developer.com/java/other/article.php/2248831/Java-Class-Loading-The-Basics.htm and on http://www.securingjava.com/chapter-two/chapter-two-7.html

 

2.Write your own ClassLoader

This is the second part of series of articles about Java’s ClassLoader. We will show here how you can write your own simple ClassLoader and “replace” the default system ClassLoader with your version.

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 “javablogging” package. If the class is not in “javablogging”, 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 “javablogging” package).

 /**
 * Our custom implementation of the ClassLoader.
 * For any of classes from "javablogging" package
 * it will use its {@link CustomClassLoader#getClass()}
 * method to load it from the specific .class file. For any
 * other class it will use the super.loadClass() method
 * from ClassLoader, which will eventually pass the
 * request to the parent.
 *
 */
public class CustomClassLoader extends ClassLoader {

    /**
     * Parent ClassLoader passed to this constructor
     * will be used if this ClassLoader can not resolve a
     * particular class.
     *
     * @param parent Parent ClassLoader
     *              (may be from getClass().getClassLoader())
     */
    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 Full class name
     */
    private Class> getClass(String name)
        throws ClassNotFoundException {
        // We are getting a name that looks like
        // javablogging.package.ClassToLoad
        // and we have to convert it into the .class file name
        // like javablogging/package/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 "javablogging" package,
     * it will load it using the
     * {@link 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("javablogging.")) {
            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;
    }
}

For a bunch of classes from the “javablogging” package (we will see one of them in a minute) we will load them ourselves from the .class files instead of passing a request for them to the parent class loader. We do it for two reasons: first is to show that in a real-world example you could easily implement the getClass() method to load the class code from some other source, like from the TCP/IP connection or from the database, second is that when we load the class directly in our class loader, instead of passing further the request, our class loader will become default class loader for the newly created class. It will be returned every time from the class.getClassLoader() method and every new request for a class, from an instance of the created class, will pass through our class loader.

It may seem a little confusing, so let’s see how it works on an example. We will load a class using this class loader, and from inside the class we will access some other class, which could be anything, like java.util.Integer.

The runMe() method of the class below simply creates an instance of Integer class and prints it to the console. We will load this class in a minute with our class loader.

public class IntegerPrinter {
    /**
     * Creates an instance of Integer class and prints it.
     */
    public void runMe() {
        System.out.println(new Integer(4));
    }
}

Now the main method that will do our experiment. We create there an instance of our custom class loader, and load our IntegerPrinter class with it using the full class name “javablogging.IntegerPrinter”. We then create a new instance of the IntegerPrinter and run its runMe() method using Reflection.

Note that we could NOT cast ‘instance’ at this point to IntegerPrinter and run its ‘runMe’ method directly, because we would get a ClassCastException! It would say something like “could not cast javablogging.IntegerPrinter to javablogging.IntegerPrinter” which seems very weird, but has good reasons. We loaded the IntegerPrinter class with our custom class loader, but the variable of type IntegerType which we would declare in IntegerPrinterTest would have been loaded with the default system class loader. To the JVM they would be classes from different namespaces, hence impossible to cast one to another. More on that in next articles.

 public class IntegerPrinterTest {
    /**
     * This main method shows a use of our CustomClassLoader for
     * loading some class and running it. All the objects referenced
     * from the IntegerPrinter class will be loaded with
     * our CustomClassLoader.
     */
    public static void main(String[] args) throws Exception {
        CustomClassLoader loader = new CustomClassLoader(
            StaticAccessorTest.class.getClassLoader());
        Class> clazz =
            loader.loadClass("javablogging.IntegerPrinter");
        Object instance = clazz.newInstance();
        clazz.getMethod("runMe").invoke(instance);
    }
}

When we run our main method, we will get following output:

loading class 'javablogging.IntegerPrinter'
loading class 'java.lang.Object'
loading class 'java.lang.System'
loading class 'java.lang.Integer'
loading class 'java.io.PrintStream'
4

First line is obvious, because we called ourselves the loadClass() method of our class loader. But where do the other lines come from? We did not call loadClass() for them. Since our class loader became the default class loader for the created class, every request for any other class from inside IntegerPrinter will go through our CustomClassLoader. We dont have to call loadClass() any more, it will be called automatically by the JVM! When you look at IntegerPrinter’s code, you will see that we use there System class, and its “out” variable which is of type PrintStream. We also create an Integer, and since IntegerPrinter, like any other class, extends java.lang.Object, we also have to load the Object class. All these requests go now through our CustomClassLoader.

One important thing to understand here is that we replaced the default system class loader only for one loaded class – IntegerPrinter. What we did has still no influence on default class loader used for example by class IntegerPrinterTest with its main method. There is however a way to replace the class loader even before running the main method, and we will show it in one of next articles.

Please note, that if you were about to write a real-world class loader, you would probably extend the URLClassLoader, because the part of loading a class from a file is there already implemented. Also, real class loaders normally ask their parent to load a class BEFORE trying to load it themselves. In our example, for the classes in “javablogging” package, we do load them without asking the parent. We will need that behavior. in examples in next parts of this series, where we will talk about namespaces, so stay tuned!.

 

3.Namespaces

In this third part of series about Java ClassLoader we will show how class loaders interact with namespaces in Java, and how using class loaders it is possible to have two or more instances of a static field.

In the previous article we learned how to change a class’ class loader. Can we do anything interesting now, that we know how to do it? We could use it for example to divide our program into two namespaces. Every different class loader represents a different namespace. Static fields of classes inside one namespace are separate from static fields of classes from other namespaces. If we set a value of a specific static field in namespace A, objects from namespace B will not be able to read it, and vice versa.

Let’s see how it works. We will have a public static Integer field in some class, and we will be trying to access this field from classes loaded with different class loaders. Here is this field:

 public class StaticHolder {
    /**
     * Static variable with default value of null. (Normally it
     * would be good to have getter and setter methods here,
     * but this time we will not declare them, in sake of simplicity.)
     */
    public static Integer value = null;
}

Below is also the class used to access the field. It will read the value of the field and then change it to 4. Default value for any class field is null, so that’s what we expect to see as the start value. In a normal, single-classloader program, after changing the value of the static field, the change would be visible to other objects reading that field. We will see what happens with multiple classloaders.

 public class StaticAccessor {
    /**
     * Accesses the static field from StaticHolder class.
     * It reads its value and after that sets it to 4.
     */
    @Override
    public void runMe() {
        System.out.println("--- Starting runMe. Static value: "
            + StaticHolder.value);
        StaticHolder.value = 4;
        System.out.println("--- Finishing runMe. Static value: "
            + StaticHolder.value);
    }
}

 

In our experimental main method we will create this time two CustomClassLoaders and load our StaticAccessor field with both of them. We will then create two instances of the StaticAccessor class, each of them from the different class loader, and run their runMe() methods.

You can find the implementation of our CustomClassLoader in the previous article.

 public class StaticAccessorTest {
    /**
     * This main method shows what happens when we load two
     * classes with two different ClassLoader's and access
     * some other class' static field from them.
     */
    public static void main(String... strings) throws Exception {
        // Using the first ClassLoader
        CustomClassLoader loader1 =
            new CustomClassLoader(StaticAccessorTest.class.getClassLoader());
        Class> clazz1 = loader1.loadClass("javablogging.StaticAccessor");
        Object instance1 = clazz1.newInstance();
        clazz1.getMethod("runMe").invoke(instance1);

        // Using the second ClassLoader
        CustomClassLoader loader2 =
            new CustomClassLoader(StaticAccessorTest.class.getClassLoader());
        Class> clazz2 = loader2.loadClass("javablogging.StaticAccessor");
        Object instance2 = clazz2.newInstance();
        clazz2.getMethod("runMe").invoke(instance2);
    }
}

When we run the program, we will get the following output:

loading class 'javablogging.StaticAccessor'
...
--- Starting runMe. Static value: null
loading class 'java.lang.Integer'
--- Finishing runMe. Static value: 4
loading class 'javablogging.StaticAccessor'
...
--- Starting runMe. Static value: null
loading class 'java.lang.Integer'
--- Finishing runMe. Static value: 4

When you look at the “— Starting runMe” lines, you will notice that both of them show null! Although we changed the static field to 4 in the first run of runMe(), the value read in the second runMe() shows once again null. The reason for it is that we have now two separate namespaces, as on the image below:



How to write a custom classloader in java

In normal programs it may be hard to see a usage for such tricks. Separating namespaces in such a way may be however useful for example when doing unit tests, when you want to be sure that every test runs in a completely separate sandbox, or when implementing frameworks or application servers, where every deployed application should be isolated from others.

You can read more about class loaders and namespaces in Java for example in this article: http://www.artima.com/underthehood/classloadersP.html.

 

4.Loading a custom ClassLoader on JVM Start

In a previous article of this series we learned how to replace the default system class loader for any given class. This time we will show how to replace it even before running the main method, so that the entire program runs from start with a custom class loader.

To do it, you should change the “java.system.class.loader” java environment variable to the full name of the class loader you want to use when running the program. Let’s create a simple program to demonstrate it. It will print out its own class loader:

 public class SimpleMain {
    /**
     * If you run this main method supplying the
     * -Djava.system.class.loader=javablogging.CustomClassLoader
     * parameter, class SimpleMain will be loaded with
     * our CustomClassLoader. Every other
     * class referenced from here will be also loaded with it.
     */
    public static void main(String... strings) {
        System.out.print("This is my ClassLoader: "
            + SimpleMain.class.getClassLoader());
    }
}

Run this program the following way:

java -Djava.system.class.loader
    =javablogging.CustomClassLoader javablogging.SimpleMain

You will get the following output, proving that our classloader is active from the beginning:

loading class 'java.lang.System'
loading class 'java.nio.charset.Charset'
loading class 'java.lang.String'
loading class 'javablogging.SimpleMain'
loading class 'java.lang.Object'
loading class 'java.lang.StringBuilder'
loading class 'java.lang.Class'
loading class 'java.io.PrintStream'
This is my ClassLoader: javablogging.CustomClassLoader@42e816

As you can see, class javablogging.SimpleMain and all classes used in it were loaded with our CustomClassLoader. In contrary to the way in the previous article we did not have to specifically create now an instance of our class loader inside the code.

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/195110/viewspace-758224/,如需轉載,請註明出處,否則將追究法律責任。

相關文章