In my previous post, I discussed five ways for tracing Java programs execution. Tracing execution can be done from a number of reasons. I focused on understanding the code. While it may be easy to read and understand a single method, understanding the whole flow may be a tough challenge. Since most design time tools fail to show a full end-to-end flow, tracing the program execution is the next best option.
I summarized my post with a conclusion that working with Aspects is the best option. I'm going to touch that in one of the next posts. In this post, I will introduce another approach: Dynamic Proxies. Although this method has its' limitations (see below), it is very easy to set-up and use.
What is a DynamicProxy?
DynamicProxy is a cool feature in Java. Most novice Java developers are not familiar with it. You can find the official tutorial here. I'll give a shorter one, focusing on tracing. The following diagram illustrates how it works:
- Step 1: Client invokes Method A, which is part of an interface.
- Step 2: The Dynamic Proxy intercepts the call, receives a call to a generic invoke method. This is the magic sauce.
- Step 3: The dynamic proxy invokes the original method using reflection, along with more logic, e.g. logging the call.
There are two supernatural phenomena here:
- You have a class which can take any shape - each instance of the class can decide, at runtime, which interfaces it will implement.
- When the instance receives a call through any of these interfaces, it will actually receive a call to one invoke method with all the relevant details: the method which was invoked and the arguments.
Other than the above, it's all plain Java code which you control.
There are some limitations to dynamic proxies, derived directly from the way they work:
- The invocations you're tracing must be part of an interface. If not, you'll need to extract the interface. Still, even if you do, it will only work for public methods invoked by outside callers.
- You should control the instantiation of the target class' instances. It should be intercepted and replaced with your own instances.
The Debug Proxy
Sun provides an example of tracing using dynamic proxy. I made some modifications to the code and I'm attaching my own version of a DebugProxy. The main limitation in the original code is that it proxies only the immediate interfaces implemented by the original class. If the class extends a super-class which implements an interface, this interface will not be "proxied" (is that a word?). I also made some more modifications:
- Used Apache commons logging to do the actual logging. The logging is done "on behalf" of the original class in trace level only.
- Logging all the method arguments using
- Correctly throwing exceptions: will throw the original exception that occurred.
- Using Java 5 features (Generics, for each loops, etc.).
You can download the file here: DebugProxy.java.
Using the Debug Proxy
First, download the DebugProxy and install it in your project. You may need to fix the package definition. If you're not using Commons Logging, you may want to change that as well.
Next, change the instantiation on your class. Here's the example:
MyInterface obj = new MyClass(...)
- New code:
MyInterface obj = (MyInterface)DebugProxy.newInstance(new MyClass(...))
That's it. You're all set. Just execute the program and check the results in the log. After the first time, you will have your proxy in place and the whole process becomes even simpler.
Using a DynamicProxy to trace runtime execution has several advantages:
- It's very simple to set up. It's laser-targeted at the class which is interesting at the moment, ignoring the rest of the system.
- You control the code, hence, you have the ultimate control on the way the events are logged. You have all the information and you can log it in any way you see fit. You can even set up a breakpoint inside your debug proxy and intercept all the calls.
- Not much overhead on the system at runtime.
Of course, there are the limitations:
- The intercepted calls must be made through an interface.
- You need to control the instantiation of the original class.
Overall, it's great for specific cases, like understanding which events were received by an Event Handler and when. In the general case, you'll need to find another solution.