Practical Introduction into Code Injection with AspectJ, Javassist, and Java Proxy

The ability to inject pieces of code into compiled classes and methods, either statically or at runtime, may be of immense help. This applies especially to troubleshooting problems in third-party libraries without source codes or in an environment where it isn't possible to use a debugger or a profiler. Code injection is also useful for dealing with concerns that cut across the whole application, such as performance monitoring. Using code injection in this way became popular under the name Aspect-Oriented Programming (AOP). Code injection isn't something used only rarely as you might think, quite the contrary; every programmer will come into a situation where this ability could prevent a lot of pain and frustration.

This post is aimed at giving you the knowledge that you may (or I should rather say "will") need and at persuading you that learning basics of code injection is really worth the little of your time that it takes. I'll present three different real-world cases where code injection came to my rescue, solving each one with a different tool, fitting best the constraints at hand.

Why You Are Going to Need It

A lot has been already said about the advantages of AOP - and thus code injection - so I will only concentrate on a few main points from the troubleshooting point of view.

The coolest thing is that it enables you to modify third party, closed-source classes and actually even JVM classes. Most of us work with legacy code and code for which we haven't the source codes and inevitably we occasionally hit the limitations or bugs of these 3rd-party binaries and need very much to change some small thing in there or to gain more insight into the code's behavior. Without code injection you have no way to modify the code or to add support for increased observability into it. Also you often need to deal with issues or collect information in the production environment where you can't use a debugger and similar tools while you usually can at least manage somehow your application's binaries and dependencies. Consider the following situations:
  • You're passing a collection of data to a closed-source library for processing and one method in the library fails for one of the elements but the exception provides no information about which element it was. You'd need to modify it to either log the offending argument or to include it in the exception. (And you can't use a debugger because it only happens on the production application server.)
  • You need to collect performance statistics of important methods in your application including some of its closed-source components under the typical production load. (In the production you of course cannot use a profiler and you want to incur the minimal overhead.)
  • You use JDBC to send a lot of data to a database in batches and one of the batch updates fails. You would need some nice way to find out which batch it was and what data it contained.
I've in fact encountered these three cases (among others) and you will see possible implementations later.

You should keep the following advantages of code injection in your mind while reading this post:
  • Code injection enables you to modify binary classes for which you haven't the source codes
  • The injected code can be used to collect various runtime information in environments where you cannot use the traditional development tools such as profilers and debuggers
  • Don't Repeat Yourself: When you need the same piece of logic at multiple places, you can define it once and inject it into all those places.
  • With code injection you do not modify the original source files so it is great for (possibly large-scale) changes that you need only for a limited period of time, especially with tools that make it possible to easily switch the code injection on and off (such as AspectJ with its load-time weaving). A typical case is performance metrics collection and increased logging during troubleshooting
  • You can inject the code either statically, at the build time, or dynamically, when the target classes are being loaded by the JVM

Mini Glossary

You might encounter the following terms in relation to code injection and AOP:

Advice
The code to be injected. Typically we talk about before, after, and around advices, which are executed before, after, or instead of a target method. It's possible to make also other changes than injecting code into methods, e.g. adding fields or interfaces to a class.
AOP (Aspect Oriented Programming)
A programming paradigm claiming that "cross-cutting concerns" - the logic needed at many places, without a single class where to implement them - should be implemented once and injected into those places. Check Wikipedia for a better description.
Aspect
A unit of modularity in AOP, corresponds roughly to a class - it can contain different advices and pointcuts.
Joint point
A particular point in a program that might be the target of code injection, e.g. a method call or method entry.
Pointcut
Roughly spoken, a pointcut is an expression which tells a code injection tool where to inject a particular piece of code, i.e. to which joint points to apply a particular advice. It could select only a single such point - e.g. execution of a single method - or many similar points - e.g. executions of all methods marked with a custom annotation such as @MyBusinessMethod.
Weaving
The process of injecting code - advices - into the target places - joint points.

The Tools

There are many very different tools that can do the job so we will first have a look at the differences between them and then we will get acquainted with three prominent representatives of different evolution branches of code injection tools.

Basic Classification of Code Injection Tools

I. Level of Abstraction

How difficult is it to express the logic to be injected and to express the pointcuts where the logic should be inserted?

Regarding the "advice" code:
  1. Direct bytecode manipulation (e.g. ASM) - to use these tools you need to understand the bytecode format of a class because they abstract very little from it, you work directly with opcodes, the operand stack and individual instructions. An ASM example:
    methodVisitor.visitFieldInsn(Opcodes.GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");


    They are difficult to use due to being so low-level but are the most powerful. Usually they are used to implement higher-level tools and only few actually need to use them.
  2. Intermediate level - code in strings, some abstraction of the classfile structure (Javassist)
  3. Advices in Java (e.g. AspectJ) - the code to be injected is expressed as syntax-checked and statically compiled Java
Regarding the specification of where to inject the code:
  1. Manual injection - you have to get somehow hold of the place where you want to inject the code (ASM, Javassist)
  2. Primitive pointcuts - you have rather limited possibilities for expressing where to inject the code, for example to a particular method, to all public methods of a class or to all public methods of classes in a group (Java EE interceptors)
  3. Pattern matching pointcut expressions - powerful expressions matching joint points based on a number of criteria with wildcards, awareness of the context (e.g. "called from a class in the package XY") etc. (AspectJ)

II. When the Magic Happens

The code can be injected at different points in time:
  • Manually at run-time - your code has to explicitly ask for the enhanced code, e.g. by manually instantiating a custom proxy wrapping the target object (this is arguably not true code injection)
  • At load-time - the modification are performed when the target classes are being loaded by the JVM
  • At build-time - you add an extra step to your build process to modify the compiled classes before packaging and deploying your application
Each of these modes of injection can be more suitable at different situations.

III. What It Can Do

The code injection tools vary pretty much in what they can or cannot do, some of the possibilities are:
  • Add code before/after/instead of a method - only member-level methods or also the static ones?
  • Add fields to a class
  • Add a new method
  • Make a class to implement an interface
  • Modify an instruction within the body of a method (e.g. a method call)
  • Modify generics, annotations, access modifiers, change constant values, ...
  • Remove method, field, etc.

Selected Code Injection Tools

The best-known code injection tools are:
  1. Dynamic Java Proxy
  2. The bytecode manipulation library ASM
  3. JBoss Javassist
  4. AspectJ
  5. Spring AOP/proxies
  6. Java EE interceptors

Practical Introduction to Java Proxy, Javassist and AspectJ

I've selected three rather different mature and popular code injection tools and will present them on real-world examples I've personally experienced.

The Omnipresent Dynamic Java Proxy

Java.lang.reflect.Proxy makes it possible to create dynamically a proxy for an interface, forwarding all calls to a target object. It is not a code injection tool for you cannot inject it anywhere, you must manually instantiate and use the proxy instead of the original object, and you can do this only for interfaces, but it can still be very useful as we will see.

Advantages:
  • It's a part of JVM and thus is available everywhere
  • You can use the same proxy - more exactly an InvocationHandler - for incompatible objects and thus reuse the code more than you could normally
  • You save effort because you can easily forward all calls to a target object and only modify the ones interesting for you. If you were to implement a proxy manually, you would need to implement all the methods of the interface in question
Disadvantages
  • You can create a dynamic proxy only for an interface, you can't use it if your code expects a concrete class
  • You have to instantiate and apply it manually, there is no magical auto-injection
  • It's little too verbose
  • Its power is very limited, it can only execute some code before/after/around a method
There is no code injection step - you have to apply the proxy manually.
Example
I was using JDBC PreparedStatement's batch updates to modify a lot of data in a database and the processing was failing for one of the batch updates because of integrity constraint violation. The exception didn't contain enough information to find out which data caused the failure and so I've created a dynamic proxy for the PreparedStatement that remembered values passed into each of the batch updates and in the case of a failure it automatically printed the batch number and the data. With this information I was able to fix the data and I kept the solution in place so that if a similar problems ever occurs again, I'll be able to find its cause and resolve it quickly.

The crucial part of the code:
LoggingStatementDecorator.java - snippet 1

class LoggingStatementDecorator implements InvocationHandler {

private PreparedStatement target; ...

private LoggingStatementDecorator(PreparedStatement target) { this.target = target; }

@Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

try { Object result = method.invoke(target, args); updateLog(method, args); // remember data, reset upon successful execution return result; } catch (InvocationTargetException e) { Throwable cause = e.getTargetException(); tryLogFailure(cause); throw cause; }

}

private void tryLogFailure(Throwable cause) { if (cause instanceof BatchUpdateException) { int failedBatchNr = successfulBatchCounter + 1; Logger.getLogger("JavaProxy").warning( "THE INJECTED CODE SAYS: " + "Batch update failed for batch# " + failedBatchNr + " (counting from 1) with values: [" + getValuesAsCsv() + "]. Cause: " + cause.getMessage()); } } ...


Notes:
  • To create a proxy, you first need to implement an InvocationHandler and its invoke method, which is called whenever any of the interface's methods is invoked on the proxy
  • You can access the information about the call via the java.lang.reflect.* objects and for example delegate the call to the proxied object via method.invoke
We've also an utility method for creating a proxy instance for a Prepared statement:
LoggingStatementDecorator.java - snippet 2

public static PreparedStatement createProxy(PreparedStatement target) {
  return (PreparedStatement) Proxy.newProxyInstance(
      PreparedStatement.class.getClassLoader(),
      new Class[] { PreparedStatement.class },
      new LoggingStatementDecorator(target));
};


Notes:
  • You can see that the newProxyInstance call takes a classloader, an array of interfaces that the proxy should implement, and the invocation handler that calls should be delegated to (the handler itself has to manage a reference to the proxied object, if it needs it)
It is then used like this:
Main.java

...
PreparedStatement rawPrepStmt = connection.prepareStatement("...");
PreparedStatement loggingPrepStmt = LoggingStatementDecorator.createProxy(rawPrepStmt);
...
loggingPrepStmt.executeBatch();
...


Notes:
  • You see that we have to manually wrap a raw object with the proxy and use the proxy further on
Alternative Solutions
This problem could be solved in different ways, for example by creating a non-dynamic proxy implementing PreparedStatement and forwarding all calls to the real statement while remembering batch data but it would be lot of boring typing for the interface has many methods. The caller could also manually keep track of the data it has send to the prepared statement but that would obscure its logic with an unrelated concern.

Using the dynamic Java proxy we get rather clean and easy to implement solution.

The Independent Javassist

JBoss Javassist is an intermediate code injection tool providing a higher-level abstraction than bytecode manipulation libraries and offering little limited but still very useful manipulation capabilities. The code to be injected is represented as strings and you have to manually get to the class-method where to inject it. Its main advantage is that the modified code has no new run-time dependencies, on Javassist or anything else. This may be the decisive factor if you are working for a large corporation where the deployment of additional open-source libraries (or just about any additional libraries) such as AspectJ is difficult for legal and other reasons.

Advantages
  • Code modified by Javassist doesn't require any new run-time dependencies, the injection happens at the build time and the injected advice code itself doesn't depend on any Javassist API
  • Higher-level than bytecode manipulation libraries, the injected code is written in Java syntax, though enclosed in strings
  • Can do most things that you may need such as "advising" method calls and method executions
  • You can achieve both build-time injection (via Java code or a custom Ant task to do execution/call advising) and load-time injection (by implementing your own Java 5+ agent [thx to Anton])
Disadvantages
  • Still little too low-level and thus harder to use - you have to deal a little with structure of methods and the injected code is not syntax-checked
  • Javassist has no tools to perform the injection and you thus have to implement your own injection code - including that there isn't support for injecting the code automatically based on a pattern
(See GluonJ below for a solution without most of the disadvantages of Javassist.)

With Javassist you create a class, which uses the Javassist API to inject code int targets and run it as a part of your build process after the compilation, for example as I once did via a custom Ant task.
Example
We needed to add some simple performance monitoring to our Java EE application and we were not allowed to deploy any non-approved open-source library (at least not without going through a time-consuming approval process). We've therefore used Javassist to inject the performance monitoring code to our important methods and to the places were important external methods were called.

The code injector:
JavassistInstrumenter.java

public class JavassistInstrumenter {

public void insertTimingIntoMethod(String targetClass, String targetMethod) throws NotFoundException, CannotCompileException, IOException { Logger logger = Logger.getLogger("Javassist"); final String targetFolder = "./target/javassist";

try { final ClassPool pool = ClassPool.getDefault(); // Tell Javassist where to look for classes - into our ClassLoader pool.appendClassPath(new LoaderClassPath(getClass().getClassLoader())); final CtClass compiledClass = pool.get(targetClass); final CtMethod method = compiledClass.getDeclaredMethod(targetMethod);

// Add something to the beginning of the method: method.addLocalVariable("startMs", CtClass.longType); method.insertBefore("startMs = System.currentTimeMillis();"); // And also to its very end: method.insertAfter("{final long endMs = System.currentTimeMillis();" + "iterate.jz2011.codeinjection.javassist.PerformanceMonitor.logPerformance(\"" + targetMethod + "\",(endMs-startMs));}");

compiledClass.writeFile(targetFolder); // Enjoy the new $targetFolder/iterate/jz2011/codeinjection/javassist/TargetClass.class

logger.info(targetClass + "." + targetMethod + " has been modified and saved under " + targetFolder); } catch (NotFoundException e) { logger.warning("Failed to find the target class to modify, " + targetClass + ", verify that it ClassPool has been configured to look " + "into the right location"); } }

public static void main(String[] args) throws Exception { final String defaultTargetClass = "iterate.jz2011.codeinjection.javassist.TargetClass"; final String defaultTargetMethod = "myMethod"; final boolean targetProvided = args.length == 2;

new JavassistInstrumenter().insertTimingIntoMethod( targetProvided? args[0] : defaultTargetClass , targetProvided? args[1] : defaultTargetMethod ); } }


Notes:
  • You can see the "low-levelness" - you have to explicitly deal with objects like CtClass, CtMethod, explicitly add a local variable etc.
  • Javassist is rather flexible in where it can look for the classes to modify - it can search the classpath, a particular folder, a JAR file, or a folder with JAR files
  • You would compile this class and run its main during your build process

Javassist on Steroids: GluonJ

GluonJ is an AOP tool building on top of Javassist. It can use either a custom syntax or Java 5 annotations and it's build around the concept of "revisers". Reviser is a class - an aspect - that revises, i.e. modifies, a particular target class and overrides one or more of its methods (contrary to inheritance, the reviser's code is physically imposed over the original code inside the target class).

Advantages
  • No run-time dependencies if build-time weaving used (load-time weaving requires the GluonJ agent library or gluonj.jar)
  • Simple Java syntax using GlutonJ's annotation - though the custom syntax is also trivial to understand and easy to use
  • Easy, automatic weaving into the target classes with GlutonJ's JAR tool, an Ant task or dynamically at the load-time
  • Support for both build-time and load-time weaving
Disadvantages
  • An aspect can modify only a single class, you cannot inject the same piece of code to multiple classes/methods
  • Limited power - only provides for field/method addition and execution of a code instead of/around a target method, either upon any of its executions or only if the execution happens in a particular context, i.e. when called from a particular class/method
If you don't need to inject the same piece of code into multiple methods then GluonJ is easier and better choice than Javassist and if its simplicity isn't a problem for you then it also might be a better choice than AspectJ just thanks to this simplicity.

The Almighty AspectJ

AspectJ is a full-blown AOP tool, it can do nearly anything you might want, including the modification of static methods, addition of new fields, addition of an interface to a class' list of implemented interfaces etc.

The syntax of AspectJ advices comes in two flavours, one is a superset of Java syntax with additional keywords like aspect and pointcut, the other one - called @AspectJ - is standard Java 5 with annotations such as @Aspect, @Pointcut, @Around. The latter is perhaps easier to learn and use but also little less powerful as it isn't as expressive as the custom AspectJ syntax.

With AspectJ you can define which joint points to advise with very powerful expressions but it may be little difficult to learn them and to get them right. There is a useful Eclipse plugin for AspectJ development - the AspectJ Development Tools (AJDT) - but the last time I've tried it it wasn't as helpful as I'd have liked.

Advantages
  • Very powerful, can do nearly anything you might need
  • Powerful pointcut expressions for defining where to inject an advice and when to activate it (including some run-time checks) - fully enables DRY, i.e. write once & inject many times
  • Both build-time and load-time code injection (weaving)
Disadvantages
  • The modified code depends on the AspectJ runtime library
  • The pointcut expressions are very powerful but it might be difficult to get them right and there isn't much support for "debugging" them though the AJDT plugin is partially able to visualize their effects
  • It will likely take some time to get started though the basic usage is pretty simple (using @Aspect, @Around, and a simple pointcut expression, as we will see in the example)
Example
Once upon time I was writing a plugin for a closed-source LMS J2EE application having such dependencies that it wasn't feasible to run it locally. During an API call, a method deep inside the application was failing but the exception didn't contain enough information to track the cause of the problem. I therefore needed to change the method to log the value of its argument when it fails.

The AspectJ code is quite simple:
LoggingAspect.java

@Aspect
public class LoggingAspect {

@Around("execution(private void TooQuiet3rdPartyClass.failingMethod(..))") public Object interceptAndLog(ProceedingJoinPoint invocation) throws Throwable { try { return invocation.proceed(); } catch (Exception e) { Logger.getLogger("AspectJ").warning( "THE INJECTED CODE SAYS: the method " + invocation.getSignature().getName() + " failed for the input '" + invocation.getArgs()[0] + "'. Original exception: " + e); throw e; } } }


Notes:
  • The aspect is a normal Java class with the @Aspect annotation, which is just a marker for AspectJ
  • The @Around annotation instructs AspectJ to execute the method instead of the one matched by the expression, i.e. instead of the failingMethod of the TooQuiet3rdPartyClass
  • The around advice method needs to be public, return an Object, and take a special AspectJ object carrying information about the invocation - ProceedingJoinPoint - as its argument and it may have an arbitrary name (Actually this is the minimal form of the signature, it could be more complex.)
  • We use the ProceedingJoinPoint to delegate the call to the original target (an instance of the TooQuiet3rdPartyClass) and, in the case of an exception, to get the argument's value
  • I've used an @Around advice though @AfterThrowing would be simpler and more appropriate but this shows better the capabilities of AspectJ and can be nicely compared to the dynamic java proxy example above
Since I hadn't control over the application's environment, I couldn't enable the load-time weaving and thus had to use AspectJ's Ant task to weave the code at the build time, re-package the affected JAR and re-deploy it to the server.
Alternative Solutions
Well, if you can't use a debugger then your options are quite limited. The only alternative solution I could think of is to decompile the class (illegal!), add the logging into the method (provided that the decompilation succeeds), re-compile it and replace the original .class with the modified one.

The Dark Side

Code injection and Aspect Oriented Programming are very powerful and sometimes indispensable both for troubleshooting and as a regular part of application architecture, as we can see e.g. in the case of Java EE's Enterprise Java Beans where the business concerns such as transaction  management and security checks are injected into POJOs (though implementations actually more likely use proxies) or in Spring.

However there is a price to be paid in terms of possibly decreased understandability as the runtime behavior and structure are different from what you'd expect based on the source codes (unless you know to check also the aspects' sources or unless the injection is made explicit by annotations on the target classes such as Java EE's @Interceptors). Therefore you must carefully weight the benefits and drawbacks of code injection/AOP - though when used reasonably, they do not obscure the program flow more than interfaces, factories etc. The argument about obscuring code is perhaps often over-estimated.

If you want to see an example of AOP gone wild, check the source codes of Glassbox, a JavaEE performance monitoring tool (for that you might need a map not to get too lost).

Fancy Uses of Code Injection and AOP

The main field of application of code injection in the process of troubleshooting is logging, more exactly gaining visibility into what an application is doing by extracting and somehow communicating interesting runtime information about it. However AOP has many interesting uses beyond - simple or complex - logging, for example:
  • Typical examples: Caching & et al (ex.: on AOP in JBoss Cache), transaction management, logging, enforcement of security, persistence, thread safety, error recovery, automatic implementation of methods (e.g. toString, equals, hashCode), remoting
  • Implementation of role-based programming (e.g. OT/J, using BCEL) or the Data, Context, and Interaction architecture
  • Testing
    • Test coverage - inject code to record whether a line has been executed during test run or not
    • Mutation testing (µJava, Jumble) - inject "random" mutation to the application and verify that the tests failed
    • Pattern Testing - automatic verification that Architecture/Design/Best practices recommendations are implemented correctly in the code via AOP
    • Simulate hardware/external failures by injecting the throwing of an exception
  • Help to achieve zero turnaround for Java applications - JRebel uses an AOP-like approach for framework and server integration plugins - namely its plugins use Javassist for "binary patching"
  • Solving though problems and avoiding monkey-coding with AOP patterns such as Worker Object Creation (turn direct calls into asynchronous with a Runnable and a ThreadPool/task queue) and Wormhole (make context information from a caller available to the callee without having to pass them through all the layers as parameters and without a ThreadLocal) - described in the book AspectJ in Action
  • Dealing with legacy code - overriding the class instantiated on a call to a constructor (this and similar may be used to break tight-coupling with feasible amount of work), ensuring backwards-compatibility  o , teaching components to react properly on environment changes
  • Preserving backwards-compatibility of an API while not blocking its ability to evolve e.g. by adding backwards-compatible methods when return types have been narrowed/widened (Bridge Method Injector - uses ASM) or by re-adding old methods and implementing them in terms of the new API
  • Turning POJOs into JMX beans

Summary

We've learned that code injection can be indispensable for troubleshooting, especially when dealing with closed-source libraries and complex deployment environments. We've seen three rather different code injection tools - dynamic Java proxies, Javassist, AspectJ - applied to real-world problems and discussed their advantages and disadvantages because different tools may be suitable for different cases. We've also mentioned that code injection/AOP shouldn't be overused and looked at some examples of advanced applications of code injection/AOP.

I hope that you now understand how code injection can help you and know how to use these three tools.

Source Codes

You can get the fully-documented source codes of the examples from GitHub including not only the code to be injected but also the target code and support for easy building. The easiest may be:

git clone git://github.com/holyjak/JavaZone-Code-Injection.git
cd JavaZone-Code-Injection/
cat README
mvn -P javaproxy test
mvn -P javassist test
mvn -P aspectj   test


(It may take few minutes for Maven do download its dependencies, plugins, and the actual project's dependencies.)

Additional Resources

Acknowledgements

I would like to thank all the people who helped me with this post and the presentation including my colleges, the JRebel folk, and GluonJ's co-author prof. Shigeru Chiba.

Tags: java library


Copyright © 2024 Jakub Holý
Powered by Cryogen
Theme by KingMob