Original Post
I'm making an MMORPG and have decided to make it with Java. I also decided I wanted to use Java as the scripting language, by having classes load dynamically. I worked on this problem for a few days. I ended up writing a custom ClassLoader because the existing ones wouldn't reload classes that had changed. I ran into all sorts of problems, such as getting ClassCastException if I loaded a class and cast it to itself! This is because each class loader has its own heirarchy (well, almost). The solution was to make each script (which is implemented as a dynamically loaded class) implement the same interface. Then I had the custom ClassLoader always load the dynamic class (script) from file, and always delegate the interface loading to the parent ClassLoader (there is a ClassLoader heirarchy). Then I was able to cast the dynamically loaded class to the interface and call its methods through that interface. While the program is running, I can change the class, recompile it and reload it, and have the main program create a new object from it and execute its methods. Exciting stuff. This gives me the ability to use Java as a scripting language in a Java program, which of course has some benefits. Here's the code: MainProgram.java DynInterface.java DynClass.java Here is some sample i/o:
import java.io.*;
import java.lang.reflect.*;
class MainProgram
{
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException
{
BufferedReader stdin = new BufferedReader(new InputStreamReader(System.in));
String command;
do
{
System.out.println("Enter a command. 'P' will invoke DynInterface.printVersion(). Blank line will end.");
command = stdin.readLine();
if (command.toLowerCase().equals("p"))
{
ClassLoader loader = new FlexibleClassLoader(new CustomDelegationFilter());
Class dynClass = loader.loadClass("DynClass");
DynInterface dynObject = (DynInterface)(dynClass.newInstance());
dynObject.printVersion();
}
}
while (command.length() > 0);
}
}
class FlexibleClassLoader extends ClassLoader
{
private DelegationFilter delegate;
// If this constructor is used, no delegation filter is specified. This constructor
// therefore creates a delegation filter that causes this class loader to implement a
// load-first delegate-last policy for all non-SDK classes.
public FlexibleClassLoader()
{
delegate = new DelegationFilter()
{
// Nothing specified to be always delegated.
public boolean alwaysDelegateClass(String className)
{
return false;
}
// Only SDK classes are predelegated. Saves time and ClassCastExceptions.
public boolean preDelegateClass(String className)
{
return isSdkClass(className);
}
// All other classes not found can be delegated.
public boolean postDelegateClass(String className)
{
return true;
}
};
}
// Assign a custom delegation filter to this class loader.
public FlexibleClassLoader(DelegationFilter delegate)
{
this.delegate = delegate;
}
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException
{
if (delegate.alwaysDelegateClass(name))
{
System.out.println("Class " + name + " is being auto-delegated to the parent loader.");
return getParent().loadClass(name);
}
else if (delegate.preDelegateClass(name))
{
try
{
System.out.println("Class " + name + " is being predelegated to the parent loader.");
return getParent().loadClass(name);
}
catch (ClassNotFoundException exception)
{
System.out.println("Predelegated class not found! Attempting to load class " + name + " from file.");
return loadClassFromFile(name, resolve);
}
}
else
{
try
{
System.out.println("Class " + name + " is loading from file.");
return loadClassFromFile(name, resolve);
}
catch (ClassNotFoundException exception)
{
System.out.println("Class " + name + " is being postdelegated to the parent loader.");
if (delegate.postDelegateClass(name))
{
return getParent().loadClass(name);
}
else
{
throw exception;
}
}
}
}
// Load a class from file
protected Class<?> loadClassFromFile(String name, boolean resolve) throws ClassNotFoundException
{
try
{
// Open the class file for reading.
File classFile = new File(name + ".class");
FileInputStream stream;
try
{
stream = new FileInputStream(classFile);
}
catch (FileNotFoundException exception)
{
throw new ClassNotFoundException(name, exception);
}
// Read the class file's raw bytes into an array.
byte[] rawClassData;
try
{
rawClassData = new byte[(int)classFile.length()];
stream.read(rawClassData);
stream.close();
}
catch (IOException exception)
{
throw new ClassNotFoundException(name, exception);
}
// Convert the array into a class.
Class<?> loadedClass;
try
{
loadedClass = defineClass(name, rawClassData, 0, rawClassData.length);
}
catch (ClassFormatError exception)
{
throw new ClassNotFoundException(name, exception);
}
// Resolve the class if requested by the caller.
if (resolve)
{
resolveClass(loadedClass);
}
System.out.println("Loaded class " + name + " from file successfully.");
return loadedClass;
}
catch (ClassNotFoundException exception)
{
// Delegate class loading to the parent.
System.out.println("Could not load class " + name + ". Propogating exception.");
throw exception;
}
}
// Load a class without resolving (the default behaviour according to the SDK docs).
public Class<?> loadClass(String name) throws ClassNotFoundException
{
return loadClass(name, false);
}
}
abstract class DelegationFilter
{
// Returns true if the specified class will be loaded ONLY by the parent loader.
public abstract boolean alwaysDelegateClass(String className);
// Returns true if the specified class will be attempted to be loaded by the
// parent loader before trying to custom load it.
public abstract boolean preDelegateClass(String className);
// Returns true if the specified class will be loaded by the parent loader
// after unsuccessfully trying to custom load it.
public abstract boolean postDelegateClass(String className);
// Is the className from the SDK?
protected boolean isSdkClass(String className)
{
return className.startsWith("java.") || className.startsWith("javax.") || className.startsWith("org.");
}
}
class CustomDelegationFilter extends DelegationFilter
{
public boolean alwaysDelegateClass(String className)
{
if (className.equals("DynInterface"))
{
return true;
}
else
{
return false;
}
}
public boolean preDelegateClass(String className)
{
if (isSdkClass(className) || className.equals("DynInterface"))
{
return true;
}
else
{
return false;
}
}
// If we haven't found what we're looking for, there's nothing left to do.
public boolean postDelegateClass(String className)
{
return false;
}
}
public interface DynInterface
{
public void printVersion();
}
/* This is a version of the DynClass class */
public class DynClass implements DynInterface
{
public DynClass()
{
}
private int version = 1;
public void printVersion()
{
System.out.println(this.toString());
}
public String toString()
{
return "This is DynClass version " + version;
}
}
$java MainProgram
Enter a command. 'P' will invoke DynInterface.printVersion(). Blank line will end.
p
Class DynClass is loading from file.
Class DynInterface is being auto-delegated to the parent loader.
Class java.lang.Object is being predelegated to the parent loader.
Loaded class DynClass from file successfully.
Class java.lang.System is being predelegated to the parent loader.
Class java.lang.StringBuilder is being predelegated to the parent loader.
Class java.io.PrintStream is being predelegated to the parent loader.
This is DynClass version 1
Enter a command. 'P' will invoke DynInterface.printVersion(). Blank line will end.
Then I modify and recompile DynClass.java... p
Class DynClass is loading from file.
Class DynInterface is being auto-delegated to the parent loader.
Class java.lang.Object is being predelegated to the parent loader.
Loaded class DynClass from file successfully.
Class java.lang.System is being predelegated to the parent loader.
Class java.lang.StringBuilder is being predelegated to the parent loader.
Class java.io.PrintStream is being predelegated to the parent loader.
This is DynClass version 2
Enter a command. 'P' will invoke DynInterface.printVersion(). Blank line will end.
[Edited by - SteveTaylor on January 17, 2006 9:51:46 AM]