Download Dynamic Class Generation in Java - CS

Survey
yes no Was this document useful for you?
   Thank you for your participation!

* Your assessment is very important for improving the work of artificial intelligence, which forms the content of this project

Document related concepts
no text concepts found
Transcript
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