Preface
It is possible to load and reload classes at runtime in Java, though it is not as straightforward as one might have hoped. This text will explain when and how you can load and reload classes in Java.
The ClassLoader
All classes in a Java application are loaded using some subclass of java.lang.ClassLoader. Loading classes dynamically must therefore also be done using ajava.lang.ClassLoader subclass. When a class is loaded, all classes it references are loaded too. This class loading pattern happens recursively, until all classes needed are loaded. This may not be all classes in the application. Unreferenced classes are not loaded until the time they are referenced.
The ClassLoader Hierarchy
Class loaders in Java are organized into a hierarchy. When you create a new standard Java ClassLoader you must provide it with a parent ClassLoader. If a ClassLoader is asked to load a class, it will ask its parent class loader to load it. If the parent class loader can't find the class, the child class loader then tries to load it itself.
Class Loading
The steps a given class loader uses when loading classes are:
When you implement a class loader that is capable of reloading classes you will need to deviate a bit from this sequence. The classes to reload should not be requested loaded by the parent class loader. More on that later.
Dynamic Class Loading
Loading a class dynamically is easy. All you need to do is to obtain a ClassLoader and call its loadClass() method. Here is an example:
- package test
- public class MainClass{
- public static void main(args)
- {
- ClassLoader classLoader = MainClass.class.getClassLoader();
- printf("\t[Info] Current ClassLoader=%s...\n", classLoader.class.name)
- def pastName = classLoader.class.name
- def parentClassLoader = classLoader.parent
- while(parentClassLoader!=null && pastName!=parentClassLoader.class.name)
- {
- printf("\t\tWith Parent ClassLoader=%s...\n", parentClassLoader.class.name)
- pastName = parentClassLoader.class.name
- parentClassLoader = classLoader.parent
- }
- try
- {
- Class aClass = classLoader.loadClass("test.MainClass");
- printf("\t[Info] aClass.getName() = " + aClass.getName());
- } catch (ClassNotFoundException e)
- {
- e.printStackTrace();
- }
- }
- }
From the above log, we can known the current class loader is AppClassLoader and its' parent is ExtClassLoader.
Dynamic Class Reloading
Dynamic class reloading is a bit more challenging. Java's builtin Class loaders always checks if a class is already loaded before loading it. Reloading the class is therefore not possible using Java's builtin class loaders. To reload a class you will have to implement your own ClassLoader subclass.
Even with a custom subclass of ClassLoader you have a challenge. Every loaded class needs to be linked. This is done using the ClassLoader.resolve() method. This method is final, and thus cannot be overridden in your ClassLoader subclass. The resolve() method will not allow any given ClassLoader instance to link the same class twice. Therefore, everytime you want to reload a class you must use a new instance of your ClassLoader subclass. This is not impossible, but necessary to know when designing for class reloading.
Designing your Code for Class Reloading
As stated earlier you cannot reload a class using a ClassLoader that has already loaded that class once. Therefore you will have to reload the class using a different ClassLoader instance. But this approach faces new challenges.
Every class loaded in a Java application is identified by its fully qualified name (package name + class name), and the ClassLoader instance that loaded it. That means, that a class MyObject loaded by class loader A, is not the same class as the MyObject class loaded with class loader B. Look at this code:
- MyObject object = (MyObject)
- myClassReloadingFactory.newInstance("test.MyObject");
If the myClassReloadingFactory object factory reloads the MyObject class using a different class loader than the class the above code resides in, you cannot cast the instance of the reloaded MyObject class to the MyObject type of the object variable. Since the two MyObject classes were loaded with different class loaders, they are regarded as different classes, even if they have the same fully qualified class name. Trying to cast an object of the one class to a reference of the other will result in a ClassCastException.
It is possible to work around this limitation but you will have to change your code in either of two ways:
Here are two corresponding code examples:
- Way1 - Using interface as variable type
- MyObjectInterface object = (MyObjectInterface)
- myClassReloadingFactory.newInstance("com.jenkov.MyObject");
- MyObjectSuperclass object = (MyObjectSuperclass)
- myClassReloadingFactory.newInstance("com.jenkov.MyObject");
ClassLoader Load / Reload Example
The text above has contained a lot of talk. Let's look at a simple example. Below is an example of a simple ClassLoader subclass. Notice how it delegates class loading to its parent except for the one class it is intended to be able to reload. If the loading of this class is delegated to the parent class loader, it cannot be reloaded later. Remember, a class can only be loaded once by the same ClassLoader instance.
- package reflection
- class MyClassLoader extends ClassLoader{
- String url = "file:C:/John/EclipseNTU/GroovyLab/bin/" +
- "reflection/MyObject.class";
- public MyClassLoader(ClassLoader parent) {
- super(parent);
- }
- @SuppressWarnings("unchecked")
- @Override
- public Class loadClass(String name) throws ClassNotFoundException {
- if(!"reflection.MyObject".equals(name))
- {
- // All non reflection.MyObject class, delegate to parent class loader.
- return super.loadClass(name);
- }
- try
- {
- // Loading reflection.MyObject class in current class loader
- // You have to give in real path of class file here:
- URL myUrl = new URL(url);
- URLConnection connection = myUrl.openConnection();
- InputStream input = connection.getInputStream();
- ByteArrayOutputStream buffer = new ByteArrayOutputStream();
- int data = input.read();
- while(data != -1){
- buffer.write(data);
- data = input.read();
- }
- input.close();
- byte[] classData = buffer.toByteArray();
- return defineClass("reflection.MyObject",
- classData, 0, classData.length);
- } catch (MalformedURLException e) {
- e.printStackTrace();
- } catch (IOException e) {
- e.printStackTrace();
- }
- return null;
- }
- }
- package reflection
- class Main {
- public static void main(args) {
- ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();
- MyClassLoader classLoader = new MyClassLoader(parentClassLoader);
- Class myObjectClass = classLoader.loadClass("reflection.MyObject");
- MyObjectInterface object =
- (MyObjectInterface) myObjectClass.newInstance();
- object.doTest()
- //create new class loader so classes can be reloaded.
- classLoader = new MyClassLoader(parentClassLoader);
- // Dynamical change loading location to load different MyObject class
- classLoader.url = "file:C:/John/EclipseNTU/GroovyLab2/bin/" +
- "reflection/MyObject.class";
- myObjectClass = classLoader.loadClass("reflection.MyObject");
- object = (MyObjectInterface) myObjectClass.newInstance();
- object.doTest()
- }
- }
- From GroovyLab
- package reflection
- class MyObject implements MyObjectInterface{
- @Override
- public void doTest()
- {
- printf("Testing from GroovyLab\n")
- }
- }
- package reflection
- class MyObject implements MyObjectInterface{
- @Override
- public void doTest()
- {
- printf("Testing from GroovyLab2\n")
- }
- }
Supplement
* 反射 : 檢視類別 (簡介 ClassLoader)
* 反射 : 檢視類別 (自訂 ClassLoader)
沒有留言:
張貼留言