Inside Paulo Abrantes' head
[ start | index | login or register ]
start > 2008-03-24 > 1

Java Programming: Bytecode Injection

Created by pabrantes. Last edited by pabrantes, 104 days ago. Viewed 488 times. #5
[diff] [history] [edit] [rdf]

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.

Show InjectMethodCall source
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @Target( { METHOD }) public @interface InjectMethodCall {

public String injectionCall() default "";

public InjectLocator location() default InjectLocator.BEFORE;

public enum InjectLocator { BEFORE, AFTER; } }

Show InjectProperty source
@Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @Target( { FIELD }) public @interface InjectProperty {

public String injectGetter() default ""; public String injectSetter() default "";

public InjectLocator getterLocation() default InjectLocator.BEFORE; public InjectLocator setterLocation() default InjectLocator.BEFORE; }

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.

Show Snip of injection process
// code snip in the generation CtClass cc = pool.get(clazz.getName()); for (Method method : clazz.getDeclaredMethods()) { if (method.isAnnotationPresent(InjectMethodCall.class)) { InjectMethodCall annotation = method .getAnnotation(InjectMethodCall.class); CtMethod ctMethod = cc.getDeclaredMethod(method.getName()); injectMethod(ctMethod, annotation .injectionCall(), annotation.location()); } } // more code

public static void injectMethod(CtMethod ctMethod, String injectionCall, InjectLocator locator) throws CannotCompileException {

String code = generateCodeWithInjector(injectionCall,ctMethod.getName()); switch (locator) { case BEFORE: ctMethod.insertBefore(code); break; case AFTER: ctMethod.insertAfter(code); break; } }

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.

Icon-Comment m4ktub, 103 days ago. Icon-Permalink

pabrantes said: 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.

Well, I understand that many people have that line of thought but it's more or less like saying that if you start editing your native binaries you open the possibility of tailoring them in very imaginative ways … like hacking a password protection smiley. You can do amazing things at that level but do you really should/want to be doing that? In my opinion you should focus on the goals of bytecode injection and why tools like javassist exist. Stay on the light side!

pabrantes said: 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.

That is actually quite interesting but, once again, lets focus on the ends instead of focusing on the means. We want to change classes in a way that is not supported by the Java language, nor natively by the JVM, when they are loaded. But why? Why during the loading phase?

In my opinion that tells us something about the Java language:

  • it's not expressive enough to allow some programmers to describe what they want to do or
  • the Java language is used by some crazy programmers.
That last point being obviously right I would have to go with the first. What do you think? Is the language limited in important ways or is it fine as a base generic language? More specific goals should seek their own ways of being described instead of this assembly language?

PS: I've remembered that while looking at GWT I've noticed that the toolkit regards HTML + Javascript (DHTML) as binary code. The problem is that many people spend they whole day programming in DHTML and they say "The code produced by GWT is completely obfuscated". Off course it is! Your looking to assembly code. The point is, bytecode is assembly. Hell! In some cases even Java is assembly. You should always be working above the compiler (compiler compiles down smiley)

Icon-Comment pabrantes, 102 days ago. Icon-Permalink

You can do amazing things at that level but do you really should/want to be doing that?
m4ktub

Most of the times - not to say always - you don't want to get your hands dirty in those hackish low level details. But, I think that to fully understand a programming language it's important not only for you to feel comfortable with the high level abstractions but also, to understand what's under the hood in a lower level (maybe it's just me). Notice, that I'm not saying that we should be developing "down there", just saying that we should have an idea of what's happening "down there".

My main motivation to actually spend some time playing with bytecode injection was mostly to understand things related with AOP since it's heavily based on code injection. But notice that I, for sanity reasons, also didn't choose the most hardcore bytecode manipulation libraries such as ASM but rather javassist, because I didn't want to get that low level. So I think I can say that still on the light sidesmiley

