Dynamic Code Generation in Java Class Loading • Class loading is the process of transforming a byte code (e.g., a .class file) into a Java class • A Java class can be loaded dynamically (i.e., during runtime) • The class is then represented by an object of class Class • You can use the method Class.forName("name") to get a pointer to the Class object, given its name (exactly one object represents each class). Dynamic Code Invocation • Using dynamic class loading, we can dynamically generate a .class file, load it, instantiate it (class.newInstance()), and invoke its methods • Therefore, we can write and execute methods within the execution of the calling program. • Q: How can we access a method of an object, if we do not know its type on compile time? • One solution is to implement a known interface. An Example • In the following example, we will invoke the method run() of a dynamically created object of class C, inherited from an interface Base public interface Base { public void run(); } An Example public class Invoker { public static void main(String[] argv) throws Exception { String code = "public class C implements Base {\n" + " public void run() {\n" +" System.out.println(\"++++++++++\");\n" + " }}"; createClassFile(code); // Implemented in the next slide Class classB = Class.forName("C"); Base b = (Base)classB.newInstance(); b.run(); } An Example public static void createClassFile(String code) throws Exception { OutputStream os = new FileOutputStream(new File("C.java")); os.write(code.getBytes()); os.close(); Process p = Runtime.getRuntime(). exec("javac -classpath . C.java"); p.waitFor(); } } The Result The Whole Process write C.java compile C.class load Class classB ne wI ns ta n ce ( ) code +++++ run() Base b downcast Object b Assumptions The example we just saw works correctly under the following assumptions: • The command javac is known to the System - e.g., the javac executable is in the PATH variable • The directory "." is in the class path of Java - Hence, Class.forName("C") will find that C.class Class Reloading String code1 = "public class C implements Base {\n" + "public void run() {\n" + "System.out.println(\"++++++++++\");\n" + "}}"; String code2 = "public class C implements Base {\n" + "public void run() {\n" + "System.out.println(\"----------\");\n" + "}}"; What is the problem here? createClass(code1); ((Base)Class.forName("C").newInstance()).run(); createClass(code2); ((Base)Class.forName("C").newInstance()).run(); The Result Class Loaders • Java classes are loaded by class loaders • Two special class loaders exist: (why not just one?) - The bootstrap class loader • Typically implemented completely in C. Loads the primitive classes that are necessary for the initialization of the JVM (rt.jar) - The system class loader • Loads the regular classes in the program (e.g., all of the classes that you have written so far in various exercises) • In addition, any number of user-defined class loaders may be defined • Each class loader has a parent class loader - Except for the bootstrap class loader - Having a null parent is the same as having the bootstrap class loader as a parent The System Class Loader • Class.forName(name) invokes loadClass(name) of the class loader of the current class, which is usually the system class loader • The system class loader can always be accessed by the static call: ClassLoader.getSystemClassLoader() • Hence, a class can explicitly be loaded by the system class loader as follows: ClassLoader.getSystemClassLoader().loadClass(name) Class Loading Mechanism • When loadClass(“C") is invoked on a class loader, the following is done by default: - check if a class names c has already been loaded by the same class loader - Otherwise, check if the parent can load the class • Hence certain classes will always be loaded by the bootstrap class loader – Why does this happen? Why is this good? - invoke the method findClass(“C") • The implementation of findClass differs between class loaders. • When you load a class, all referenced classes are recursively loaded by the same class loader - Including implemented interfaces and parent classes Back to our Example • In the previous example, the class loader didn’t reload the class since it previously loaded a class named C • So what should we do to reload a class? • Solution 1: Use a unique (randomized?) name each time we rewrite the code • Solution 2: Use a different user-defined class loader each time - make sure that this loader does not have a parent capable of loading class C - One way to implement this is using a new ClassLoader object obtained by instantiating java.net.URLClassLoader URLClassLoader • A URLClassLoader loads classes which are accessible via a URL (either a local file system URL or a remote URL) • It stores an array of URLs (“http://...”, “file://...”, etc.) of either directories or JAR files, and it loads classes from the resources of the URLs • Constructor: URLClassLoader(URLs,parent) • We will set the URLs to contain only the URL of “.”, and the parent to be null Fixed(?) Example URL[] urls = {new File(".").toURL()}; createClass(code1); ClassLoader loader = new URLClassLoader(urls,null); Class classB = loader.loadClass("C"); What is the problem here? ((Base)classB.newInstance()).run(); createClass(code1); loader = new URLClassLoader(urls,null); classB = loader.loadClass("C"); ((Base)classB.newInstance()).run(); A Problem • The interface Base is also being loaded by the new class loader - But the system already has one interface called Base • Each newly created interface is deemed a unique interface that is different from the Base interface that is identified at compilation time and loaded by the system class loader. • Hence, it is impossible to cast C to Base Solutions • Solution 1: to invoke run(), use reflection rather than down casting • Solution 2: use the system class loader as a parent, but call findClass() directly, instead of loadClass() - problem: this method is protected - Solution? • Solution 3: Create a common parent to all these class loaders, capable of loading only the Base interface. Fixed(!) Example URL[] urls = {new File(".").toURL()}; createClass(code1); ClassLoader loader = new URLClassLoader(urls,null); Class classB = loader.loadClass("C"); Method runMethod = classB.getMethod("run", null); runMethod.invoke(classB.newInstance(),null); createClass(code2); classB = new URLClassLoader(urls,null).loadClass("C"); classB.getMethod("run",null).invoke(classB.newInstance(),null); Finally, Different Outputs