220 likes | 385 Views
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
E N D
Class Loading • Class loadingis 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
publicinterfaceBase{ publicvoidrun(); } An Example publicclass Invoker { publicstaticvoid main(String[] argv)throwsException{ Stringcode="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 publicstaticvoid createClassFile(String code) throwsException { OutputStream os = newFileOutputStream(newFile("C.java")); os.write(code.getBytes()); os.close(); Process p =Runtime.getRuntime(). exec("javac -classpath . C.java"); p.waitFor(); } }
Assumptions The example we just saw works correctlyunder 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 Stringcode1="public class C implements Base {\n" +"public void run() {\n" +"System.out.println(\"++++++++++\");\n" + "}}"; Stringcode2="public class C implements Base {\n" + "public void run() {\n" +"System.out.println(\"----------\");\n" + "}}"; createClass(code1); ((Base)Class.forName("C").newInstance()).run(); createClass(code2); ((Base)Class.forName("C").newInstance()).run(); What is the problem here?
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 ={newFile(".").toURL()}; createClass(code1); ClassLoader loader =new URLClassLoader(urls,null); Class classB = loader.loadClass("C"); ((Base)classB.newInstance()).run(); createClass(code1); loader =new URLClassLoader(urls,null); classB = loader.loadClass("C"); ((Base)classB.newInstance()).run(); What is the problem here?
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 ={newFile(".").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);