Java Seminars by Bill Venners
Dynamic Extension in Java
Lecture Handout
Agenda
-
Give some background
-
Show simple progression
-
Give class loader examples in 1.1 and 1.2
-
Discuss the parent delegation model
-
Discuss class unloading
-
Give some design insights
Dynamic Extension
-
Deciding at runtime types to load and use
-
May not know type names at compile time
-
Telltale sign: type names as
String
s
-
Two kinds:
forName()
and class loaders
Who Cares?
-
Part of paradigm shift
-
Can use in designs
-
Example: Web Browsers
-
Example: Internationalization
-
Example: Painters
-
Example: Jini Services and Service UIs
Class loaders
-
Two flavors -- bootstrap and user-defined:
-
Bootstrap Class Loader -- part of JVM
-
User-Defined Class Loader -- part of application
Why Customize Loading?
-
Grab files not in CLASSPATH
-
Decipher encrypted class files
-
Download files across a network
-
Pull classes out of a database
-
Create classes on the fly
Name Spaces
-
Each class loader has a namespace.
-
Namespace: the set of type names already loaded.
-
Each name unique within a namespace...
-
...but not necessarily unique across namespaces.
Duplicate Names
Names and Definitions
Why Name Spaces?
-
Deal with name conflicts.
-
Spoil plans of malicious code.
How Do Name Spaces Work?
-
Referenced types are loaded via the same class loader that
loaded referencing type.
// In file dynaext/ex1/Rodent.java
public class Rodent {
public void scurry() {
System.out.println("Rodent scurrying");
}
}
// In file dynaext/ex1/Mouse.java
public class Mouse extends Rodent {
public void scurry() {
System.out.println("Mouse scurrying");
}
}
// In file dynaext/ex1/Cat.java
public class Cat {
public static void main(String[] args) {
Rodent myToy = new Mouse();
myToy.scurry();
}
}
The Rule in Action
// In file dynaext/ex1/Cat.java
public class Cat {
public static void main(String[] args) {
Rodent myToy = new Mouse();
myToy.scurry();
}
}
// In file dynaext/ex1/Rodent.java
public class Rodent {
public void scurry() {
System.out.println("Rodent scurrying");
}
}
// In file dynaext/ex1/Mouse.java
public class Mouse extends Rodent {
public void scurry() {
System.out.println("Mouse scurrying");
}
}
java.lang.Class
-
Used in dynamic extension
-
Represents types (front end to method area)
-
Can get information about a type
-
Can create a new instance of a class
forName()
Follows the Rule
// In file dynaext/ex2/Cat.java
public class Cat {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
Class c = Class.forName(args[0]);
Rodent myToy = (Rodent) c.newInstance();
myToy.scurry();
}
}
// In file dynaext/ex2/Rodent.java
public class Rodent {
public void scurry() {
System.out.println("Rodent scurrying");
}
}
// In file dynaext/ex2/Mouse.java
public class Mouse extends Rodent {
public void scurry() {
System.out.println("Mouse scurrying");
}
}
loadClass()
Breaks the Rule
To load a type into a different name space, invoke
loadClass()
on a class loader object.
// In file dynaext/ex3/Cat.java
public class Cat {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
RodentClassLoader rcl = new RodentClassLoader();
Class c = rcl.loadClass(args[0]);
Rodent myToy = (Rodent) c.newInstance();
myToy.scurry();
}
}
// In file dynaext/ex2/Rodent.java
public class Rodent {
public void scurry() {
System.out.println("Rodent scurrying");
}
}
// In file dynaext/ex2/Mouse.java
public class Mouse extends Rodent {
public void scurry() {
System.out.println("Mouse scurrying");
}
}
// In file RodentClassLoader.java
import java.io.*;
import java.util.Hashtable;
public class RodentClassLoader extends ClassLoader {
public synchronized Class loadClass(String typeName,
boolean resolveIt) throws ClassNotFoundException {
// See if type as already been loaded by
// this class loader
Class result = findLoadedClass(typeName);
if (result != null) {
// Return an already-loaded class
return result;
}
// Check with the primordial class loader
try {
result = super.findSystemClass(typeName);
// Return a system class
return result;
}
catch (ClassNotFoundException e) {
}
// Don't attempt to load a system file except
// through the primordial class loader
if (typeName.startsWith("java.")) {
throw new ClassNotFoundException();
}
// Try to load it from subdirectory hole.
byte typeData[] = getTypeFromHole(typeName);
if (typeData == null) {
throw new ClassNotFoundException();
}
// Parse it
result = defineClass(typeName, typeData, 0,
typeData.length);
if (result == null) {
throw new ClassFormatError();
}
if (resolveIt) {
resolveClass(result);
}
// Return class from hole
return result;
}
private byte[] getTypeFromHole(String typeName) {
FileInputStream fis;
String fileName = "hole" + File.separatorChar +
typeName.replace('.', File.separatorChar)
+ ".class";
try {
fis = new FileInputStream(fileName);
}
catch (FileNotFoundException e) {
return null;
}
BufferedInputStream bis =
new BufferedInputStream(fis);
ByteArrayOutputStream out =
new ByteArrayOutputStream();
try {
int c = bis.read();
while (c != -1) {
out.write(c);
c = bis.read();
}
}
catch (IOException e) {
return null;
}
return out.toByteArray();
}
}
How the Name Spaces Look
// In file dynaext/ex3/Cat.java
public class Cat {
public static void main(String[] args)
throws ClassNotFoundException, IllegalAccessException,
InstantiationException {
RodentClassLoader rcl = new RodentClassLoader();
Class c = rcl.loadClass(args[0]);
Rodent myToy = (Rodent) c.newInstance();
myToy.scurry();
}
}
Talking Between Namespaces
- Cast to a known interface (Greeters)
- Cast to a known class (Rodents, Applets)
- Use reflection (Bean Builders)
The Greet Application
// In file dynaext/ex4/com/artima/greeter/Greeter.java
package com.artima.greeter;
public interface Greeter {
void greet();
}
// In file dynaext/ex4/greeters/Hello.java
import com.artima.greeter.Greeter;
public class Hello implements Greeter {
public void greet() {
System.out.println("Hello, world!");
}
}
// In file dynaext/ex4/Greet.java
import com.artima.greeter.*;
public class Greet {
// Arguments to this application:
// args[0] - path name of directory in which class
// files for greeters are stored
// args[1], args[2], ... - class names of greeters
// on which to load and invoke greet().
//
// All greeters must implement the
// com.artima.greeter.Greeter interface.
//
static public void main(String[] args) {
if (args.length <= 1) {
System.out.println(
"Enter base path and greeter class names.");
return;
}
GreeterClassLoader gcl =
new GreeterClassLoader(args[0]);
for (int i = 1; i < args.length; ++i) {
try {
// Load the greeter specified on the
// command line
Class c = gcl.loadClass(args[i]);
// Instantiate it into a greeter object
Object o = c.newInstance();
// Cast the Object ref to the Greeter
// interface type so greet() can be
// invoked on it
Greeter greeter = (Greeter) o;
// Greet the world in this greeter's
// special way
greeter.greet();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
// In file dynaext/ex4/greeters/Salutations.java
import com.artima.greeter.Greeter;
public class Salutations implements Greeter {
public void greet() {
System.out.println("Salutations, orb!");
}
}
// In file dynaext/ex4/greeters/Greetings.java
import com.artima.greeter.Greeter;
public class Greetings implements Greeter {
public void greet() {
System.out.println("Greetings, planet!");
}
}
// In file dynaext/ex4/greeters/HowDoYouDo.java
import com.artima.greeter.Greeter;
public class HowDoYouDo implements Greeter {
public void greet() {
System.out.println("How do you do, globe!");
}
}
// In file dynaext/ex4/greeters/Surprise.java
import com.artima.greeter.Greeter;
public class Surprise implements Greeter {
public void greet() {
// Choose one of four greeters pseudo-randomly and
// invoke its greet() method.
int choice = (int) (Math.random() * 3.99);
Greeter g;
switch(choice) {
case 0:
g = new Hello();
g.greet();
break;
case 1:
g = new Greetings();
g.greet();
break;
case 2:
g = new Salutations();
g.greet();
break;
case 3:
g = new HowDoYouDo();
g.greet();
break;
}
}
}
// In file dynaext/ex4/greeters/HiTime.java
import com.artima.greeter.Greeter;
import java.util.Date;
public class HiTime implements Greeter {
public void greet() {
// Date's no-arg constructor initializes itself
// to the current date and time
Date date = new Date();
int hours = date.getHours();
// Some hours: midnight, 0; noon, 12; 11PM, 23;
if (hours >= 4 && hours <= 11) {
System.out.println("Good morning, world!");
}
else if (hours >= 12 && hours <= 16) {
System.out.println("Good afternoon, world!");
}
else if (hours >= 17 && hours <= 21) {
System.out.println("Good evening, world!");
}
else if (hours >= 22 || hours <= 3) {
System.out.println("Good night, world!");
}
else {
// This should never happen.
System.out.println("Hello, world!");
}
}
}
GreeterClassLoader
// In file dynaext/ex4/com/artima/greeter/GreeterClassLoader.java
package com.artima.greeter;
import java.io.*;
import java.util.Hashtable;
public class GreeterClassLoader extends ClassLoader {
// basePath gives the path to which this class
// loader appends "/.class" to get the
// full path name of the class file to load
private String basePath;
public GreeterClassLoader(String basePath) {
this.basePath = basePath;
}
public synchronized Class loadClass(String typeName,
boolean resolveIt) throws ClassNotFoundException {
// See if type has already been loaded by this
// class loader
Class result = findLoadedClass(typeName);
if (result != null) {
// Return an already-loaded class
return result;
}
// Check with the primordial class loader
try {
result = findSystemClass(typeName);
// Return a system class
return result;
}
catch (ClassNotFoundException e) {
}
// Don't attempt to load a system file except
// through the primordial class loader
if (typeName.startsWith("java.")) {
throw new ClassNotFoundException();
}
// Try to load it from the basePath directory.
byte typeData[] = getTypeFromBasePath(typeName);
if (typeData == null) {
throw new ClassNotFoundException();
}
// Parse it
result = defineClass(typeName, typeData, 0,
typeData.length);
if (result == null) {
throw new ClassFormatError();
}
if (resolveIt) {
resolveClass(result);
}
// Return class from basePath directory
return result;
}
private byte[] getTypeFromBasePath(String typeName) {
FileInputStream fis;
String fileName = basePath + File.separatorChar
+ typeName.replace('.', File.separatorChar)
+ ".class";
try {
fis = new FileInputStream(fileName);
}
catch (FileNotFoundException e) {
return null;
}
BufferedInputStream bis =
new BufferedInputStream(fis);
ByteArrayOutputStream out =
new ByteArrayOutputStream();
try {
int c = bis.read();
while (c != -1) {
out.write(c);
c = bis.read();
}
}
catch (IOException e) {
return null;
}
return out.toByteArray();
}
}
Changes in 1.2
-
Delegation (parent loaders)
-
Default
loadClass()
-
Can just override
findLocalClass()
-
SecureClassLoader
and URLClassLoader
The Parent-Delegation Model
A 1.2 GreeterClassLoader
// On CD-ROM in file
// linking/ex7/com/artima/greeter/GreeterClassLoader.java
package com.artima.greeter;
import java.io.*;
public class GreeterClassLoader extends ClassLoader {
// basePath gives the path to which this class
// loader appends "/.class" to get the
// full path name of the class file to load
private String basePath;
public GreeterClassLoader(String basePath) {
this.basePath = basePath;
}
public GreeterClassLoader(ClassLoader parent, String basePath) {
super(parent);
this.basePath = basePath;
}
protected Class findClass(String className)
throws ClassNotFoundException {
byte classData[];
// Try to load it from the basePath directory.
classData = getTypeFromBasePath(className);
if (classData == null) {
throw new ClassNotFoundException();
}
// Parse it
return defineClass(className, classData, 0,
classData.length);
}
private byte[] getTypeFromBasePath(String typeName) {
FileInputStream fis;
String fileName = basePath + File.separatorChar
+ typeName.replace('.', File.separatorChar)
+ ".class";
try {
fis = new FileInputStream(fileName);
}
catch (FileNotFoundException e) {
return null;
}
BufferedInputStream bis = new BufferedInputStream(fis);
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
int c = bis.read();
while (c != -1) {
out.write(c);
c = bis.read();
}
}
catch (IOException e) {
return null;
}
return out.toByteArray();
}
}
Unloading Types
-
Primordially loaded types never unloaded
-
Class loader object and its types are unloaded together
// On CD-ROM in file linking/ex7/GreetAndForget.java
import com.artima.greeter.*;
public class GreetAndForget {
// Arguments to this application:
// args[0] - path name of directory in which class files
// for greeters are stored
// args[1], args[2], ... - class names of greeters to load
// and invoke the greet() method on.
//
// All greeters must implement the com.artima.greeter.Greeter
// interface.
//
static public void main(String[] args) {
if (args.length <= 1) {
System.out.println(
"Enter base path and greeter class names as args.");
return;
}
for (int i = 1; i < args.length; ++i) {
try {
GreeterClassLoader gcl =
new GreeterClassLoader(args[0]);
// Load the greeter specified on the command line
Class c = gcl.loadClass(args[i]);
// Instantiate it into a greeter object
Object o = c.newInstance();
// Cast the Object ref to the Greeter interface type
// so greet() can be invoked on it
Greeter greeter = (Greeter) o;
// Greet the world in this greeter's special way
greeter.greet();
// Forget the class loader object, Class
// instance, and greeter object
gcl = null;
c = null;
o = null;
greeter = null;
// At this point, the types loaded through the
// GreeterClassLoader object created at the top of
// this for loop are unreferenced and can be unloaded
// by the virtual machine.
}
catch (Exception e) {
e.printStackTrace();
}
}
}
}
Design Insights
-
Dynamic extension facilitates customization
Applets, Resources, Servlets, Aglets, Content and Protocol Handlers,
Channels, Painters, Greeters, Rodents, ...
-
Prefer common supertype over reflection
-
forName()
: simple
-
Class loader objects: name spaces, security, custom loading,
dynamic replacement