How can I make a browser execute a part of code before loading a jar? I want to make a splashscreen for a big applet that is contained in a jar, but the jars are loaded before the splashimage is displayed.
Created May 4, 2012
When you ask a java runtime to load a class (e.g. by giving it as a command-line argument to the java executable, or by loading a webpage containing an applet tag) the runtime tries to load that named class (e.g. "foo.class"). In general (as in this case), this can only complete once all the classes that foo depends on are also loaded. A class is dependant on any other class that:
- is a superclass of it.
- is an interface that it implements, or a superinterface of any such interface.
- that it explicitly references.
So the reason that a big applet isn't able to do anything
(like painting a "loading..." message) quickly is because the
browser has to wait until all of the classes of the applet
are downloaded.
In order to have a "splashscreen" load quickly, and have it
run before (and during) the loading of the rest of the applet,
we have to break the explicit dependancy between the initial
class and the other classes in the applet. The following example
shows how to do this, and in the process shows off some of the
amazing things you can do with the powerful java Classloader
class.
The example shows a tiny "loader" applet (initial.java) which
serves to purposes:
And here's second.java, the source for our simple example second-phase:
You'll see from initial that it loads the subsequent code
in a new thread - this is because Class.forName()
blocks - if we didn't spawn a new thread then init()
wouldn't terminate until second was loaded - and initial
can't paint until its init() method exits.
Here's the HTML code for loading the applet:
Notice that CODE points just to initial.class,
not to subsequent classes like second.class - but the
applet classloader will still need to be able to load second.class
later, so it must be accessible, i.e. it must be in a place
specified by the CODEBASE parameter.
Now, ideally we'd have many classes in the second part,
and the logical place to keep them would be a JAR file, but
we can't name that JAR file in the applet's ARCHIVE parameter,
as the applet environment always loads all the JARs mentioned
in the ARCHIVE parameter, even if they don't appear to be
referenced initially. Thus we're stuck loading individual
classfiles across the network.
The clever bit is that the loader doesn't explicitly reference
the rest of the applet (in this case "second.java"), to the
compiler doesn't pick this up as a dependancy - as far as it
it concerned, initial is a freestanding class. Instead,
initial references second programmatically, using the
forName() method of class Class. Here's initial.java:
import java.awt.*;
import java.applet.Applet;
public class initial extends Applet implements Runnable {
private String classToLoad;
private void fetchClass(String className) {
classToLoad = className;
Thread t = new Thread(this);
t.start();
}
public void run() {
try {
Class mainClass = Class.forName(classToLoad);
Component mainInstance =
(Component)mainClass.newInstance();
add(mainInstance);
}
catch (Exception e) {
e.printStackTrace();
}
invalidate();
validate();
}
/**
* Called when the applet is initialised -
* here we start the asynchronous load of the rest of
* the applet.
*/
public void init() {
setLayout(new BorderLayout());
fetchClass("second");
}
/**
* Display a simple message
*/
public void paint(Graphics g) {
g.drawString("loading...",30,30);
}
}
import java.awt.*;
public class second extends Canvas {
public void paint(Graphics g){
Dimension d = getSize();
g.setColor(Color.green);
g.fillRect(0,0,d.width, d.height);
}
}
<h1>multi-stage applet<h1>
<applet code=initial.class width=300 height=300>
</applet>