Abstract: In this newsletter, Dr Christoph Jung amazes us by showing how we can build a minimalist application server by playing around with class loaders.
Welcome to the 22nd issue of The Java(tm) Specialists' Newsletter, sponsored by infor business solutions AG, an ERP company based in Germany. 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 newsletters will go over your head. Don't give up!
javaspecialists.teachable.com: Please visit our new self-study course catalog to see how you can upskill your Java knowledge.
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.
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 ;-), 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 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 ;-)
As an educated audience 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 */ protected final 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) { } 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) {} } // 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 ;-) ((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])})); } } /** tearDown services means unPublish */ public static void stop() { Iterator allServices=services.iterator(); while(allServices.hasNext()) { try{ Naming.unbind("//localhost/"+allServices.next()); } catch(Exception e) {} } } }
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!
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)
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!):
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 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) { } } } // 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 (https://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!
Thanks for taking the time to read this newsletter, please also take the time to give us feedback, we always appreciate it.
Heinz
We are always happy to receive comments from our readers. Feel free to send me a comment via email or discuss the newsletter in our JavaSpecialists Slack Channel (Get an invite here)
We deliver relevant courses, by top Java developers to produce more resourceful and efficient programmers within their organisations.