4

Motivation

I have a SomeObject.java file:

class SomeObject {
   String name;
}

Compiling it creates a bytecode-containing SomeObject.class file.

0xCAFEBABE...

If we use SomeObject on the JVM, it is loaded by the current classloader and all works fine.

Now let's assume that I'd like to have some dynamic code generation. I can write my custom annotation

@Target(ElementType.TYPE)
public @interface Data {
   ...
}

and add it as a modifier to the class declaration:

@Data
class SomeObject {
   String name;
}

I can also retain it for the runtime with @Retention(RetentionPolicy.RUNTIME):

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Data {
   ...
}

Question

Where are annotations used for bytecode injection? Does a classloader inject bytecode when loading the class with the appropriate runtime retained annotation like in this figure:

source -(compile)-> bytecode -(classloader bytecode injection)-> injected bytecode -(classloading)-> JVM loaded bytecode   
ipavlic
  • 4,906
  • 10
  • 40
  • 77
  • This is a very interesting question @ipavlic! If anyone can provide links to literature on the internals of Spring + AOP, it would be greatly appreciated. I would like to write my own Spring annotations which inject code. – Jeach Apr 02 '15 at 14:02

1 Answers1

6

Yes, it would be possible to have your custom classloader to load a class and through bytecode manipulation tools such as Javassist or ASM perform the modifications, loading into memory not the bytecode in the class file but rather the modified one. Although there are easier (and better, in my opinion) ways of doing it.

Annotation Processor Tool (APT)

Since Java 6 you have APT which allows you to hook into the compiling process (via -processor argument in javac). With APT you have access to the code's AST (Abstract Syntax Tree) and you can perform modifications directly when compiling using the javax.lang.model. This means that your class file will be generated with the needed modifications.

In this case the chain would be something like something like:

source -(compile and performs modifications at model level)-> bytecode already modified - regular class loader -> loads class into memory

Post-Compiling processing

Another approach which can be used is to perform bytecode injection after compilation as a post-compiling process. In this cases you use bytecode modification tools (once again javassist, asm, among others), which can perform the modifications you need when the desired annotation is found, generating a new class file with the injected bytecode.

In such case your chain would be:

source -compile -> bytecode -post-compile-> modified bytecode - regular class loader -> loads class into memory

Runtime modifications

Finally we reach runtime bytecode modifications. Even though your idea is possible, in my opinion I would leave the class loader magic and use tools such has Javassist that also allows you to have dynamic proxies which can be modified and reloaded.

In javassist particular case the chain would be

source -compile -> bytecode -post-compile-> modified bytecode - regular class loader -> loaded into memory - javassist proxy -> modified class - javassist hot swapper -> re-modified class

Proxies aren't perfect though (well nothing is). You'll have a performance hit and you won't be able to modify the public interface of your class (sidenote: both APT and post-compile process can allow you to modify the class public interface). I could go on a bit more on this, but I think this is already enough information to give you food for thought. Feel free to leave a comment if you need additional information.

pabrantes
  • 2,161
  • 1
  • 28
  • 34