A friend of mine (he�s a real Java crack located in ZA!) edits a newsletter
series
that might be of interest for you and other people from the JBoss
development
community.
I was so free to compile some of our classloading thoughts to a (hopefully)
didactic
example ...
If you are interested, subscribe and ask him to send the back-issue archive
...
he had already and still has a lot of ill ideas (switching over objects,
virtual methods)
that he distributes in that open manner.
Best,
CGJ
-----Urspr�ngliche Nachricht-----
Von: Heinz Kabutz [mailto:[EMAIL PROTECTED]]
Gesendet: Donnerstag, 7. Juni 2001 23:53
Betreff: 2001-06-07 TJSN [022] - Classloaders Revisited: "Hotdeploy"
2001-06-07 TJSN [022] - Classloaders Revisited: "Hotdeploy"
Welcome to the 22nd issue of "The Java(tm) Specialists'
Newsletter", sponsored by infor business solutions AG, an ERP
company based in Germany (http://www.infor.de). Many thanks go
to Christoph Jung for again making an excellent contribution to
"The Java(tm) Specialists' Newsletter" by taking the time to
write about some of the advanced things he's busy with. This is
the longest newsletter so far, and also the most in-depth. Don't
start expecting the same quality when I write ;-), in fact, I
might skip next week's letter to give you some time to digest
this one.
As always, I would like to ask you to forward this newsletter to
anybody you know who might be interested in Java. Please also
remember that if you are a beginner in Java, some of these news-
letters will go over your head. Don't give up!
The minimalist application server
Or
Classloading revisited
C.G.Jung (mailto:[EMAIL PROTECTED])
Sponsored by
infor business solutions AG
http://www.infor.de
----------------------------------
A few weeks ago, I was in the middle of an ICQ chat with
our admired Dr. Kabutz when he suddenly uttered a definite
"hi, hi" which took me by surprise as it was different to
the ubiquitous "emoticons" or smileys that I am used to.
Nevertheless, I was immediately able to catch the thousands
of denotations that this very special textual laughter was
transporting. In this case, the semantics can be very well
subsumed by the German term "verschmitzt" which is difficult
to translate to English but would probably be a combination of
"roguish", "twisty", "experienced" and "savvy". Similarly,
"ha, ha" (a very open reaction to a joke) and "ho, ho"
(kind of patronizing Santa Claus stance) are able to express
much more context than any smiley construction I know of,
at a minimum overhead of bandwidth. Measurements using WinZip
have shown verbose laughter statistically being only 5% more
expensive than its certainly poorer smiley counterpart.
And now ... I hope you kept a copy of newsletter 18 where Heinz
illuminated us with the insight that class identity in the
virtual machine is quite independent of even fully-qualified
class names. The magical constructs that he used for his
investigations were the java.lang.ClassLoader and suitable
derivatives, such as the java.net.URLCLassloader. These are,
amongst other innovations of the Java(tm) 2 Runtime, indeed
a fascinating subject for experimentation.
Classes and ClassLoaders
------------------------
Perhaps you found that having several classes called "A" in your
java.exe was not very useful. On the contrary, it probably
required quite an effort to keep the various sources and class
files of that newsletter in separate directories. However, I
would like to demonstrate how this rather theoretical possibility
has significant practical value when it comes to building real
programs that are able to simultaneously host multiple Java
applications. The most well known examples of such programs are
found in the J2EE(tm) application servers.
For this purpose, let us briefly revisit what the head of Maximum
Solutions Ltd., by the way, one of the best Java consultancies I
know of ;-) [hk: hoho], has demonstrated to our surprised eyes
and what may be already apparent from the terminology: A
java.lang.ClassLoader is an object that is used to locate
external resources (such as *.class file placed in a directory or
zipped into a jar-file) and to import the contained byte code for
constructing java.lang.Class instances at runtime.
Each java.lang.Class in the JVM is hence associated with its
so-called "defining classloader" that has imported it and that
can be obtained using the Class.getClassLoader() method. For
example, regard the following classes stock.Warehouse (interface
to a mono-product warehouse exposing a reservation method) and
sales.Order (remotely connects/links to a warehouse to satisfy
the order in quantities of size "bunch").
// stock/Warehouse.java
package stock;
/** Sample remote interface that is linked by sales.Order. */
public interface Warehouse extends java.rmi.Remote {
/** Reserve items in this warehouse */
void reserve(int quantity) throws java.rmi.RemoteException;
}
// sales/Order.java
package sales;
import java.rmi.*;
import stock.Warehouse;
/** Class that links in another jar */
public class Order {
/** the stock.Warehouse which hosts our items */
private final Warehouse wHouse;
/** how much do we need? */
private final int amount;
/** quantize reservations */
private static int bunch=5;
/** constructs a new order */
public Order(String itemName, int quantity)
throws java.net.MalformedURLException, NotBoundException,
RemoteException {
// look into the rmi registry to locate the warehouse
wHouse=(Warehouse)Naming.lookup("//localhost/"+itemName);
this.amount=quantity;
}
/** method that delegates reservation to warehouse */
public void reserve() throws RemoteException {
for(int count = 0; count < amount; count += bunch)
wHouse.reserve(bunch);
}
}
Although sophisticated names and nifty logic should suggest an
increasing level of practicability compared to Heinz's previous
excursions, please remember that this is a technical newsletter
and not a business logic course. Hence, instead of flaming about
the obvious flaws and, at the same time, selling the attached
files as ready-made ERP-competitor to our infor:COM, compile the
sources into two separate directories "classes/stock" and
"classes/sales":
javac -d classes\stock\ stock\*.java
javac -classpath classes\stock\ -d classes\sales\ sales\*.java
// server/Loader.java
package server;
import java.net.*;
import java.io.*;
public class Loader {
/** Demonstration of some classloading issues */
public static void main(String[] args) throws Exception {
// construct a tiny classloader hierarchy
ClassLoader stockLoader = new URLClassLoader(
new URL[] {getStockLocation()});
ClassLoader salesLoader = new URLClassLoader(
new URL[] {getSalesLocation()}, stockLoader);
// load order class
Class orderC = salesLoader.loadClass("sales.Order");
System.out.println(orderC + " loaded from " + salesLoader +
"; defined by " + orderC.getClassLoader());
// load warehouse class
Class wHouseC = salesLoader.loadClass("stock.Warehouse");
System.out.println(wHouseC + " loaded from " + salesLoader +
"; defined by " + wHouseC.getClassLoader());
// analyse class links
System.out.println("loading and linking same " +
wHouseC.equals(orderC.getDeclaredField("wHouse").getType()));
System.exit(0);
}
/** where the stock classes can be found */
protected static URL getStockLocation() throws IOException {
return new File("classes/stock/").toURL();
}
/** where the sales classes can be found */
protected static URL getSalesLocation() throws IOException {
return new File("classes/sales/").toURL();
}
}
Whenever the virtual machine (VM) gets hold of your just produced
sales.Order - maybe using the loadClass("sales.Order") statement
in the preceding server.Loader code - it will also automatically
ask the defining classloader of sales.Order to additionally load
the linked class into memory:
salesLoader.loadClass("stock.Warehouse").
Since we have placed the byte code of stock.Warehouse into the
classes\stock directory that is not under the hood of salesLoader,
salesLoader will delegate the call to its parent stockLoader and,
upon successful resolution, the respective class-representations
in the VM are linked together. These issues will become apparent
if you run Loader from the "classes\server" folder (using the
attached "lazy developer" policy file which you should never use
in production environments, you have been warned!).
javac -d classes\server\ server\*.java
java -Djava.security.policy=server.policy -classpath
classes\server server.Loader
// server.policy
grant {
// Allow everything for now [hk: *ouch*]
permission java.security.AllPermission;
};
The output will most likely look similarly to this:
class sales.Order loaded from java.net.URLClassLoader@253498;
defined by java.net.URLClassLoader@253498
interface stock.Warehouse defined by java.net.URLClassLoader@209f4e
loading versus linking: true
Note that java.exe operates a bit lazily since
salesLoader.loadClass("stock.Warehouse") and hence
stockLoader.loadClass("stock.Warehouse") will not happen
until you begin to inspect or instantiate the imported Order
class! In our Loader code, this is implicitly triggered by
the getDeclaredField("wHouse") call. This way, you will not
end up loading every class file that is under control by any
classloader into memory when you were just asking for Order.
You will really appreciate this feature if you have experienced
the class inflation phenomenon typical in an OO project ;-)
Hot-Deploy
==========
As an educated audience [hk: i.e. you read all my newsletter ;-}]
you did know all this before of course. This loading & linking
is certainly not restricted to our Order and Warehouse, but is
used to intern every class into the VM. It even does this with
the main class and the Java(tm) 2 runtime representations that
the main class depends upon. The big difference to our example
is, however, that you usually specify a single classpath/
classloader ("classes\server\") and a single main class
("server.Loader") at startup of java.exe, hence a single java
"application".
With our freshly acquired knowledge, we have revealed the
opportunity to dynamically start/shutdown applications at
runtime which is often called "deploying": java.exe starts
with a minimal setup, e.g., the below server.ApplicationServer,
and is then equipped with additional application specifications
on-the-fly. An application specification that is processed by
the server's deploy(...) and start(...) methods consists of the
path to the byte-code, the name of the main class, and the set
of initial arguments.
// server/ApplicationServer.java
package server;
import java.net.*;
import java.util.*;
import java.io.*;
/** A tiny "application server" */
public class ApplicationServer extends Loader {
/** this map stores application urls->active-threads */
final protected Map applications = new HashMap();
/** the deploy method interns an application
* @param url jar where the application is packed
* @param main fully-qualified name of Main class
* @param args arguments to main method
* @param parent parent classloader
* @throws ClassNotFoundException if application not found
* @return the classloader that has been generated for that app
*/
public synchronized ClassLoader deploy(final URL url,
String main, final String[] args, ClassLoader parent)
throws ClassNotFoundException {
System.out.println("Deploying "+url);
// is this a redeploy?
if(applications.containsKey(url)) // yes: tear it down first
undeploy(url);
// generate a new classloader
final ClassLoader loader = constructClassLoader(
new URL[] {url}, parent);
// load the mainclass
final Class mainClass = loader.loadClass(main);
// construct a new "main" thread
Thread newThread = new Thread() {
public void run() {
try{
// run the main method with the given args
Class[] params = {String[].class}; // args types
mainClass.getMethod("main", params).invoke(
mainClass, new Object[]{args});
// keep application alive until teared down
synchronized(this) { this.wait(); }
} catch(java.lang.reflect.InvocationTargetException e) {
e.getTargetException().printStackTrace();
} catch(Exception e) { // [hk hmmmmm]
} finally {
// we lock the appServer
synchronized(ApplicationServer.this) {
try{
// in case that any application error occurs
// (or the application is to be undeployed)
System.out.println("Stopping " + url);
// remove entry if still there
applications.values().remove(this);
// call cleanup method
mainClass.getMethod("stop", new Class[0]).invoke(
mainClass, new Object[0]);
} catch(Exception _e) {} // [hk hmmmmmmmmmmm]
} // synchronized(appServer.this)
} // finally
}}; // method run(); class Thread()
// set the thread context
newThread.setContextClassLoader(loader);
// register application x thread
applications.put(url, newThread);
// return classloader
return loader;
} // method deploy()
/** starts an application that has already been deployed */
public synchronized void start(URL url) {
System.out.println("Starting " + url);
((Thread)applications.get(url)).start();
}
/** Undeploys a running application. Never, I repeat, NEVER, do
* this using Thread.stop() but use the various options that
* are proposed by your JDK documentation to gracefully notify
* a thread of shutdown.
* @param url url where the application is packed
* @throws Exception if the app could not be teared down
*/
public synchronized void undeploy(URL url) {
// uh uh. bastard. But for Heinz newsletter, its ok ;-)
// [hk: gee thanks]
((Thread) applications.get(url)).stop(new Exception("stop"));
}
/** class loader factory method */
protected ClassLoader constructClassLoader(URL[] urls,
ClassLoader parent) {
return new URLClassLoader(urls,parent);
}
/** example usage of the appServer */
public static void main(String[] args) throws Exception {
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
ApplicationServer appServer = new ApplicationServer();
ClassLoader stockLoader = appServer.deploy(
getStockLocation(), "stock.Main",
new String[] {"screwdriver", "stock.WarehouseImpl",
"screwdriver", "200"},
null);
appServer.start(getStockLocation());
stdin.readLine();
appServer.deploy(getSalesLocation(),
"sales.Main",
new String[] {"screwdriver","50"},
stockLoader);
appServer.start(getSalesLocation());
stdin.readLine();
appServer.deploy(appServer.getSalesLocation(),
"sales.Main",
new String[] {"screwdriver","80"},
stockLoader);
appServer.start(appServer.getSalesLocation());
stdin.readLine();
appServer.undeploy(appServer.getSalesLocation());
appServer.undeploy(appServer.getStockLocation());
System.exit(0);
}
}
As example applications that are to be deployed by the presented
server logic, we sketch below an exemplary batch sales. Main
that constructs and processes a set of sales.Order. And we
introduce an exemplary stock application stock. Main that
exports remote warehouse services such as implemented by
stock.WarehouseImpl.
// sales/Main.java
package sales;
/** An example batch application */
public class Main {
/** starts the batch */
public static void main(String[] args) throws Exception {
// analyse command-line
for(int count=0; count<args.length; count++)
// construct order and reserve
new Order(args[count++],
new Integer(args[count]).intValue()).reserve();
}
}
// stock/WarehouseImpl.java
package stock;
import java.rmi.server.UnicastRemoteObject;
/** Example implementation of a remote service */
public class WarehouseImpl extends UnicastRemoteObject
implements Warehouse {
/** number of stored items */
protected int quantity;
/** constructs warehouse */
public WarehouseImpl(String itemName, int quantity)
throws java.rmi.RemoteException {
this.quantity=quantity;
}
/** reserves items*/
public void reserve(int amount) {
System.out.println(this + " is about to reserve " +
amount + " items.");
if(quantity < amount) empty(amount - quantity);
quantity -= amount;
}
/** what to do if the warehouse is empty */
protected void empty(int underLoad) {
throw new IllegalArgumentException("warehouse empty");
}
}
// stock/Main.java
package stock;
import java.rmi.*;
import java.util.*;
/**
* Example service-publishing application
*/
public class Main {
/** the services provided by this application */
protected static Collection services = new Vector();
/** create and export services */
public static void main(String[] args) throws Exception {
System.setSecurityManager(new RMISecurityManager());
for(int count=0; count<args.length; count++) {
services.add(args[count]);
// use context classloader to resolve class names
Naming.rebind("//localhost/"+args[count++],
(Remote)Thread.currentThread().getContextClassLoader().
loadClass(args[count++]).getConstructor(
new Class[] {String.class,int.class}).newInstance(
new Object[] {args[count++],
new Integer(args[count])}));
// [hk: spend some minutes trying to understand the above
// 6 lines - if you do manage to understand you should
// probably be writing C code?]
}
}
/** tearDown services means unPublish */
public static void stop() {
Iterator allServices=services.iterator();
while(allServices.hasNext()) {
try{
Naming.unbind("//localhost/"+allServices.next());
} catch(Exception e) {} // [HK: HMMMMMMM]
}
}
}
If you follow the steps below, you should see these two
applications interoperate via a dynamically created classloader
hierarchy that is quite similar to the previous example (the
RMI-codebase property is required to enable dynamic classloading
through rmiregistry; it's value does not matter).
javac -d classes\stock\ stock\*.java
rmic -d classes\stock\ stock.WarehouseImpl
javac -classpath classes\stock\ -d classes\sales\ sales\*.java
javac -d classes\server\ server\*.java
start rmiregistry
pause // until rmiregistry is up
java -Djava.security.policy=server.policy
-Djava.rmi.server.codebase=infor -classpath classes\server
server.ApplicationServer
Pressing <Enter> for the first time outputs:
Deploying file:/J:/misc/deployer/classes/stock/
Starting file:/J:/misc/deployer/classes/stock/
Deploying file:/J:/misc/deployer/classes/sales/
Starting file:/J:/misc/deployer/classes/sales/
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
stock.WarehouseImpl[RemoteStub [ref: [endpoint:[192.168.202.184:2053]
(local),objID:[0]]]] is about to reserve 5 items.
... etc.
We can now experience how flexible our application server has
become. After the first sales batch has been executed, please
set the static "bunch" variable in sales.Order to "10". Recompile
the class and press <Enter> for the second time just to see the
changed byte-code running in place of the outdated sales
representations. That we could realise this behaviour without
having to cycle java.exe, even without affecting the referred
stock logic in memory, is called "hot-redeploy" - a very useful
feature when it comes to incrementally debug server-side logic or
to update customer sites on the fly.
Deploying file:/J:/misc/deployer/classes/sales/
Stopping file:/J:/misc/deployer/classes/sales/
Starting file:/J:/misc/deployer/classes/sales/
stock.WarehouseImpl[RemoteStub [ref:
[endpoint:[192.168.202.184:2053](local),objID:[0]]]] is
about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2053](local),objID:[0]]]]
is about to reserve 10 items.
-- Warning --
The deprecated Thread.stop() method used in the unDeploy()
implementation has allowed me to write concise "newsletter code".
Never, I repeat, NEVER use Thread.stop() because it is inherently
unsafe: In our example, there is indeed the small opportunity
that handling of an application error (an InvocationTargetException
reported through reflecting the main method) has not yet obtained
the lock to the ApplicationServer.this instance and a simultaneous
unDeploy(...) will implant a shutdown exception on top of the
cleanup logic. Please, consult your JDK documentation for safe
ways to shutdown your threads! [hk: this actually happened once
when I ran the tests!]
A second side note: With JDK/JRE 1.3, the apparent relationship
between threads and classloaders has been made explicit by
associating any thread with a so-called "context classloader"
(Thread.setContextClassLoader(ClassLoader loader);
Thread.getContextClassLoader()). They are however still unrelated
according to 99% of the JDK code which still uses
Class.forName(String name) - an awful static method that
delegates to the "calling class" defining classloader. The
correct way of loading classes in the light of our new knowledge
should be always (see ApplicationServer.deploy() and
stock.Main.main()):
Thread.currentThread().getContextClassLoader().loadClass(name)
Alternative Classloading Delegation
===================================
There is an unwritten law for middleware engineers that no matter
how sophisticated your framework (and with exception of the
Thread.stop(), we can be really proud of our ApplicationServer,
couldn't we?), after two month of releasing the system basis, the
application developers will cooperatively abuse it in ways that
you have never imagined before.
Be sure that this st**pid stock team will have (as pressed by the
product managers and ... sic! ... customers with ... sicut! ...
wishes) compiled another, undoubtfully useful, but technically
devastating warehouse implementation that is able to delegate
unsatisfied reservation calls to other (vendor) warehouses:
// DelegatingWarehouseImpl.java
package stock;
import sales.Order;
/** Example service that links against sales.Order and introduces
* a mutual dependency */
public class DelegatingWarehouseImpl extends WarehouseImpl {
/** name of the item at our vendor */
protected String vendorItem;
/** construct a new delegating warehouse */
public DelegatingWarehouseImpl(String vendorItem, int quantity)
throws java.rmi.RemoteException {
super(vendorItem, quantity);
this.vendorItem = vendorItem;
}
/** Overrides the underCapacity reaction to order at vendor */
protected void empty(int underLoad) {
try {
new Order(vendorItem, underLoad).reserve();
quantity += underLoad;
} catch(Exception ex) {
throw new IllegalArgumentException(
"Could not place order " + ex);
}
}
}
javac -classpath classes\sales\;classes\stock\
-d classes\stock\ stock\DelegatingWarehouseImpl.java
Guess what ... they will phone you and tell you that there is an
ugly NoClassDefFoundError: sales.Order thrown when trying to
deploy and run the freshly compiled stock (with a voice
indicating that this could not be much of a problem for these
Java cracks in the tech department, knowhaddimean, nudge,
nudge?).
Boom! Your whole nice server-side architecture collapses as SUN
does not allow you to have the salesLoader (hosting
DelegatingWarehouseImpl and the Warehouse that is needed by Order)
both as parent AND child of the stockLoader (hosting Order that
is needed by DelegatingWarehouseImpl). They would not call them
children and parent, otherwise, would they?
Your options are now (leaving the Java(tm) platform is not an
option, stupid! [hk: why actually not?!?]):
Yelling: "This is not the right project structure! We must
extract a more abstract masterdata. Order that is deployed in a
separate application and that is used by both sales and stock."
But, with a tree-like application structure that follows the SUN
classloader hierarchy, this cannot be done ad infinitum. You will
most likely end up having most classes in masterdata and under
the responsibility of a single person (which in any middle-sized
company, sits on the same floor as the system basis programmer,
hence this would be not a good idea).
Crying: "Forget classloader delegation! We deploy everything
into a single URLClassloader by exposing the protected addURL(URL
url) method." But then, you loose the hot-deploy feature, again!
In order to exchange a tiny class in a tiny module in the server,
you will have to tear down and restart the whole logic. Now try
to sell your customer these large extra coffee breaks at
maintenance time!
Thinking: You sit down silently and release a new server.
SmartApplicationServer which implements classloader delegation a
bit differently to SUN without your colleagues even noticing
except that they will no more detect any NoClassDefFoundErrors
when implementing the next of their great ideas while you are
having that large extra coffee break, a Cohiba Siglo V [hk: I'd
opt for a Partagas Series D] and the new Linux Magazine:
// SmartApplicationServer.java
package server;
import java.io.*;
import java.net.*;
import java.util.*;
/** An "application server" that copes with mutual application
* dependencies */
public class SmartApplicationServer extends ApplicationServer {
/** A classloader extension that is able to delegate to other
* application�s classloaders */
protected class SmartClassLoader extends URLClassLoader {
/** mirrors parent constructor */
public SmartClassLoader(URL[] urls, ClassLoader parent) {
super(urls,parent);
}
/** dispatch "normal" loadClass method to another name */
protected Class loadClassNormal(String name, boolean resolve)
throws ClassNotFoundException {
return super.loadClass(name,resolve);
}
/** override "normal" loadClass method in order to delegate */
protected Class loadClass(String name, boolean resolve)
throws ClassNotFoundException {
try{
// first we try it traditionally
return loadClassNormal(name,resolve);
} catch(ClassNotFoundException e) {
// if this doesnt help, we ask the other application
// threads for help
synchronized(SmartApplicationServer.this) {
Iterator allThreads = applications.values().iterator();
while(allThreads.hasNext()) {
SmartClassLoader nextLoader = (SmartClassLoader)
((Thread)allThreads.next()).getContextClassLoader();
if(nextLoader!=null && !nextLoader.equals(this)) {
try{
// try the context class loader of next thread
return nextLoader.loadClassNormal(name,resolve);
} catch(ClassNotFoundException _e) {
} // [hk: HMMHMHMHMHMM]
}
}
// they could not help us, hence we throw an exception
throw new ClassNotFoundException(
"class could not be found amongst applications.");
} // synchronized(SmartApplicationServer.this)
} // catch
} // method loadClass()
} // class
/** override parents factory method to construct dedicated
* classloaders */
public ClassLoader constructClassLoader(URL[] urls,
ClassLoader parent) {
return new SmartClassLoader(urls,parent);
}
/** example of the improved application server */
public static void main(String[] args) throws Exception {
BufferedReader stdin = new BufferedReader(
new InputStreamReader(System.in));
ApplicationServer appServer=new SmartApplicationServer();
appServer.deploy(getStockLocation(), "stock.Main",
new String[] {"screwdriver", "stock.DelegatingWarehouseImpl",
"vendorScrewer", "20", "vendorScrewer", "stock.WarehouseImpl",
"vendorScrewer","200"},null);
appServer.deploy(getSalesLocation(), "sales.Main",
new String[] {"screwdriver","50"}, null);
appServer.start(getStockLocation());
stdin.readLine();
appServer.start(getSalesLocation());
stdin.readLine();
appServer.undeploy(getSalesLocation());
appServer.undeploy(getStockLocation());
System.exit(0);
}
}
javac -d classes\server\ server\*.java
java -Djava.security.policy=server.policy
-Djava.rmi.server.codebase=infor -classpath classes\server
server.SmartApplicationServer
Let me elaborate a bit on the steps that we need to take to
realise this third, comfortably sounding scenario. The key
ingredient is the introduction of a
SmartApplication.SmartClassLoader inner class derived from
java.net.URLClassLoader that is intimately coupled to the
SmartApplicationServer, more specifically, to its "applications"
map that stores the running deployments.
In java.lang.ClassLoader, loadClass(String name) is fixedly and
simply implemented to call loadClass(name,false) whose task is to
not only to locate and intern the class, but also to immediately
resolve linked classes if the second boolean parameter is set to
true. We can now easily implement a different delegation semantics
by overriding the second method:
First we try to call the "standard" classloading semantics which
we have re-exposed as loadClassNormal(String name, boolean resolve).
If this succeeds we return the found class (at the minimum
"overhead" of a try/catch block).
Else (in the above example in which we have removed any
classloading hierarchy, this happens both if sales.Order tries to
link stock.Warehouse as well as if stock.WarehouseImpl tries to
link sales.Order) we catch the ClassNotFoundException and iterate
through all other deployed applications to also try
loadClassNormal(...) through their main thread's context
classloader until the right resource has been found.
Only if the class could not be found in all deployed applications,
we throw the final ClassNotFoundException.
By these "minimally invasive" extensions, we finally produce the
desired output:
Deploying file:/J:/misc/deployer/classes/stock/
Deploying file:/J:/misc/deployer/classes/sales/
Starting file:/J:/misc/deployer/classes/stock/
Starting file:/J:/misc/deployer/classes/sales/
stock.DelegatingWarehouseImpl[RemoteStub [ref:
[endpoint:[192.168.202.184:2269](local),objID:[0]]]]
is about to reserve 10 items.
stock.DelegatingWarehouseImpl[RemoteStub [ref:
[endpoint:[192.168.202.184:2269](local),objID:[0]]]] is about
to reserve 10 items.
stock.DelegatingWarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2269](local),objID:[0]]]] is
about to reserve 10 items.
stock.WarehouseImpl[RemoteStub [ref:
[endpoint:[192.168.202.184:2269](local),objID:[1]]]] is about
to reserve 10 items.
stock.DelegatingWarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2269](local),objID:[0]]]] is
about to reserve 10 items.
stock.WarehouseImpl[RemoteStub [ref:
[endpoint:[192.168.202.184:2269](local),objID:[1]]]] is about
to reserve 10 items.
stock.DelegatingWarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2269](local),objID:[0]]]]
is about to reserve 10 items.
stock.WarehouseImpl[RemoteStub
[ref: [endpoint:[192.168.202.184:2269](local),objID:[1]]]]
is about to reserve 10 items.
Stopping file:/J:/misc/deployer/classes/sales/
Stopping file:/J:/misc/deployer/classes/stock/
Ideally, this has been just the beginning of a wonderful
exploration into the world of "how can I tweak these suboptimal
JDK-classes to fit my practical needs". For example, you will
most likely ask how the hell an application programmer or even a
customer deployer should be able to manage all those dependencies
between applications (usually: jar-files, such as stock.jar and
sales.jar) once your system has evolved to a particular size.
The solution to this is quite straightforward: Jar
dependencies are usually encoded in the Manifest.mf class-path
entry and that is where your application server can obtain
them in order to identify deployment needs and deployment
order.
Similarly, the loadClass calls delegated from one application to
another (in a full-blown implementation, also calls to
getResource(...) must be delegated) introduce runtime
dependencies: If you cycle the byte code of the "defining
application" you must also cycle the dependent byte-code of the
consuming application. I leave this (together with caching of
class locations) as an exercise for you.
Alternatively, if you are a bit lazy, nevertheless curious, i.e.,
your are a system programmer, you can have a look at the latest
org.jboss.deployment.scope.* sources of the JBoss Open Source
application server (http://www.jboss.org) whose ingenious
mastermind, Marc Fleury, has been the originator of the presented
classloading semantics. Besides clustering support, the goal for
the next major release of Jboss (3.0 - Project Rabbit Hole) is to
have a deployer that can manage multiple scopes, i.e., "virtual
applications" such as we have today constructed out of stock.*
and sales.*
He, he!
[hk: hihi]
--------------------
Thanks for taking the time to read this newsletter, please also
take the time to give us feedback, we always appreciate it.
Heinz
--
Dr. Heinz M. Kabutz
Maximum Solutions - The Java(tm) Specialists
mailto:[EMAIL PROTECTED]
tel:+27 (083) 340-5633
-----------------------------------------------------------------
To join the newsletter, please click on:
mailto:[EMAIL PROTECTED]?subject=SubscribeJavaSpecialists22
To unsubscribe from the newsletter, please click on:
mailto:[EMAIL PROTECTED]?subject=UnsubscribeJavaSpecialists22
To get a zipped archive of past issues, please click on:
mailto:[EMAIL PROTECTED]?subject=SendArchive
_______________________________________________
Jboss-development mailing list
[EMAIL PROTECTED]
http://lists.sourceforge.net/lists/listinfo/jboss-development