Transcript ppt

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