Jakub is a Java EE developer since 2005 and occasionally a project manager, working currently with Iterate AS. He's highly interested in developer productivity (and tools like Maven and AOP/AspectJ), web frameworks, Java portals, testing and performance and works a lot with IBM technologies. A native to Czech Republic, he lives now in Oslo, Norway. Jakub is a DZone MVB and is not an employee of DZone and has posted 155 posts at DZone. You can read more from them at their website. View Full User Profile

Injecting Better Logging Into a Binary .class Using Javassist

05.23.2010
| 7941 views |
  • submit to reddit

Have you ever been struck by a completely useless exception message somewhere from the depths of a 3rd party application or library you had to use in your code? Have you ever wanted the bloody nameless programmer to have written a truly informative and helpful error message so that you wouldn’t need to spend hours trying to figure out what was the problem that would have been easily discovered if only more context information available at the moment when the exception occured was included in its error message? Have you wondered how only you could inject the necessary logging into the spot? Read on to get the answer.

Recently I was testing my extension to Saba, a monstrous J2EE learning management system, and got an exception from a Saba class saying that the expected and actual data types of a custom attribute don’t match.  The problem was that I had no idea which one of the 10s of custom attributes could be the cause and I even wasn’t sure which object’s attributes I should check. It would be so much easier if the "nameless bloody Saba programmer" (no offense :-) ) included the attribute’s name and preferably also its actual & expected data types and the actual and new values. How could I insert there logging of these properties? Needless to say that I had to use Java 1.4 (no agents…) and couldn’t afford more than modifying this single class file (i.e. no additional libraries etc.) because the changes I could do to the development environment where the application ran were limited.

Of course the easiest would have been to decomile the class, add the loging before the exception is thrown, recompile it and replace it on the server. But not only is decompiling illegal here, it also sometimes simply doesn’t work. Fortunatelly there is another solution – JBoss Javassist is a byte code manipulation library that supports not only runtime manipulation but also post-comilation time manipulation, i.e. you can modify and save the class and use it to replace the original file. There are quite a few byte code manipulation libraries but Javassist has the great advantage that you don’t need to know much about bytecode, you can simply pass a String with java statements that should be inserted before/after/… method call or into a new catch statement. There is a nice tutorial that describes it (see part 4.1, Inserting source text at the beginning/end of a method body):

addCatch() inserts a code fragment into a method body so that the code fragment is executed when the method body
throws an exception and the control returns to the caller. In the source text representing the inserted code fragment,
the exception value is referred to with the special variable $e.


For example, this program:

ClassPool pool = ClassPool.getDefault();
CtClass compiledClass = pool.get("mypackage.MyClass");
CtMethod m = compiledClass.getDeclaredMethod("myExceptionThrowingMethod");
CtClass etype = ClassPool.getDefault().get("java.io.IOException");
m.addCatch("{ System.out.println($e); throw $e; }", etype);

translates the method body represented by m into something like this:

try {
the original method body
}
catch (java.io.IOException e) {
System.out.println(e);
throw e;
}


Note that the inserted code fragment must end with a throw or return statement.

You can use $e to access the exception, $0 to access "this", $1 to access the 1st parameter of the method etc. At the end you just call compiledClass.writeFile();  and use the modified mypackage/MyClass.class to replace the original class in the application.

Lovely, isn’t it?

 

From http://theholyjava.wordpress.com/2008/10/02/injecting-better-logging-into-a-bi/

Published at DZone with permission of Jakub Holý, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Tags:

Comments

Jose Maria Arranz replied on Mon, 2010/05/24 - 2:13am

I agree Javassist is a very nice tool to do this kind of low level AOP magic, some years ago I was using it to provide "automatic transparent native sychronization" to POJOs like C++ objects to JNIEasy, basically when you change a field in Java the same native field is changed in native memory and when you read a Java field the native value is the final value got, of course this behavior only can be done injecting new Java (byte)code in any field read/write action, and it worked like a charm (with some minor issues and some limitations).

 

Howard Lewis Ship replied on Mon, 2010/05/24 - 3:48pm

I used to be a big fan of Javassist (Tapestry 3, 4 and 5 all make heavy use of it). However, I'm in the process of moving beyond Javassist to something simpler and more stable. Tapestry users are constantly having issues with Javassist on JDK 1.6, even against the latest version of Javassist. In the past, I was a big fan of being able to perform all kinds of manipulations of classes using the Javassist pseudo-Java. However, I've been reconceptualizing those changes in terms of a stable set of simple AOP concepts (around method advice, first-class field and method access for non-public fields and methods, control over injection of field values, and around access to field reads and writes). Those can be implemented using a simpler better supported framework than Javassist, and that's the way I'm headed.

Jose Maria Arranz replied on Tue, 2010/05/25 - 1:25am in response to: Howard Lewis Ship

What kind of problems Howard? Other options?

For pure AOP of course there are better alternatives (in spite of Javassist can be used to develop AOP frameworks like GluonJ), the problem of pure AOP approach is, in those days, they are too rigid, too declarative, usually because they are designed to add aspects to concrete already known classes. Javassist offered me the same with a fully programmatic approach.

 

Senthil Balakrishnan replied on Tue, 2010/05/25 - 12:01pm

Really cool!!!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.