程式扎記: [ Java 文章收集 ] Java Reflection - Dynamic Class Loading and Reloading

標籤

2015年2月2日 星期一

[ Java 文章收集 ] Java Reflection - Dynamic Class Loading and Reloading

Source From Here 
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: 
1. Check if the class was already loaded.
2. If not loaded, ask parent class loader to load the class.
3. If parent class loader cannot load class, attempt to load it in this class loader.

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: 
  1. package test  
  2.   
  3. public class MainClass{  
  4.     public static void main(args)  
  5.     {  
  6.         ClassLoader classLoader = MainClass.class.getClassLoader();  
  7.         printf("\t[Info] Current ClassLoader=%s...\n", classLoader.class.name)  
  8.         def pastName = classLoader.class.name  
  9.         def parentClassLoader = classLoader.parent        
  10.         while(parentClassLoader!=null && pastName!=parentClassLoader.class.name)  
  11.         {  
  12.             printf("\t\tWith Parent ClassLoader=%s...\n", parentClassLoader.class.name)  
  13.             pastName = parentClassLoader.class.name  
  14.             parentClassLoader = classLoader.parent            
  15.         }  
  16.         try   
  17.         {  
  18.             Class aClass = classLoader.loadClass("test.MainClass");  
  19.             printf("\t[Info] aClass.getName() = " + aClass.getName());  
  20.         } catch (ClassNotFoundException e)   
  21.         {  
  22.             e.printStackTrace();  
  23.         }         
  24.     }  
  25. }  
The execution result: 
[Info] Current ClassLoader=sun.misc.Launcher$AppClassLoader...
With Parent ClassLoader=sun.misc.Launcher$ExtClassLoader...
[Info] aClass.getName() = test.MainClass

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 twiceTherefore, 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:
  1. MyObject object = (MyObject)  
  2.     myClassReloadingFactory.newInstance("test.MyObject");  
Notice how the MyObject class is referenced in the code, as the type of the object variable. This causes the MyObject class to be loaded by the same class loader that loaded the class this code is residing in

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: 
1. Use an interface as the variable type, and just reload the implementing class.
2. Use a superclass as the variable type, and just reload a subclass.

Here are two corresponding code examples: 
- Way1 - Using interface as variable type 
  1. MyObjectInterface object = (MyObjectInterface)  
  2.     myClassReloadingFactory.newInstance("com.jenkov.MyObject");  
- Way2 - Using Superclass 
  1. MyObjectSuperclass object = (MyObjectSuperclass)  
  2.     myClassReloadingFactory.newInstance("com.jenkov.MyObject");  
To make this work you will of course need to implement your class loader to let the interface or superclass be loaded by its parent. When your class loader is asked to load the MyObject class, it will also be asked to load the MyObjectInterface class, or the MyObjectSuperclass class, since these are referenced from within the MyObject class. Your class loader must delegate the loading of those classes to the same class loader that loaded the class containing the interface or superclass typed variables. 

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
  1. package reflection  
  2.   
  3. class MyClassLoader extends ClassLoader{  
  4.     String url = "file:C:/John/EclipseNTU/GroovyLab/bin/" +  
  5.                             "reflection/MyObject.class";  
  6.       
  7.     public MyClassLoader(ClassLoader parent) {  
  8.         super(parent);  
  9.     }  
  10.       
  11.     @SuppressWarnings("unchecked")  
  12.     @Override  
  13.     public Class loadClass(String name) throws ClassNotFoundException {  
  14.         if(!"reflection.MyObject".equals(name))  
  15.         {  
  16.             // All non reflection.MyObject class, delegate to parent class loader.  
  17.             return super.loadClass(name);  
  18.         }  
  19.   
  20.         try   
  21.         {  
  22.             // Loading reflection.MyObject class in current class loader  
  23.             // You have to give in real path of class file here:              
  24.             URL myUrl = new URL(url);  
  25.             URLConnection connection = myUrl.openConnection();  
  26.             InputStream input = connection.getInputStream();  
  27.             ByteArrayOutputStream buffer = new ByteArrayOutputStream();  
  28.             int data = input.read();  
  29.   
  30.             while(data != -1){  
  31.                 buffer.write(data);  
  32.                 data = input.read();  
  33.             }  
  34.   
  35.             input.close();  
  36.   
  37.             byte[] classData = buffer.toByteArray();  
  38.   
  39.             return defineClass("reflection.MyObject",  
  40.                     classData, 0, classData.length);  
  41.   
  42.         } catch (MalformedURLException e) {  
  43.             e.printStackTrace();  
  44.         } catch (IOException e) {  
  45.             e.printStackTrace();  
  46.         }  
  47.   
  48.         return null;  
  49.     }  
  50. }  
Below is an example use of the MyClassLoader
  1. package reflection  
  2.   
  3. class Main {  
  4.     public static void main(args) {  
  5.         ClassLoader parentClassLoader = MyClassLoader.class.getClassLoader();  
  6.         MyClassLoader classLoader = new MyClassLoader(parentClassLoader);  
  7.         Class myObjectClass = classLoader.loadClass("reflection.MyObject");  
  8.   
  9.         MyObjectInterface       object =  
  10.                 (MyObjectInterface) myObjectClass.newInstance();  
  11.         object.doTest()  
  12.   
  13.         //create new class loader so classes can be reloaded.  
  14.         classLoader = new MyClassLoader(parentClassLoader);  
  15.           
  16.         // Dynamical change loading location to load different MyObject class  
  17.         classLoader.url = "file:C:/John/EclipseNTU/GroovyLab2/bin/" +  
  18.                             "reflection/MyObject.class";  
  19.         myObjectClass = classLoader.loadClass("reflection.MyObject");  
  20.   
  21.         object = (MyObjectInterface) myObjectClass.newInstance();  
  22.         object.doTest()  
  23.     }  
  24. }  
Below are two different MyObject classes belongs to different project: 
- From GroovyLab 
  1. package reflection  
  2.   
  3. class MyObject implements MyObjectInterface{  
  4.   
  5.     @Override  
  6.     public void doTest()   
  7.     {  
  8.         printf("Testing from GroovyLab\n")        
  9.     }  
  10. }  
- From GroovyLab2 
  1. package reflection  
  2.   
  3. class MyObject implements MyObjectInterface{  
  4.   
  5.     @Override  
  6.     public void doTest()   
  7.     {  
  8.         printf("Testing from GroovyLab2\n")       
  9.     }  
  10. }  
Execution result: 
Testing from GroovyLab
Testing from GroovyLab2

Supplement 
反射 : 檢視類別 (簡介 ClassLoader) 
反射 : 檢視類別 (自訂 ClassLoader)

沒有留言:

張貼留言

網誌存檔

關於我自己

我的相片
Where there is a will, there is a way!