That is actually quite interesting but, once again, lets focus on the ends instead of focusing on the means. We want to change classes in a way that is not supported by the Java language, nor natively by the JVM, when they are loaded. But why? Why during the loading phase?
m4ktub

Can I answer "because I can"? smiley I mentioned it mostly because I thought it was an interesting concept, although in my opinion, it's not usable in production environments mostly because it's too slow.

I do think every programming language has it's expressive problems - I say all because there is no perfect language - but I don't think we can solve Java problems with bytecode injection.
To me the biggest problem in java is not having native support to add and remove things from the class interface at runtime, such problem, can't be solved with bytecode injection. I do know you can always implement >>MetaObjectProtocol to solve partially this problem but that's another story.

Icon-Comment m4ktub, 102 days ago. Icon-Permalink

pabrantes said: To me the biggest problem in java is not having native support to add and remove things from the class interface at runtime, such problem, can't be solved with bytecode injection.

Asking that of Java is strange. Those guys from AOP had a similar requirement. They wanted to be able to add new behavior without needing to manually change all classes (because in OOP only allows you to easily change existing behaviour). But AOP changes the compile process. At runtime things are as fixed as they were.

Anyway, I still believe that it's all about "what are you trying to do" instead of "how can you do it". Normally this is a 1-N relation. In AOP, after the concept stabilized a bit, they developed a series of languages and tools to provide the needed abstraction and infrastructure. If I had to guess I would say they are happier now.

pabrantes said: But, I think that to fully understand a programming language it's important not only for you to feel comfortable with the high level abstractions but also, to understand what's under the hood in a lower level (maybe it's just me).

I guess you're implying a bottom up approach. You look at the details and start to build up. Ok, but build up in what direction. Because, although it's useful to know that a computer has a CPU, I'm not exactly programming for the CPU. A long time has passed since my last int 0x10.

Icon-Comment pabrantes, 102 days ago. Icon-Permalink

Asking that of Java is strange.
m4ktub

As long the language isn't dead - and I don't think Java is - it can evolve and do things that weren't expected to do in earlier versions. Maybe, what I'm asking is too extreme for Java maybe it isn't.

I guess you're implying a bottom up approach. You look at the details and start to build up
m4ktub

I know it seems I'm advocating bottom up approaches. But that's not what I'm thinking of. My line of thought is quite simple, know what you can do on the dirt so when you're doing a top down approach you know what to expect at the bottom.

Please login to www.pabrantes.net.
Who am I?
paulo-roca2My name is Paulo Abrantes AKA pabrantes and I'm a software developer. I'm currently employed at >>CIIST working as a Java developer in >>FenixEDU.

This blog is mostly about Java programming, domain driven design and snipsnap bliki developing. Everything written in this blog is my personal opinion and it may not reflect the opinions of my employer and co-workers.


Blog subscription
subscribe by rss subscribe by email

Links
>> Home
>> Paulo's Profile
>> Post History
>> Add to Technorati Favorites
>> Paulo's Photo Gallery
>> WishList
>> Posting without Login

Search Blog
Fellow Bloggers

Recent Posts

Java Programming: Bytecode Injection
Intermission: Sorry For Downtime
Software Developing: Studying The Bliki Domain Model
SnipSnap Developing: Trying to settle a roadmap
System Administration: Load Balancing with Apache
Blogging: Two years have passed
Software Developing: The SnipSnap Saga
Java Programming: Getting your code spicy with Groovy
Software Developing: Fluent Interfaces
Software Developing: Implementing a ShoutBox on SnipsSnip
Software Developing: SnipSnap, SnipIt and SnipSnip
Java Programming: Proxies and Access Control
Java Programming: Proxies and References
Java Programming: References' Package
YALM: Yet Another Layout Modification

For older posts, please refer to post-history for a complete Post History

Logged in Users: (0)
… and 22 Guests.
This is a modified version of snipsnap.org created by >>Paulo Abrantes