WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips,
May 30, 2000. This issue celebrates the public release of
the JavaTM 2 Platform, Standard Edition (J2SE) v1.3
for Windows platforms. (J2SE v1.3 for Solaris and Linux
platforms will be available soon.) Today's tips cover two
features that are new in J2SE v1.3: dynamic proxies and timer
classes. These features are discussed below in:
This issue of the JDC Tech Tips is written by Stuart Halloway,
a Java specialist at DevelopMentor.
USING DYNAMIC PROXIES TO LAYER NEW FUNCTIONALITY OVER EXISTING CODE
Dynamic proxies allow you to implement new interfaces at runtime
by forwarding all calls to an InvocationHandler
. This tip shows
you how to use dynamic proxies to add new capabilities without
modifying existing code.
Consider the following program. The program includes an interface
named Explorer
. The interface models the movement of an "explorer"
around a Cartesian grid. The explorer can travel in any compass
direction, and can report its current location. The class
ExplorerImpl
is a simple implementation of the Explorer
interface.
It uses two integer values to track the explorer's progress around
the grid. The TestExplorer
class sends the explorer on 100 random
steps, and then logs the explorer's position.
import java.lang.reflect.Method;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
interface Explorer {
public int getX();
public int getY();
public void goNorth();
public void goSouth();
public void goEast();
public void goWest();
}
class ExplorerImpl implements Explorer {
private int x;
private int y;
public int getX() {return x;}
public int getY() {return y;}
public void goNorth() {y++;}
public void goSouth() {y--;}
public void goEast() {x++;}
public void goWest() {x--;}
}
public class TestExplorer {
public static void test(Explorer e) {
for (int n=0; n<100; n++) {
switch ((int)(Math.random(
) * 4)) {
case 0: e.goNorth(); break;
case 1: e.goSouth(); break;
case 2: e.goEast(); break;
case 3: e.goWest(); break;
}
}
System.out.println("Explorer ended at "
+ e.getX() + "," +
e.getY());
}
public static void main(String[] args) {
Explorer e = new ExplorerImpl();
test(e);
}
}
Try running the TestExplorer class. You should get one line of
output, similar to this:
Explorer ended at -2,8
Now, imagine that the requirements for the system change, and you
need to log the explorer's movement at each step. Because the
client programmed against an interface, this is straightforward;
you could simply create a LoggedExplorer
wrapper class that logs
each method call before delegating to the original Explorer
implementation. This is a nice solution because it does not require
any changes to ExplorerImpl
. Here's the new LoggingExplorer
wrapper
class:
class LoggingExplorer implements Explorer {
Explorer realExplorer;
public LoggingExplorer(Explorer realExplorer) {
this.realExplorer = realExplorer;
}
public int getX() {
return realExplorer.getX();
}
public int getY() {
return realExplorer.getY();
}
public void goNorth() {
System.out.println("goNorth");
realExplorer.goNorth();
}
public void goSouth() {
System.out.println("goSouth");
realExplorer.goSouth();
}
public void goEast() {
System.out.println("goEast");
realExplorer.goEast();
}
public void goWest() {
System.out.println("goWest");
realExplorer.goWest();
}
}
The LoggingExplorer
class delegates to an underlying realExplorer
interface, which allows you to add logging to any existing Explorer
implementation. The only change clients of the Explorer
interface
need to make is to construct the LoggingExplorer
so that it wraps
the Explorer
interface. To do this, modify TestExplorer
's main
method as follows:
public static void main(String[] args) {
Explorer real = new ExplorerImpl();
Explorer wrapper = new LoggingExplorer(real);
test(wrapper);
}
Now your output should be similar to
goWest
goNorth
...several of these...
goWest
goNorth
Explorer ended at 2,2
By delegating to an underlying interface, you added a new layer of
function without changing the ExplorerImpl
code. And you did it
with only a trivial change to the test client.
The LoggingExplorer
wrapper class is a good start to using
delegation, but this "by-hand" approach has two major drawbacks.
First, it's tedious. Each individual method of the Explorer
interface must be reimplemented in the LoggingExplorer
implementation. The second drawback is that the problem (that is,
logging) is generic, but the solution is not. If you want to log
some other interface, you need to write a separate wrapper class.
The Dynamic Proxy Class API can solve both of these problems.
A dynamic proxy is a special class created at runtime by the
JavaTM virtual machine*. You can request a proxy class that
implements any interface, or even a group of interfaces, by
calling:
Proxy.newProxyInstance(ClassLoader
classLoaderToUse,
Class[] interfacesToImplement,
InvocationHandler objToDelegateTo)
The JVM manufactures a new class that implements the interfaces you
request, forwarding all calls to InvocationHandler
's single method:
public Object invoke(Object proxy,
Method meth, Object[] args)
throws Throwable;
All you have to do is implement the invoke method in a class that
implements the InvocationHandler
interface. The proxy class then
forwards all calls to you.
Let's make this work for the Explorer
interface. Replace the
LoggingExplorer
wrapper class with the following Logger
class.
class Logger implements InvocationHandler {
private Object delegate;
public Logger(Object o) {
delegate = o;
}
public Object invoke(Object proxy,
Method meth, Object[] args)
throws Throwable {
System.out.println(meth.getName());
return meth.invoke(delegate, args);
}
}
This implementation of the invoke method can log any method call on
any interface. It uses reflective invocation on the Method
object
to delegate to the real object.
Now modify the TestExplorer
main method as follows to create
a dynamic proxy class:
public static void main(String[] args) {
Explorer real = new ExplorerImpl();
Explorer wrapper = (Explorer)
Proxy.newProxyInstance(
Thread.currentThread(
).getContextClassLoader(),
new Class[] {Explorer.class},
new Logger(real));
test(wrapper);
}
The static method Proxy.newProxyInstance
creates a new proxy
that implements the array of interfaces passed as its second
parameter. In this example, the proxy implements the Explorer
interface. All invocations of Explorer
methods are then handed off
to the InvocationHandler that is passed as the third parameter.
Try running the updated code. You should see that each step of the
Explorer
is logged to System.out
.
The dynamic proxy class solves both of the problems of the
"by-hand" approach. There is no tedious copying and pasting of
methods because invoke
can handle all methods. Also, the logger
presented here can be used to log calls to any interface in the
JavaTM language. Try inserting some loggers in your own code to
trace program flow.
Notice that the logging operation is method generic, that is,
logging does not require any decision making based on the
specifics of the method being called. Dynamic proxies are at their
best when adding method-generic services. Logging is one area
where dynamic proxies can be used to advantage; others include
generic stubs for RMI, automatic parameter validation, transaction
enlistment, authentication and access control, and rule-based
parameter, modification, and error handling.
Dynamic proxies, like all reflective code, are somewhat slower than
"normal" code. In many situations this performance difference is
not crucial. If you want to evaluate the performance of dynamic
proxies for delegation, download the benchmarking code and execute the
TimeMethodInvoke.cmd
script. This script measures times for various
styles of method invocation in the Java programming language.
For more info on dynamic proxies, see Dynamic Proxy Classes.
USING TIMERS TO RUN RECURRING OR FUTURE TASKS ON A BACKGROUND THREAD
Many applications need to schedule tasks for future execution, or
to schedule them to recur at a regular interval. J2SE v1.3 meets
this need with the addition of two Timer
classes: java.util.Timer
and java.util.TimerTask
. This tip demonstrates various scheduling
strategies for using these Timer
classes. The tip also shows you
how to handle poorly-behaved tasks, that is, tasks that run too
long or that crash.
The java.util.Timer
and java.util.TimerTask
classes are simple to
use. As with many things threaded, the TimerTask
class implements
the Runnable
interface. To use the class, simply write a subclass
with a run method that does the work; then plug the subclass into
a Timer
instance. Here's an example:
import java.util.*;
import java.io.*;
public class TestTimers {
public static void doMain()
throws Exception {
Timer t = new Timer(true);
t.schedule(new Ping("Fixed delay")
, 0, 1000);
Thread.currentThread().sleep(12000);
}
public static void main(String[] args) {
try {
doMain();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
class Ping extends TimerTask {
private String name;
public Ping(String name) {
this.name = name;
}
public void run() {
System.out.println(name + " Ping at
" + new Date());
}
}
The class TestTimers
creates a Timer. By passing the Timer the
boolean value true, TestTimers
forces the Timer to use a daemon
thread. The main thread then sleeps, allowing you see the Timer
at work. However you never actually see any thread classes or
instances; those details are encapsulated by the Timer
class.
In the statement,
t.schedule(new Ping("Fixed delay"), 0, 1000);
the parameters to the schedule method cause a Ping
object's run
method to be invoked after an initial delay of 0 milliseconds;
the method is repeatedly invoked every 1000 milliseconds. Ping
's
run method logs output to System.out
. (In your own applications,
you would use the run method to do something more interesting.)
If you run TestTimers
, you will see output similar to this:
Fixed delay ping at Thu May 18 14:18:56 EDT 2000
Fixed delay ping at Thu May 18 14:18:57 EDT 2000
Fixed delay ping at Thu May 18 14:18:58 EDT 2000
Fixed delay ping at Thu May 18 14:18:59 EDT 2000
Fixed delay ping at Thu May 18 14:19:00 EDT 2000
Fixed delay ping at Thu May 18 14:19:01 EDT 2000
Fixed delay ping at Thu May 18 14:19:02 EDT 2000
Fixed delay ping at Thu May 18 14:19:03 EDT 2000
Fixed delay ping at Thu May 18 14:19:04 EDT 2000
Fixed delay ping at Thu May 18 14:19:05 EDT 2000
Fixed delay ping at Thu May 18 14:19:06 EDT 2000
Fixed delay ping at Thu May 18 14:19:07 EDT 2000
Fixed delay ping at Thu May 18 14:19:08 EDT 2000
The output confirms that Ping
is running about once per second,
exactly as requested. Better still, the Timer can handle multiple
TimerTasks
, each with different start times and repeat periods.
This leads to an interesting question: If a TimerTask
takes a very
long time to complete, will other tasks in the list be thrown off?
To answer this question, you need to understand how the Timer uses
threads. Each Timer instance has a single dedicated thread that
all the TimerTasks
share. So, if one task takes a long time, all
the other tasks wait for it to complete. Consider this long-running
task:
class PainstakinglySlowTask
extends TimerTask {
public void run() {
//simulate some very
//slow activity by sleeping
try {
Thread.currentThread(
).sleep(6000);
System.out.println("Painstaking
task ran at " + new Date());
}
catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
The PainstakinglySlowTask
class sleeps for six full seconds. It
prevents any other tasks from executing during that time. What
happens if you add the painstakingly slow task to TestTimers
?
Let's see.
public static void doMain()
throws Exception {
Timer t = new Timer(true);
t.schedule(new Ping("Fixed delay"),
0, 1000);
t.schedule(new PainstakinglySlowTask(),
2000);
Thread.currentThread().sleep(12000);
}
If you recompile and run TestTimers
, you will see output like this:
Fixed delay Ping at Thu May 18 15:41:33 EDT 2000
Fixed delay Ping at Thu May 18 15:41:34 EDT 2000
Fixed delay Ping at Thu May 18 15:41:35 EDT 2000
Painstaking task ran at Thu May 18 15:41:41 EDT 2000
Fixed delay Ping at Thu May 18 15:41:41 EDT 2000
Fixed delay Ping at Thu May 18 15:41:42 EDT 2000
Fixed delay Ping at Thu May 18 15:41:43 EDT 2000
Fixed delay Ping at Thu May 18 15:41:44 EDT 2000
Fixed delay Ping at Thu May 18 15:41:45 EDT 2000
During the time that PainstakinglySlowTask
runs (from 15:41:35
to 15:41:41), no pings occur. This is what is meant by a "fixed
delay". The Timer tries to make the delay between pings as
precise as possible, even if that means that some pings are lost
during the running time of another, long-running task.
A scheduling alternative is "fixed rate." With fixed rate
scheduling, the Timer tries to make the processing rate as
accurate as possible over time. So, if one task runs for a long
time, other tasks can instantaneously run several times in order
to catch up. You can specify fixed rate scheduling by using the
scheduleAtFixedRate
method:
//commented out the fixed delay version
//t.schedule(new Ping("Fixed delay"), 0, 1000);
t.scheduleAtFixedRate(new Ping("Fixed rate"), 0, 1000);
t.schedule(new PainstakinglySlowTask(), 2000);
If you run TestTimers
with a fixed rate ping, you should
see output like this:
Fixed rate Ping at Thu May 18 15:48:33 EDT 2000
Fixed rate Ping at Thu May 18 15:48:34 EDT 2000
Fixed rate Ping at Thu May 18 15:48:35 EDT 2000
Painstaking task ran at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:41 EDT 2000
Fixed rate Ping at Thu May 18 15:48:42 EDT 2000
Fixed rate Ping at Thu May 18 15:48:43 EDT 2000
Fixed rate Ping at Thu May 18 15:48:44 EDT 2000
Fixed rate Ping at Thu May 18 15:48:45 EDT 2000
This time, several Pings run right after PainstakinglySlowTask
finishes; the Pings all run at 15:48:41. This keeps the rate of
Pings as close as possible to the desired 1000 msec average. The
price paid is occasionally having Pings run at approximately the
same time.
Both fixed-rate and fixed-delay scheduling have their uses.
However, neither totally eliminates the interference caused by
long-running tasks. If you have different tasks that might run
for a very long time, you might want to minimize the interference
between the tasks. This is especially true if you need to take
advantage of multiple CPUs. A single Timer provides no obvious
way to do this. You cannot control the Timer
thread, because it
is encapsulated as a private field of the Timer
class. Instead,
you can create multiple Timers, or have one Timer call notify()
and have other threads do the actual work.
Tasks that throw exceptions pose more of a problem than
long-running tasks. Here's an example. Replace the
PainstakinglySlowTask
class with the following CrashingTask
class:
class CrashingTask extends TimerTask {
public void run() {
throw new Error("CrashingTask");
}
}
//new version of TestTimers
public static void doMain()
throws Exception {
Timer t = new Timer(true);
t.scheduleAtFixedRate(new Ping(
"Fixed rate"), 0, 1000);
t.schedule(new CrashingTask(),
5000, 1000);
Thread.currentThread().sleep(12000);
}
If you run TestTimers
with CrashingTask
, you should see output
that looks something like this:
Fixed rate Ping at Thu May 18 15:58:53 EDT 2000
Fixed rate Ping at Thu May 18 15:58:54 EDT 2000
Fixed rate Ping at Thu May 18 15:58:55 EDT 2000
Fixed rate Ping at Thu May 18 15:58:56 EDT 2000
Fixed rate Ping at Thu May 18 15:58:57 EDT 2000
Fixed rate Ping at Thu May 18 15:58:58 EDT 2000
java.lang.Error: CrashingTask
at CrashingTask.run(TestTimers.java:37)
at java.util.TimerThread.mainLoop(Timer.java:435)
at java.util.TimerThread.run(Timer.java:385)
After CrashingTask
throws an exception, it never runs again. This
should come as no surprise. What may surprise you is that no other
task on the same Timer will run again, either. A wayward
exception will cancel the Timer, causing any future attempt to
schedule a task to throw an exception. However, there is no
mechanism to notify your existing tasks that they have been
brutally de-scheduled. It is up to you to make sure that errant
TimerTasks
do not destroy your Timers. One strategy is to guarantee
that your TimerTasks
never throw exceptions back into the Timer.
You can do this by enclosing the TimerTasks
in a try block that
catches the exception. If you need to be notified of the exception,
you can create a simple mechanism to notify the program that
a failure occurred. Here's an example:
import java.util.*;
import java.io.*;
interface ExceptionListener {
public void exceptionOccurred(
Throwable t);
}
class ExceptionLogger
implements ExceptionListener {
public void exceptionOccurred(
Throwable t) {
System.err.println("Exception
on Timer thread!");
t.printStackTrace();
}
}
public class TestTimers {
public static void doMain()
throws Exception {
Timer t = new Timer(true);
//t.schedule(new Ping("Fixed
delay"), 0, 1000);
t.scheduleAtFixedRate(new Ping(
"Fixed rate"), 0, 1000);
t.schedule(new CrashingTask(new
ExceptionLogger()), 5000, 5000);
//t.schedule(new
//PainstakinglySlowTask(), 2000);
Thread.currentThread().sleep(12000);
}
public static void main(String[] args) {
try {
doMain();
}
catch (Exception e) {
e.printStackTrace();
}
}
}
class Ping extends TimerTask {
private String name;
public Ping(String name) {
this.name = name;
}
public void run() {
System.out.println(name + "
Ping at " + new Date());
}
}
class CrashingTask extends TimerTask {
ExceptionListener el;
public CrashingTask(ExceptionListener
el) {
this.el = el;
}
public void run() {
try {
throw new Error("CrashingTask");
}
catch (Throwable t) {
cancel();
el.exceptionOccurred(t);
}
}
}
This code is very similar to the previous version, except that this
time CrashingTask
's run method never propagates exceptions of any
type. Instead, it uses a catch block to catch all Throwables
and
then uses a callback interface to report the exception. Here's the
output:
Fixed rate Ping at Thu May 18 16:41:03 EDT 2000
Fixed rate Ping at Thu May 18 16:41:04 EDT 2000
Fixed rate Ping at Thu May 18 16:41:05 EDT 2000
Fixed rate Ping at Thu May 18 16:41:06 EDT 2000
Fixed rate Ping at Thu May 18 16:41:07 EDT 2000
Fixed rate Ping at Thu May 18 16:41:08 EDT 2000
Exception on Timer thread!
java.lang.Error: CrashingTask
at CrashingTask.run(TestTimers.java:54)
at java.util.TimerThread.mainLoop(Timer.java:435)
at java.util.TimerThread.run(Timer.java:385)
Fixed rate Ping at Thu May 18 16:41:09 EDT 2000
Fixed rate Ping at Thu May 18 16:41:10 EDT 2000
Fixed rate Ping at Thu May 18 16:41:11 EDT 2000
Fixed rate Ping at Thu May 18 16:41:12 EDT 2000
Fixed rate Ping at Thu May 18 16:41:13 EDT 2000
Fixed rate Ping at Thu May 18 16:41:14 EDT 2000
Fixed rate Ping at Thu May 18 16:41:15 EDT 2000
When CrashingTask
throws an exception, it calls cancel on itself
to remove itself from the Timer. It then logs the exception by
calling an implementation of the ExceptionListener
interface.
Because the exception never propagates back into the Timer thread,
the Pings continue to function even after CrashingTask
fails. In
a production system, a more robust implementation of
ExceptionListener
could take action to deal with the exception
instead of simply logging it.
There is another Timer
class in the Java Platform,
javax.swing.Timer
. Which Timer should you use? The Swing Timer is
designed for a very specific purpose. It does work on the AWT event
thread. Because much of the Swing package code must execute on the
AWT event thread, you should use the Swing Timer if you are
manipulating the user interface. For other tasks, use the
java.util.Timer
for its flexible scheduling.
For more info on the Timer classes, see
Class Timer.
The names on the JDCSM
mailing list
are used for internal Sun MicrosystemsTM
purposes only. To remove your name from the list, see
Subscribe/Unsubscribe
below.
Feedback
Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster
Subscribe/Unsubscribe
To unsubscribe from JDC email, go
to the following
address and enter the email address you wish to remove from
the mailing list:
http://developer.java.sun.com/unsubscribe.html
To become a JDC member and subscribe to this newsletter go to:
http://java.sun.com/jdc/