Wednesday, February 9, 2011

Java Programming: Bytecode Injection

Java Programming: Bytecode Injection

Java Programming: Bytecode Injection

As probably all Java developers know, when compiling Java source code the compiler doesn't generate machine code like, for example, a C compiler does when compiling C source code, but rather an intermediate code. That intermediate code, called bytecode, is what is understood by the Java Virtual Machine (JVM).
Knowing how bytecode works isn't actually necessary for Java developers, although knowing it, or at least, knowing how to interact with it gives news possibilities in the developing process.
Bytecode Injection is one of those possibilities and it consists in changing existing bytecode, that is, in modifying a Java compiled resource (a class file). This post will explain how it can be done, creating a simple injection framework, discussing the advantages and disadvantages of different processes and for the code junkies there will be plenty of source code available.
While reading, some might think that the examples will be similar to Aspect Oriented Programming (AOP), that thought isn't wrong, since this subject can be the basis for AOP.
Bytecode Manipulation
Like it has been said, Java bytecode is intermediate code that is ran within the JVM. This code is generated by the Java Compiler, this means that, the class files generated from a Java compilation are in fact files that contain bytecode. Each instruction is one byte long and it might resemble code from the assembly language.
There are various libraries available for bytecode manipulation, each one has their goals. To mention a few there are >>ASM, >>BCEL and >>javassist.
Javassist is a bit different from the previous two because, instead of making the developer actually write bytecode, creates an abstraction that allows to write normal Java code - with some restrictions - converting it automatically to bytecode. Because of such feature, Javassist will be the library used in the example provided.
Until now it might not be clear why bytecode manipulation is an interesting concept. I see it as interesting concept because, it allows the developer to create a separation of concerns. For example, access control and persistent can be implemented into a working application using bytecode injection.
The Framework
Since the idea is to create a simple general purpose injection framework first, it has to be understood how to design it. In my opinion, there are four main topics regarding this subject:
  1. How to identify an injection
  2. Where can injections be done
  3. What is the injected code context
  4. What to code to inject
1. Identifying an Injection
An injection should be identified in a simple and concise way. In my opinion, the best way to achieve this is by using Java Annotations - more about annotations and how to write them in >>Java Programming: Doing your own annotations.
2. Where can injections be done
Since injections are being identified by Java Annotations, there is a limited set where to place injections. In this case, only methods and class properties will be allowed to contain injections. The properties injection will be only a shortcut to define injections for the property's getter and setter.
Two different annotations can be created, one for methods and another one for properties. They must hold at least, a marker for the injection call - which in this particular case will be the class name - and if the code should be injected before or after the existing method code is executed.
Below is presented the code of each one of the annotations.
3. What is the injected code context
In order to treat any type of injection code the same way, an interface shall be defined, this interface also allows to define what is available to the injected code. I think three things should be available:
3.1 The object where the method is being called
3.2 The name of the method
3.3 The arguments received in the method
With all this information the following interfaced, named InjectionCall, can be written in the following way:
package business.injectionCall;
public interface InjectionCall {
public void run(Object object, String methodName, Object[] args); }
Creating a new injection call, consists in creating another object that implements the InjectionCall interface. This interface, with the provided context is able to do all sort of things from checking the object's internal state to perform access control or persist values.
4. What to code to inject
The injection interface has been presented, although the actual code that execute the call to those interfaces, the actual code that is being injected in the methods, wasn't yet presented. That code is quite simple and is generated by a method called generateCodeWithInjector (in InjectionUtils). The code follows:
private static String generateCodeWithInjector(String injectorName, String methodName) { return "{ try { Class clazz = Class.forName(\"" + injectorName + "\");" + "business.injectionCall.InjectionCall injectionCall =" + "(business.injectionCall.InjectionCall) clazz.newInstance();" + "injectionCall.run(this,\"" + methodName + "\",$args); }" + "catch(Exception e) { e.printStackTrace(); } }"; }
First the injector's class - which will be implementing the InjectionCall interface - is loaded, then instantiated and finally the method run is called with the object itself, the method name and a special argument, $args. This is a special variable that is understood by javassist while compiling the code and replaced by an Object array containing the arguments of the current method.
More about the javassist's special variables can be read - along with examples - in the >>javassist documentation.
The four main concerns regarding the framework were presented, but there's still an important question left.
How is that simple snip of code just showed injected into the bytecode? That leads us to the injection process.
Injecting the code
injectionProcess This is the most interesting part, which is how can the injection actually be done. There are various ways of doing it, and two different ways will be presented:
  1. Injecting during the build process
  2. Injecting at runtime using a custom class loader
Even though there are obvious differences between the two previous methods, the actual injection process for a class is performed in the same way. The algorithm that performs the injection is described in the flowchart that is presented on figure at the right.
When a class in being looked into for possible injections, it first gets all its declared methods - that means not only public accessed method, but all - visited looking for an injection annotation. When that annotation is found, the information needed is retrieved from the annotation - such as the injection call that should be used - and the bytecode for that method is modified.
After visiting all the declared methods, the algorithm starts visiting all declared fields - once again declared, not public, hence all fields - looking for a specific injection annotation. When the annotation is found, the property is injected. By "injecting the property" should be understood as that property's setter and getter methods injection.
Below there's the code that implements the first part of the flowchart, visiting all declared methods and injecting code into them when needed.
buildProcessInjection 1. Build Process
Doing the injection during the build process consists in having an application that reads the class files, perform the injection process in each class - meaning manipulating the class bytecode - and finally write the new class files over the old ones.
Everyone using this option should pay attention that this isn't a idempotent action, meaning that if for some reason the injector is ran twice in the build process, the code will be injected twice.
Below the main code for the injector application is presented.
The code is quite simple but there are two classes that are worth mentioning, they are, ClassPool which is the javassist pool for class representation, each time a class is requested to the pool, if already exists a representation for it it is returned, otherwise, it is created and, CtClass which is the javassist class representation for a Class object.
There are other CtXXX objects, each one of them represents an object from the class structure, such as CtMethod or CtField. Each one of this objects provides an interface to inject code, before, after or around it among other operations.

List<Class> classesToScan = getClassesInDirectory(args[0]); ClassPool pool = ClassPool.getDefault(); for (Class clazz : classesToScan) { CtClass classRepresentation = pool.get(clazz.getName()); performInjection(classRepresentation); classRepresentation.writeFile(InjectionUtils.BUILD_DIR); }
ClassLoaderInjection 2. Injecting at runtime using a custom class loader
This solution uses a custom made ClassLoader, anyone interested in knowing more about ClassLoaders should read >>Java Programming: First steps with ClassLoaders and >>Java Programming: Hot Deploy
The idea is simple, instead of parsing all classes at build time, each class is injected on demand at runtime when is requested to the class loader to load it. The injection is only done in memory. This makes the operation idempotent, since the class is loaded into memory only once and then the class code is cached.
This solution also allows a better flexibility, because by just switching the used class loader the classes can be used in the application with or without the injection code.
But there are disadvantages, the class loading will take more time since every class has to be processed and injected if needed. Also there's a bit of code modifications to use the code with such custom class loader.
The code snip below shows how a class - TestSample class in the business package - could be loaded with such custom class loader:
InjectionClassLoader classLoader = new InjectionClassLoader(TestInjectionClassLoader.class.getClassLoader()); Class clazz = classLoader.loadClass("business.TestSample"); TestSample testSample = (TestSample) clazz.newInstance();
Since using this option introduces an overhead at runtime a benchmark was done - benchmark code is also present in the available code - which loaded a Java Class containing injection code, and other that didn't, both cases with the InjectionClassLoader and the standard JVM class loader. The values presented below are an average of running the benchmark five times:
 Class with InjectionClass without Injection
Custom class loader406ms7ms
Standard class loader0ms0ms
The time of loading a class without Injection is linear with the number of methods and fields it contains. This time could be optimised by creating a class annotation that would tell the class loader that such class needed to be parsed in order to find code injections. Although the biggest problem isn't that but the actual loading of a class with injection which took almost half a second.
After seeing such results, the following advantages/disadvantages table regarding both methods can be presented:
 AdvantagesDisadvantages
During BuildRuntime performance isn't affected, transparency on the application codeNot idempotent, needs modification of the build process
ClassLoaderFlexibility, idempotent operationPerformance, code needs modification to use the custom class loader
Conclusions
Using the proper tools bytecode injections isn't complicated to perform and can easily provide separation of concerns. Two different ways of performing the actual injection were presented, both had advantages and disadvantages, using each one of them depends on what the developers want.
Anyone interested in seeing this concepts working in actual code, can download my >>code example, which contains the Injection Generator, the Injection Class Loader, the class loader benchmark and some examples objects that implement InjectionCall and a object that uses them. An ant build file is also provided in order to make things easier.
Hope the article was interesting.

2 comments:

  1. Hi,
    Can we perform byte code injection in a already running java application on windows?
    If yes,please tell me how?

    Thanks,
    Pralay

    ReplyDelete